📝目录
- 1.稀疏数组
-
- 稀疏数组的应用场景
- 稀疏数组基本介绍
- 稀疏数组的处理方法
- 二维数组转稀疏数组的思路
- 稀疏数组转原始的二维数组的思路
- 代码实现
-
- [☕️ Java版本](#☕️ Java版本)
- 🐍python版本
- 2.队列
- 3.环形队列
1.稀疏数组
稀疏数组的应用场景
在存储 棋类游戏(例如五子棋)棋盘数据 时,棋盘上的黑子和白子通常只占据少量位置,而棋盘的大部分位置都是空的。
一种直观的实现方式是:
使用 二维数组 来表示整个棋盘。例如:
- 没有棋子的地方记录为
0 - 黑子记录为
1 - 白子记录为
2
这样就可以得到一个用于保存棋盘状态的二维数组。
但是这种方式存在一个问题:
由于棋盘上大部分位置都是空的(值为 0),二维数组中会存储 大量没有实际意义的数据 ,从而造成 存储空间的浪费。
为了解决这个问题,可以使用 稀疏数组(Sparse Array) 来对数据进行压缩存储,只记录 有意义的数据(非 0 元素)的位置和值 ,从而减少存储空间的占用。

稀疏数组基本介绍
当一个数组中大部分元素为 0 ,或者为 同一个值 的数组时,可以使用 稀疏数组 来保存该数组。
稀疏数组的处理方法
- 记录数组一共有 几行几列 ,以及 有多少个不同的值
- 把 具有不同值的元素的行、列及值 记录在一个 小规模的数组 中,从而 缩小程序的规模
二维数组(暂时用表格表示)
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 22 | 0 | 0 | 15 |
| 1 | 0 | 11 | 0 | 0 | 0 | 17 | 0 |
| 2 | 0 | 0 | 0 | -6 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 | 39 | 0 |
| 4 | 91 | 0 | 0 | 0 | 0 | 0 | 0 |
| 5 | 0 | 0 | 28 | 0 | 0 | 0 | 0 |
下面将二维数组转化为稀疏数组
注意第一行表示这个二维数组有6行7列里面有8个非0值,后面第二行开始才是记录数组的具体信息
| index | row | col | value |
|---|---|---|---|
| 0 | 6 | 7 | 8 |
| 1 | 0 | 3 | 22 |
| 2 | 0 | 6 | 15 |
| 3 | 1 | 1 | 11 |
| 4 | 1 | 5 | 17 |
| 5 | 2 | 3 | -6 |
| 6 | 3 | 5 | 39 |
| 7 | 4 | 0 | 91 |
| 8 | 5 | 2 | 28 |
二维数组转稀疏数组的思路
- 遍历原始的二维数组,得到有效数据的个数
sum - 根据
sum就可以创建稀疏数组sparseArr int[sum+1][3] - 将二维数组的有效数据存入到稀疏数组
稀疏数组转原始的二维数组的思路
- 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的
chessArr2 = int[11][11] - 在读取稀疏数组后几行的数据,并赋给原始的二维数组即可。
代码实现
☕️ Java版本
java
package org.example.sparseArray;
public class MySparseArray {
public static void main(String[] args) {
// 1.准备数据,要转化为稀疏数组的二维数组(大部分为0,少数部分为非零)
int[][] arr = new int[10][10];
arr[1][2] = 1;
arr[2][3] = 2;
arr[4][5] = 6;
arr[5][5] = 5;
arr[7][8] = 10;
arr[9][9] = 2;
// 2. 打印原始数组
System.out.println("原始数组");
for (int[] row : arr) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
// 3. 转化为稀疏数组
int[][] sparseArr = toSparseArray(arr);
// 4. 稀疏数组打印
System.out.println("稀疏数组");
for (int[] row : sparseArr) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
// 5. 稀疏数组还原成二维数组
int[][] arr2 = toArray(sparseArr);
// 6. 恢复二维数组打印
System.out.println("恢复二维数组");
for (int[] row : arr2) {
for (int data : row) {
System.out.printf("%d\t", data);
}
System.out.println();
}
}
public static int[][] toSparseArray(int[][] arr) {
// 1. 获取二维数组的行数和列数
int row = arr.length;
int col = arr[0].length;
// 2. 获取二维数组中非零的个数用于创建稀疏数组
int sum = 0;
for (int[] rowData : arr) {
for (int data : rowData) {
if (data != 0) {
sum++;
}
}
}
// 3. 创建稀疏数组
int[][] sparseArr = new int[sum + 1][3];
// 4. 稀疏数组的第一行记录二维数组的行数、列数、非零元素的个数
sparseArr[0][0] = row;
sparseArr[0][1] = col;
sparseArr[0][2] = sum;
// 5. 遍历二维数组,将非零元素记录到稀疏数组中
int count = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (arr[i][j] != 0) {
count++;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = arr[i][j];
}
}
}
return sparseArr;
}
public static int[][] toArray(int[][] sparseArr) {
// 1. 获取稀疏数组第一行数据 创建二维数组
int row = sparseArr[0][0];
int col = sparseArr[0][1];
int[][] arr = new int[row][col];
// 2. 遍历稀疏数组,将数据还原成二维数组
for (int i = 1; i < sparseArr.length; i++) {
int rowIndex = sparseArr[i][0];
int colIndex = sparseArr[i][1];
int data = sparseArr[i][2];
arr[rowIndex][colIndex] = data;
}
return arr;
}
}
🐍python版本
python
def main():
# 1. 准备数据, 要转化为稀疏数组的二维数组(大部分为0, 少数部分为非零)
# Python 中没有原生的二维数组,通常使用嵌套列表(列表推导式)来创建
arr = [[0] * 10 for _ in range(10)]
arr[1][2] = 1
arr[2][3] = 2
arr[4][5] = 6
arr[5][5] = 5
arr[7][8] = 10
arr[9][9] = 2
# 2. 打印原始数组
print("原始数组")
print_array(arr)
# 3. 转化为稀疏数组
sparse_arr = to_sparse_array(arr)
# 4. 稀疏数组打印
print("稀疏数组")
print_array(sparse_arr)
# 5. 稀疏数组还原成二维数组
arr2 = to_array(sparse_arr)
# 6. 恢复二维数组打印
print("恢复二维数组")
print_array(arr2)
def print_array(arr):
"""封装打印逻辑,保持主函数整洁"""
for row in arr:
for data in row:
# end='\t' 表示用制表符结尾,不换行
print(f"{data}\t", end='')
# 内层循环结束后换行
print()
def to_sparse_array(arr):
# 1. 获取二维数组的行数和列数
row = len(arr)
col = len(arr[0])
# 2. 获取二维数组中非零的个数用于创建稀疏数组
sum_val = 0
for row_data in arr:
for data in row_data:
if data != 0:
sum_val += 1
# 3. 创建稀疏数组 (sum_val + 1) 行,3 列
sparse_arr = [[0] * 3 for _ in range(sum_val + 1)]
# 4. 稀疏数组的第一行记录二维数组的行数、列数、非零元素的个数
sparse_arr[0][0] = row
sparse_arr[0][1] = col
sparse_arr[0][2] = sum_val
# 5. 遍历二维数组, 将非零元素记录到稀疏数组中
count = 0
for i in range(row):
for j in range(col):
if arr[i][j] != 0:
count += 1
sparse_arr[count][0] = i
sparse_arr[count][1] = j
sparse_arr[count][2] = arr[i][j]
return sparse_arr
def to_array(sparse_arr):
# 1. 获取稀疏数组第一行数据 创建二维数组
row = sparse_arr[0][0]
col = sparse_arr[0][1]
# 初始化全 0 的二维数组
arr = [[0] * col for _ in range(row)]
# 2. 遍历稀疏数组, 将数据还原成二维数组
# 注意:Python 的 range(1, n) 不包含 n,所以直接遍历到 sparse_arr.length 即可
for i in range(1, len(sparse_arr)):
row_index = sparse_arr[i][0]
col_index = sparse_arr[i][1]
data = sparse_arr[i][2]
arr[row_index][col_index] = data
return arr
if __name__ == "__main__":
main()
运行结果:

2.队列
注意这里先实现普通队列(没有取模),队列数组只能使用一次,若要多次复用数组空间,看下面的环形队列讲解。
队列的应用场景
队列数据结构与秒杀场景
场景:1000 人同时抢购
- 数据结构视角 :系统维护一个队列。
- 入队 :1000 个用户的点击请求,作为 1000 个数据元素,被依次追加到队列的尾部。
- 出队 :服务器作为消费者,每次从队列的头部取出一个请求进行处理。
- 核心逻辑 :严格遵循 FIFO 原则,先点击的请求先被处理。
队列介绍
- 队列是一个有序列表,可以用数组或是链表来实现。
- 遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入的要后取出。
数组模拟队列
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下图,其中maxSize是该队列的最大容量。
- 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据输出而改变,而rear则是随着数据输入而改变,如图所示:
- 示意图:(使用数组模拟队列示意图)

数组示意图说明
- 左侧图:初始状态,
rear = -1,front = -1,队列为空。 - 中间图:添加数据后,
rear向后移动(如rear=3),front仍为-1。 - 右侧图:删除数据后,
front向后移动(如front=2),rear保持为3。
数组示意图核心概念
- MaxSize:队列的最大容量。
- front :指向队列头部,初始值为
-1,指向队列得第一个数据的前一个位置。 - rear :指向队列尾部,初始值为
-1,指向队列尾部,是包含队列尾部内各数据的。 - 数据存入时,
rear向后移动;数据取出时,front向后移动。
入队操作思路梳理(addQueue)
处理步骤
-
移动指针 :将尾指针后移:
rear + 1。 -
判断与存入 :
-
若尾指针
rear小于队列的最大下标maxSize - 1,则将数据存入rear所指的数组元素中。 -
否则无法存入数据(即
rear == maxSize - 1时,队列满)。
3.判断空
- 当
front==rear的时候队列是空。
代码实现
☕️Java版本
java
package org.example.arrayQueue;
public class ArrayQueueDemo {
public static void main(String[] args) {
// 1. 创建队列
ArrayQueue queue = new ArrayQueue(3);
queue.add(1);
queue.add(2);
queue.add(3);
queue.show();
System.out.println("---------------------");
// 2. 出队
System.out.println("出队数据:" + queue.get());
queue.show();
System.out.println("---------------------");
// 添加数据
// queue.add(4); // 注意这里会报错因为目前队列不是环形队列,没有取模所以当尾指针移动到数组末尾的时候,因为没有取模,所以会报错
// queue.show();
System.out.println("---------------------");
System.out.println("出队数据:" + queue.get());
System.out.println("出队数据:" + queue.get());
queue.show();
}
}
class ArrayQueue {
private final int maxSize; // 队列最大容量
private int front; // 队列头
private int rear; // 队列尾
private final int[] arr; // 用于存放数据,模拟队列
// 创建队列(构造器)
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
this.arr = new int[maxSize];
this.front = -1;
this.rear = -1;
}
// 判断队列是否满
public boolean isFull() {
return rear == maxSize - 1;
}
// 判断队列是否为空
public boolean isEmpty() {
return rear == front;
}
// 添加数据到队列
public void add(int n) {
// 判断队列是否满
if (isFull()) {
throw new RuntimeException("队列已满,不能加入数据");
}
rear++;
arr[rear] = n;
}
// 获取队列数据,出队列
public int get() {
if (isEmpty()) {
throw new RuntimeException("队列为空,不能取数据");
}
front++;
return arr[front];
}
// 显示队列所有数据
public void show() {
if (isEmpty()) {
System.out.println("队列为空,没有数据");
return;
}
for (int i = front + 1; i <= rear; i++) {
System.out.printf("arr[%d]=%d\n", i, arr[i]);
}
}
}
运行结果:

3.环形队列
模运算是什么(前序知识)?
什么是模运算
模运算,也叫取模或取余,可以理解为"分东西后剩下的部分"。
核心概念: 计算一个数(被除数)被另一个数(除数)整除后,剩下的余数是多少。我们通常用符号 % 来表示。
计算公式: a % b = r
a是被除数b是除数r是余数
如何理解
我们可以用"分苹果"的例子来理解:
-
情况一:能分出去
6 % 4 = 2想象你有 6 个苹果,要分给朋友,每人 4 个。你可以分给 1 个朋友,自己还剩下 2 个。这个"2"就是余数。
-
情况二:不够分
5 % 12 = 5想象你有 5 个苹果,但朋友每人要 12 个。你一个朋友都满足不了,所以一个都没分出去,剩下的还是你原来的 5 个。
生活中的例子
模运算在我们的生活中无处不在,下面是一些常见的例子:
-
时钟
这是最典型的模运算。一个 12 小时制的时钟,就是以 12 为模数。
- 现在是 9 点,再过 5 个小时是几点?
9 + 5 = 14,但时钟上没有 14 点。 - 我们计算
14 % 12 = 2,所以是下午 2 点。 - 当时间"绕"完一圈(12 小时)后,会重新从 0 开始计算。
- 现在是 9 点,再过 5 个小时是几点?
-
星期计算
一周有 7 天,所以星期就是以 7 为模数。
- 今天是星期三,100 天后是星期几?
- 我们计算
100 % 7 = 2。 - 这意味着 100 天包含了 14 个完整的星期,还多出 2 天。从星期三再往后数 2 天,就是星期五。
思路整理
-
front 变量的含义调整 :
front就指向队列的第一个元素,也就是说arr[front]就是队列的第一个元素。front的初始值 =0
-
rear 变量的含义调整 :
rear指向队列的最后一个元素的后一个位置。因为希望空出一个空间做为约定。rear的初始值 =0
-
队列满的条件 :
(rear + 1) % maxSize = front【满】 -
队列为空的条件 :
rear == front【空】 -
队列中有效数据的个数 :
(rear + maxSize - front) % maxSize- 示例:
rear = 1,front = 0
- 示例:
流程图梳理

代码实现
java
package org.example.circularQueue;
public class circularQueueDemo {
public static void main(String[] args) {
circularQueue queue = new circularQueue(4); // 注意这里有效容量如果是3,那么有数组大小应该为4,因为要预留一个位置做运算
queue.add(1);
queue.add(2);
queue.add(3);
queue.show();
System.out.println("---------------------");
System.out.println("出队数据:" + queue.get());
queue.show();
System.out.println("---------------------");
queue.add(4);
queue.show();
}
}
class circularQueue {
int front; // 环形队列的头指针->直接指向队列头部的元素
int rear; // 环形队列的尾指针->直接指向队列尾部的元素的后一个位置
int maxSize; // 环形队列的容量
int[] arr; // 环形队列的数组
public circularQueue(int maxSize) {
this.maxSize = maxSize;
this.front = 0; // 初始值为0
this.rear = 0; // 初始值为0
this.arr = new int[maxSize]; // 注意这里有效容量如果是3,那么有数组大小应该为4,因为要预留一个位置做运算
}
public boolean isEmpty() {
return front == rear;
}
public boolean isFull() {
return (rear + 1) % maxSize == front;
}
public void add(int n) {
if (isFull()) {
throw new RuntimeException("队列已满,不能加入数据");
}
arr[rear] = n;
rear = (rear + 1) % maxSize;
}
public int get() {
if (isEmpty()) {
throw new RuntimeException("队列已空,不能取数据");
}
int value = arr[front];
front = (front + 1) % maxSize;
return value;
}
public void show() {
if (isEmpty()) {
System.out.println("队列已空,没有数据");
return;
}
// 打印头指针和尾指针位置
System.out.println("头指针位置:" + front);
System.out.println("尾指针位置:" + rear);
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;
}
}
运行结果:
