数据结构包括:线性结构和非线性结构
线性结构
非线性结构
非线性结构包括:二维数组、多维数组、广义表、树结构、图结构
基本介绍
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组
稀疏数组的处理方法是:
package com.yhs.sparsearray;
import java.io.*;
public class SparseArray {
public static void main(String[] args) throws IOException {
//创建一个二维数组 11 * 11
int[][] chessArr1 = new int[11][11];
chessArr1[1][2] = 1;
chessArr1[2][3] = 2;
chessArr1[4][5] = 2;
//输出原始的二维数组
System.out.println("原始的二维数组");
for (int[] row : chessArr1) {
for (int data : row) {
System.out.print(data + "\t");
}
System.out.println();
}
//将原始数组转稀疏数组
//1. 先遍历二维数组 得到非0数据的个数
int sum = 0;
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1.length; j++) {
if (chessArr1[i][j] != 0) {
sum++;
}
}
}
//2. 创建对应的稀疏数组
int sparseArr[][] = new int[sum + 1][3];
// 给稀疏数组赋值
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
// 遍历二维数组,将非零的值存放到稀疏数组
int count = 0; //用来记录是第几个非零数据
for (int i = 0; i < chessArr1.length; i++) {
for (int j = 0; j < chessArr1.length; j++) {
if (chessArr1[i][j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr1[i][j];
}
}
}
//输出稀疏数组的形式
System.out.println();
System.out.println("得到的稀疏数组为");
for (int i = 0; i < sparseArr.length; i++) {
System.out.printf("%d\t%d\t%d\t\n", sparseArr[i][0], sparseArr[i][1], sparseArr[i][2]);
}
System.out.println();
//将稀疏数组 --> 恢复成原始的二维数组
//读取稀疏数组后几行数据,并赋值给原始数组
//1. 先读取稀疏数组第一行,得到原始数组的大小
int[][] chessArr2 = new int[sparseArr[0][0]][sparseArr[0][1]];
for (int i = 1; i < sparseArr.length; i++) {
chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
//输出恢复后的二维数组
System.out.println();
System.out.println("恢复后的二维数组");
for (int[] row : chessArr2) {
for (int data : row) {
System.out.print(data + "\t");
}
System.out.println();
}
//将稀疏数组写入文件
System.out.println("稀疏数组写入map.data文件");
String filePath = "d:\\map.data";
File file = new File(filePath);
OutputStreamWriter writer = null;
try {
System.out.println("文件创建成功");
writer = new OutputStreamWriter(new FileOutputStream(file), "GB2312");
} catch (IOException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < sparseArr.length; i++) {
if (i == sparseArr.length - 1) {
writer.write(sparseArr[i][0] + "," + sparseArr[i][1] + "," + sparseArr[i][2]);
} else {
writer.write(sparseArr[i][0] + "," + sparseArr[i][1] + "," + sparseArr[i][2] + ",");
}
}
writer.close();
System.out.println("写入成功");
//稀疏数组转二维数组首先读取文件
InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(file));
StringBuffer sbf = new StringBuffer();
while (inputStreamReader.ready()) {
sbf.append((char) inputStreamReader.read());
}
System.out.println("读出文件" + sbf.toString());
inputStreamReader.close();
//把读出的文件,赋值给稀疏数组
String[] data = sbf.toString().split(",");
System.out.println(data.length / 3);
int[][] sparseArray2 = new int[data.length / 3][3];
int i = 0;
for (String s : data) {
System.out.println(i % 3);
sparseArray2[i / 3][i % 3] = Integer.parseInt(s);
i++;
}
//输出读取到的稀疏数组
System.out.println("读取到的稀疏数组如下++++++++++++");
for (int j = 0; j < sparseArray2.length; j++) {
System.out.print(sparseArray2[j][0] + "\t" + sparseArray2[j][1] + "\t" + sparseArray2[j][2]);
System.out.println();
}
//1. 先读取稀疏数组的第一行,找出二维数组的大小
int[][] charArray2 = new int[sparseArray2[0][0]][sparseArray2[0][1]];
//2. 读取稀疏数组的值,给到二维数组
for (int k = 1; k < sparseArray2.length; k++) {
charArray2[sparseArray2[k][0]][sparseArray2[k][1]] = sparseArray2[k][2];
}
System.out.println("恢复后的二维数组");
for (int[] row : charArray2) {
for (int ds : row) {
System.out.print(ds + "\t");
}
System.out.println();
}
}
}
对列是一个有序列表,可以用数组或链表来实现
遵循先入先出的原则,即:先存入队列的数据,要 先取出,后存入队列的数据,要后退出
当我们将数据存入队列时称为“addQueue", “addQueue” 的处理需要有两个步骤;
代码实现
package com.yhs.queue;
import java.util.Scanner;
public class Queue {
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(3);
char key = ' ';
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单
while (loop) {
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
System.out.print("请输入你的选择: ");
key = scanner.next().charAt(0);
switch (key) {
case 's':
arrayQueue.showQueue();
break;
case 'a':
System.out.print("请输入添加的数据: ");
int value = scanner.nextInt();
arrayQueue.addQueue(value);
break;
case 'g':
try {
int res = arrayQueue.getQueue();
System.out.printf("取出的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = arrayQueue.headQueue();
System.out.printf("取出的头数据=%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
//使用数组模拟队列编写一个ArrayQueue类
class ArrayQueue {
private int maxSize; //表示数组最大容量
private int front; //队列头
private int rear; //队列尾
private int[] arr; //该数组用于存放数据,模拟队列
//添加构造器
public ArrayQueue(int arrMaxsize) {
maxSize = arrMaxsize;
arr = new int[maxSize];
front = -1; //指向队列头部,front指向队列头的前一个位置
rear = -1; //指向队列尾部,指向队列尾的数据
}
//判断数组是否为空
public boolean isEmpty() {
return front == rear;
}
//判断数组是否已满
public boolean isFull() {
return rear == maxSize - 1;
}
//添加数据
public void addQueue(int n) {
if (! isFull()) {
rear++;
arr[rear] = n;
} else {
System.out.println("队列已满不能添加数据");
}
}
//获取队列数据
public int getQueue() {
if (! isEmpty()) {
front++; //因为front代表队列头的前一个数据,所以需要++
return arr[front];
} else {
throw new RuntimeException("队列为空 不能取出数据");
}
}
//显示队列的所有数据
public void showQueue() {
if (! isEmpty()) {
for (int i = 0; i < arr.length; i++) {
System.out.printf("arr[%d]=%d\n", i, arr[i]);
}
} else {
System.out.println("队列为空,无法遍历数据");
}
}
//显示队列的头数据
public int headQueue() {
if (! isEmpty()) {
return arr[front+1];
} else {
throw new RuntimeException("队列为空,无法取出");
}
}
}
在上一个ArrayQueueDemo的基础上优化程序,达到复用的效果
思路如下:
代码实现
package com.yhs.queue;
import java.util.Scanner;
@SuppressWarnings("all")
public class CircleArrayQueue {
public static void main(String[] args) {
//测试
//这里设置为4,其队列有效数据最大为3
//约定的位置是动态变化的
CircleArray circleArray = new CircleArray(4);
char key = ' ';
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单
while (loop) {
System.out.println("s(show): 显示队列");
System.out.println("e(exit): 退出程序");
System.out.println("a(add): 添加数据到队列");
System.out.println("g(get): 从队列取出数据");
System.out.println("h(head): 查看队列头的数据");
System.out.print("请输入你的选择: ");
key = scanner.next().charAt(0);
switch (key) {
case 's':
circleArray.showQueue();
break;
case 'a':
System.out.print("请输入添加的数据: ");
int value = scanner.nextInt();
circleArray.addQueue(value);
break;
case 'g':
try {
int res = circleArray.getQueue();
System.out.printf("取出的数据是%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'h':
try {
int res = circleArray.headQueue();
System.out.printf("取出的头数据=%d\n", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e':
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
class CircleArray {
private int maxSize; //数组最大容量
//front 变量的含义作调整:指向队列的第一个元素,也就是arr[front] = arr[0];
//front 的初始值 = 0
private int front;
//rear 变量的含义作调整: 指向队列的最后一个元素的后一个位置
//rear 的初始值 = = 0
private int rear; //队列尾
private int[] arr;
//构造器
public CircleArray(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[maxSize];
}
//判断是否为空
public boolean isEmpty() {
return front == rear;
}
//判断是否已满
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
//添加数据到队列
public void addQueue(int n) {
// 判断队列是否满
if (!isFull()) {
arr[rear] = n;
rear = (rear + 1) % maxSize;
} else {
System.out.println("队列已满,不能加入...");
}
}
//获取队列中的数据
public int getQueue() {
if (!isEmpty()) {
//front指向队列中的第一个元素
//1. 先将front存入一个零时变量
//2. 将front 后移动,考虑取模
//3. 将临时保存的变量返回
int value = arr[front];
front = (front + 1) % maxSize;
return value;
} else {
throw new RuntimeException("队列为空,无法取出");
}
}
//显示队列中的数据
public void showQueue() {
if (!isEmpty()) {
for (int i = front; i < front + dataNum(); i++) {
System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
}
} else {
System.out.println("队列为空,无法显示...");
}
}
//求出当前队列有效数据的数量
public int dataNum() {
return (rear + maxSize - front) % maxSize;
}
public int headQueue() {
//判断
if (!isEmpty()) {
return arr[front];
} else {
throw new RuntimeException("队列为空,没有数据...");
}
}
}
链表(Linked List)链表是有序的列表,是链式存储结构
【添加方式一】
【添加方式二】
package com.yhs.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试
//创建节点
HeroNode h1 = new HeroNode(1, "詹姆斯", "小皇帝");
HeroNode h2 = new HeroNode(2, "奥尼尔", "大鲨鱼");
HeroNode h3 = new HeroNode(3, "邓肯", "石佛");
HeroNode h4 = new HeroNode(4, "库里", "小学生");
//创建一个链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
//方式一:加入
// singleLinkedList.addHeroNode(h1);
// singleLinkedList.addHeroNode(h4);
// singleLinkedList.addHeroNode(h2);
// singleLinkedList.addHeroNode(h3);
//方式二:加入按照编号的顺序
singleLinkedList.addByOrder(h1);
singleLinkedList.addByOrder(h4);
singleLinkedList.addByOrder(h2);
singleLinkedList.addByOrder(h3);
//显示
singleLinkedList.list();
//测试修改节点的代码
HeroNode newHeroNode = new HeroNode(2, "科比", "小飞侠");
singleLinkedList.update(newHeroNode);
System.out.println("=========修改后=========");
singleLinkedList.list();
//测试删除节点
singleLinkedList.delete(1);
singleLinkedList.delete(2);
singleLinkedList.delete(3);
singleLinkedList.delete(4);
System.out.println("=============删除后===========");
singleLinkedList.list();
}
}
//定义一个SingleLinkedList 管理我们的英雄
class SingleLinkedList {
//初始化一个头节点,头节点不存放数据
private HeroNode headN = new HeroNode(0, "", "");
//【方式一】添加节点到单项链表,不考虑编号顺序时
//1. 找到当前链表的最后的节点
//2. 将最后的节点的next 指向新的节点
public void addHeroNode(HeroNode heroNode) {
//因为head节点不能动,因此我们需要一个辅助的变量 temp
HeroNode temp = headN;
//遍历链表,找到最后
while (true) {
if (temp.next == null) {
break;
}
//如果没有找到最后,就将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向新的节点
temp.next = heroNode;
}
//【方式二】添加节点时,根据排名将英雄插入到指定位置
//(如果有这个排名,则添加失败,并给出提示)
public void addByOrder(HeroNode heroNode) {
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
HeroNode temp = headN;
boolean flag = false; //标志添加的编号是否存在,默认为false
while (true) {
if (temp.next == null) {
//说明temp已经在链表的最后
break;
}
if (temp.next.no > heroNode.no) {
//位置找到就在temp后面插入
break;
} else if (temp.next.no == heroNode.no) {
//说明希望添加的heroNode编号已存在
flag = true;
break;
}
temp = temp.next;
}
//判断flag的值
if (flag) {
//编号已经存在,不能添加
System.out.printf("准备插入的英雄编号 %d 已经存在,不能添加\n", heroNode.no);
} else {
//插入到链表,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改变
//1. 根据 newHeroNode 的 no 来进行修改
public void update(HeroNode newHeroNode) {
//判断是否为空
if (headN.next == null) {
System.out.println("链表为空...");
return;
}
//找到需要修改的节点
//定义一个辅助指针
HeroNode temp = headN.next;
boolean loop = false; //表示是否找到该节点
while (true) {
if (temp.no == newHeroNode.no) {
loop = true;
break;
}
temp = temp.next;
if (temp == null) {
return;
}
}
if (loop) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
} else {
System.out.printf("没有找到编号= %d 的节点,不能修改", newHeroNode.no);
}
}
//删除节点
//1. head 不能动,因此我们需要一个辅助指针找到删除节点的前一个节点
//2. 说明我们比较时,时temp.next.no 和 需要删除的节点no比较
public void delete(int no) {
HeroNode temp = headN;
boolean loop = false; //标志是否找到待删除的节点
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.no == no) {
loop = true;
break;
}
temp = temp.next;
}
//判断是否找到该节点
if (loop) {
//找到
//删除该节点
temp.next = temp.next.next;
} else {
System.out.printf("删除失败...该节点 %d不存在", no);
}
}
//显示链表
public void list() {
//先判断链表是否为空
if (headN.next == null) {
System.out.println("链表为空");
return;
}
//因为头节点不能动,因此我们需要定义一个辅助变量来遍历
HeroNode temp = headN.next;
while (true) {
//判断是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
//将temp后移
temp = temp.next;
// 第二种写法
// while (true) {
// System.out.println(temp);
// temp = temp.next;
// if (temp == null) {
// break;
// }
// }
}
}
}
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
//构造器
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
//为了方便显示,重写toString
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
public static int getLength(HeroNode headN) {
if (headN.next == null) {
return 0;
}
int length = 0;
HeroNode temp = headN.next;
while (temp != null) {
length++;
temp = temp.next;
}
return length;
}
//1. 编写一个方法,接收head节点,同时接收一个index
//2. index 表示倒数第index个节点
//3. 先把链表从头到尾遍历,得到链表的总长度
//4. 得到size后,我们从链表的第一个开始遍历(size - index)个,就可以得到
//5. 如果找到了,则返回该节点,否则返回null
public HeroNode getKNode(HeroNode headN, int index) {
if (headN.next == null) {
return null;
}
int size = 0;
HeroNode temp = headN.next;
while (temp != null) {
size++;
temp = temp.next;
}
//第二次遍历 size-index 位置,就是倒数的第k个节点
//先做一个index的校验
if (index <= 0 || index > size) {
return null;
}
temp = headN.next;
//for循环 定位到倒数的index
for (int i = 0; i < size - index; i++) {
temp = temp.next;
}
return temp;
}
方式一
//将单链表反转
public static void reverseList(HeroNode headN) {
//如果当前链表为空,或者只有一个节点,无序反转,直接返回
if (headN.next == null || headN.next.next == null) {
return;
}
//定义一个辅助的指针,帮助我们遍历原来的链表
HeroNode cur = headN.next;
HeroNode next = null; //指向当前节点[cur]的下一个节点
HeroNode reverseHead = new HeroNode(0,"", "");
//并从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
while (cur != null) {
next = cur.next; //先暂时保存当前节点的下一个节点,因为后面需要使用
cur.next = reverseHead.next;
reverseHead.next = cur; //将cur连接到新的链表上
cur = next; //让temp后移
}
//将head.next 指向 reverseHead.next 实现链表反转
headN.next = reverseHead.next;
}
方式二
//将单链表反转 -- 双指针解法
public void reverseList(HeroNode headN) {
//如果当前链表为空,或者只有一个节点,无序反转,直接返回
if (headN.next == null || headN.next.next == null) {
return;
}
//定义一个辅助的指针,帮助我们遍历原来的链表
HeroNode cur = headN.next;
HeroNode pre = null;
HeroNode temp = null; //辅助指针
while (cur != null) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
headN.next = pre;
}
测试Stack(栈)的使用
public class TestStock {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
//入栈
stack.add("jack");
stack.add("tom");
stack.add("smith");
//出栈
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
}
//倒序打印链表
//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印效果
public void reversePrint(HeroNode headN) {
if (headN.next == null) {
return;
}
//创建一个栈,将各个节点压入栈中
Stack<HeroNode> stack = new Stack<>();
HeroNode cur = headN.next;
//将链表的所有节点压入栈
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
//将栈中的节点进行打印 pop出栈
while (stack.size() > 0) {
System.out.println(stack.pop()); //stack的特点:先进后出
}
}
使用单项链表的缺点分析
代码实现
package com.yhs.linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
//测试
//创建节点
System.out.println("~~~~~~~~双向链表测试~~~~~");
HeroNode2 h1 = new HeroNode2(1, "詹姆斯", "小皇帝");
HeroNode2 h2 = new HeroNode2(2, "奥尼尔", "大鲨鱼");
HeroNode2 h3 = new HeroNode2(3, "邓肯"