01 稀疏数组和队列 【数据结构与算法学习笔记(Java)】

数据结构与算法(Java实现)

我的学习资料:
视频:尚硅谷Java数据结构与java算法(Java数据结构与算法)
书籍:《大话数据结构》
笔记中包括学习的内容,代码,同时自己总结了知识点速记(部分会带页内跳转,可点击跳转)供快速回顾和记忆学到的知识点。

0.线性结构和非线性结构

  • 数据结构的底层存储方式只有两种:数组(顺序存储)和链表(链式存储)。

0.1 线性结构

特点:数据元素之间存在一对一的线性关系

顺序存储的线性表叫做顺序表,顺序表中的存储元素是连续的
链式存储的线性表叫做链表,链表中的存储元素不一定是连续的

线性结构常见的有:数组,队列,列表和栈。

0.2 非线性结构

常见的有:二维数组,多维数组,广义表,树,图

一、稀疏( sparsearray)数组

1.1 实际需求

在这里插入图片描述
对于一个五子棋盘上棋子的情况,可以使用二维数组记录,0表示无棋子,1和2分别表示两种颜色的棋子,因为该二维数组的很多值是默认值 0 ,因此记录了很多没有意义的数据,我们将其转为稀疏数组进行存储。

1.2 介绍

1.2.1 基本介绍

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

1.2.2 处理方法及举例

稀疏数组的处理方法是:

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

第1行:记录原始二维数组的行数,列数,非0元素(或者说有效数据)的总个数

  • 第1列:原始二维数组的行数
  • 第2列:原始二维数组的列数
  • 第3列:原始二维数组的非0元素的个数

剩余几行:记录原始二维数组非0元素(或者说有效数据)在原始二维数组的下标(横纵坐标)及它的值

  • 第1列:非0元素(有效数据)的横坐标

  • 第2列:非0元素(有效数据)的纵坐标

  • 第3列:非0元素(有效数据)的值

  • 二维数组——>稀疏数组举例
    在下面的棋盘中,原始二维数组规模是6×7,转化为稀疏数组规模变为9×3(注意数组的坐标是从0开始的,但我们可能习惯描述第几行是从第1行开始的)
    1. 搞定第一行
    因为原始规模6*7,即【6行7列8个非零值】,因此在稀疏数组第1行(注意行坐标是0)依次填入【6,7,8】
    2. 剩余的元素按照“行”的方式遍历,依次填入。
    比如第一个非零元素是22,横纵坐标【0,3】因此在稀疏数组下一行依次填入它的坐标及它的值【0,3,22】
    接下来按照行遍历,应该依次填入的元素是15,11,17,-6,39,91,28,完成稀疏数组。
    在这里插入图片描述

1.3 应用实例

1.3.1实例说明

  • 使用稀疏数组,来保留类似前面的二维数组(棋盘、地图等等)
  • 把稀疏数组存盘,并且可以重新恢复原来的二维数组数
    在这里插入图片描述

二维数组——>稀疏数组的思路

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

稀疏数组——>原始的二维数组的思路

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

1.3.2 代码实现

package dataStructures;

import java.util.Arrays;

public class GoBang {

    public static int  BTsize=11;
    int[][] realCheckBoard = new int[BTsize][BTsize];
    int[][] sparseArray;
    int count=0;

    public static void main(String[] args) {

        GoBang gb = new GoBang();
        gb.realCheckBoard[1][2]=1;
        gb.realCheckBoard[2][3]=2;
        
        System.out.println();

        for (int[]a1:gb.realCheckBoard
             ) {
            System.out.println();
            
            for (int a2:a1
                 ) {
                System.out.print(a2+" ");
            }

        }

        int[][] array1 =gb.realCheckBToSparseArray();
        int[][] array2 = gb.sparseArrayToRealCheckBT();

    }

    public GoBang(){

    }

    public GoBang(int[][] realCheckBoard) {
        this.realCheckBoard = realCheckBoard;
    }

    public int[][] realCheckBToSparseArray(){
        System.out.println();
        System.out.println("得到稀疏数组为:");

        //遍历二维数组找到非0元素值的个数,用count表示
        for (int i=0;i<realCheckBoard.length;i++){
            for (int j=1; j<realCheckBoard[i].length;j++){
                if (realCheckBoard[i][j]!=0){
                    count++;
                }
            }
        }

            sparseArray = new int[count + 1][3];
            sparseArray[0][0] = BTsize;
            sparseArray[0][1] = BTsize;
            sparseArray[0][2] = count;

        if (count!=0) {
            //遍历二维数组
            for (int i = 0; i < realCheckBoard.length; i++) {
                for (int j = 1; j < realCheckBoard[i].length; j++) {
                    if (realCheckBoard[i][j] != 0) {
                        sparseArray[i + 1][0] = i;
                        sparseArray[i + 1][1] = j;
                        sparseArray[i + 1][2] = realCheckBoard[i][j];
                    }
                }
            }
        }

        for (int i=0;i<sparseArray.length;i++){
            System.out.printf("%d\t%d\t%d\t\n",sparseArray[i][0],sparseArray[i][1],sparseArray[i][2]);
        }
        return sparseArray;
    }

    public  static void printArray(int[][] array){
        for (int i=0; i<array.length;i++){
            System.out.println("=====");
            for (int j =0;j<array[i].length;j++){
                System.out.print(array[i][j]+"  ");
            }
        }

    }
    public int[][] sparseArrayToRealCheckBT(){
        System.out.println();
        System.out.println("根据稀疏矩阵转换的二维数组");
        for (int i=1; i<this.sparseArray.length;i++){
            this.realCheckBoard[ this.sparseArray[i][0] ][ this.sparseArray[i][1] ]= this.sparseArray[i][2];
        }
        
        //for each结构遍历二维数组
        for (int[] a1:realCheckBoard
             ) {
            System.out.println();
            for (int a2:a1
                 ) {
                System.out.print(a2+" ");
            }

        }
        return this.realCheckBoard;
    }
}

1.4稀疏数组知识点速记

  • 稀疏数组:当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。稀疏数组N行3列。第一行记录行、列、有效数据个数,剩下的行记录:行遍历顺序下有效数据的横纵坐标和它的具体值。
  • 编程思路:
    二维——>稀疏:遍历二维找非0值个数,根据个数确定稀疏数组行数(列数固定为3),再次遍历二维数组将非0的横纵坐标记录入稀疏数组。
    稀疏——>二维:根据第一行初始化二维矩阵的维数,然后根据剩下行的坐标和具体值还原二维数组。
  • 技巧:
    双重for each遍历二维数组,printf方法(加入\t,\n)规范化输出形式。

二、队列

2.1 实际需求

银行排队的叫号系统
在这里插入图片描述

2.2 介绍

2.2.1 基本介绍

  1. 队列是一个有序列表,可以用数组或是链表来实现。

  2. 遵循先入先出(FIFO,first in first out) 的原则。即:先存入队列的数据,要先取出。后存入的要后取出。
    在这里插入图片描述

  3. 示意图:(使用数组模拟队列示意图)
    在这里插入图片描述

图2.2.1-1 使用数组模拟队列示意图

图1表示Queue类,maxSize 是该队列的最大容量,rear表示队尾,初始化值为-1,front表示队首的前一个位置**,初始化值为-1。
图2表示,当向队列放4个数据时,front没有变化,rear增加变为3。
图3表示,当从队列取2个数据时,front增加变为2,rear保持不变。
放入数据,rear变化,取出数据,front变化

2.2.2 处理方法及举例

用数组模拟队列:

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

举例(思路分析):
成员变量:
maxSize :该队列的最大容量
rear:表示队尾,初始值为-1
front:表示队首的前一个位置,初始值为-1
arr[][]:用于模拟队列的数组

成员方法:
判断为空:isFull
判断为满:isEmpty
加入元素:addQueue
出队列操作:getQueue
显示(遍历)队列的情况:showQueue
查看队列头元素:headQueue

基本操作:
判断队列为空: front==rear
判断队列满:rear=MaxSize-1
队列元素个数:rear-front
队列入队:先判状态,队列不满才能入,arr[++rear] = value
队列出队:先判状态,队列不空才能出,return arr[++front]

2.2.3 代码实现

package dataStructures.Queue;

import java.util.Scanner;

public class ArrayQueueDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ArrayQueue a =new ArrayQueue(4);
        boolean loop =true;
        char key =' ';//接收用户输入

        while(loop){
            System.out.println("请选择你的操作:");
            System.out.println("a(add):添加元素");
            System.out.println("g(get):取出元素");
            System.out.println("s(show):显示队列元素");
            System.out.println("h(head):显示头元素");
            System.out.println("e(exit):退出");

            key=scanner.next().charAt(0);

            switch (key){
                case 'a':
                    System.out.println("请输入进入队列的数:");
                    try {
                        a.addQueue(scanner.nextInt());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'g':
                    try {
                        int get=a.getQueue();
                        System.out.printf("取出的数是:%d",get);

                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 's':
                    try {
                        a.showQueue();
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int head = a.headQueue();
                        System.out.printf("队首元素是:%d",head);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }

                    break;
                case 'e':
                    loop=false;
                    break;
            }
        }
        System.out.println("感谢使用,退出程序");

    }
}

class ArrayQueue{

    private int MaxSize;
    private int front;
    private int rear;
    private int[] arr;

    public ArrayQueue(int maxSize) {
        MaxSize = maxSize;
        front = -1;
        rear = -1;
        arr = new int[MaxSize];
    }

    public boolean isFull(){
        return rear == MaxSize-1;
    }

    public boolean isEmpty(){
        return rear == front;
    }

    //添加数据
    public void addQueue(int n){
        if(isFull()){
            throw new RuntimeException("队列满,不能加数据");
        }
        arr[++rear]=n;
    }

    //取出数据
    public int getQueue(){
        if(isEmpty()){
            throw new RuntimeException("队列空,不能取数据");
        }

        return arr[++front];
    }

    //显示队列元素
    public void showQueue(){
        if (!isEmpty()){
            System.out.println("队列元素为:");
            for (int i=0;i<arr.length;i++){
                System.out.printf("arr[%d]为:%d\n",i,arr[i]);
            }

        }else{
            throw new RuntimeException("对列为空");
        }
    }

    //查看头元素
    public int headQueue(){
        if(!isEmpty()){
            return arr[front+1];
        }else{
            throw new RuntimeException("对列为空");

        }

    }

}

存在的问题:
目前数组使用一次就不能用, 没有达到复用的效果
理解:
(注意:这里front指向队首,rear指向队尾的下一个位置)
在这里插入图片描述
将这个数组使用算法,改进成一个列 环形的队列 取模:%,改进见2.3节

2.3 改进:使用数组模拟环形队列(循环队列)

2.3.1 改进思路

解决前面的假溢出的问题,就是后面满了,就再从头开始,也就是头尾相接的循环,将数组看做是一个环形的,我们把队列的这种头尾相接的顺序存储结构称为循环队列(通过 取模的方式来实现即可,取模运算相当于取余数)。

改进思路理解:
为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向队头元素rear 指针指向队尾元素的下一个位置,这样当front = =rear时,此队列不是还剩一个元素,而是空队列

刚才的例子继续,图4-12-5的 rear可以改为指向下标为0的位置,这样就不会造成指针指向不明的问题了,如图4-12-6所示。
在这里插入图片描述
此时问题又出来了,我们刚才说,空队列时,front等于rear,现在当队列满时,也是 front等于rear,那么如何判断此时的队列究竟是空还是满呢?
办法一是设置一个标志变量flag,当front = = rear,且 flag =0时为队列空,当front = = rear,且 flag=1时为队列满。

(重点)办法二是当队列空时,条件就是 front = rear,当队列满时,我们修改其条件,保留一个元素空间。也就是说,队列满时,数组中还有一个空闲单元
例如图4-12-8所示,我们就认为此队列已经满了,也就是说,我们不允许图4-12-7的右图情况出现。
在这里插入图片描述

  • 我们重点来讨论第二种方法,由于rear可能比front大,也可能比front小,所以尽管它们只相差一个位置时就是满的情况,但也可能是相差整整一圈。所以若队列的最大尺寸为 maxSize ,那么队列满的条件是(rear+1)%maxSize = = front(取模“%”的目的就是为了整合rear与front的大小为一个问题)

  • 比如上面这个例子,maxSize = 5,图4-12-8的左图中 front=0,而rear=4,(4+1)%5=0,所以此时队列满。

  • 再比如图4-12-8中的右图,front = 2而rear = 1。(1 +1) %5=2,所以此时队列也是满的。而对于图4-12-6,front=2而rear =0,(0+1)%5=1,1≠2,所以此时队列并没有满。

然后我们来考虑队列的长度(队列中有效的数据的个数):

  • 当rear > front时,此时队列的长度为rear-front (比如图4-12-4的右图和4-12-5的左图,)。
  • 当rear < front时,队列长度分为两段,一段是 maxSize -front,另一段是0 + rear,加在一起,队列长度为rear-front + maxSize (如图4-12-6和图4-12-7的左图)。
  • 因此通用的计算队列长度公式为:(rear- front + maxSize )%maxSize

2.3.2 循环队列(环形队列)代码实现

相比前面增加了size()方法,注意

package dataStructures.Queue;

import java.util.Scanner;

public class CiecleArrayQueue {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        CircleQueue a =new CircleQueue(4);
        boolean loop =true;
        char key =' ';//接收用户输入

        while(loop){
            System.out.println("请选择你的操作:");
            System.out.println("a(add):添加元素");
            System.out.println("g(get):取出元素");
            System.out.println("s(show):显示队列元素");
            System.out.println("h(head):显示头元素");
            System.out.println("e(exit):退出");

            key=scanner.next().charAt(0);

            switch (key){
                case 'a':
                    System.out.println("请输入进入队列的数:");
                    try {
                        a.addQueue(scanner.nextInt());
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'g':
                    try {
                        int get=a.getQueue();
                        System.out.printf("取出的数是:%d",get);

                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 's':
                    try {
                        a.showQueue();
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int head = a.headQueue();
                        System.out.printf("队首元素是:%d",head);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }

                    break;
                case 'e':
                    loop=false;
                    break;
            }



        }
        System.out.println("感谢使用,退出程序");

    }
}

class CircleQueue{

    private int MaxSize;//数组最大容量
    private int front;//指向队首
    private int rear;//指向最后一个元素的后一个位置
    private int[] arr;//存放队列

    public CircleQueue(int maxSize) {
        MaxSize = maxSize;
        front = 0;
        rear = 0;
        arr = new int[MaxSize];
    }

    public boolean isFull(){
        return (rear+1)% MaxSize == front;
    }

    public boolean isEmpty(){
        return rear == front;
    }

    //添加数据
    public void addQueue(int n){
        if(isFull()){
            throw new RuntimeException("队列满,不能加数据");
        }
        arr[rear]=n;
        rear=(rear+1)% MaxSize;
    }

    //取出数据
    public int getQueue(){
        if(isEmpty()){
            throw new RuntimeException("队列空,不能取数据");
        }
        int key = arr[front];
        front=(front+1)%MaxSize;
        return key;

    }
    //获取队列中元素个数
    public int size(){
        return (rear-front+MaxSize)%MaxSize;
    }
 
    //显示队列元素
    public void showQueue(){
        if (!isEmpty()){
            System.out.println("队列元素为:");

            for (int i=front;i<front+this.size();i++){
                System.out.printf("arr[%d]为:%d\n",i%MaxSize,arr[i%MaxSize]);
            }

        }else{
            throw new RuntimeException("队列为空");
        }
    }

    //查看头元素
    public int headQueue(){
        if(!isEmpty()){
            return arr[front];
        }else{
            throw new RuntimeException("队列为空");

        }

    }

}

2.3.3 循环队列知识点速记

  • 队列(queue):只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
  • 编程思路及技巧:
  1. front指针 : front 指向队首,即队列的第一个元素, 也就是说 arr[front] 就是队列的第一个元素,front 的初始值 = 0
  2. rear指针 :rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定。rear 的初始值 = 0
  3. 队列满: (rear + 1) % maxSize == front 【满】
  4. 队列空: rear == front 【空】
  5. 队列中有效的数据的个数size() :(rear + maxSize - front) % maxSize 点此跳转
  6. 指针后移操作不是简单地累加,而是:(front+1)%maxSize或者(rear+1)%maxSize点此跳转
  7. 显示队列元素,使用for循环实现,条件不能直接粗暴按照数组length来打印,循环中i范围是front——>front+size(),对应数组下标的范围 front%maxSize——>( front+size() )%maxSize
  8. 操作:存元素(注意指针操作),取元素(注意指针操作),元素个数,显示队列元素(注意for循环里的取模技巧),显示头元素。
  • 小tips:
    读取输入的字符调用.next().charAt()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值