java数据结构和算法
1 队列、堆栈与数组、链表的关系与区分
首先,明确两个概念:数据结构与数据存储结构!
数据结构:是指相互之间存在一种或多种特定关系的数据元素的 集合。听起来是不是很抽象,简单理解:数据结构就是描述对象间逻辑关系的学科。比如:队列就是一种先进先出的逻辑结构,栈是一种先进后出的逻辑结构,家谱 是一种树形的逻辑结构!(初学数据结构的时候很不理解为什么有“栈”这个东西;队列很容易理解—无论购物就餐都需要排队;栈可以认为就是个栈道— 只允许一个人通过的小道,而且只能从一端进入,然后再从这端返回,比如你推了个箱子进去啦,第二个人也推个箱子进去,此时只能等后进来的这个人拉着箱子出 去后,你才能退出。)
数据存储结构:它是计算机的一个概念,简单讲,就是描述数据在计算机中存储方式的学科;常用的数据存储 方式就两种:顺序存储,非顺序存储!顺序存储就是把数据存储在一块连续的存储介质(比如硬盘或内存)上—-举个例子:从内存中拿出第100个字节到 1000个字节间的连续位置,存储数据;数组就是典型的顺序存储!非顺序存储就是各个数据不一定存在一个连续的位置上,只要每个数据知道它前面的数据和后 面的数据,就能把所有数据连续起来啦;链表就是典型的非顺序存储啦!
至此,基本就应该明白它们之间的区别了吧!
队列、栈是线性数据结构的典型代表,而数组、链表是常用的两种数据存储结构;队列和栈均可以用数组或链表的存储方式实现它的功能!
//========================简单分析===============================
数 组属于顺序存储中,由于每个元素的存储位置都可以通过简单计算得到,所以访问元素的时间都相同(直接访问数组下标);链表属于数据的链接存储,由于每个元 素的存储位置是保存在它的前驱或后继结点中的,所以只有当访问到其前驱结点或后继结点后才能够按指针访问到自己,访问任一元素的时间与该元素结点在链接存 储中的位置有关。
链表和数组是常用的两种数据存储结构,都能用来保存特定类型的数据。两者存在着一些差异:
1.占用的内存空间
链表存放的内存空间可以是连续的,也可以是不连续的,数组则是连续的一段内存空间。一般情况下存放相同多的数据数组占用较小的内存,而链表还需要存放其前驱和后继的空间。
2.长度的可变性
链表的长度是按实际需要可以伸缩的,而数组的长度是在定义时要给定的,如果存放的数据个数超过了数组的初始大小,则会出现溢出现象。
3.对数据的访问
链表方便数据的移动而访问数据比较麻烦;数组访问数据很快捷而移动数据比较麻烦。
链表和数组的差异决定了它们的不同使用场景,如果需要很多对数据的访问,则适合使用数组;如果需要对数据进行很多移位操作,则设和使用链表。
上面提到的都是栈,而不是堆栈,那堆栈是什么呢?
首先,堆栈是计算机语言中常用术语,堆栈是栈的俗称!
比如在Java中我们常常说堆栈什么什么的,其实就是说栈内信息!此时有人就问:Java中明明有堆和栈两个概念呀?!不错,堆和栈的确是两种不同的内存操作单元,它们用途不同,但堆栈就是栈的俗称,你可以理解它其实就是栈!
堆和栈有什么区别:
\1. 栈具有数据结构中栈的特点,后进先出,所有存放在它里面的数据都是生命周期很明确(当然要求它不能存放太久,占有的空间确定而且占用空间小),能够快速反应的!所有在Java中它存放的是8个基本数据类型和引用变量的,用完就马上销毁
\2. 堆可以理解它就是个一个可大可小,任你分配的听话的内存操作单元;因此它的特点就是动态的分配内存,适合存放大的数据量!比如一个对象的所有信息,虽然它的引用指向栈中的某个引用变量;所有Java中堆是存放new出来的对象的。
堆和栈因为不同的特性,所有在计算机中应用甚广!
2.稀疏数组
1.1、实际需求
- 编写的五子棋程序中,有存盘退出和续上盘的功能
- 因为该二维数组的很多值是默认值 0 ,因此记录了很多没有意义的数据,我们将其转为稀疏数组进行存储

1.2、稀疏数组应用
1.2.1、稀疏数组处理方法
1.2.2、举例说明

1.3、应用实例
1.3.1、思路分析
- 使用稀疏数组, 来保留类似前面的二维数组(棋盘、 地图等等)
- 把稀疏数组存盘, 并且可以从新恢复原来的二维数组数

1.3.2、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| public class SparseArray {
public static void main(String[] args) { 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.printf("%d\t", data); } System.out.println(); }
int sum = 0; for (int i = 0; i < chessArr1.length; i++) { for (int j = 0; j < chessArr1[i].length; j++) { if (chessArr1[i][j] != 0) { sum++; } } }
int sparseArr[][] = new int[sum + 1][3]; sparseArr[0][0] = chessArr1.length; sparseArr[0][1] = chessArr1[0].length; sparseArr[0][2] = sum;
int count = 0; for (int i = 0; i < chessArr1.length; i++) { for (int j = 0; j < chessArr1[i].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\n", sparseArr[i][0], sparseArr[i][1], sparseArr[i][2]); } System.out.println();
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.printf("%d\t", data); } System.out.println(); } }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 原始的二维数组~~ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
得到稀疏数组为~~~~ 11 11 3 1 2 1 2 3 2 4 5 2
恢复后的二维数组 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1234567891011121314151617181920212223242526272829303132
|
1.4、课后练习
- 在前面的基础上, 将稀疏数组保存到磁盘上, 比如 map.data
- 恢复原来的数组时, 读取 map.data 进行恢复
2、队列
2.1、队列使用场景

2.2、队列介绍
- 队列是一个有序列表, 可以用数组或是链表来实现。
- 遵循先入先出的原则, 即: 先存入队列的数据, 要先取出,后存入的要后取出
- 示意图: (使用数组模拟队列示意图)

2.3、数组模拟队列
2.3.1、思路分析
- maxSize :队列容量(数组的长度)
- arr :模拟队列的数组
- front :指向队列头部元素的前一个元素,初始值为 -1
- rear :指向队列尾部元素,初始值为 -1

- 基本操作
- 队列判空:front == rear
- 队列判满:rear == (maxSize - 1) ,即 rear 是否已经指向了数组的最后一个位置
- 队列元素个数:rear - front
- 队列入队:队列不满才能入队,arr[++rear] = value
- 队列出队:队列不空才能出队,return arr[front++]
2.3.2、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| 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; rear = -1; }
public boolean isFull() { return rear == maxSize - 1; }
public boolean isEmpty() { return rear == front; }
public void addQueue(int n) { if (isFull()) { System.out.println("队列满,不能加入数据~"); return; } rear++; arr[rear] = n; }
public int getQueue() { if (isEmpty()) { throw new RuntimeException("队列空,不能取数据"); } front++; return arr[front];
}
public void showQueue() { if (isEmpty()) { System.out.println("队列空的,没有数据~~"); return; } for (int i = front + 1; i <= rear; i++) { System.out.printf("arr[%d]=%d\n", i, arr[i]); } }
public int headQueue() { if (isEmpty()) { throw new RuntimeException("队列空的,没有数据~~"); } return arr[front + 1]; } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class ArrayQueueDemo {
public static void main(String[] args) { ArrayQueue queue = 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.println(); key = scanner.next().charAt(0); switch (key) { case 's': queue.showQueue(); break; case 'a': System.out.println("输出一个数"); int value = scanner.nextInt(); queue.addQueue(value); break; case 'g': try { int res = queue.getQueue(); System.out.printf("取出的数据是%d\n", res); } catch (Exception e) { System.out.println(e.getMessage()); } break; case 'h': try { int res = queue.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("程序退出~~"); }
} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
s 队列空的,没有数据~~ s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 1 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 2 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
s arr[0]=1 arr[1]=2 arr[2]=3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 4 队列满,不能加入数据~ s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 取出的数据是1 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 取出的数据是2 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 取出的数据是3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 队列空,不能取数据 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
|
2.4、数组模型环形队列
2.4.1、提出问题
- 目前数组使用一次就不能用, 没有达到复用的效果,造成内存空间的浪费
- 将这个数组使用算法, 改进成一个环形的队列(**取模: %**)
2.4.2、思路分析
- 对前面的队列进行优化,改造为环形队列(通过取模实现)
- maxSize :队列容量(数组的长度)
- arr :模拟队列的数组
- front :指向队列头部元素,初始值为 0
- rear :指向队列尾部元素的后一个元素,初始值为 0

- 基本操作
- 队列判空:front == rear
- 队列判满:
- 为何要在 rear 之后,front 之前空出一个元素的空间?因为如果不空出一个元素,队列判空条件为:front == rear ,队列判满的条件也是:front == rear ,有歧义!
- 队列容量:因为空出了一个元素,所以队列容量就变成了 (maxSize - 1)
- 当空出一个元素的空间,如何判满?当还剩一个元素时,队列就已经满了,所以判断条件为 (rear + 1) % maxSize == front
- 队列元数个数:
- 计算公式:**(rear + maxSize - front) % maxSize** ,这样来思考:
- 当 rear 比 front 大时,即 (rear -front) > 0 ,这时还没有形成环形结构,**(rear -front)** 即是队列元素个数
- 当 rear 比 front 小时,即 (rear -front) < 0 ,这时已经形成了环形结构,**(rear -front)** 表示数组还差多少个元素存满(负数),**(rear + maxSize - front)** 即是队列元素个数
- 综上:**(rear + maxSize - front) % maxSize**
- 队列入队:
- 首先,队列不满才能入队
- 由于 rear 指向队列尾部元素的后一个元素,所以直接设置即可: arr[rear] = value
- 接下来,rear 应该向后移动一个位置:rear = (rear + 1) % maxSize
- 取模是为了防止数组越界,让指针从新回到数组第一个元素
- 队列出队:
- 首先,队列不空才能出队
- 由于 front 直接指向队列头部元素,所以直接返回该元素即可:int value = arr[front ]
- 接下来,front 应该向后移动一个位置:front = (front + 1) % maxSize
- 取模是为了防止数组越界,让指针从新回到数组第一个元素
2.4.3、代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| class CircleArray { private int maxSize; private int front; private int rear; private int[] arr;
public CircleArray(int arrMaxSize) { maxSize = arrMaxSize; arr = new int[maxSize]; }
public boolean isFull() { return (rear + 1) % maxSize == front; }
public boolean isEmpty() { return rear == front; }
public void addQueue(int n) { if (isFull()) { System.out.println("队列满,不能加入数据~"); return; } arr[rear] = n; rear = (rear + 1) % maxSize; }
public int getQueue() { if (isEmpty()) { throw new RuntimeException("队列空,不能取数据"); } int value = arr[front]; front = (front + 1) % maxSize; return value;
}
public void showQueue() { if (isEmpty()) { System.out.println("队列空的,没有数据~~"); return; } for (int i = front; i < front + size(); i++) { System.out.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]); } }
public int size() { return (rear + maxSize - front) % maxSize; }
public int headQueue() { if (isEmpty()) { throw new RuntimeException("队列空的,没有数据~~"); } return arr[front]; } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class CircleArrayQueueDemo {
public static void main(String[] args) {
System.out.println("测试数组模拟环形队列的案例~~~");
CircleArray queue = 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.println(); key = scanner.next().charAt(0); switch (key) { case 's': queue.showQueue(); break; case 'a': System.out.println("输出一个数"); int value = scanner.nextInt(); queue.addQueue(value); break; case 'g': try { int res = queue.getQueue(); System.out.printf("取出的数据是%d\n", res); } catch (Exception e) { System.out.println(e.getMessage()); } break; case 'h': try { int res = queue.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("程序退出~~"); }
} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| 测试数组模拟环形队列的案例~~~ s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 1 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 2 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
s arr[0]=1 arr[1]=2 arr[2]=3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
a 输出一个数 4 队列满,不能加入数据~ s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 取出的数据是1 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 取出的数据是2 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
s arr[2]=3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 取出的数据是3 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
g 队列空,不能取数据 s(show): 显示队列 e(exit): 退出程序 a(add): 添加数据到队列 g(get): 从队列取出数据 h(head): 查看队列头的数据
|