2021-10-13 一、数组与队列

1. 稀疏数组

1.1 何为稀疏数组

  • 稀疏数组,首先把稀疏拆出来,理解为写程序的你,头发日渐稀疏,渐少。
  • 比如一个棋盘,可以用二位数组表示,这个棋牌(只有三种数据,0为没有棋子,1为黑棋,2为白棋),我们可以只提取出黑白棋的位置构成稀疏数组。

1.2 应用场景

  • 比如 一些量大,且多数为重复值的时候,可以使用(棋牌,地图等等)

1.3 图文解析

在这里插入图片描述

  1. 首先创建新的数组来记录数组有几行、几列、有多少不同的值
  2. 然后把元素中的列、行、值(棋子的颜色)记录在一个小规模的数组中,从而缩小程序的规模。

1.4 设计思路

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

  1. 首先注意的是,行和列这里设计都从零开始。
  2. 遍历原始二位数组,获取到有效值【1(黑棋)、2(白棋)】的个数 sum
  3. 再根据 sum 创建出稀疏数组sparsearray[sum + 1][3]
    注意: 因为稀疏数组的第一个一维数组要保存原始数组的行、列、有效值个数,所以稀疏数组的一维数组个数为sum+1
  4. 将原始二维数组的有效数据存放到稀疏数组中
    (有效数据:元素所在行,元素所在列,元素的值【1,2】)

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

  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); // 得到的有效值总个数:3

        // 步骤二:稀疏数组的有效数据初始化
        int[][] sparseArray = new int[sum + 1][3];
        // 给稀疏数组的有效数据 初始化
        sparseArray[0][0] = 11;
        sparseArray[0][1] = 11;
        sparseArray[0][2] = sum;

        // 遍历原始二位数组,将有效棋子(非0)放入sparseArray
        // i表示 行,j表示列
        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) {
        // 创建一个原始棋牌 大小为 11*11
        // 1 黑棋子 , 2 白棋子 ,0 无棋子
        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 队列基本说明

  1. 队列是一个有序列表,可以用数组或者链表实现
  2. 遵循先进先出原则,先存入数据先取出,后存入后取出

2.2 数组模拟队列

在这里插入图片描述
解析:

  1. 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图, 其中 maxSize 是该队列的最大容量
  2. 因为队列的输出、输入是分别从两端来处理,因此需要两个变量 front及 rear分别记录队列两端的下标,front 会随着数据输出而改变,而 rear则是随着数据输入而改变

2.3 设计思路

讲解入队出队的两个操作(前置条件:front和rear的初始值都是指向队列第一个数的前面,也就是front=rear=-1
当我们将数据存入队列时称为addQueueaddQueue 的处理需要有两个步骤:

  1. 首先判断队列是否已满(rear == maxSize - 1 表示队列已满),如果满则无法将数据存入队列。
  2. 然后将尾指针往后移:rear + 1,并将数据放入到队列中。

当我们将数据从队列取出(出队)称为getQueuegetQueue的处理需要有两个步骤:

  1. 首先判断队列是否为空(rear == front 表示队列为空),如果为空则无法取出数据。
  2. 然后将头指针往后移: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;// 指向队列头部,初始化的 front 是指向队列头的前一个位置
        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++; // 让 rear 后移
        arr[rear] = n;
    }

    // 获取队列数据(出队列)
    public int getQueue() {
        // 判断队列是否为空
        if (isEmpty()) {
            // 通过异常抛出 null
            throw new RuntimeException("队列为空,无法取数据~~~");
        }
        front++;// front后移
        int result = arr[front]; // 取出当前出队的值
        arr[front] = 0;// 将取出后的位置重置为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()) {
            // 通过异常抛出 null
            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) {
                        // TODO: handle exception
                        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 环形队列设计思路(实现方式有很多,仅供参考)

  1. front 变量调整: front的初始值 = 0,直接指向队列的第一个数。
  2. rear 变量的调整:rear 的初始值 = 0。始终指向队列最后一个数的后一个位置(这里我们入队的逻辑是先入队再++)
  3. 判断队列满的公式是 (rear + 1) % maxSize == front 这里希望空出一个空间做为约定,用于判断队列满。例如数组长度为5,那么存放4个数据就判断他已经满了(必须要浪费掉一个空间,不然没办法区分为空或者是满的情况)
  4. 判断队列为空的公式, rear == front,(如果front到达rear的位置,就表示队列取完了)
  5. 队列中有效的数据的个数 (rear + maxSize - front) % maxSize
    关于第五点的公式推导,正常情况下求有效数据的个数应该为rear-front,但是环形队列可能存在rear的下标小于front的情况,所以在这里加上maxSize保证rear一定大于front,然后再进行取模,达到求出有效数据的效果。

2.7 代码实现

// 环形队列实体类
public class CircleArrayQueue {
    // 数组最大的容量
    private final int maxSize;
    private final int[] arr;
    // front 就指向队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素
    private int front;
    // rear 指向队列的最后一个元素的后一个位置
    private int rear;

    // 创建队列构造器
    public CircleArrayQueue(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[arrMaxSize];
        // front rear 默认值为0,声明的默认值也为0,所以不需要重复赋值
    }

    // 判断队列是否满
    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 = (rear + 1) % maxSize;
    }

    // 获取队列数据(出队列)
    public int getQueue() {
        // 判断队列是否为空
        if (isEmpty()) {
            // 通过异常抛出 null
            throw new RuntimeException("队列为空,无法取数据~~~");
        }
        // front 是指向队列的第一个元素
        // 先 将 front 值保存到临时变量,用于返回
        // 然后再进行后移
        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()) {
            // 通过异常抛出 null
            throw new RuntimeException("队列为空,无法取数据~~~");
        }
        // 显示
        return arr[front];
    }
}

// 测试类
public class Demo3 {
    public static void main(String[] args) {
        //创建一个队列
        CircleArrayQueue queue = new CircleArrayQueue(4);// 有效数据为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) {
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h': //查看队列头的数据
                    try {
                        int res = queue.headQueue();
                        System.out.printf("队列头的数据是%d\n", res);
                    } catch (Exception e) {
                        // TODO: handle exception
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e': //退出
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }

        System.out.println("程序退出~~");
    }
}

总目录下一篇
数据结构篇(Java)目录二、链表
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值