1. 稀疏数组
1.1 何为稀疏数组
- 稀疏数组,首先把稀疏拆出来,理解为写程序的你,头发日渐稀疏,渐少。
- 比如一个棋盘,可以用二位数组表示,这个棋牌(只有三种数据,0为没有棋子,1为黑棋,2为白棋),我们可以只提取出黑白棋的位置构成稀疏数组。
1.2 应用场景
- 比如 一些量大,且多数为重复值的时候,可以使用(棋牌,地图等等)
1.3 图文解析
- 首先创建新的数组来记录数组有几行、几列、有多少不同的值
- 然后把元素中的列、行、值(棋子的颜色)记录在一个小规模的数组中,从而缩小程序的规模。
1.4 设计思路
二维数组转稀疏数组 思路:
- 首先注意的是,行和列这里设计都从零开始。
- 遍历原始二位数组,获取到有效值【1(黑棋)、2(白棋)】的个数 sum
- 再根据 sum 创建出稀疏数组
sparsearray[sum + 1][3] 注意: 因为稀疏数组的第一个一维数组要保存原始数组的行、列、有效值个数,所以稀疏数组的一维数组个数为sum+1 - 将原始二维数组的有效数据存放到稀疏数组中
(有效数据:元素所在行,元素所在列,元素的值【1,2】)
稀疏数组转二维数组 思路:
- 先根据稀疏数组的第一行,创建出原始二维数组
- 从第二行开始读取稀疏数组的行、列、值,将值赋值回原始的二维数组中
代码中包括了序列化与反序列化,如果不会可以去看Java基础篇
1.5 代码实现
public class Demo1 {
public final static String RESOURCE_PATH = "";
private static int[][] arr2Sparse(int[][] chessArr) {
int sum = 0;
for (int[] row : chessArr) {
for (int data : row) {
if (data != 0) {
sum++;
}
}
}
System.out.println("得到的有效值总个数:" + sum);
int[][] sparseArray = new int[sum + 1][3];
sparseArray[0][0] = 11;
sparseArray[0][1] = 11;
sparseArray[0][2] = sum;
int count = 0;
for (int i = 0; i < chessArr.length; i++) {
for (int j = 0; j < chessArr.length; j++) {
if (chessArr[i][j] != 0) {
count++;
sparseArray[count][0] = i;
sparseArray[count][1] = j;
sparseArray[count][2] = chessArr[i][j];
}
}
}
return sparseArray;
}
private static int[][] sparse2Arr(int[][] sparseArray) {
int[][] chessArr = new int[sparseArray[0][0]][sparseArray[0][1]];
for (int i = 1; i < sparseArray.length; i++) {
chessArr[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
}
return chessArr;
}
private static void print(int[][] array) {
for (int[] row : array) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
}
public static void main(String[] args) {
int[][] chessArr = new int[11][11];
chessArr[1][2] = 1;
chessArr[3][4] = 2;
chessArr[5][7] = 1;
System.out.println("输出我的棋盘:");
print(chessArr);
System.out.println("------------------------------------二维数组转稀疏数组-----------------------------------");
int[][] sparseArray = arr2Sparse(chessArr);
System.out.println("稀疏数组:");
print(sparseArray);
System.out.println("------------------------------------稀疏数组转二维数组(复原棋盘)-----------------------------------");
int[][] chessArr2 = sparse2Arr(sparseArray);
System.out.println("还原之后的二维数组");
print(chessArr2);
sparseSerialize(sparseArray);
int[][] arr = (int[][]) sparseDSerialize();
if (arr != null) {
System.out.println("最后得到类class类型" + arr.getClass().getName());
print(arr);
}
}
private static void sparseSerialize(int[][] array) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(RESOURCE_PATH));
oos.writeObject(array);
oos.flush();
} catch (Exception e) {
e.getStackTrace();
} finally {
try {
if (oos != null) {
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Object sparseDSerialize() {
ObjectInputStream ois = null;
Object arr;
try {
ois = new ObjectInputStream(new FileInputStream(RESOURCE_PATH));
arr = ois.readObject();
return arr;
} catch (Exception e) {
e.getStackTrace();
} finally {
try {
if (ois != null) {
ois.close();
}
} catch (Exception e2) {
e2.getStackTrace();
}
}
return null;
}
}
输出结果:
输出我的棋盘:
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 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 1 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
------------二维数组转稀疏数组-------------
得到的有效值总个数:3
稀疏数组:
11 11 3
1 2 1
3 4 2
5 7 1
----------稀疏数组转二维数组(复原棋盘)----------
还原之后的二维数组
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 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 1 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
2. 队列(数组模拟)
2.1 队列基本说明
- 队列是一个有序列表,可以用数组或者链表实现
- 遵循先进先出原则,先存入数据先取出,后存入后取出
2.2 数组模拟队列
解析:
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图, 其中 maxSize 是该队列的最大容量。
- 因为队列的输出、输入是分别从两端来处理,因此需要两个变量 front及 rear分别记录队列两端的下标,front 会随着数据输出而改变,而 rear则是随着数据输入而改变。
2.3 设计思路
讲解入队和出队的两个操作(前置条件:front和rear的初始值都是指向队列第一个数的前面,也就是front=rear=-1) 当我们将数据存入队列时称为addQueue ,addQueue 的处理需要有两个步骤:
- 首先判断队列是否已满(rear == maxSize - 1 表示队列已满),如果满则无法将数据存入队列。
- 然后将尾指针往后移:rear + 1,并将数据放入到队列中。
当我们将数据从队列取出(出队)称为getQueue ,getQueue 的处理需要有两个步骤:
- 首先判断队列是否为空(rear == front 表示队列为空),如果为空则无法取出数据。
- 然后将头指针往后移:front + 1,并将数据从队列中取出,最后将该数据的位置重置为0。
2.4 代码实现
public class ArrayQueue {
private final int maxSize;
private int front;
private int rear;
private final int[] arr;
public ArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[arrMaxSize];
front = -1;
rear = -1;
}
public boolean isFull() {
return rear == maxSize - 1;
}
public boolean isEmpty() {
return front == rear;
}
public void addQueue(int n) {
if (isFull()) {
System.out.println("队列已满,无法添加数据~~~~");
return;
}
rear++;
arr[rear] = n;
}
public int getQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空,无法取数据~~~");
}
front++;
int result = arr[front];
arr[front] = 0;
return result;
}
public void showQueue() {
if (isEmpty()) {
System.out.println("队列为空,无法显示数据~~~~");
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.printf("arr[%d]=%d\n", i, arr[i]);
}
}
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空,无法取数据~~~");
}
return arr[front + 1];
}
}
public class Demo2 {
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): 查看队列头的数据");
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("程序退出~~");
}
}
2.5 此队列出现的问题
- 目前设计的队列只能使用一次(因为数组只能有一次),没有达到复用效果
- 因此,我们需要将此数组用算法改进,成一个环形队列(取模 %)
2.6 环形队列设计思路(实现方式有很多,仅供参考)
- front 变量调整: front的初始值 = 0,直接指向队列的第一个数。
- rear 变量的调整:rear 的初始值 = 0。始终指向队列最后一个数的后一个位置(这里我们入队的逻辑是先入队再++)
- 判断队列满的公式是 (rear + 1) % maxSize == front 这里希望空出一个空间做为约定,用于判断队列满。例如数组长度为5,那么存放4个数据就判断他已经满了(必须要浪费掉一个空间,不然没办法区分为空或者是满的情况)
- 判断队列为空的公式, rear == front,(如果front到达rear的位置,就表示队列取完了)
- 队列中有效的数据的个数 (rear + maxSize - front) % maxSize
关于第五点的公式推导,正常情况下求有效数据的个数应该为rear-front,但是环形队列可能存在rear的下标小于front的情况,所以在这里加上maxSize保证rear一定大于front,然后再进行取模,达到求出有效数据的效果。
2.7 代码实现
public class CircleArrayQueue {
private final int maxSize;
private final int[] arr;
private int front;
private int rear;
public CircleArrayQueue(int arrMaxSize) {
maxSize = arrMaxSize;
arr = new int[arrMaxSize];
}
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
public boolean isEmpty() {
return front == rear;
}
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 - front + maxSize) % maxSize;
}
public int headQueue() {
if (isEmpty()) {
throw new RuntimeException("队列为空,无法取数据~~~");
}
return arr[front];
}
}
public class Demo3 {
public static void main(String[] args) {
CircleArrayQueue queue = new CircleArrayQueue(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): 查看队列头的数据");
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("程序退出~~");
}
}
|