1.简介

线性结构和非线性结构

数据结构包括:线性结构非线性结构

线性结构

  1. 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系(如数组:a[0]只有一个元素)
  2. 线性结构有两种不同的存储结构,即顺序存储结构(数组)和链式存储结构(链表)。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的(如数组,在内存中创建的是连续的内存块)
  3. 链式存储的线性表称之为链表,列表中存储的素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息(内存中不是连续的内存块)
  4. 线性结构常见的有:数组、队列、链表和栈

 

非线性结构

  非线性结构包括:二维数组,多维数组,广义表,树结构,图结构

 

1.稀疏数组和队列

  1.1 稀疏数组sparsearray

  

  1.1.1 稀疏数组基本介绍

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

  1.1.2 稀疏数组的处理方法是:
    1. 记录数组一共有几行几列,有多少个不同的值
    2. 把具有不同值的元素的行和列记录在一个小规模的数组中(稀疏数组),从而缩小程序的规模

  

1.1.3 应用实例
  • 使用稀疏数组,来保存类似前面的二维数组(棋盘地图等等)
  • 把稀疏数组存盘,并且可以从新恢复原来的二维数组
  • 整体思路分析:

二维数组转稀疏数组的思维
    1.遍历原始的二维数组,得到有效的数据个数(sum)
    2.根据sum就可以创建稀疏数组sparseArr int[sum+1][3]
        sun+1行(第一行是原始数组的描述:多少行,多少列,有效数字有几个)
        3列:后续有效数字的描述(原始数组中第几行,第几列,有效值是多少)
    3.将二维数组的有效数据存入到稀疏数组中


数组转原始的二维数组思路
    1.先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如chessArr=int[11][11]
    2.再读取稀疏数组的后几行数据,并赋给原始的二维数组即可。
1.1.4 代码实现
package cn.com.sparsearray;

/**
 * 稀疏数组演示
 */
public class SparseArray {
    public static void main(String[] args) {
        /*
            重点1:创建一个原始的二维数组棋盘
            1:黑子
            2:白子
         */
        int chessAr1[][] = new int[11][11];
        chessAr1[1][2] = 1;
        chessAr1[2][3] = 2;
        System.out.println("原始数组===>");
        for (int[] row : chessAr1) {
            for (int data : row) {
                System.out.print(data + "  ");
            }
            System.out.println();
        }
        /*
            重点2:将二维数组转为稀疏数组
         */
        int sum = 0;
        //遍历二维数组,得到有效数据个数
        for (int[] row : chessAr1) {
            for (int data : row) {
                if (data != 0) {
                    sum++;
                }
            }
        }
        //创建对应的稀疏数组:sum+1行(第一行是描述原始二维数组有多少行,多少列,有效数据有几个)
        int sparseArr[][] = new int[sum + 1][3];
        //赋值稀疏数组第一行:描述原始二维数组:多少行,多少列,有效数字个数
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;
        //计数器:因为稀疏数组的行是递增的,这个用来计数
        int count = 0;
        //遍历原始二维数组,并将其转为稀疏数组
        for (int i = 0; i < chessAr1.length; i++) {
            for (int j = 0; j < chessAr1[i].length; j++) {
                if (chessAr1[i][j] != 0) {
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessAr1[i][j];
                }
            }
        }
        //遍历稀疏数组
        System.out.println("稀疏数组=====>");
        for (int[] row : sparseArr) {
            for (int data : row) {
                System.out.print(data + "   ");
            }
            System.out.println();
        }

        /*
            重点3:稀疏数组还原到原始二维数组
         */
        //稀疏数组的第一行第一列=原始数组的行
        int row = sparseArr[0][0];
        //稀疏数组的第一行第二列=原始数组的列
        int cloum = sparseArr[0][1];
        int[][] chessArr2 = new int[row][cloum];
        for (int i = 1; i < sparseArr.length; i++) {
            chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }
        //遍历转换后的二维数组
        System.out.println("稀疏数组转换二维数组=====>");
        for (int[] rows :chessArr2){
            for (int data :rows){
                System.out.print(data+"  ");
            }
            System.out.println();
        }
    }
}

问题:

  因为数组的类型只能有一个,在定义稀疏数组时,必须创建为int型的

   int sparseArr[][] = new int[sum + 1][3];

为什么只能定义为int型呢?

  因为稀疏数组的第一行:第一列=原始二维数组的行数(int),第二列=原始二维数组的列数(int),第三列(原始二维数组的有效数个数int)

所以,原始二维数组的内容只能是int类型,才能转为稀疏数组,假如是String类型时,我们在稀疏数组赋值时:稀疏数组[i][2]="",和数组的格式不统一,导致出错!这是个坑 

 

2.队列

2.1 简介

队列介绍

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

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

1.rear是队列的最后【含】

2.front是队列最前的元素【不包含】

  • 当我们将数据存入队列时称为"addQueue",addQueue的处理需要两个步骤
    • 将尾指针往后移:rear+1,当ront==rear【空】,初始队列没有值时
    • 若尾指针rear小于队列的最大下标maxSize-1,则将数据存入rear所值的数组元素中,否则无法存入数据。rear==maxSize-1【队列满】

代码实现:

   

package cn.com.query;

import java.util.Scanner;

public class ArrayQueryDemo {
    public static void main(String[] args) {
        //创建容量为3的队列
        ArrayQuery query = new ArrayQuery(3);
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);
        while (loop) {
            System.out.println("输入a:添加元素");
            System.out.println("输入g:获取元素");
            System.out.println("输入h:输出头部元素");
            System.out.println("输入s:遍历元素");
            System.out.println("输入e:退出程序");
            char key = scanner.next().charAt(0);
            switch (key) {
                case 'a':
                    try {
                        System.out.println("输入添加元素...");
                        int data = scanner.nextInt();
                        query.addQuery(data);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'g':
                    try {
                        int getData = query.getQuery();
                        System.out.println("获取数据:" + getData);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int headData = query.headQuery();
                        System.out.println("头部数据:" + headData);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 's':
                    System.out.println("遍历列表..");
                    query.show();
                    break;
                case 'e':
                    System.out.println("退出程序~~");
                    loop = false;
                    scanner.close();
            }
        }
    }
}

/**
 * 使用数组模拟队列
 */
class ArrayQuery {
    //队列的最大长度
    private int maxSize;
    //队列头:
    private int front;
    //队列尾
    private int rear;
    //模拟队列
    private int[] arr;

    public ArrayQuery(int maxSize) {
        this.maxSize = maxSize;
        //指向队列头:并且注意front一直执行队列第一个元素的前一个位置
        this.front = -1;
        //执行队列尾:指向的是队列的最后一个数据(即就是队列的最后一个数据)
        this.rear = -1;
        this.arr = new int[maxSize];
    }

    /**
     * 1:判断队列是否已满
     *
     * @return 是否已满
     */
    public boolean isFull() {
        //判断队列尾是否==最大值-1
        return rear == maxSize - 1;
    }

    /**
     * 2:添加数据
     *
     * @param data 数据
     */
    public void addQuery(int data) {
        if (isFull()) {
            throw new RuntimeException("队列已满,不能添加数据");
        }
        //尾指针后移一位
        rear++;
        arr[rear] = data;
    }

    /**
     * 3:判断对垒是否为空
     *
     * @return 队列是否为空
     */
    public boolean isEmpty() {
        //front指向队列第一个元素的前一个位置,rear指向队列的最后一个元素,当两值相等时,即队列没有元素
        return rear == front;
    }

    /**
     * 4:获取数据
     *
     * @return 返回的元素
     */
    public int getQuery() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,不能获取数据");
        }
        front++;
        return arr[front];
    }

    /**
     * 5:遍历输出数据
     */
    public void show() {
        if (isEmpty()) {
            System.out.println("队列为空,没有需要遍历的数据");
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(i + ":" + arr[i]);
        }
    }

    /**
     * 6:展示队列的头元素,只是展示
     *
     * @return 头元素
     */
    public int headQuery() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,没有头元素");
        }
        //只是展示,这里不能用front++,因为使用这个方法,front本身值发生变化
        return arr[front + 1];
    }
}

测试:

1.新增3个元素:10,20,30
2.输入s遍历列表
    s
    遍历列表..
    0:10
    1:20
    2:30
3.输入h获取列表头部元素:
    多次输入发现返回一致,因为获取头部元素只是获取,不移动内部front指针位置,
    内部采用的是front+1的下标获取方式,并没有采用front++
    h
    头部数据:10
    h
    头部数据:10
4.3个元素已满,再添加
    a
    输入添加元素...
    40
    队列已满,不能添加数据
5.获取元素
    g
    获取数据:10
6.获取后查看头部元素:发现头部元素发生变化
    h
    头部数据:20
7.取完元素后,再取
    g
    队列为空,不能获取数据

问题分析优化:

  1.目前数组只能使用一次,没有达到复用效果,如:新增3个数据后再取完,是不能再往里放数据了,因为rear已经到达最大容量-1位置,isfull判断返回true

  改进:将这个数组使用算法,改进为环形队列,使用取模的方法:%

  2.虽然数据已经取完,但是遍历发现数据依然存储在数组中,因为底层移动的只是front指针,并没有对原始位置置空或处理

2. 使用数组模拟环形队列解决上述问题:

  思路如下:(该算法是老师分析的算法,最重要的一点是rear变量指向队尾元素的后一位,因为希望多出一个空间作为约定,即当创建5个容量的元素,实际只能存储4个元素

  算法有和多种,这只是展示的一种

  1.front变量含义做一个调整:front就指向队列的第一个数据,也就是说arr[front]就是队列的第一个数据【初始值是0

  2.rear变量的含义做一个调整:rear执行队列的最后一个元素,因为希望多出一个空间作为约定【初始值是0

  3.当队列满时,如下图:条件是(rear+1)%maxSize=front【满】

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

  5.有效数字个数:(rear+maxSize-front)%maxSize  

 

代码示例:

package cn.com.queue;

import java.util.Scanner;

public class CircleArrayQueueDemo {
    public static void main(String[] args) {
        //创建容量为5的队列,只能放4个元素
        CircleArray query = new CircleArray(5);
        boolean loop = true;
        Scanner scanner = new Scanner(System.in);
        while (loop) {
            System.out.println("输入a:添加元素");
            System.out.println("输入g:获取元素");
            System.out.println("输入h:输出头部元素");
            System.out.println("输入s:遍历元素");
            System.out.println("输入e:退出程序");
            char key = scanner.next().charAt(0);
            switch (key) {
                case 'a':
                    try {
                        System.out.println("输入添加元素...");
                        int data = scanner.nextInt();
                        query.addQuery(data);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'g':
                    try {
                        int getData = query.getQuery();
                        System.out.println("获取数据:" + getData);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        int headData = query.headQuery();
                        System.out.println("头部数据:" + headData);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case 's':
                    System.out.println("遍历列表..");
                    query.show();
                    break;
                case 'e':
                    System.out.println("退出程序~~");
                    loop = false;
                    scanner.close();
            }
        }
    }
}

/**
 * 使用数组模拟队列
 */
class CircleArray {
    //队列的最大长度
    private int maxSize;
    //队列头:
    private int front;
    //队列尾
    private int rear;
    //模拟队列
    private int[] arr;

    public CircleArray(int maxSize) {
        this.maxSize = maxSize;
        //指向队列头:并且注意front一直指向队列的第一个元素,front的初始值是0
        //也就是说arr[front]就是队列的第一个数据
        this.front = 0;
        //指向队列尾:指向的是队列的最后一个数据的后一个位置,rear初始值为0
        //因为希望多出一个空间作为约定
        this.rear = 0;
        this.arr = new int[maxSize];
    }

    /**
     * 判断是否满了
     */
    public boolean isFull() {
        return (rear + 1) % maxSize == front;
    }

    /**
     * 判断是否为空
     */
    public boolean isEmpty() {
        return rear == front;
    }

    /**
     * 添加数据
     *
     * @param data 数据
     */
    public void addQuery(int data) {
        if (isFull()) {
            throw new RuntimeException("队列已满,不能添加数据");
        }
        //直接将数据放入
        arr[rear] = data;
        //将rear后移,这里必须考虑取模
        rear = (rear + 1) % maxSize;
    }

    /**
     * 获取数据
     *
     * @return 返回的元素
     */
    public int getQuery() {
        if (isEmpty()) {
            throw new RuntimeException("队列为空,不能获取数据");
        }
        //这里需要分析出front是指向队列的第一个元素
        //1.将front对应的值保留到一个临时变量
        int value = arr[front];
        //2.将front后移
        front = (front + 1) % maxSize;
        //3.将临时变量返回
        return value;
    }

    /**
     * 遍历输出数据
     */
    public void show() {
        if (isEmpty()) {
            System.out.println("队列为空,没有需要遍历的数据");
            return;
        }
        //从front开始便利,便利多少个元素(有效数字)
        for (int i = front; i < front+size(); i++) {
            System.out.println(i + ":" + arr[i]);
        }
    }

    /**
     * 返回当前队列有效数据大小
     */
    public int size() {
        return (rear + maxSize - front) % maxSize;
    }

    /**
     * 查看头元素
     */
    public int headQuery() {
        return arr[front];
    }
}

结果演示:

1.往队列里添加数据,容量为5,最后一个空间是约定空间,不存储数据,所以只能存储4个(10,20,30,40)当添加50时
    50
    队列已满,不能添加数据
2.查看队列
    s
    遍历列表..
    0:10
    1:20
    2:30
    3:40
3.查看头元素,并不移动底层指针,所以不影响列表
    h
    头部数据:10

4.取出数据
    g
    获取数据:10
5.再次便利列表时:发现取出的数据不会再被遍历出,解决了上述问题
    s
    遍历列表..
    1:20
    2:30
    3:40

重点总结:

  1.这只是一种算法,有很多种算法

  2.按照这个算法思路,会留出最后一个空间作为约定,即5个容量只能容纳4个数据

  3.该算法中最有意思的是有效数字个数的计算:

(rear + maxSize - front) % maxSize;

 

 

 

  

posted @ 2022-11-12 17:41  努力的达子  阅读(33)  评论(0编辑  收藏  举报