02 稀疏数组和队列

1 稀疏数组

1.1 引入

棋盘
上面的这个棋盘,如何进行存储呢?

最简单的就是一个二维数组,1表示黑子,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,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)

因为该二维数组的很多值是默认值 0 ,因此记录了很多没有意义的数据 =====> 就可以采用稀疏数组

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

1.2 何谓稀疏数组

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

1.2.1 稀疏数组的处理方法:
  • 记录数组一共有几行几列,有多少个不同的值
  • 把具有不同值的元素的行列以及记录在一个小的规模的数组里,从而缩小程序的规模
    稀疏数组
1.2.2 稀疏数组的转换思路
二维数组转稀疏数组
  1. 遍历二维数组,得到有效的数据个数sum
  2. 根据sum就可以创建一个稀疏数组sparseArr int[sum+1][3]
  3. 把二维数组的有效数据存入稀疏数组

比如刚刚的那个棋盘:是一个11*11的二维数组,就可以变成下面的稀疏数组:

rowcolval
011112
1121
2232
稀疏数组转二维数组
  1. 先读取稀疏数组的第一行数据,根据第一行数据,创建原始的二维数组,比如上面棋盘:chessArr = int[11][11]
  2. 在读取稀疏数组的后几行的数据,赋值给原始的数组

1.3 代码实现

以刚刚的棋盘为例,实现一下上面的思路

package study.wyy.struct.sparsearr;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2021/1/4 9:47 下午
 * 演示
 */
public class SparesArrayDemo {

    public static void main(String[] args) {
        // 创建一个原始的二维数组
        // 0: 表示没有棋子, 1 表示 黑子 2 表蓝子
        int sourceArr[][] = new int[11][11];
        sourceArr[1][2] = 1;
        sourceArr[2][3] = 2;
        // 输出
        System.out.println("~~~~~输出原始二维数组~~~~");
        for (int[] row : sourceArr) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            // 换个行
            System.out.println();
        }
        System.out.println("~~~~~输出原始二维数组end~~~~");
        /***********************二维数组转稀疏数组************/
        System.out.println("/***********************二维数组转稀疏数组************/");
        // 1 遍历二维数组统计出二维数组中有效数字的个数,本场景下就是非零数字的个数
        int sum = 0;
        for (int[] row : sourceArr) {
            for (int data : row) {
                if (data != 0) {
                    sum++;
                }
            }
        }
        System.out.println("二维数组中有效数字个数是:" + sum);
        // 2 遍历二维数组,进行转换
        // 定义稀疏数组:
        int sparesArray[][] = new int[sum + 1][3];
        // 第一行记录的是二维数组的形状
        // 第一行的第一个元素记录的是原数组的行数
        sparesArray[0][0] = sourceArr.length;
        // 第一行的第2个元素记录的是原数组的列数
        sparesArray[0][1] = sourceArr[0].length;
        // 第一行的第3个元素记录的是原数组的有效数字个数
        sparesArray[0][2] = sum;
        // 遍历二维数组,将有效的数字存放到稀疏数组中
        // count 用于记录第几个有效数字
        int count = 0;
        for (int i = 0; i < sourceArr.length; i++) {
            for (int j = 0; j < sourceArr[i].length; j++) {
                if (sourceArr[i][j] != 0) {
                    count++;
                    // 每一行的第一个存放的是有效数字在原始二维数组中的所在行数
                    sparesArray[count][0] = i;
                    // 每一行的第2个存放的是有效数字在原始二维数组中的所在列数
                    sparesArray[count][1] = j;
                    // 每一行的第一个存放的是有效数字的值
                    sparesArray[count][2] = sourceArr[i][j];
                }
            }
        }
        // 输出稀疏数组
        System.out.println("~~~~~输出稀疏数组~~~~");
        for (int[] row : sparesArray) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            // 换个行
            System.out.println();
        }
        System.out.println("~~~~~输出稀疏数组 end~~~~");

        /***********************稀疏数组转二维数组***********/
        System.out.println("/***********************稀疏数组转二维数组************/");
        // 1 定义一个二维数组
        // 稀疏数组的第一行记录了二维数组的形状
        int rows = sparesArray[0][0];
        int columns = sparesArray[0][1];
        int targetArray[][] = new int[rows][columns];

        // 遍历稀疏数组,进行数据恢复(从第一行开始遍历)
        for (int i = 1; i < sparesArray.length; i++) {
            int row = sparesArray[i][0];
            int column =  sparesArray[i][1];
            int data =  sparesArray[i][2];
            targetArray[row][column] = data;
        }

        System.out.println("~~~~~输出恢复二维数组~~~~");
        for (int[] row : targetArray) {
            for (int data : row) {
                System.out.printf("%d\t", data);
            }
            // 换个行
            System.out.println();
        }
        System.out.println("~~~~~输出恢复二维数组end~~~~");
    }
}

2 队列

2.1 队列介绍

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

2.2 数组模拟队列

2.2.1 思路分析

队列:先进先出,也就是从队尾添加数据,队首取出数据

  • 添加数据的时候,从队尾开始添加
  • 取数据肯定是从队首取,保证最先进去的数据,才可以先被取出来
思路一:

添加数据图解:

在这里插入图片描述
总结:需要定义一个标记,指向队尾,队尾所在的位置一定是最后一个数据所在的位置,在添加数据的时候,先将队尾标记后移,再将数据放到队尾(因为初始化的时候,将指向队尾的标记是放在-1处,添加的时候需要先进行后移)

取出数据图解:

在这里插入图片描述
总结:

  • 定义一个标记用于标记队首的位置,指向的是队首的前一个位置,取数据的时候,先后移标记,然后取出数据
  • 队列是否已经满了,当队尾指针等于max(数组容量)-1的时候,也就是说队尾指针已经移动到数组索引的最后位置
  • 队列是否为空了,很简单当队首和队尾指向同一处时
思路二

在这里插入图片描述
在这里插入图片描述

  • 定义一个标记用于标记队首的位置,指向的是队首的前一个位置,取数据的时候,先取出数据,后移标记
  • 队列是否已经满了,当队尾指针等于max(数组容量)的时候
  • 队列是否为空了,很简单当队首和队尾指向同一处时
2.2.2 代码实现

定义一个接口

package study.wyy.struct.queue;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2021/1/5 9:33 下午
 */
public interface Queue<T> {

    /****
     * 添加数据
     * @param t  数据
     */
    void add(T t);

    /****
     * 获取数据
     * @return
     */
    T get();


    /****
     * 是否是空队列
     * @return
     */
    boolean isEmpty();

    /****
     * 队列是否已满
     * @return
     */
    boolean isFull();

    /****
     * 查看队首数据,并不会取出数据,移动队首指针
     * @return
     */
    T peek();


}

思路一
package study.wyy.struct.queue.array;

import study.wyy.struct.queue.Queue;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2021/1/5 9:37 下午
 */
public class ArrayQueue<T> implements Queue<T> {

    // 队列的大小
    private final int max;

    private int size;
    // 指向队首的标记
    private int front;

    // 指向队尾的标记
    private int real;

    private Object[] data;

    public ArrayQueue(int max) {
        if (max <= 0) {
            // 简单校验入参
            throw new RuntimeException("queue max size can not < 0");
        }
        this.max = max;
        this.front = -1;
        this.real = -1;
        data = new Object[max];
    }

    @Override
    public void add(T t) {
        // 1 如果队列满了,不能再添加数据了
        if(this.isFull()){
            throw new RuntimeException("queue has full");
        }
        // 移动队尾
        this.real++;
        // 添加数据
        this.data[real] = t;
    }

    @Override
    public T get() {
        if(isEmpty()){
            throw new RuntimeException("queue is empty");
        }
        // 移动队首
        this.front++;
        T t = (T) this.data[front];
        return t ;
    }


    @Override
    public boolean isEmpty() {
        // 队列是否为空了,很简单当队首和队尾指向同一处时
        return this.front==this.real;
    }

    @Override
    public boolean isFull() {
        // 队列是否已经满了,当队尾指针等于max-1的时候,也就是说队尾指针已经移动到数组索引的最后位置
        return this.real == this.max-1;
    }

    @Override
    public T peek() {
        if(isEmpty()){
            throw new RuntimeException("queue is empty");
        }
        // 这里就不能移动队首,只是看数据
        T t = (T) data[front+1];
        return t ;
    }
}


@org.junit.Test
public void test1(){
     Queue<Integer> queue = new ArrayQueue<Integer>(3);
     queue.add(1);
     queue.add(2);
     queue.add(3);
     try {
         // 这里会抛出异常
        queue.add(4);
     }catch (Exception e){
         System.out.println(e.getMessage());
     }
     Integer data = queue.get();
     Assert.that(data.equals(1),"数据不对");
      data = queue.get();
     Assert.that(data.equals(2),"数据不对");
      data = queue.get();
     Assert.that(data.equals(3),"数据不对");
     try {
         // 这里会抛出异常
         queue.get();
     }catch (Exception e){
         System.out.println(e.getMessage());
     }

 }
思路二实现
package study.wyy.struct.queue.array;

import study.wyy.struct.queue.Queue;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2021/1/5 9:37 下午
 */
public class ArrayQueue2<T> implements Queue<T> {

    // 队列的大小
    private final int max;

    private int size;
    // 指向队首的标记
    private int front;

    // 指向队尾的标记
    private int real;

    private Object[] data;

    public ArrayQueue2(int max) {
        if (max <= 0) {
            // 简单校验入参
            throw new RuntimeException("queue max size can not < 0");
        }
        this.max = max;
        this.front = 0;
        this.real = 0;
        data = new Object[max];
    }

    @Override
    public void add(T t) {
        // 1 如果队列满了,不能再添加数据了
        if (this.isFull()) {
            throw new RuntimeException("queue has full");
        }
        // 添加数据
        this.data[real] = t;
        // 移动队尾
        this.real++;
    }

    @Override
    public T get() {
        if (isEmpty()) {
            throw new RuntimeException("queue is empty");
        }

        T t = (T) this.data[front];
        // 移动队首
        this.front++;
        return t;
    }


    @Override
    public boolean isEmpty() {
        // 队列是否为空了,很简单当队首和队尾指向同一处时
        return this.real == this.front;
    }

    @Override
    public boolean isFull() {
        // 队列是否已经满了,当队尾指针等于max(数组容量)的时候
        return this.real==this.max;
    }

    @Override
    public T peek() {
        if (isEmpty()) {
            throw new RuntimeException("queue is empty");
        }
        // 这里就不能移动队首,只是看数据
        T t = (T) data[front + 1];
        return t;
    }
}


@org.junit.Test
public void test2(){
    Queue<Integer> queue = new ArrayQueue2<Integer>(3);
    queue.add(1);
    queue.add(2);
    queue.add(3);
    try {
        // 这里会抛出异常
        queue.add(4);
    }catch (Exception e){
        System.out.println(e.getMessage());
    }
    Integer data = queue.get();
    Assert.that(data.equals(1),"数据不对");
    data = queue.get();
    Assert.that(data.equals(2),"数据不对");
    data = queue.get();
    Assert.that(data.equals(3),"数据不对");
    try {
        // 这里会抛出异常
        queue.get();
    }catch (Exception e){
        System.out.println(e.getMessage());
    }

}

问题

  • 目前这个队列只能一次,不能重复使用,可运行下面的测试代码观察该问题

    @org.junit.Test
    public void test2(){
        Queue<Integer> queue = new ArrayQueue2<Integer>(3);
        queue.add(1);
        queue.add(2);
        queue.add(3);
        try {
            // 这里会抛出异常
            queue.add(4);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        Integer data = queue.get();
        Assert.that(data.equals(1),"数据不对");
        data = queue.get();
        Assert.that(data.equals(2),"数据不对");
        data = queue.get();
        Assert.that(data.equals(3),"数据不对");
        try {
            // 这里会抛出异常
            queue.get();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        // 这里数据已经取完了,但是我存数据还是会告知队列已满已满
        try {
            // 这里会抛出异常
            queue.add(5);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }

    }

2.3 数组模拟环形队列

我更习惯思路二的思维方式,就在思路二的基础上进行扩展:

2.3.1 思路图解

1
2.
2
3. 3
4.
4

总结:

  • 队列判空:front == rear
  • 队列判满:(real+1)%n = front (n为数组的大小)
    • 数组还剩一个位置,也就是说当real后移一下(real+1)的时候队首和队尾就重合
    • 队列最多max=n-1个元素(n = max +1)
  • 队列中有效元素个数:
    • 当 rear 比 front 大时,即 (rear -front) > 0 ,(rear -front) 即是队列元素个数
    • 当 rear 比 front 小时,即 (rear -front) < 0 ,(rear -front) 表示数组还差多少个元素存满(负数),(rear + n - front) 即是队列元素个数
    • 综上:(rear + n - front) % n

**甚至都不需要去计算,可以定义一个变量(size)来记录队列中实际的元素个数,通过这个来判断是否已满,是否为空 **

  • 队列满判断就很简单了:size=max
  • 队列空判断就很简单了:size=0

基于此,接口中,完全可以声明一个方法,返回队列中元素的个数

package study.wyy.struct.queue;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2021/1/5 9:33 下午
 */
public interface Queue<T> {

    /****
     * 添加数据
     * @param t  数据
     */
    void add(T t);

    /****
     * 获取数据
     * @return
     */
    T get();


    /****
     * 是否是空队列
     * @return
     */
    boolean isEmpty();

    /****
     * 队列是否已满
     * @return
     */
    boolean isFull();

    /****
     * 查看队首数据,并不会取出数据,移动队首指针
     * @return
     */
    T peek();

    /**
     *  返回队列中元素的个数
     */
    int size();

}

还有个问题:
real能一直++后移吗,超了数组下界的时候,放元素就会抛出数组越界异常啊

  • real=n-1(索引从0开始)的时候,在进行后移的时候必须对数组大小n取余 real = (real+1)%n
    • 比如,上面real=4的时候,real后移就要移动到0的位置:(4+1)% 5 = 0
    • real =2 (<n-1)后移的时候的同样也满足取余,这就是取余的作用:(3 +1)% 5 = 4

队首也是如此,后移的时候不能超出数组下界

这就是取余的作用:

  • m对n取余的结果一定在 [0,n-1]
2.3.2 代码实现
package study.wyy.struct.queue.array;

import study.wyy.struct.queue.Queue;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2021/1/5 9:37 下午
 * 环形队列
 */
public class ArrayQueue3<T> implements Queue<T> {

    // 队列中最多可以放多少个元素,数组的大小-1
    private final int max;
    // 队首指针
    private int front;
    // 队尾指针
    private int real;

    private Object[] data;

    // 队列中元素的个数
    private int size;

    public ArrayQueue3(int max) {
        this.max = max;
        // 数组的大小 = max+1
        data = new Object[max + 1];
        // size初始化0
        this.size = 0;
        front = 0;
        real = 0;
    }


    @Override
    public void add(T t) {
        if(isFull()){
            throw new RuntimeException("队列已满");
        }
        // 放元素,放到队尾所在的索引处
        data[real] = t;
        // 后移, 需要考虑的是real不能一直++,不能超了数组下界
        // 是个循环,要取余
        real = (real+1) % (max + 1);
        size++;

    }

    @Override
    public T get() {
        if(isEmpty()){
            throw new RuntimeException("队列为空");
        }
        // 取出队首所在索引位置
        T t = (T) data[front];
        // 后移,和real一样
        front = (front + 1) % (max + 1);
        size--;
        return t;
    }

    @Override
    public boolean isFull() {
        // 队列满判断就很简单了:size=max
        return this.size == this.max;
    }

    @Override
    public boolean isEmpty() {
        // 队列空判断就很简单了:size=0
        return size == 0;
    }

    @Override
    public T peek() {
        // 判断
        if (isEmpty()) {
            throw new RuntimeException("队列空的,没有数据~~");
        }
        return (T) data[front];
    }

    @Override
    public int size() {
        return this.size;
    }
}

@org.junit.Test
public void test3(){
   Queue<Integer> queue = new ArrayQueue3<Integer>(3);
   queue.add(1);
   queue.add(2);
   queue.add(3);
   try {
       // 这里会抛出异常 队列已满
       queue.add(4);
   }catch (Exception e){
       System.out.println(e.getMessage());
   }
   Assert.that(queue.size()==3,"数据不对");
   Integer data = queue.get();
   Assert.that(data.equals(1),"数据不对");
   Assert.that(queue.size()==2,"数据不对");
   data = queue.get();
   Assert.that(data.equals(2),"数据不对");
   Assert.that(queue.size()==1,"数据不对");
   data = queue.get();
   Assert.that(data.equals(3),"数据不对");
   Assert.that(queue.size()==0,"数据不对");
   try {
       // 这里会抛出异常 队列为空
       queue.get();
   }catch (Exception e){
       System.out.println(e.getMessage());
   }
   // 这里数据已经取完了,在放数据
   try {
       // 这里不会抛出异常
       queue.add(5);
   }catch (Exception e){
       System.out.println(e.getMessage());
   }
   Assert.that(queue.size()==1,"数据不对");
   data = queue.get();
   Assert.that(data.equals(5),"数据不对");
   Assert.that(queue.size()==0,"数据不对");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值