【数据结构与算法01】数组和队列


1. 概述

课程前言

0基础如何学起?宋红康30天搞定Java核心:BV1Kb411W75N

【尚硅谷】数据结构与算法(Java数据结构与算法):https://www.bilibili.com/video/BV1E4411H73v

课程代码及课件:
链接:https://pan.baidu.com/s/1vZRp_5m4xEjR8NdUxjzU0A
提取码:brnh

参考资料:

韩顺平老师的课件资料
尚硅谷图解Java数据结构和算法.pdf
尚硅谷Java数据结构和算法【最新版】.pptx
图解.xlsx
代码.7z

_课程内容包括: _
稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、
栈、前缀、中缀、后缀表达式、中缀表达式、转换为后缀表达式、
递归与回溯、迷宫问题、八皇后问题
算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析
二分查找、插值查找、斐波那契查找
散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)
图,图的深度优先和广度优先
程序员常用的10种算法

(面试/考试速查)常考数据结构类型速查速补表

  • 数组
    • 稀疏数组
  • 链表
    • P16~P23 单链表
    • P24~P26 双向链表
    • P27~P29 约瑟夫环(环形链表)
  • P30~P35 栈·栈实现计算器
  • P36~P42 前缀,中缀,后缀表达式,逆波兰计算器的实现
  • P43~P49 递归,迷宫回溯,八皇后
  • 排序算法
    • P50~P53 排序算法基础
    • 54~56 冒泡排序
    • 57~59 选择排序
    • 60~62 插入排序
    • 63~65 希尔排序
    • 66~68 快速排序
    • 69~71 归并排序
    • 72~75 基数排序
    • 76各种排序的比较
  • 树结构
    • 127~131 二叉排序树
    • 132~134 BST删除一棵子树的节点
    • 135 二叉平衡树
  • 146~151 图,图的深度优先和广度优先
  • 程序员常用的10种算法
    • 156~159 动态规划
    • 160~163 暴力匹配和KMP算法
    • 164~ 167 贪心算法

数据结构和算法的关系

  • 数据data结构(structure)是一门研究组织数据方式的学科,有了编程语言也就有了数据结构.学好数据结构可以编写出更加漂亮,更加有效率的代码。
  • 要学习好数据结构就要多多考虑如何将生活中遇到的问题,用程序去实现解决.
  • 程序 = 数据结构 + 算法
  • 数据结构是算法的基础, 换言之,想要学好算法,需要把数据结构学到位。

几个实际编程中遇到的问题

1、

public static void main(String[] args) {
    String str = "Java,Java, hello,world!";
    String newStr = str.replaceAll("Java", "尚硅谷~");
    System.out.println("newStr=" + newStr);
}

问:试写出用单链表表示的字符串类及字符串结点类的定义,并依
次实现它的构造函数、以及计算串长度、串赋值、判断两串相等、
求子串、两串连接、求子串在串中位置等7个成员函数。

2、

3、

4、

线性结构和非线性结构

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

  1. 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系
  2. 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的
  3. 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息
  4. 线性结构常见的有:数组、队列、链表和栈,后面我们会详细讲解.

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

2. 数组

稀疏数组sparsearray

先看一个实际的需求

基本介绍

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

稀疏数组的处理方法

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

    稀疏数组转换思路的分析


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

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

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

  • 先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组,比如Integer[11][11] chessArr2
  • 再读取稀疏数组后几行的数据,并赋值给原始的二维数组即可

代码实现

// 创建一个原始的二维数组 11 * 11
// 0:表示没有棋子,1表示黑子,2表示篮子
int[][] chessArr = new int[11][11];
chessArr[1][2] = 1;
chessArr[2][3] = 2;
chessArr[3][5] = 2;
// 输出原始的二维数组
System.out.println("原始的二维数组--------------->");
for (int[] row : chessArr) {
    for (int data : row) {
        System.out.printf("%d\t",data);
    }
    System.out.println();
}
// 将二维数组 转 稀疏数组
// 1.先遍历二维数组,得到非零数据的个数
int sum = 0;
for (int[] row : chessArr) {
    for (int data : row) {
        if(data != 0){
            sum++;
        }
    }
}
// 2.创建对应的稀疏数组
int[][] sparseArr = new int[sum + 1][3];
// 3.给稀疏数组赋基本值
sparseArr[0][0] = 11;
sparseArr[0][1] = 11;
sparseArr[0][2] = sum;
// 3.遍历二维数组,将非0的值存放在sparseArr中
int count = 0;
for (int i = 0; i < 11; i++) {
    for (int j = 0; j < 11; j++) {
        if(chessArr[i][j] != 0){
            count++;
            sparseArr[count][0] = i;
            sparseArr[count][1] = j;
            sparseArr[count][2] = chessArr[i][j];
        }
    }
}
// 4.打印稀疏数组的形式
System.out.println("打印稀疏数组========================>");
for (int i = 0 ; i< sparseArr.length;i++) {
    System.out.printf("%d\t%d\t%d\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
}
System.out.println();
// 将稀疏数组恢复成原始数组
// 1.先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
int row = sparseArr[0][0];
int col = sparseArr[0][1];
int[][] newArr = new int[row][col];
// 2.读取稀疏数组的后几行数据(从第二行开始),并复制给原始的二维数组即可
for (int i = 1; i < sparseArr.length; i++) {
    newArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
// 3.输出恢复后的二维数组
System.out.println("恢复后的二维数组========================>");
for (int[] newrow : newArr) {
    for (int data : newrow) {
        System.out.printf("%d\t",data);
    }
    System.out.println();
}

课后练习

要求:

  1. 在前面的基础上,将稀疏数组保存到磁盘上,比如 map.data
  2. 恢复原来的数组时,读取map.data 进行恢复

代码:

public class SparseArray {
    public static File file =  new File("./map.data");
    public static void main(String[] args) throws IOException {
        IntArrayConvertToSparseArr(file);
        convertSparseIntArray(file);
    }
    public static void IntArrayConvertToSparseArr(File file) throws IOException {
        // 创建一个原始的二维数组 11 * 11
        // 0:表示没有棋子,1表示黑子,2表示篮子
        int[][] chessArr = new int[11][11];
        chessArr[1][2] = 1;
        chessArr[2][3] = 2;
        chessArr[3][5] = 2;
        // 输出原始的二维数组
        System.out.println("原始的二维数组--------------->");
        for (int[] row : chessArr) {
            for (int data : row) {
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
        // 将二维数组 转 稀疏数组
        // 1.先遍历二维数组,得到非零数据的个数
        int sum = 0;
        for (int[] row : chessArr) {
            for (int data : row) {
                if(data != 0){
                    sum++;
                }
            }
        }
        // 2.创建对应的稀疏数组
        int[][] sparseArr = new int[sum + 1][3];
        // 3.给稀疏数组赋基本值
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;
        // 3.遍历二维数组,将非0的值存放在sparseArr中
        int count = 0;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if(chessArr[i][j] != 0){
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArr[i][j];
                }
            }
        }
        // 4.打印稀疏数组的形式
        System.out.println("打印稀疏数组========================>");
        for (int i = 0 ; i< sparseArr.length;i++) {
            System.out.printf("%d\t%d\t%d\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
        }
        System.out.println();
        //5、将稀疏数组保存到文件map.data 然后读取文件还原为原来数组
        if(!file.exists()){
            file.createNewFile();
        }
        FileWriter fw = new FileWriter(file, Charset.defaultCharset(),false);
        String jsonString = JSON.toJSONString(sparseArr);
        System.out.println(jsonString);
        fw.write(JSON.toJSONString(jsonString));
        fw.flush();
        fw.close();
    }
    //读取文件(将稀疏数组转为正常数组)
    public static void convertSparseIntArray(File file) throws IOException {
        //读取文件然后将稀疏数组恢复成原始数组
        FileReader fr = new FileReader(file);
        int len = 0;
        char[] temp = new char[1024];
        StringBuilder sb = new StringBuilder();
        while ((len = fr.read(temp)) != -1 ){
            sb.append(new String(temp,0,len));
        }
        fr.close();
        String s = sb.toString().substring(1,sb.length()-1);
        System.out.println("------>从文件中读取到的字符:"+s);
        int[][] sparseArr = JSON.parseObject(s,int[][].class);
        // 将稀疏数组恢复成原始数组
        // 1.先读取稀疏数组的第一行,根据第一行的数据,创建原始的二维数组
        int row = sparseArr[0][0];
        int col = sparseArr[0][1];
        int[][] newArr = new int[row][col];
        // 2.读取稀疏数组的后几行数据(从第二行开始),并复制给原始的二维数组即可
        for (int i = 1; i < sparseArr.length; i++) {
            newArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }
        // 3.输出恢复后的二维数组
        System.out.println("恢复后的二维数组========================>");
        for (int[] newrow : newArr) {
            for (int data : newrow) {
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
    }
}

普通队列

环形队列

环形队列可以用数组实现,也可以使用循环链表实现.在使用数组实现循环队列的时候,需要理解清楚队列头和队列尾的判断空还是满的
处理方法;
环形队列的优点:

  • 避免假溢出现象(由于入队和出队的操作,头尾指针只增加不减少,致使被删元素的空间永远无法重新利用,当队列继续存储元素时,出现尾指针已经到达了队列尾,而实际头指针前面并未满的情况),可以将队列空间充分重复利用
  • 首尾相连的FIFO的数据结构,采用数据的线性空间,数据组织简单,能快速知道队列是否满/空
  • 广泛用于网络数据收发,和不同程序间数据交换,均使用了环形队列

原文链接:https://blog.csdn.net/qq_41704415/article/details/117565894
环形队列实现原理

  • 内存上并没有环形的结构,因此环形队列实际上是数组的线性空间来实现的。
  • 当数据到了尾部该如何处理呢?它将转回到原来位置进行处理,通过取模操作来实现

环形队列的几个判断条件
front:指向队列的第一个元素,初始值front=0
rear: 指向队列的最后一个元素的后一个位置(预留一个空间作为约定),初始值rear=0
maxSize: 数组的最大容量
队空:front == rear
队满:(rear+1)%maxSize == front
队列中的有效数据个数:(rear+maxSize-front)% maxSize

其中判断队列满的思想的话,可以看下图,因为是环形的,起初front=rear=0,每当添加元素时,将rear++,但是其实预留了一个长
度没有用,比如定义的队列数组长度为5时,但是实际上可以使用的地址就是0,1,2,3,此时rear=4,
4这个空间用来判断队满的条件(rear+1)%maxSize==front


代码实现:

public class CircleQueueDemo {
    public static void main(String[] args) {
        // 创建一个环形队列
        CircleQueue queue = new CircleQueue(4);     //说明设置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':
                    try {
                        queue.showQueue();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
                case 'a':
                    System.out.println("输出一个数");
                    int value = scanner.nextInt();
                    try {
                        queue.addQueue(value);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
                case 'g': // 取出数据
                    try {
                        int res = queue.getQueue();
                        System.out.printf("取出的数据是%d\n", res);
                    } catch (Exception e) {
                        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("程序退出~~");
    }
    @Test
    public void test01(){
        System.out.println(16%8);
    }
}
class CircleQueue{
    private int maxSize; // 表示数组的最大容量
    private int front;  // 指向队列的第一个元素 初始值 = 0
    //rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定.
    private int rear;   // 队列尾
    private int[] arr; // 该数据用于存放数据, 模拟队列
    public CircleQueue(int maxSize){
        this.maxSize = maxSize;
        arr = new int[maxSize];
    }
    // 判断队列是否满
    public boolean isFull(){
        return size() == (maxSize-1);
    }
    // 判断队列是否为空
    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("当前队列为空~~~");
        }
        // 这里需要分析出 front是指向队列的第一个元素
        // 1. 先把 front 对应的值保留到一个临时变量
        // 2. 将 front 后移, 考虑取模
        // 3. 将临时保存的变量返回
        int value = arr[front];
        front = (front + 1)%maxSize;
        return value;
    }
    // 显示队列的所有数据
    public void showQueue(){
        if(isEmpty()){
            throw new RuntimeException("当前队列为空~~~");
        }
        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;
    }
    // 显示队列的头数据, 注意不是取出数据
    public int headQueue(){
        if(isEmpty()){
            throw new RuntimeException("当前队列为空~~~");
        }
        return arr[front];
    }
}

文章作者: CoderXiong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CoderXiong !
  目录