数据结构->1.稀疏数组,2.数组队列(没有取模),3.环形队列

📝目录

1.稀疏数组

稀疏数组的应用场景

在存储 棋类游戏(例如五子棋)棋盘数据 时,棋盘上的黑子和白子通常只占据少量位置,而棋盘的大部分位置都是空的。

一种直观的实现方式是:

使用 二维数组 来表示整个棋盘。例如:

  • 没有棋子的地方记录为 0
  • 黑子记录为 1
  • 白子记录为 2

这样就可以得到一个用于保存棋盘状态的二维数组。

但是这种方式存在一个问题:

由于棋盘上大部分位置都是空的(值为 0),二维数组中会存储 大量没有实际意义的数据 ,从而造成 存储空间的浪费

为了解决这个问题,可以使用 稀疏数组(Sparse Array) 来对数据进行压缩存储,只记录 有意义的数据(非 0 元素)的位置和值 ,从而减少存储空间的占用。

稀疏数组基本介绍

当一个数组中大部分元素为 0 ,或者为 同一个值 的数组时,可以使用 稀疏数组 来保存该数组。

稀疏数组的处理方法

  1. 记录数组一共有 几行几列 ,以及 有多少个不同的值
  2. 具有不同值的元素的行、列及值 记录在一个 小规模的数组 中,从而 缩小程序的规模

二维数组(暂时用表格表示)

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

二维数组转稀疏数组的思路

  1. 遍历原始的二维数组,得到有效数据的个数 sum
  2. 根据 sum 就可以创建稀疏数组 sparseArr int[sum+1][3]
  3. 将二维数组的有效数据存入到稀疏数组

稀疏数组转原始的二维数组的思路

  1. 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如上面的 chessArr2 = int[11][11]
  2. 在读取稀疏数组后几行的数据,并赋给原始的二维数组即可。

代码实现

☕️ 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 = -1front = -1,队列为空。
  • 中间图:添加数据后,rear 向后移动(如 rear=3),front 仍为 -1
  • 右侧图:删除数据后,front 向后移动(如 front=2),rear 保持为 3

数组示意图核心概念

  • MaxSize:队列的最大容量。
  • front :指向队列头部,初始值为 -1,指向队列得第一个数据的前一个位置。
  • rear :指向队列尾部,初始值为 -1,指向队列尾部,是包含队列尾部内各数据的。
  • 数据存入时,rear 向后移动;数据取出时,front 向后移动。

入队操作思路梳理(addQueue)

处理步骤

  1. 移动指针 :将尾指针后移:rear + 1

  2. 判断与存入

  • 若尾指针 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 个。

生活中的例子

模运算在我们的生活中无处不在,下面是一些常见的例子:

  1. 时钟

    这是最典型的模运算。一个 12 小时制的时钟,就是以 12 为模数。

    • 现在是 9 点,再过 5 个小时是几点? 9 + 5 = 14,但时钟上没有 14 点。
    • 我们计算 14 % 12 = 2,所以是下午 2 点。
    • 当时间"绕"完一圈(12 小时)后,会重新从 0 开始计算。
  2. 星期计算

    一周有 7 天,所以星期就是以 7 为模数。

    • 今天是星期三,100 天后是星期几?
    • 我们计算 100 % 7 = 2
    • 这意味着 100 天包含了 14 个完整的星期,还多出 2 天。从星期三再往后数 2 天,就是星期五。

思路整理

  1. front 变量的含义调整
    front 就指向队列的第一个元素,也就是说 arr[front] 就是队列的第一个元素。

    • front 的初始值 = 0
  2. rear 变量的含义调整
    rear 指向队列的最后一个元素的后一个位置。因为希望空出一个空间做为约定。

    • rear 的初始值 = 0
  3. 队列满的条件
    (rear + 1) % maxSize = front 【满】

  4. 队列为空的条件
    rear == front 【空】

  5. 队列中有效数据的个数
    (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;
    }


}

运行结果:

相关推荐
光影少年2 小时前
数组去重方法
开发语言·前端·javascript
我命由我123452 小时前
浏览器的 JS 模块化支持观察记录
开发语言·前端·javascript·css·html·ecmascript·html5
SilentSlot2 小时前
【数据结构】Hash
数据结构·算法·哈希算法
软件开发技术深度爱好者2 小时前
用python + pillow实现GUI界面图片GUI处理工具
开发语言·python
weyyhdke2 小时前
基于SpringBoot和PostGIS的省域“地理难抵点(最纵深处)”检索及可视化实践
java·spring boot·spring
ILYT NCTR2 小时前
【springboot】Spring 官方抛弃了 Java 8!新idea如何创建java8项目
java·spring boot·spring
weixin_425023002 小时前
PG JSONB 对应 Java 字段 + MyBatis-Plus 完整实战
java·开发语言·mybatis
是娇娇公主~3 小时前
Lambda表达式详解
数据结构·c++
leaves falling3 小时前
C++ string 类:从入门到模拟实现
开发语言·c++