Java数据结构与算法

Java数据结构与算法

线性结构和 非线性结构

在这里插入图片描述

稀疏数组SparseArray

需求

记录棋盘的位置 可用用二位数组将它保存 ,但是会发现记录很多没有意义的数据很多空间浪费 可以用稀疏数组进行优化

在这里插入图片描述

介绍

稀疏数组就是压缩缩多余的冗余数据

在这里插入图片描述

第一行的记录原表的行列数和 非0值的个数

在这里插入图片描述

实例

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.sparsearray;

/**
 * @author cbx
 * @date 2021/11/30
 * @apiNote
 */
public class SparseArray {
    public static void main(String[] args) {
        // 创建一个原始的二维数组11*11
        // 0:表示没有棋子,1表示 黑子 2表示 蓝子
        int chessArr1[][] = new int[11][11];
        chessArr1[1][2] = 1;
        chessArr1[2][3] = 2;
        // 输出原始的二维数组
        System.out.println("原始的二维数组~~");
        for (int[] row : chessArr1) {
            for (int data : row) {
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }

        // 将二维数组 转 稀疏数组的思路
        // 1. 先遍历二维数组  得到非0数据的个数
        int sum = 0;
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (chessArr1[i][j] != 0){
                    sum++;
                }
            }
        }

        // 2. 创建对应的稀疏数组
        int sparseArr[][] = new int[sum+1][3];
        // 给稀疏数组赋值
        sparseArr[0][0] = 11;
        sparseArr[0][1] = 11;
        sparseArr[0][2] = sum;

        // 遍历二维数组,将非0的值存到sparseArr中
        int count = 0; // count 用于记录是第几个非0数据
        for (int i = 0; i < 11; i++) {
            for (int j = 0; j < 11; j++) {
                if (chessArr1[i][j] != 0){
                    count++;
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = chessArr1[i][j];
                }
            }
        }
        // 输出稀疏数组的形式
        System.out.println();
        System.out.println("得到的稀疏数组~~");
        for (int i = 0; i < sparseArr.length; i++) {
            System.out.printf("%d\t%d\t%d\t\n",sparseArr[i][0],sparseArr[i][1],sparseArr[i][2]);
        }
        System.out.println();

        // 稀疏数组恢复成二维数组
        // 1 读取稀疏数组的第一行 根据第一行的数据,  创建原始的二维数组
        int chessArr2[][] = new int[sparseArr[0][0]][sparseArr[0][1]];

        // 2 在读取稀疏数组的后几行 并赋值(从第二行开始)

        for (int i = 1; i < sparseArr.length; i++) {
            chessArr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
        }

        // 输出恢复后的二维数组
        System.out.println();
        System.out.println("恢复后的二维数组");

        for (int[] row : chessArr2) {
            for (int data : row) {
                System.out.printf("%d\t",data);
            }
            System.out.println();
        }
    }
}

代码执行结果

输出原始的二维数组
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 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数据的个数 sum=2
得到的稀疏数组为:
11 11 2
1 2 1
2 3 2
输出恢复的二维数组
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 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

队列

介绍

在这里插入图片描述

上图的清晰图 rear待表队列的尾部 front代表数据的头部 存入数据时 front不变 rear变化 存数据时 rear位置不变 front 的位置在变化

在这里插入图片描述

数组模拟队列思路

rear == maxSize-1 为满 front指向队列头部的头一个位置 rear指向队列尾部的具体位置

在这里插入图片描述

代码实现

package com.cbx.queue;

import java.util.Scanner;

/**
 * @author cbx
 * @date 2021/12/1
 * @apiNote
 */
public class ArrayQueueDemo {
    public static void main(String[] args) {
        // 测试
        // 创建一个队列
        ArrayQueue arrayQueue = 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':
                    arrayQueue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    arrayQueue.addQueue(value);
                    break;
                case 'g':
                    try{
                        System.out.println("取出的数据:"+arrayQueue.getQueue());
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        System.out.println("队列头部的数据:"+arrayQueue.headQueue());
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出");
    }
}

// 使用数组模拟队列-编写一个ArrayQueue类
class ArrayQueue{
    private int maxSize; // 表示数组最大容量
    private int front;  // 队列头
    private int rear; // 队列尾
    private int[] arr;  // 该数组用于存放数据,模拟队列

//  创建队列的构造器
    public ArrayQueue(int arrMaxSize){
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        front = -1; // 指向队列头部,分析出front是指向队列头的前一个位置
        rear = -1; // 指向队列尾,指向队列尾的数据(即就是队列的最后一个数据)
    }

    // 判断队列是否满了
    public boolean isFull(){
        return rear == maxSize - 1;
    }

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

    // 添加数据到队列
    public void addQueue(int n){
        // 判断队列是否
        if (isFull()){
            System.out.println("队列满,不能加入数据~");
            return;
        }
        rear++; // 让rear 后移
        arr[rear] = n;
    }

    // 获取队列的数据,出队列
    public int getQueue(){
        // 判断队列是否为空
        if (isEmpty()){
            // 通过抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        front++;
        return arr[front];
    }

    // 显示队列的所有数据
    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()){
            // 通过抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        return arr[front+1];
    }
}

控制台结果

s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
队列为空
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=0
arr[2]=0
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=20
arr[2]=30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
40
队列满,不能加入数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h
队列头部的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h
队列头部的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
队列为空不能取数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h
队列为空,没有头数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据

问题

现在队列经历了加满 也全部取出 当前列表理论上为空 但是任然无法加入数据 这个数组无法复用 只能使用一次 没有到达复用的效果 需要将这个数组由算法改造成一个环形数组(取模)

在这里插入图片描述

数组模拟环形队列

注意现在调整了front 和 rear 队列的

在这里插入图片描述

别人规定的循环队列

那么,循环队列为什么用空一个元素的位置呢???

这个是根据需要来用的
循环队列中,由于入队时尾指针向前追赶头指针;出队时头指针向前追赶尾指针,造成队空和队满时头尾指针均相等。因此,无法通过条件front==rear来判别队列是"空"还是"满"。
解决这个问题的方法至少有三种:
①另设一布尔变量以区别队列的空和满;
②少用一个元素的空间。约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空);
③使用一个计数器记录队列中元素的总数(即队列长度)。

环形队列代码实现

package com.cbx.queue;

import java.util.Scanner;

/**
 * @author cbx
 * @date 2021/12/1
 * @apiNote
 */
public class CircleArrayQueue {
    public static void main(String[] args) {
        // 测试
        // 创建一个环形队列
        CircleQueue arrayQueue = 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':
                    arrayQueue.showQueue();
                    break;
                case 'a':
                    System.out.println("输入一个数");
                    int value = scanner.nextInt();
                    arrayQueue.addQueue(value);
                    break;
                case 'g':
                    try{
                        System.out.println("取出的数据:"+arrayQueue.getQueue());
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'h':
                    try {
                        System.out.println("队列头部的数据:"+arrayQueue.headQueue());
                    }catch (Exception e){
                        System.out.println(e.getMessage());
                    }
                    break;
                case 'e':
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出");
    }



}

class CircleQueue {
    private int maxSize; // 表示数组最大容量
    // front 的变量的含义 做调整 就指向队列的第一个元素 也就是 arr[front] 是第一个头元素
    // front初始值变为0
    private int front;  // 队列头
    // rear 变量的含义做一个调整 rear指向队列的最后元素的后一个位置 因为希望空出一个空间做约定  rear的初始值为0
    private int rear; // 队列尾
    private int[] arr;  // 该数组用于存放数据,模拟队列

    //  创建队列的构造器
    public CircleQueue(int arrMaxSize) {
        maxSize = arrMaxSize;
        arr = new int[maxSize];
        //front = rear = 0 ; // 队列头部和尾部 front指向队列头部的头一个位置  rear指向队列尾部的具体位置

    }

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

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

    // 添加数据到队列
    public void addQueue(int n) {
        // 判断队列是否
        if (isFull()) {
            System.out.println("队列满,不能加入数据~");
            return;
        }
        // rear指向对位的后一个位置 直接将数据加入  在后移rear指针
        arr[rear] = n;
        // 要考虑取模 如果rear已经在 数组的最后位置  需要进行取模到数组的开始部分
        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()) {
            System.out.println("队列空的,没有数据~~");
            return;
        }
        // 思路: 从front开始遍历, 遍历多少个元素
        //
        for (int i = front; i < front + size(); i++) {
            System.out.println("arr["+ i % maxSize +"]="+arr[i % maxSize]);
        }
    }

    // 显示队列的头数据,注意不是取数据
    public int headQueue() {
        // 判断队列是否为空
        if (isEmpty()) {
            // 通过抛出异常
            throw new RuntimeException("队列空,不能取数据");
        }
        return arr[front];
    }

    // 求出当前队列的有效数据的个数
    public int size() {
        return  (rear + maxSize - front) % maxSize;
    }
}

运行结果

s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
队列为空
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 20
输入一个数
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 
输入一个数
30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[0]=10
arr[1]=20
arr[2]=30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a
输入一个数
40
队列满,不能加入数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据

g
取出的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[1]=20
arr[2]=30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 10
输入一个数
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
s
arr[1]=20
arr[2]=30
arr[3]=10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 70
输入一个数
队列满,不能加入数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
h 
队列头部的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g 
取出的数据:20
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:30
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
取出的数据:10
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
g
队列为空不能取数据
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a 50
输入一个数
s(show): 显示对列
e(exit): 退出程序
a(add): 添加数据到对列
g(get): 从对列取出数据
h(head): 查看对列头的数据
a

链表

在这里插入图片描述

小结上图:

链表是以节点的方式来存储, 是链式存储
每个节点包含 data 域, next 域:指向下一个节点.
如图:发现 链表的各个节点不一定是连续存储.
链表分 带头节点的链表和 没有头节点的链表,根据实际的需求来确定

单链表

单链表看似连续 但是并不是 这只是逻辑结构

在这里插入图片描述

不考虑排名

在这里插入图片描述

代码:

package com.cbx.linkedlist;

/**
 * @author cbx
 * @date 2021/12/2
 * @apiNote
 */
public class SingleLinkedLIstDemo {
    public static void main(String[] args) {
        // 进行测试
        // 先创建节点
        HeroNode hero1 = new HeroNode(1,"卡特","不详之刃");
        HeroNode hero2 = new HeroNode(2,"亚索","疾风剑豪");
        HeroNode hero3 = new HeroNode(3,"劫","影流之主");
        HeroNode hero4 = new HeroNode(4,"乐芙兰","诡术妖姬");

        // 创建链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        // 加入
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero4);

        // 显示
        singleLinkedList.list();
    }
}

// 定义SingleLinkedList 管理我们的英雄
class SingleLinkedList{
    // 先初始化一个头节点,头节点不要动,不要存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    // 添加节点到单向链表
    // 思路,当不考虑编号顺序时
    // 1. 找到当前链表的最后节点
    // 2. 将最后这个节点的next 指向 新的节点

    public void add(HeroNode heroNode){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = head;
        // 遍历链表,找到最后
        while (true){
            // 找到链表的最后
            if (temp.next == null){
                break;
            }
            // 如果没有找到最后,将temp后移
            temp = temp.next;
        }
        // 当退出while循环时,temp就指向了链表的最后
        // 将最后这个节点的next 指向 新的节点
        temp.next = heroNode;
    }

    // 显示链表[遍历]
    public void list(){
        // 判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = head.next;
        while (true){
            // 判断是否到链表最后
            if (temp == null){
                break;
            }
            // 输出节点的信息
            System.out.println(temp);
            // 将temp后移,一定小心
            temp = temp.next;
        }
    }
}

// 定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode{
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; // 指向下一个节点
    // 构造器
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname +"}";
    }
}

考虑排名

在这里插入图片描述

修改

思路(1) 先找到该节点,通过遍历,(2) temp.name = newHeroNode.name ; temp.nickname= newHeroNode.nickname

删除

在这里插入图片描述

代码:

package com.cbx.linkedlist;

/**
 * @author cbx
 * @date 2021/12/2
 * @apiNote
 */
public class SingleLinkedLIstDemo {
    public static void main(String[] args) {
        // 进行测试
        // 先创建节点
        HeroNode hero1 = new HeroNode(1,"卡特","不详之刃");
        HeroNode hero2 = new HeroNode(2,"亚索","疾风剑豪");
        HeroNode hero3 = new HeroNode(3,"劫","影流之主");
        HeroNode hero4 = new HeroNode(4,"乐芙兰","诡术妖姬");

        // 创建链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        // 加入
//        singleLinkedList.add(hero1);
//        singleLinkedList.add(hero3);
//        singleLinkedList.add(hero2);
//        singleLinkedList.add(hero4);

        singleLinkedList.addByOrder(hero1);
        singleLinkedList.addByOrder(hero3);
        singleLinkedList.addByOrder(hero2);
        singleLinkedList.addByOrder(hero4);
//        singleLinkedList.addByOrder(hero4);

        // 显示
        singleLinkedList.list();

        // 测试修改节点的代码
        HeroNode newHeroNode = new HeroNode(2,"风男","疾风剑豪~~");
        singleLinkedList.update(newHeroNode);

        System.out.println("修改后的链表情况~~");
        singleLinkedList.list();

        // 删除一个节点
        singleLinkedList.del(1);
        singleLinkedList.del(4);
        singleLinkedList.del(2);
        singleLinkedList.del(3);
        System.out.println("删除后的链表情况~~");
        singleLinkedList.list();

    }
}

// 定义SingleLinkedList 管理我们的英雄
class SingleLinkedList{
    // 先初始化一个头节点,头节点不要动,不要存放具体的数据
    private HeroNode head = new HeroNode(0,"","");

    // 添加节点到单向链表
    // 思路,当不考虑编号顺序时
    // 1. 找到当前链表的最后节点
    // 2. 将最后这个节点的next 指向 新的节点

    public void add(HeroNode heroNode){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = head;
        // 遍历链表,找到最后
        while (true){
            // 找到链表的最后
            if (temp.next == null){
                break;
            }
            // 如果没有找到最后,将temp后移
            temp = temp.next;
        }
        // 当退出while循环时,temp就指向了链表的最后
        // 将最后这个节点的next 指向 新的节点
        temp.next = heroNode;
    }

    // 第二种方式在添加英雄时,根据排名将英雄插入到指定的位置
    // (如果有这个排名,则添加失败,并给出提示)
    public void addByOrder(HeroNode heroNode){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        // 因为单链表,因此我们找的temp是位于添加位置的前一个节点,否则插入不了
        HeroNode temp = head;
        boolean flag = false; // flag标志添加的编号是否存在,默认为false
        while (true){
            if (temp.next == null){ // 说明temp已经在链表的最后
                break;
            }
            if (temp.next.no > heroNode.no){ // 位置找到,就在temp的后面插入
                break;
            }else if (temp.next.no == heroNode.no){ // 说明希望添加的heroNode的编号已然存在
                flag = true; // 说明编号存在
                break;
            }
            temp = temp.next; // 后移,遍历当前链表
        }
        // 判断flag的值
        if (flag){
            System.out.printf("准备插入的英雄的编号%d已经存在了,不能加入\n",heroNode.no);
        }else {
            // 插入到链表中,temp的后面
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    // 修改节点的信息,根据no编号来修改,即no编号不能改
    // 说明
    // 1. 根据newHeroNode 的 no来修改即可
    public void update(HeroNode newHeroNode){
        // 判断是否空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 找到需要修改的节点,根据no编号
        // 定义一个辅助变量
        HeroNode temp = head.next;
        boolean flag = false; // 表示是否找到节点
        while (true){
            if (temp == null){
                break; // 已经遍历完链表
            }
            if (temp.no == newHeroNode.no){
                // 找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 根据flag 判断是否找到要修改的节点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else { // 没有找到
            System.out.printf("没有找到编号%d的节点,不能修改\n",newHeroNode.no);
        }
    }

    // 思路
    // 1. head不能动,因此我们需要一个temp辅助节点找到待删除节点的前一个节点
    // 2. 说明我们在比较时,是temp.next.no 和 需要删除的节点的no比较
    public void del(int no){
        HeroNode temp = head;
        boolean flag = false; // 标志是否找到待删除的节点
        while(true){
            if (temp.next == null){
                break;
            }
            if (temp.next.no == no){
                // 找到了待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 判断flag
        if (flag) { // 找到
            // 可以删除
            temp.next = temp.next.next;
        }else {
            System.out.printf("要删除的%d节点不存在\n",no);
        }
    }

    // 显示链表[遍历]
    public void list(){
        // 判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = head.next;
        while (true){
            // 判断是否到链表最后
            if (temp == null){
                break;
            }
            // 输出节点的信息
            System.out.println(temp);
            // 将temp后移,一定小心
            temp = temp.next;
        }
    }
}

// 定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode{
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; // 指向下一个节点
    // 构造器
    public HeroNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname +"}";
    }
}

单链表面试题

在这里插入图片描述

求单链表的有效节点的个数

// 方法: 获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
    /**
     *
     * @param head 链表的头节点
     * @return 返回的就是有效节点的个数
     */
    public static int getLength(HeroNode head){
        if (head.next == null){ //空链表
            return 0;
        }
        int length = 0;
        // 定义一个辅助的常量,这里我们没有统计头节点
        HeroNode cur = head.next;
        while (cur != null){
            length++;
            cur = cur.next; // 遍历
        }
        return length;
    }

求单链表的倒数第K个节点

// 查找单链表中的倒数第k个节点[新浪面试题]
    // 思路
    // 1. 编写一个方法,接收head节点,同时接受一个index
    // 2. index表示是倒数第index个节点
    // 3. 先把链表从头到尾遍历,得到的链表总长度getLength
    // 4. 得到size后,我们从链表的第一个开始遍历(size-index)个,就可以得到
    // 5. 如果找到了,则返回该节点,否则返回null
    public static HeroNode findLastIndexNode(HeroNode head,int index){
        // 判断如果链表为空,返回null
        if (head.next == null){
            return null;
        }
        // 第一个遍历得到链表的长度(节点个数)
        int size = getLength(head);
        // 第二次遍历size-index位置,就是我们倒数的第K个节点
        // 先做一个index的校验
        if (index <= 0||index > size){
            return null;
        }
        // 定义给辅助变量,for循环定位到倒数的index
        HeroNode cur =head.next;//3 //3-1=2
        for (int i = 0; i < size - index; i++) {
            cur = cur.next;
        }
        return cur;
    }

单链表的反转 (有点难度)

 // 将单链表反转
    public static void reverseList(HeroNode head){
        // 如果当前链表为空,或者只有一个节点,无需反转,直接返回
        if (head.next==null || head.next.next==null){
            return;
        }

        // 定义一个辅助的指针(变量),帮助我们遍历原来的链表
        HeroNode cur = head.next;
        HeroNode next = null; // 指向当前节点[cur]的下一个节点
        HeroNode reverseHead = new HeroNode(0,"","");
        // 遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
        // 动脑筋
        while (cur != null){
            next = cur.next;// 先暂时保存当前节点的下一个节点,因为后面需要用到
            cur.next = reverseHead.next; // 将cur的下一个节点指向新的链表的最前端
            reverseHead.next = cur;// 将cur连接到新的链表上
            cur = next;// 让cur后移
        }
        // 将head.next指向reverseHead.next,实现单链表的反转
        head.next = reverseHead.next;
    }

从尾到头打印单列表

在这里插入图片描述

  // 方式2
    // 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进先出的特点,就实现了逆序打印的效果
    public static void reversePrint(HeroNode head){
        if (head.next == null){
            return;// 空链表,不能打印
        }
        // 创建一个栈,将各节点压入栈中
        Stack<HeroNode> stack = new Stack<>();
        HeroNode cur = head.next;
        // 将链表的所有节点压入栈
        while (cur!=null){
            stack.push(cur);
            cur = cur.next; // cur后移,这样就可以压入下一个节点
        }
        // 将栈中的节点进行打印,pop出栈
        while (stack.size()>0){
            System.out.println(stack.pop());
        }
    }

合并两个有序链表,合并后依然有序(递归)

public static HeroNode mergeTwoLists(HeroNode l1, HeroNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;

        HeroNode head = null;
        if (l1.no <= l2.no){
            head = l1;
            head.next = mergeTwoLists(l1.next, l2);
        } else {
            head = l2;
            head.next = mergeTwoLists(l1, l2.next);
        }
        return head;
    }

双向链表

单链表的问题

在这里插入图片描述

package com.cbx.linkedlist;

/**
 * @author cbx
 * @date 2021/12/4
 * @apiNote
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        //测试
        System.out.println("双向链表的测试");

        // 先创建节点
        HeroNode2 hero1 = new HeroNode2(1,"卡特1","不详之刃");
        HeroNode2 hero2 = new HeroNode2(4,"亚索4","疾风剑豪");
        HeroNode2 hero3 = new HeroNode2(5,"劫5","影流之主");
        HeroNode2 hero4 = new HeroNode2(7,"乐芙兰7","诡术妖姬");

        // 创建一个双向链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.add(hero1);
        doubleLinkedList.add(hero2);
        doubleLinkedList.add(hero3);
        doubleLinkedList.add(hero4);

        doubleLinkedList.list();

        // 修改测试
        HeroNode2 newHeroNod = new HeroNode2(7,"乐芙兰77777","诡术妖姬7");
        doubleLinkedList.update(newHeroNod);
        System.out.println("修改后");
        doubleLinkedList.list();

        //删除
        doubleLinkedList.del(4);
        System.out.println("删除后");
        doubleLinkedList.list();
    }
}

// 创建一个双向链表的类
class DoubleLinkedList{
    // 先初始化一个头节点,头节点不要动,不要存放具体的数据
    private HeroNode2 head = new HeroNode2(0,"","");

    public HeroNode2 getHead() {
        return head;
    }

    // 遍历双向链表的方法
    public void list(){
        // 判断链表是否为空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode2 temp = head.next;
        while (true){
            // 判断是否到链表最后
            if (temp == null){
                break;
            }
            // 输出节点的信息
            System.out.println(temp);
            // 将temp后移,一定小心
            temp = temp.next;
        }
    }

    // 添加一个节点到双向链表的最后
    public void add(HeroNode2 heroNode){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode2 temp = head;
        // 遍历链表,找到最后
        while (true){
            // 找到链表的最后
            if (temp.next == null){
                break;
            }
            // 如果没有找到最后,将temp后移
            temp = temp.next;
        }
        // 当退出while循环时,temp就指向了链表的最后
        // 形成一个双向链表
        temp.next = heroNode;
        heroNode.pre = temp;
    }

    // 修改一个节点的内容,可以看到双向链表的节点内容修改和单向链表一样
    // 只是 节点类型改成HeroNode2
    public void update(HeroNode2 newHeroNode){
        // 判断是否空
        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 找到需要修改的节点,根据no编号
        // 定义一个辅助变量
        HeroNode2 temp = head.next;
        boolean flag = false; // 表示是否找到节点
        while (true){
            if (temp == null){
                break; // 已经遍历完链表
            }
            if (temp.no == newHeroNode.no){
                // 找到
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 根据flag 判断是否找到要修改的节点
        if (flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else { // 没有找到
            System.out.printf("没有找到编号%d的节点,不能修改\n",newHeroNode.no);
        }
    }

    // 从双向链表中删除一个节点
    // 说明
    // 1. 对于双向链表,我们可以直接找到要删除的这个节点
    // 2. 找到后,自我删除即可
    public void del(int no){
        // 判断当前链表是否为空
        if (head.next == null){
            System.out.println("链表为空,无法删除");
            return;
        }

        HeroNode2 temp = head.next; // 辅助变量(指针)
        boolean flag = false; // 标志是否找到待删除的节点
        while(true){
            if (temp == null){ // 已经到链表的最后节点的next
                break;
            }
            if (temp.no == no){
                // 找到了待删除节点的前一个节点temp
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 判断flag
        if (flag) { // 找到
            // 可以删除
            // temp.next = temp.next.next;[单项链表处理方式]
            temp.pre.next=temp.next;
            // 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
            if (temp.next != null){
                temp.next.pre=temp.pre;
            }
        }else {
            System.out.printf("要删除的%d节点不存在\n",no);
        }
    }

}


// 定义HeroNode2,每个HeroNode对象就是一个节点
class HeroNode2{
    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next; // 指向下一个节点,默认为null
    public HeroNode2 pre;// 指向前一个节点,默认为null
    // 构造器
    public HeroNode2(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname +"}";
    }
}

双向链表的第二种添加方式,按照编号顺序添加

   // 第二种方式在添加英雄时,根据排名将英雄插入到指定的位置
    // (如果有这个排名,则添加失败,并给出提示)
    public void addByOrder(HeroNode2 heroNode2){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode2 temp = head;
        boolean flag = false; // flag标志添加的编号是否存在,默认为false
        while (true){
            if (temp.next == null){ // 说明temp已经在链表的最后
                break;
            }
            if (temp.next.no > heroNode2.no){ // 位置找到,就在temp的后面插入
                break;
            }else if (temp.next.no == heroNode2.no){ // 说明希望添加的heroNode的编号已然存在
                flag = true; // 说明编号存在
                break;
            }
            temp = temp.next; // 后移,遍历当前链表
        }
        // 判断flag的值
        if (flag){
            System.out.printf("准备插入的英雄的编号%d已经存在了,不能加入\n",heroNode2.no);
        }else {
            // 插入到链表中,temp的后面
            if (temp.next == null){ // 说明temp已经在链表的最后
                temp.next=heroNode2;
                heroNode2.pre = temp;
            }else {
                heroNode2.next = temp.next;
                temp.next.pre = heroNode2;
                temp.next = heroNode2;
                heroNode2.pre = temp;
            }


        }
    }

单项环形链表

约瑟夫问题

在这里插入图片描述

单向环形链表介绍

在这里插入图片描述

在这里插入图片描述

思路分析

在这里插入图片描述

package com.cbx.linkedlist;

/**
 * @author cbx
 * @date 2021/12/5
 * @apiNote
 */
public class Josepfu {
    public static void main(String[] args) {
        // 测试构建和遍历
        CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
        circleSingleLinkedList.addBoy(5);
        circleSingleLinkedList.showBoy();

        // 测试小孩出圈是否正确
        circleSingleLinkedList.countBoy(1,2,5);
    }
}

// 创建一个环形的单向链表
class CircleSingleLinkedList{
    // 创建一个first节点(指向第一个节点的指针),当前没有编号
    private Boy first = null;
    // 添加小孩节点,构建成一个环形的链表
    public void addBoy(int nums){
        // nums做一个数据校验
        if (nums<1){
            System.out.println("nums的值不正确");
            return;
        }
        Boy curBoy = null; // 辅助指针,帮助构建环形链表
        // 使用for来创建我们的环形链表
        for (int i = 1; i <= nums; i++) {
            // 根据编号,创建小孩节点
            Boy boy = new Boy(i);
            // 如果是第一个小孩
            if (i == 1){
                first = boy;
                first.setNext(first); // 构成环
                curBoy = first; // 让curBoy指向第一个小孩
            } else {
                curBoy.setNext(boy);
                boy.setNext(first);
                curBoy = boy;
            }
        }
    }

    // 遍历当前的环形链表
    public void showBoy(){
        // 判断链表是否为空
        if (first == null){
            System.out.println("没有任何小孩~~");
            return;
        }
        // 因为first不能动,因此我们仍然使用一个辅助指针完成遍历
        Boy curBoy = first;
        while (true){
            System.out.printf("小孩的编号 %d \n",curBoy.getNo());
            if (curBoy.getNext() == first){ // 说明已经遍历完毕
                break;
            }
            curBoy = curBoy.getNext(); // 让curBoy后移
        }
    }

    // 根据用户的输入,计算出小孩出圈的顺序
    /**
     *
     * @param startNo 表示从第几个小孩开始报数
     * @param countNum 表示数几下
     * @param nums 表示最初有多少个小孩在圈中
     */
    public void countBoy(int startNo,int countNum,int nums){
        // 先对数据进行校验
        if (first == null || startNo < 1 || startNo > nums){
            System.out.println("链表为空或者参数输入有误,请重新输入");
            return;
        }
        // 创建一个辅助指针,帮助完成小孩出圈,先让这个辅助指针指向环形链表的最后一个节点
        Boy helper = first;
        // 需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后一个节点
        while (true){
            if (helper.getNext() == first){ // 说明helper已经指向最后一个小孩节点
                break;
            }
            helper = helper.getNext();
        }
        // 小孩报数前,先让 first 和 helper 移动到指定的位置,即第一个开始报数的小孩节点位置
        // 让first和helper移动k-1次
        for (int j = 0; j < startNo - 1; j++) {
            first = first.getNext();
            helper = helper.getNext();
        }
        // 当小孩报数时,让first和helper指针同时的移动m-1次(因为报数的第一个节点本身也要报数),然后出圈
        // 这里是一个循环操作,知道圈中只有一个节点
        while (true){
            if (helper == first){ // 说明圈中只有一个节点
                break;
            }
            // 让first和helper指针同时的移动countNum-1次
            for (int j = 0; j < countNum-1; j++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            // 这时first指向的节点,就是要出圈的小孩节点
            System.out.printf("小孩%d出圈\n",first.getNo());
            // 这时将first指向的小孩节点出圈
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.printf("最后留在圈中的小孩编号 %d \n",helper.getNo());
    }
}

// 创建一个Boy类,表示一个节点
class Boy{
    private int no;// 编号
    private Boy next;// 指向下一个节点,默认为null
    public Boy(int no){
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }
}

在这里插入图片描述

介绍

在这里插入图片描述

在这里插入图片描述

数组模拟栈

在这里插入图片描述

代码实现

package com.cbx.stack;

import java.util.Scanner;

/**
 * @author cbx
 * @date 2021/12/6
 * @apiNote
 */
public class ArrayStackDemo {
    public static void main(String[] args) {
        // 测试一下ArrayStack是否正确
        // 先创建一下ArrayStack对象->表示栈
        ArrayStack stack = new ArrayStack(4);
        String key = "";
        boolean loop = true;// 控制是否退出菜单
        Scanner scanner = new Scanner(System.in);

        while (loop){
            System.out.println("show: 表示显示栈");
            System.out.println("exit: 退出程序");
            System.out.println("push: 表示添加数据到栈中(入栈)");
            System.out.println("pop: 表示从栈中取出数据(出栈)");
            System.out.println("请输入你的选择");
            key = scanner.next();
            switch (key){
                case "show":
                    stack.list();
                    break;
                case "push":
                    System.out.println("请输入一个数");
                    int value = scanner.nextInt();
                    stack.push(value);
                    break;
                case "pop":
                    try {
                        int res = stack.pop();
                        System.out.printf("出栈的数据是 %d\n",res);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                case "exit":
                    scanner.close();
                    loop = false;
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~");
    }
}

// 定义一个ArrayStack 表示栈
class ArrayStack{
    private int maxSize;// 栈的大小
    private int[] stack;// 数组,数组模拟栈,数据就放在该数组中
    private int top = -1;// top表示栈顶,初始化为-1

    // 构造器
    public ArrayStack(int maxSize){
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }

    // 栈满
    public boolean isFull(){
        return top == maxSize - 1;
    }

    // 栈空
    public boolean isEmpty(){
        return top == -1;
    }

    // 入栈 --push
    public void push(int value){
        // 先判断栈是否满
        if (isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }

    // 出栈 -pop,将栈顶的数据返回
    public int pop(){
        // 先判断是否为空
        if (isEmpty()){
            // 抛出异常
            throw new RuntimeException("栈空,没有数据");
        }
        int value = stack[top];
        top--;
        return value;
    }

    // 显示栈的情况[遍历栈],遍历时,需要从栈顶开始显示数据
    public void list(){
        if (isEmpty()){
            System.out.println("栈空,没有数据");
            return;
        }
        // 需要从栈顶开始显示数据
        for (int i = top; i >= 0 ; i--) {
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
}

链表模拟栈

代码实现

package com.cbx.stack;

/**
 * @author cbx
 * @date 2021/12/6
 * @apiNote
 */
public class LinkedListStackDemo {
    public static void main(String[] args) {
            LLStack llStack=new LLStack();
            llStack.push(10);
            llStack.push(20);
            llStack.push(30);
            llStack.push(40);
            llStack.push(50);
            System.out.println("栈里面值的个数为:"+llStack.getLength());
            llStack.pop();
            System.out.println("pop一个之后,栈里面的个数 为 :"+llStack.getLength());
            System.out.println("pop一个之后,栈顶的值为:"+llStack.top());
            llStack.list();

    }
}

class LLStack {
    private LLNode headnode= new LLNode(null);

    public LLNode getHeadnode(){
        return headnode;
    }

    public boolean isEmpty(){//判断是否为空的
        return headnode==null;
    }

    public void push(Object data){//入栈
        if(headnode.getData()==null){//判断头结点的值为空的时候
            headnode.setData(data);
        }
        else if(headnode==null){
            headnode=new LLNode(data);
        }
        else {
            LLNode newnode=new LLNode(data);
            newnode.setNext(headnode);
            headnode=newnode;
        }
    }


    public Object pop(){//出栈(返回栈顶的值,并且删除)
        Object data=null;
        if(isEmpty()){
            System.out.println("栈为空,返回值为0");
            return 0;
        }
        data=headnode.getData();
        headnode=headnode.getNext();
        return data;
    }

    public Object top(){//返回栈顶的值,但是不删除
        Object data=null;
        if(isEmpty()){
            System.out.println("栈为空,返回值为0");
            return 0;
        }
        data=headnode.getData();
        return data;
    }

    public int getLength(){//得到栈里面值的个数
        int count=0;
        LLNode tempnode=headnode;
        if(isEmpty()||tempnode.getData()==null)//当头结点为空,并且值也为空的时候就返回0
        {
            count=0;
        }
        else
        {
            while(tempnode!=null)
            {
                count++;
                tempnode=tempnode.getNext();
            }
        }
        return count;
    }

    public void list(){//得到栈里面值的个数
        LLNode tempnode=headnode;
        if(isEmpty()||tempnode.getData()==null)//当头结点为空,并且值也为空的时候就返回0
        {
            System.out.println("栈为空");
            return;
        }
        else
        {
            System.out.println("栈里面的值为");
            while(tempnode!=null)
            {
                System.out.println(tempnode.getData());
                tempnode=tempnode.getNext();
            }
            return;
        }
    }

}

class LLNode {

    private Object data;//存放数据
    private LLNode next;//指向下一个节点

    public LLNode() {

    }

    public LLNode(Object data) {
        this.data = data;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public LLNode getNext() {
        return next;
    }

    public void setNext(LLNode next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "LLNode{" +
                "data=" + data +
                '}';
    }
}

栈实现计算器

思路分析(中缀表达式)

在这里插入图片描述

在这里插入图片描述

代码实现(不考虑小括号)

package com.cbx.stack;

/**
 * @author cbx
 * @date 2021/12/6
 * @apiNote
 */
public class Calculator {
    public static void main(String[] args) {
        String expression = "80+2*6-2";
        // 创建两个栈,数栈,一个符号栈
        ArrayStack2 numStack = new ArrayStack2(10);
        ArrayStack2 operStack = new ArrayStack2(10);
        // 定义需要的相关变量
        int index = 0;
        int num1 = 0;
        int num2 = 0;
        int oper = 0;
        int res = 0;
        char ch = ' '; // 将每次扫描到的char存入到ch
        String keepNum = ""; // 用于拼接多位数的
        // 开始while循环的扫描expression
        while (true){
            // 依次得到expression的每一个字符
            ch = expression.substring(index,index+1).charAt(0);
            // 判断ch是什么
            if (operStack.isOper(ch)){// 如果是运算符
                // 判断当前符号栈是否为空
                if (!operStack.isEmpty()){
                    // 如果符号栈有操作符 就进行比较 如果当前的操作符的优先级小于等于栈中的操作符,就需要从数栈中pop出两个数
                    // 再从符号栈中pop出一个符号 ,进行运算,将得到的结果,入数栈 然后将 当前的操作符入符号栈
                    if (operStack.priority(ch) <= operStack.priority(operStack.peek())){
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        oper = operStack.pop();
                        res = numStack.cal(num1,num2,oper);
                        // 把运算结果存入数字栈中
                        numStack.push(res);
                        // 再把新的运算符压入符号栈中
                        operStack.push(ch);
                    }else {
                        // 当前操作符的优先级大于符号栈中的操作符,直接压入
                        operStack.push(ch);
                    }
                }else {
                    // 符号栈如果为空,直接压入
                    operStack.push(ch);
                }
            }else { // 如果为数字,则直接入数栈 注意这是字符  不是真正的数字
                // numStack.push(ch - 48);  这样子有问题 13 + 1 => '1' '3' '+' '1'
                // 在处理多位数时, 不能发现是一个数就立即入栈 因为他可能是多位数
                // 在处理数,需要向expression的表达式的index 后再看一位 , 如果是数 就进行扫描, 如果是符号才入栈
                // 因此我们需要定义一个变量 keepNum字符串 ,用于拼接多位数
                //numStack.push(ch-48); // ch为'1','3'这种字符char,并不是int,通过ascll码表可得真正得的数字为ch-48

                //处理多为数
                keepNum += ch;

                // 如果ch是expression的最后一位 直接入栈
                if (index == expression.length()-1){
                    numStack.push(ch - 48);
                }else {
                    // 判断下一个字符 是不是数组 ,就继续扫描 ,如果是运算符 则入栈
                    // 只看后一位 不是index++
                    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
                        // 如果后一位是运算符 则数字入栈
                        numStack.push(Integer.parseInt(keepNum));
                        // 重要的!!!!!! 清空keepNum
                        keepNum = "";
                    }
                }
            }
            // 让index+1,并判断是否扫描到表达式的最后
            index++; // index为索引下标,从0开始
            if (index >= expression.length()){
                break;
            }
        }

        // 当表达式扫描完毕,就顺序从数栈和符号栈中弹出相应的数字和符号,并运行
        while (true){
            // 如果符号栈为空,则数字栈中最后只剩下一个数字,即为计算结果
            if (operStack.isEmpty()){
                break;
            }
            num1 = numStack.pop();
            num2 = numStack.pop();
            oper = operStack.pop();
            res = numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        // 将数栈中的最后一个数字即计算结果弹出
        int res2 = numStack.pop();
        System.out.printf("表达式 %s = %d \n",expression,res2);
    }
}

// 定义一个ArrayStack 表示栈
class ArrayStack2{
    private int maxSize;// 栈的大小
    private int[] stack;// 数组,数组模拟栈,数据就放在该数组中
    private int top = -1;// top表示栈顶,初始化为-1

    // 构造器
    public ArrayStack2(int maxSize){
        this.maxSize = maxSize;
        stack = new int[this.maxSize];
    }

    // 返回栈顶的值
    public int peek(){
        return stack[top];
    }

    // 栈满
    public boolean isFull(){
        return top == maxSize - 1;
    }

    // 栈空
    public boolean isEmpty(){
        return top == -1;
    }

    // 入栈 --push
    public void push(int value){
        // 先判断栈是否满
        if (isFull()){
            System.out.println("栈满");
            return;
        }
        top++;
        stack[top] = value;
    }

    // 出栈 -pop,将栈顶的数据返回
    public int pop(){
        // 先判断是否为空
        if (isEmpty()){
            // 抛出异常
            throw new RuntimeException("栈空,没有数据");
        }
        int value = stack[top];
        top--;
        return value;
    }

    // 显示栈的情况[遍历栈],遍历时,需要从栈顶开始显示数据
    public void list(){
        if (isEmpty()){
            System.out.println("栈空,没有数据");
            return;
        }
        // 需要从栈顶开始显示数据
        for (int i = top; i >= 0 ; i--) {
            System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }

    // 返回运算符的优先级,优先级使用数字来表示
    // 数字越大,优先级越高
    public int priority(int oper){
        if (oper == '*'||oper == '/'){
            return 1;
        }else if (oper == '+'||oper == '-'){
            return 0;
        }else {
            return -1; // 假定目前的表达式只有+,-,*,/
        }
    }

    // 判断是不是一个运算符
    public boolean isOper(char val){
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    // 计算方法
    public int cal(int num1,int num2,int oper){
        int res = 0; // 用于存放计算的结果
        switch (oper){
            case '+':
                res = num1 + num2;
                break;
            case '-':
                res = num2 - num1;
                break;
            case '*':
                res = num2 * num1;
                break;
            case '/':
                res = num2 / num1;
                break;
            default:
                break;
        }
        return res;
    }
}


前缀 中缀 后缀(逆波兰)表达式

前缀表达式(波兰表达式)

在这里插入图片描述

在这里插入图片描述

中缀

在这里插入图片描述

后缀(逆波兰表达式)

在这里插入图片描述

逆波兰计算器

在这里插入图片描述

代码实现

package com.cbx.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author cbx
 * @date 2021/12/7
 * @apiNote
 */
public class PolandNotation {
    public static void main(String[] args) {
        // 先定义一个逆波兰表达式
        //(3+4)*5-6 => 3 4 + 5 * 6 -
        // 为了方便 中间用空格隔开
//        String suffixExpression = "3 4 + 5 * 6 -";
        String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
        // 1 现将suffixExpression 放到一个ArrayList中
        // 2 将ArrayList 传递一个方法 遍历ArrayList 配合栈 完成计算

        List<String> list = getListString(suffixExpression);
        System.out.println("rpnList="+list);
        int res = calculate(list);
        System.out.println("计算的结果是="+res);
    }

    // 将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中
    public static List<String> getListString(String suffixExpression){
        // 将suffixExpression分割
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String ele : split) {
            list.add(ele);
        }
        return list;
    }

    // 完成对逆波兰表达式的运算
    // 1.从左至右扫描,将 3 和 4 压入堆栈;
    // 2.遇到+运算符,因此弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3+4 的值,得 7,再将 7 入栈;
    // 3.将 5 入栈;
    // 4.接下来是×运算符,因此弹出 5 和 7,计算出 7×5=35,将 35 入栈;
    // 5.将 6 入栈;
    // 6.最后是-运算符,计算出 35-6 的值,即 29,由此得出最终结果

    public static int calculate(List<String> ls){
        // 创建一个栈,只需要一个栈即可
        Stack<String> stack = new Stack<String>();
        // 遍历ls
        for (String item : ls) {
            // 这里使用正则表达式来取数
            if (item.matches("\\d+")){ // 匹配的是多位数
                // 入栈
                stack.push(item);
            }else { // 如果是运算符
                // pop出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")){
                    res = num1+num2;
                }else if (item.equals("-")){
                    res = num1-num2;
                }else if (item.equals("*")){
                    res = num1*num2;
                }else if (item.equals("/")){
                    res = num1/num2;
                }else {
                    throw new RuntimeException("运算符有误");
                }
                // 把res入栈
                stack.push(""+res);
            }
        }
        // 最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }
}

中缀表达式转换为后缀表达式

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * @author cbx
 * @date 2021/12/7
 * @apiNote
 */
public class PolandNotation {
    public static void main(String[] args) {

        // 完成将中缀表达式转成后缀代达式的功能
        // 1.  1+((2+3)*4)-5   =>     1 2 3 + 4 * + 5 -
        // 2.  因为直接对Str 进行操作不方便  因此 先将 1+((2+3)*4)-5 转成中缀表达式对应的List
        //     即  ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
        // 3.  将得到的中缀表达式的list => 后缀表达式的List
        // 4.  ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =>  ArrayList [1,2,3,+,4,*,+,5]

        String expression = "1+((2+3)*4)-5";
        List<String> infixExpressionList = toInfixExpressionList(expression);
        System.out.println("中缀表达式:"+infixExpressionList); // ArrayList[1,+,(,(,2,+,3,),*,4,),-,5]
        List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);
        System.out.println("后缀表达式:"+suffixExpressionList);
        System.out.println("expression="+calculate(suffixExpressionList));

        // 先定义一个逆波兰表达式
        //(3+4)*5-6 => 3 4 + 5 * 6 -
        // 为了方便 中间用空格隔开
//        String suffixExpression = "3 4 + 5 * 6 -";
      /*  String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
        // 1 现将suffixExpression 放到一个ArrayList中
        // 2 将ArrayList 传递一个方法 遍历ArrayList 配合栈 完成计算

        List<String> list = getListString(suffixExpression);
        System.out.println("rpnList="+list);
        int res = calculate(list);
        System.out.println("计算的结果是="+res);*/
    }

    // 将得到的中缀表达式的list => 后缀表达式的List ls=ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]=>ArrayList[1,2,3,+,4,*,+,5,-]
    public static List<String> parseSuffixExpressionList(List<String> ls){
        // 定义两个栈
        Stack<String> s1 = new Stack<String>(); // 符号栈
        // 第二个栈 在整个转换过程中 没有pop操作, 而且后面我们号需要逆序输出 还要逆序输出 我们同 List<String> s2 代替
        // Stack<String> s2 = new Stack<String>();
        List<String> s2 = new ArrayList<String>(); // 储存中间结果的List s2

        // 遍历ls
        for (String item : ls) {
            // 如果是一个数,加入s2
            if (item.matches("\\d+")){
                s2.add(item);
            }else if(item.equals("(")){
                s1.push(item);
            }else if (item.equals(")")){
                // 如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢及
                while (!s1.peek().equals("(")){
                    s2.add(s1.pop());
                }
                s1.pop();// !! 将( 弹出s1栈,消除小括号
            }else {
                // 当item的优先级 小于 s1栈顶的运算符的优先级 将 s1 栈顶的运算符弹出并加入到 s2 中,再次转到(4.1)与 s1 中新的栈顶运算符相比较
                while (s1.size()!=0 && Operation.getValue(s1.peek())>=Operation.getValue(item)){
                    s2.add(s1.pop());
                }
                // 还需要将item压入栈
                s1.add(item);
            }
        }
        // 将s1中剩余的运算符压入到s2
        while (s1.size()!=0){
            s2.add(s1.pop());
        }
        return s2; // 注意因为是存放到List,因此按顺序输出就是对应的后缀表达式对应的List
    }

    // 将 中缀表达式转成对应的List
    public static List<String> toInfixExpressionList(String s){
        // 定义一个ArrayList 存放中缀表达式的内容
        List<String> ls = new ArrayList<String >();
        int i = 0;
        String str; // 做对多位数的拼接
        char c; // 每遍历到一个字符放入c中
        do {
            // 如果c是一个非数字 直接加入到ls
            if ((c=s.charAt(i))<48||(c=s.charAt(i))>57){
                ls.add(""+c);
                i++;
            }else {// 如果是一个数 需要考虑多位数的问题
                str = ""; // str 置空
                while(i<s.length() && (c=s.charAt(i))>=48&&(c=s.charAt(i))<=57){
                    str += c;// 拼接
                    i++;
                }
                ls.add(str);
            }
        }while (i < s.length());
        return ls;
    }

    // 将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中
    public static List<String> getListString(String suffixExpression){
        // 将suffixExpression分割
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<>();
        for (String ele : split) {
            list.add(ele);
        }
        return list;
    }

    // 完成对逆波兰表达式的运算
    // 1.从左至右扫描,将 3 和 4 压入堆栈;
    // 2.遇到+运算符,因此弹出 4 和 3(4 为栈顶元素,3 为次顶元素),计算出 3+4 的值,得 7,再将 7 入栈;
    // 3.将 5 入栈;
    // 4.接下来是×运算符,因此弹出 5 和 7,计算出 7×5=35,将 35 入栈;
    // 5.将 6 入栈;
    // 6.最后是-运算符,计算出 35-6 的值,即 29,由此得出最终结果

    public static int calculate(List<String> ls){
        // 创建一个栈,只需要一个栈即可
        Stack<String> stack = new Stack<String>();
        // 遍历ls
        for (String item : ls) {
            // 这里使用正则表达式来取数
            if (item.matches("\\d+")){ // 匹配的是多位数
                // 入栈
                stack.push(item);
            }else { // 如果是运算符
                // pop出两个数,并运算,再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")){
                    res = num1+num2;
                }else if (item.equals("-")){
                    res = num1-num2;
                }else if (item.equals("*")){
                    res = num1*num2;
                }else if (item.equals("/")){
                    res = num1/num2;
                }else {
                    throw new RuntimeException("运算符有误");
                }
                // 把res入栈
                stack.push(""+res);
            }
        }
        // 最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }
}

// 编写一个类,可以返回一个运算符对应的优先级
class Operation{
    private static int ADD = 1;
    private static int SUB = 1;
    private static int MUL = 2;
    private static int DIV = 2;

    // 写一个方法,返回对应的优先级数字
    public static int getValue(String operation){
        int result = 0;
        switch (operation){
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                System.out.println("不存在该运算符");
                break;
        }
        return result;
    }
}

递归

在这里插入图片描述

递归可以解决的问题

在这里插入图片描述

递归的重要规则

在这里插入图片描述

迷宫问题

在这里插入图片描述

代码实现

package com.cbx.recursion;

/**
 * @author cbx
 * @date 2021/12/9
 * @apiNote
 */
public class MiGong {
    public static void main(String[] args) {
        // 先创建一个二维数组,模拟迷宫
        // 地图
        int[][] map = new int[8][7];
        // 使用1 表示墙
        // 上去全部置为1
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }

        // 左右全部置为1
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        // 设置挡板,1表示
        map[3][1] = 1;
        map[3][2] = 1;
        //map[1][2] = 1;
        //map[2][2] = 1;
        // 输出地图
        System.out.println("地图的情况");
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j]+" ");
            }
            System.out.println();
        }

        // 使用递归回溯给小球找路
        setWay(map,1,1);

        // 输出新的地图,小球走过,并标识过的递归
        System.out.println("小球走过,并标识过的 地图的情况");
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j]+" ");
            }
            System.out.println();
        }

    }

    // 使用递归回溯给小球找路
    // 说明 map是地图
    //     i,j表示从哪个位置开始出发(i,j)
    //     如果小球到map[6][5] = 则说明通路找到
    //     约定 当map[i][j] 为0时 表示该点没有走过 1为墙 2为通路(小球行径路径) 3为该位置已经走过 但是走不通
    //     再走迷宫时 需要确定一个策略(行径方向 当走不通时往策略的下一个方向)  下->右->上->左 ,如果该点都走不通 在回溯
    /**
     *
     * @param map 地图
     * @param i 从哪个位置开始找
     * @param j
     * @return 如果找到通路 返回true 否则为false
     */
    public static boolean setWay(int[][] map, int i, int j){
        if (map[6][5] == 2){// 通路已经找到OK
            return true;
        }else {
            if (map[i][j] == 0){ // 如果当前这个点还没有走过
                // 按照策略 下->右->上->左 走
                map[i][j] = 2; // 假定该点可以走通,
                if (setWay(map,i+1,j)){ // 向下走
                    return true;
                } else if (setWay(map,i,j+1)){// 向右走
                    return true;
                } else if (setWay(map,i-1,j)){ // 向上走
                    return true;
                } else if (setWay(map,i,j-1)){ // 向左走
                    return true;
                } else {
                    // 说明该点是走不通,是死路
                    map[i][j] = 3;
                    return false;
                }
            }else {
                // 如果map[i][j] != 0,可能是1,2,3
                return false;
            }
        }
    }
}

最短路径分析

目前比较笨的方法 上下左右四个方向 设置所有的策略方法 (上下左右 、上下右左、上左下右 。。。24种) 都走一次 找到最短的那个策略

八皇后问题

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.recursion;

/**
 * @author cbx
 * @date 2021/12/10
 * @apiNote
 */
public class Queen8 {
    // 定义一个max表示共有多少个皇后
    int max = 8;
    // 定义数组array,保存皇后放置位置的结果,比如arr = {0,4,7,5,2,6,1,3}
    int[] array = new int[max];
    static int count = 0;
    static int judgeCount = 0;
    public static void main(String[] args) {
        // 测试一把
        Queen8 queen8 = new Queen8();
        queen8.check(0);
        System.out.println("一共有"+count+"个解法");
        System.out.println("一共判断了"+judgeCount+"次冲突");

    }

    // 编写一个方法,放置第n+1个皇后,n从0开始
    // 特别注意: check 是 每一次递归时, 进入到check中都有 for(int i = 0;i < max;i++),因此会有回溯
    private void check(int n){
        if (n == max){ // n=8,表示已经放了8个皇后了
            print();
            return;
        }

        // 依次放入皇后,并判断是否冲突
        for (int i = 0; i < max; i++) {
            // 先把当前这个皇后n,放到该行的第一列
            array[n] = i;
            // 判断当放置第n个皇后到i列时,是否冲突
            if (judge(n)){ // 不冲突
                // 接着放第n+1个皇后,即开始递归
                check(n+1); //
            }
            // 如果冲突,就继续执行 array[n] = i;即将第n个皇后,放置在本行的后移的一个位置,i++
        }
    }

    // 查看当我们放置第n个皇后,就去检测该皇后是否和前面已经放置的皇后冲突
    /**
     *
     * @param n 表示第n+1个皇后,n从0开始
     * @return
     */
    private boolean judge(int n){
        judgeCount++;
        for (int i = 0; i < n; i++) {
            // 说明
            // 1. array[i] == array[n] 表示判断第n个皇后是否和前面的n-1个皇后在同一列
            // 2. Math.abs(n-i) == Math.abs(array[n]-array[i]) 表示判断第n个皇后是否和第i皇后是否在同一斜线
            // n = 1 放置第2列 n = 1 array[1] = 1
            // Math.abs(1-0) == 1 Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1
            // 3. 判断是否在同一行,没有必要判断,因为n每次都在递增,i永远小于n
            if (array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n]-array[i])){
                return false;
            }
        }
        return true;
    }

    // 写一个方法,可以将皇后摆放的位置输出
    private void print(){
        count++;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i]+" ");
        }
        System.out.println();
    }

}

排序算法

在这里插入图片描述

时间复杂度

在这里插入图片描述

时间频度

在这里插入图片描述

在这里插入图片描述

结论
	1. 2n+20 和 2n 随着 n 变大,执行曲线无限接近, 20 可以忽略	
	2. 3n+10 和 3n 随着 n 变大,执行曲线无限接近, 10 可以忽略

在这里插入图片描述

结论
	1. 2n^2+3n+10 和 2n^2 随着 n 变大, 执行曲线无限接近, 可以忽略 3n+10
	2. n^2+5n+20 和 n^2 随着 n 变大,执行曲线无限接近, 可以忽略 5n+20

忽略系数(3次方及以上不能忽略)

在这里插入图片描述

时间复杂度

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

空间复杂度

在这里插入图片描述

冒泡排序

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始), 依次比较 相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。

优化: 因为排序的过程中,各元素不断接近自己的位置, 如果一趟比较下来没有进行过交换 , 就说明序列有序,因此要在 排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排 序写好后,在进行)

在这里插入图片描述

代码实现(步骤)

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/11
 * @apiNote
 */
public class BubbleSort {
    public static void main(String[] args) {
        int arr[] = {3,9,-1,10,-2};

        // 展示冒泡排序的演变过程 比较arr.length-1 次 就可以得出结果
        /**
         * 第一趟排序后的数组
         * [3, -1, 9, -2, 10]
         * 第二趟排序后的数组
         * [-1, 3, -2, 9, 10]
         * 第三趟排序后的数组
         * [-1, -2, 3, 9, 10]
         * 第四趟排序后的数组
         * [-2, -1, 3, 9, 10]
         */

        // 第一趟排序 就是将最大的数字放到最后
        int temp = 0; // 临时变量
        for(int j = 0; j < arr.length - 1; j++ ){
            if(arr[j] > arr[j + 1]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第一趟排序后的数组");
        System.out.println(Arrays.toString(arr));

        // 第二趟排序 , 把第二大的数放在倒数第二位  最后一个数字不参与排序
        for(int j = 0; j < arr.length - 2; j++ ){
            if(arr[j] > arr[j + 1]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第二趟排序后的数组");
        System.out.println(Arrays.toString(arr));

        // 第三趟排序 , 把第三大的数放在倒数第三位  最后两个数字不参与排序
        for(int j = 0; j < arr.length - 3; j++ ){
            if(arr[j] > arr[j + 1]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第三趟排序后的数组");
        System.out.println(Arrays.toString(arr));

        // 第四趟排序 , 把第四大的数放在倒数第四位  最后三个数字不参与排序
        for(int j = 0; j < arr.length - 4; j++ ){
            if(arr[j] > arr[j + 1]){
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j + 1] = temp;
            }
        }
        System.out.println("第四趟排序后的数组");
        System.out.println(Arrays.toString(arr));
    }
}

整合后的代码

  // 冒泡排序
        int temp = 0; // 临时变量
        for (int i = 0; i < arr.length-1; i++) {
            for(int j = 0; j < arr.length - 1-i; j++ ){
                if(arr[j] > arr[j + 1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j + 1] = temp;
                }
            }
            System.out.println("第"+(i+1)+"趟排序后的数组");
            System.out.println(Arrays.toString(arr));
        }

时间复杂度为O(n^2)

冒泡排序的优化

 // 将前面的冒泡排序算法,封装成一个方法
    public static void bubbleSort(int[] arr){
        // 冒泡排序
        int temp = 0; // 临时变量
        boolean flag = false; // 标识变量,表示是否进行过交换
        for (int i = 0; i < arr.length-1; i++) {
            for(int j = 0; j < arr.length - 1-i; j++ ){
                if(arr[j] > arr[j + 1]){
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j + 1] = temp;
                }
            }
//            System.out.println("第"+(i+1)+"趟排序后的数组");
//            System.out.println(Arrays.toString(arr));
            if (!flag){ // 在一趟排序中,一次交换都没有发生过
                break;
            }else {
                flag = false; // 重置flag,进行下一趟判断
            }
        }
    }

韩老师的电脑大概20秒处理8万个随机数

选择排序算法

在这里插入图片描述

在这里插入图片描述

代码实现

在这里插入图片描述

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/12
 * @apiNote
 */
public class SelectSort {
    public static void main(String[] args) {
        int[] arr = {101,34,119,1,23,121};
        System.out.println("排序前~~");
        System.out.println(Arrays.toString(arr));
        selectSort(arr);
        System.out.println("排序后~~");
        System.out.println(Arrays.toString(arr));
    }

    // 选择排序
    public static void selectSort(int[] arr){

        // 在推导的过程中,发现规律,因此,可以使用for来解决

        for (int i = 0; i <arr.length-1; i++) {
            int minIndex = i;
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]){ // 说明假定的最小值,并不是最小
                    min = arr[j];  // 重置min
                    minIndex = j;   // 重置minIndex
                }
            }
            // 将最小值,放在arr[0],即交换
            if (minIndex!=i){
                arr[minIndex] = arr[i];
                arr[i] = min;
            }

//            System.out.println("第"+(i+1)+"轮后~~");
//            System.out.println(Arrays.toString(arr));
        }



      /*  // 使用逐步推导的方式,来讲解选择排序
        // 第1轮
        // 原始的数组: 101,34,119,1
        // 第一轮排序: 1,34,119,101
        // 算法 先简单--> 做复杂,就是可以把一个复杂的算法,拆分成简单的问题->逐步解决

        int minIndex = 0;
        int min = arr[0];
        for (int j = 0 + 1; j < arr.length; j++) {
            if (min > arr[j]){ // 说明假定的最小值,并不是最小
                min = arr[j];  // 重置min
                minIndex = j;   // 重置minIndex
            }
        }
        // 将最小值,放在arr[0],即交换
        if (minIndex!=0){
            arr[minIndex] = arr[0];
            arr[0] = min;
        }

        System.out.println("第1轮后~~");
        System.out.println(Arrays.toString(arr));


        // 第2轮
        minIndex = 1;
        min = arr[1];
        for (int j = 1 + 1; j < arr.length; j++) {
            if (min > arr[j]){ // 说明假定的最小值,并不是最小
                min = arr[j];  // 重置min
                minIndex = j;   // 重置minIndex
            }
        }
        // 将最小值,放在arr[0],即交换
        if (minIndex!=1){
            arr[minIndex] = arr[1];
            arr[1] = min;
        }
        System.out.println("第2轮后~~");
        System.out.println(Arrays.toString(arr));

        // 第3轮
        minIndex = 2;
        min = arr[2];
        for (int j = 2 + 1; j < arr.length; j++) {
            if (min > arr[j]){ // 说明假定的最小值,并不是最小
                min = arr[j];  // 重置min
                minIndex = j;   // 重置minIndex
            }
        }
        // 将最小值,放在arr[0],即交换
        if (minIndex!=2){
            arr[minIndex] = arr[2];
            arr[2] = min;
        }


        System.out.println("第3轮后~~");
        System.out.println(Arrays.toString(arr));
*/
    }
}

时间复杂度O(n^2)

韩老师的电脑处理8万个随机数大概3秒

插入排序法

在这里插入图片描述

在这里插入图片描述

代码实现

有一群小牛, 考试成绩分别是 101, 34, 119, 1 请从小到大排序

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/12
 * @apiNote
 */
public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {101,34,119,1,-1,89};
        System.out.println("插入排序前~~");
        System.out.println(Arrays.toString(arr));
        insertSort(arr);
        System.out.println("插入排序后~~");
        System.out.println(Arrays.toString(arr));
    }

    // 插入排序
    public static void insertSort(int[] arr){

        int insertVal = 0;
        int insertIndex = 0;

        // 使用for循环简化代码
        for (int i = 1; i < arr.length; i++) {
            // 定义待插入的数
            insertVal = arr[i];
            insertIndex = i-1; // 即arr[1]的前面这个数的下标

            // 给insertVal找到插入的位置
            // 说明
            // 1. insertIndex >= 0 保证在给insertVal找插入位置时,不越界
            // 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
            // 3. 就需要将arr[insertIndex]后移
            while (insertIndex >= 0 && insertVal < arr[insertIndex]){
                arr[insertIndex+1] = arr[insertIndex];// arr[insertIndex]
                insertIndex--;
            }
            // 当退出while循环时,说明插入的位置找到,insertIndex+1
            // 判断一下是否需要赋值
            if (insertIndex + 1 != i){
                arr[insertIndex+1] = insertVal;
            }


      /*      System.out.println("第"+i+"轮插入");
            System.out.println(Arrays.toString(arr));*/
        }




       /* // 使用逐步推论的方式
        // 第1轮{101,34,119,1}=>{34,101,119,1}

        // 定义待插入的数
        int insertVal = arr[1];
        int insertIndex = 1-1; // 即arr[1]的前面这个数的下标

        // 给insertVal找到插入的位置
        // 说明
        // 1. insertIndex >= 0 保证在给insertVal找插入位置时,不越界
        // 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
        // 3. 就需要将arr[insertIndex]后移
        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex+1] = arr[insertIndex];// arr[insertIndex]
            insertIndex--;
        }
        // 当退出while循环时,说明插入的位置找到,insertIndex+1
        arr[insertIndex+1] = insertVal;

        System.out.println("第1轮插入");
        System.out.println(Arrays.toString(arr));

        // 第2轮
        insertVal = arr[2];
        insertIndex = 2 - 1;

        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex+1] = arr[insertIndex];// arr[insertIndex]
            insertIndex--;
        }

        arr[insertIndex+1] = insertVal;

        System.out.println("第2轮插入");
        System.out.println(Arrays.toString(arr));

        // 第3轮
        insertVal = arr[3];
        insertIndex = 3 - 1;

        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex+1] = arr[insertIndex];// arr[insertIndex]
            insertIndex--;
        }

        arr[insertIndex+1] = insertVal;

        System.out.println("第3轮插入");
        System.out.println(Arrays.toString(arr));*/

    }
}

时间复杂度O(n^2)

韩老师的电脑 8万个随机数排序 大致5秒

希尔排序算法图解

简单插入排序的问题

在这里插入图片描述

希尔排序是插入排序的一种高效版本

在这里插入图片描述

在这里插入图片描述

代码实现(交换法)

效率并不是很高 韩老师的电脑对8万个随机数进行排序 花了17秒

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/13
 * @apiNote
 */
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8,9,1,7,2,3,5,4,6,0};
        shellSort(arr);

    }

    // 使用逐步推导的方式
    // 希尔排序时,对有序序列在插入时用交换法
    // 思路(算法)---> 代码
    public static void shellSort(int[] arr){
        int temp = 0;
        int count = 0;
        // 根据下面的逐步分析,使用循环处理
        for (int gap = arr.length / 2; gap > 0; gap /= 2 ) {
            for (int i = gap; i < arr.length; i++) {
                // 遍历各组中所有的元素(共gap组),步长gap
                for (int j = i-gap; j >= 0 ; j -= gap) {
                    // 如果当前元素大于加长步长后的元素,说明交换
                    if (arr[j] > arr[j+gap]){
                        temp = arr[j];
                        arr[j] = arr[j+gap];
                        arr[j+gap] = temp;
                    }
                }
            }
            System.out.println("希尔排序第"+(++count)+"轮后="+ Arrays.toString(arr));
        }

      /*  // 希尔排序的第一轮排序
        // 因为第一轮排序,是将10个数据排序分为了5组
        for (int i = 5; i < arr.length; i++) {
            // 遍历各组中所有的元素(共5组,每组有2个元素),步长5
            for (int j = i-5; j >= 0 ; j -= 5) {
                // 如果当前元素大于加长步长后的元素,说明交换
                if (arr[j] > arr[j+5]){
                    temp = arr[j];
                    arr[j] = arr[j+5];
                    arr[j+5] = temp;
                }
            }
        }
        System.out.println("希尔排序1轮后="+ Arrays.toString(arr));


        // 希尔排序的第2轮排序
        // 因为第2轮排序,是将10个数据排序分为了5/2=2组
        for (int i = 2; i < arr.length; i++) {
            // 遍历各组中所有的元素(共5组,每组有2个元素),步长5
            for (int j = i-2; j >= 0 ; j -= 2) {
                // 如果当前元素大于加长步长后的元素,说明交换
                if (arr[j] > arr[j+2]){
                    temp = arr[j];
                    arr[j] = arr[j+2];
                    arr[j+2] = temp;
                }
            }
        }
        System.out.println("希尔排序2轮后="+ Arrays.toString(arr));


        // 希尔排序的第3轮排序
        // 因为第3轮排序,是将10个数据排序分为了2/2 = 1组
        for (int i = 1; i < arr.length; i++) {
            // 遍历各组中所有的元素(共5组,每组有2个元素),步长5
            for (int j = i-1; j >= 0 ; j -= 1) {
                // 如果当前元素大于加长步长后的元素,说明交换
                if (arr[j] > arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        System.out.println("希尔排序3轮后="+ Arrays.toString(arr));*/
    }
}

代码实现(移位法)

效率较高 韩老师的电脑对8万个随机数进行排序 花了1秒

// 对交换式的希尔排序进行优化->移位法
    public static void shellSort2(int[] arr){
        // 增量gap,并逐步的缩小增量
        for (int gap = arr.length / 2; gap > 0; gap /= 2 ) {
            // 从第gap个元素,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] < arr[j-gap]){
                    while (j - gap >= 0 && temp < arr[j-gap]){
                        // 移动
                        arr[j] = arr[j-gap];
                        j -= gap;
                    }
                    // 当退出while后,就给temp找到插入的位置
                    arr[j] = temp;
                }
            }
        }
    }

时间复杂度O(n^(1.3—2))

快速排序算法

第一种方式

在这里插入图片描述

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/14
 * @apiNote
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9,78,0,23,-567,70,70,70};
        quickSort(arr,0,arr.length-1);
        System.out.println("arr: "+ Arrays.toString(arr));

    }

    /**
     *
     * @param arr
     * @param left
     * @param right
     */
    public static void quickSort(int[] arr,int left,int right){
        int l = left;// 左下标
        int r = right; // 右下标
        int temp = 0; // 临时变量
        // pivot 中轴
        int pivot = arr[(left+right)/2];
        // 只要右边的索引大于左边的索引就继续循环  目的是 让 比pivot小的值放到左边 比 pivot 值大的放在右边
        while (l<r){
            // 在pivot的左边 一直找 找到大于等于pivot的值 ,才退出
            while (arr[l] < pivot){//最多找到pivot本身
                l++;
            }
            // 在pivot的左边 一直找 找到小于等于pivot的值 ,才退出
            while (arr[r] > pivot){//最多找到pivot本身
                r--;
            }

            // 如果成立 说明 pivot 左边的值, 已经全部小于等于pivot的值, 右边, 已经全部大于等于pivot的值
            if(l>=r){
                break;
            }

            //交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;

            // 判断 如果交换完后 发现arr[l] = pivot r向前移动 l--
            if (arr[l] == pivot){
                r -= 1;
            }else if (arr[r] == pivot){// 判断 如果交换完后 发现arr[r] = pivot l向后移动 l++
                l++;
            }
        }

        // 如果 l == r 必须 l++ 和 r--, 否则会出现栈溢出
        if (l == r ){
            l++;
            r--;
        }
        // 向左递归
        if (left < r) {
            quickSort(arr,left,r);
        }
        // 向右递归
        if (right > l) {
            quickSort(arr, l, right);
        }
    }
}

韩老师的电脑对8万个随机数进行排序 花了不到一秒 80w数据也不到1秒 800w 2-3秒

快排 时间复杂度O (nlogn) 但是空间复杂度较高

第二种方式

image-20211214142343472

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/14
 * @apiNote
 */
public class QuickSort2 {
    public static void main(String[] args) {
        int[] arr = {-9,78,0,23,-567,70,70,70};
        quickSort(arr,0,arr.length-1);
        System.out.println("arr: "+ Arrays.toString(arr));
    }

    public static void quickSort(int[] arr,int left,int right){
        // 进行判断,如果左边索引比右边索引要大,是不合法的,直接使用return结束这个方法
        if (left > right){
            return;
        }

        // 定义变量保存基准数
        int base = arr[left];
        // 定义变量i,指向最左边
        int i = left;
        // 定义变量j,指向最右边
        int j = right;

        int temp = 0;

        // 当i和j不相遇时
        while (i != j){
            // 由j从右往左检索比基准数小的,如果检索到比基准数小的就停下
            // 如果检索到比基准数大或者相等的,就继续检索
            while (arr[j] >= base && i < j){
                j--; // j从右往左移动
            }

            // i从左往右检索
            while (arr[i] <= base && i < j){
                i++; // i从左往右移动
            }

            // 代码走到这里,i停下了,j也停下。然后交换i和j位置的元素
            temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        // 如果上面while循环的条件不成立,会跳出这个循环,往下执行。
        // 如果这个条件不成立说明i和j相遇了
        // 如果i和j相遇了,就交换基准数这个元素和相遇位置的元素。
        // 把相遇位置的元素赋值给基准数这个位置的元素
        arr[left] = arr[i];
        // 把基准数赋值给相遇位置的元素。
        arr[i] = base;

        // 基准数在这里就归位了,左边的数字都比他小,右边的都比他大
        // 排基准数的左边
        quickSort(arr,left,i - 1);
        // 排右边
        quickSort(arr,j + 1,right);
    }
}

归并排序

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/15
 * @apiNote
 */
public class MergeSort {
    public static void main(String[] args) {
//        int[] arr = {8,4,5,7,1,3,6,2};
        int[] arr = new int[80000];
        int[] temp = new int[arr.length]; // 归并排序需要额外的空间
        for (int i = 0;i < 80000;i++){
            arr[i] = (int) (Math.random()*8000000); // 生成一个[8,8000000)数
        }
        long before = System.currentTimeMillis();
        mergeSort(arr,0, arr.length - 1,temp);
        long after = System.currentTimeMillis();
        System.out.println("耗时"+(after-before)+"ms");
//        System.out.println("归并排序后: "+ Arrays.toString(arr));
    }


    // 分+合方法
    public static void mergeSort(int[] arr,int left,int right,int[] temp){
        if (left < right){
            int mid = (left + right) / 2; // 中间索引
            // 向左递归进行分解
            mergeSort(arr,left,mid,temp);
            // 向右递归进行分解
            mergeSort(arr,mid+1,right,temp);
            // 合并
            merge(arr,left,mid,right,temp);
        }
    }

    // 合并的方法
    /**
     *
     * @param arr 排序的原始数组
     * @param left  左边有序序列的初始索引
     * @param mid  中间索引
     * @param right 右边索引
     * @param temp  做中转的数组
     */
    public static void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left; // 初始化i,左边有序序列的初始索引
        int j = mid + 1; // 初始化j,右边有序序列的初始索引
        int t = 0; // 指向temp数组的当前索引

        //(一)
        // 先把左右两边有序的数据按照规则填充到temp数组
        // 直到左右两边的有序序列,有一边处理完毕为止
        while(i <= mid && j <= right){// 继续
            // 如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            // 即将左边的当前元素,填充到temp数组
            // 然后t++,i++
            if (arr[i] <= arr[j]){
                temp[t] = arr[i];
                t++;
                i++;
            }else { // 反之,将右边有序序列的当前元素,填充到temp数组
                temp[t] = arr[j];
                t++;
                j++;
            }
        }

        //(二)
        // 把有剩余数据的一边的数据依次全部填充到temp
        while(i <= mid){ // 左边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[i];
            t++;
            i++;
        }

        while (j <= right){
            temp[t] = arr[j];
            t++;
            j++;
        }

        //(三)
        // 将temps数组的元素拷贝到arr
        // 注意,并不是每次都拷贝所有
        t = 0;
        int tempLeft = left;
        // 第一次合并 tempLeft = 0,right = 1 // 第二次 tempLeft = 2,right = 3//第三次 tl = 0,ri=3
        // 最后一次tempLeft = 0,right = 7
        while (tempLeft <= right){
            arr[tempLeft] = temp[t];
            t++;
            tempLeft++;
        }
    }

}

**时间复杂度O(nlogn) **

韩老师的电脑对8万个随机数进行排序 花了不到0-1秒 80w 一秒 800w3-4秒

基数排序 (桶排序)

在这里插入图片描述

稳定排序 : 如果要排序的数组有两个相同的数字 如a1 =a2 =1 {a1,2,3,a2} 排序后 {a1,a2,2,3} a1还是在a2的前面

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.sort;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/16
 * @apiNote
 */
public class RadixSort {
    public static void main(String[] args) {
        int[] arr = {53,3,542,748,14,214};
//        radixSort(arr);
        radixSortAll(arr);
    }

    // 基数排序法
    public static void radixSort(int[] arr){
        // 模拟对一轮(针对每个元素的个位数进行排序处理)

        // 定义一个二维数组,表示一个二维数组, 每个桶就是一个一维数组
        // 说明 二维数组包含10个一维数组
        //     为了防止放入数的时候 数据溢出 ,则每一个一维数组的大小为array.length()
        //     很明显 空间换时间
        int[][] bucket = new int[10][arr.length];
        // 为了记录每个桶中 实际存放了多少个数据 我们定义一个一维数组来记录每个桶的个数
        // 比如bucketElementCounts[0] 就是bucket[0] 的放入数据的个数
        int[] bucketElementCounts = new int[10];
        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的个位
            int digitOfElement = arr[j] % 10;
            // 放入到对应的桶
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        // 将桶里的数据放入
        int index = 0;
        // 遍历每一个桶,将桶里面的数据放入,原来的数组中
        for (int k = 0; k < bucket.length; k++) {
            if (bucketElementCounts[k] != 0){
                for (int j = 0; j < bucketElementCounts[k]; j++) {
                    arr[index++] = bucket[k][j];
                }
            }
            // 第一轮需要对bucketElementCount数组置零
            bucketElementCounts[k] = 0;
        }
        System.out.println("第一轮 对个位数进行处理后的数组: "+ Arrays.toString(arr));


        // 模拟对二轮(针对每个元素的十位数进行排序处理)
        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的个位
            int digitOfElement = arr[j] / 10 % 10;
            // 放入到对应的桶
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        // 将桶里的数据放入
        index = 0;
        // 遍历每一个桶,将桶里面的数据放入,原来的数组中
        for (int k = 0; k < bucket.length; k++) {
            if (bucketElementCounts[k] != 0){
                for (int j = 0; j < bucketElementCounts[k]; j++) {
                    arr[index++] = bucket[k][j];
                }
            }
            // 第一轮需要对bucketElementCount数组置零
            bucketElementCounts[k] = 0;
        }
        System.out.println("第二轮 对十位数进行处理后的数组: "+ Arrays.toString(arr));

        // 模拟对三轮(针对每个元素的百位数进行排序处理)
        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的个位
            int digitOfElement = arr[j] / 100 % 10;
            // 放入到对应的桶
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        // 将桶里的数据放入
        index = 0;
        // 遍历每一个桶,将桶里面的数据放入,原来的数组中
        for (int k = 0; k < bucket.length; k++) {
            if (bucketElementCounts[k] != 0){
                for (int j = 0; j < bucketElementCounts[k]; j++) {
                    arr[index++] = bucket[k][j];
                }
            }
            // 第一轮需要对bucketElementCount数组置零
            bucketElementCounts[k] = 0;
        }
        System.out.println("第三轮 对百位数进行处理后的数组: "+ Arrays.toString(arr));
    }

    // 总的基数排序
    public static void radixSortAll(int[] arr){
        int[][] bucket = new int[10][arr.length];
        int[] bucketElementCounts = new int[10];
        int index;
        // 需要获取最大的数
        int max = arr[0];
        for (int i = 1; i < arr.length; i++){
            if (max < arr[i]){
                max = arr[i];
            }
        }
        int maxLength = (max+"").length();
        for (int i = 0, n = 1; i < maxLength; i++, n *=10) {
            for (int j = 0; j < arr.length; j++){
                int digitOfElement = arr[j] / n % 10;
                // 放入到对应的桶
                bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
                bucketElementCounts[digitOfElement]++;
            }
            // 将桶里的数据放入
            index = 0;
            // 遍历每一个桶 将桶里的数据放入 原来的数组中
            for (int k = 0;k < bucket.length; k++){
                if (bucketElementCounts[k] != 0){
                    for (int j = 0; j < bucketElementCounts[k]; j++){
                        arr[index++] = bucket[k][j];
                    }
                }
                bucketElementCounts[k]= 0;
            }

        }
        System.out.println("基数排序的结果" + Arrays.toString(arr));
    }

}

**时间复杂度O(nlogn) 韩老师的电脑对8万个随机数进行排序 花了不到0-1秒 80w 1秒不到 800w1秒 **

**8000w 80000000 11 4 /1024/1024/1024 = 3.3G的内存

在这里插入图片描述

如果有负数就不使用基数排序

八大排序算法的对比

在这里插入图片描述

堆排序要学了二叉树才能理解

查找算法

在这里插入图片描述

顺序(线性)查找

有序无序数组都可以使用线性查找

代码实现

package com.cbx.search;

/**
 * @author cbx
 * @date 2021/12/17
 * @apiNote
 */
public class SeqSearch {
    public static void main(String[] args) {
        int[] arr = {1,9,11,-1,34,89}; // 没有顺序的数组
        int i = seqSearch(arr, 11);
        if (i!=-1){
            System.out.println("找到了,下标是"+i);
        }else {
            System.out.println("未找到");
        }

    }

    public static int seqSearch(int[] arr,int value){
        // 线性查找是逐一比对,发现有相同值,就返回下标
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == value){
                return i;
            }
        }
        return -1;
    }
}

二分查找算法

在这里插入图片描述

在这里插入图片描述

代码实现(要求数组有序)

package com.cbx.search;

import java.util.ArrayList;
import java.util.List;

/**
 * @author cbx
 * @date 2021/12/17
 * @apiNote
 */
public class BinarySearch {
	public static void main(String[] args) {
		int[] arr = {1,8,10,89,1000,1000,1000,1234};
		System.out.println(binarySearch(arr, 0, arr.length-1, 10));
		System.out.println(binarySearchUpgrade(arr, 0, arr.length-1, 1000));
	}

	// 二分查找算法

	/**
	 *
	 * @param arr 数组
	 * @param left 查找时 左边的索引
	 * @param right 查找时 右边的索引
	 * @param findVal 要查找的值
	 * @return 如果找到就返回下标 没找到返回 -1
	 */
	public static int binarySearch(int[] arr, int left, int right, int findVal){
		int mid = (left + right) / 2;
		int midVal = arr[mid];
		if (left > right) return -1;// 没有找到
		if(findVal > midVal){ // 向右递归
			return binarySearch(arr, mid+1, right, findVal);
		}else if(findVal < midVal){ // 向左递归
			return binarySearch(arr, left, mid-1, findVal);
		}else {
			return mid;
		}
	}

	// 对多个相同值的查找
	// 思路分析 在找到mid的时候不要马上返回 向mid的索引的左/右边扫描  将所有满足的查找值1000的所有下标 放入数组中

	public static List<Integer> binarySearchUpgrade(int[] arr, int left, int right, int findVal){
		List<Integer> resIndexList = new ArrayList<>();
		int mid = (left + right) / 2;
		int midVal = arr[mid];
		if (left > right) {
			resIndexList.add(-1);// 没有找到
			return resIndexList;
		}
		if(findVal > midVal){ // 向右递归
			return binarySearchUpgrade(arr, mid+1, right, findVal);
		}else if(findVal < midVal){ // 向右递归
			return binarySearchUpgrade(arr, left, mid-1, findVal);
		}else {
			int temp = mid;
			// 向左扫描
			while (arr[--temp] == midVal){
				resIndexList.add(temp);
			}
			resIndexList.add(mid);
			temp = mid;
			// 向右扫描
			while (arr[++temp] == midVal){
				resIndexList.add(temp);
			}
			return resIndexList;
		}
	}


}

插值查找法

在这里插入图片描述

代码实现(要求数组有序)

package com.cbx.search;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/18
 * @apiNote
 */
public class InsertValueSearch {
    public static void main(String[] args) {
        int[] arr = new int[100];
        for (int i = 0; i < 100; i++) {
            arr[i] = i + 1;
        }
        System.out.println(Arrays.toString(arr));
        System.out.println(insertValueSearch(arr,0,arr.length-1,55));
    }

    // 编写插值查找算法

    /**
     * 插值查找算法的前提要求也是数组要有序
     * @param arr 传入的数组
     * @param left 左边的索引
     * @param right 右边的索引
     * @param findVal 要查找的值
     * @return
     */
    public static int insertValueSearch(int[] arr,int left,int right,int findVal){
//        System.out.println("插值查找方法~");
        if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]){ // 优化查找,且防止越界
            return -1;
        }

        // 求出mid
        int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
        int midVal = arr[mid];
        if (findVal > midVal){ // 向右递归
            return insertValueSearch(arr,mid + 1,right,findVal);
        }else if (findVal < midVal){ // 向左递归
            return insertValueSearch(arr,left,mid - 1,findVal);
        }else {
            return mid;
        }
    }
}

使用场景

在这里插入图片描述

斐波那契(黄金分割)查找算法

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.search;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2021/12/18
 * @apiNote
 */
public class FibonacciSearch {
    public static int maxSize = 20;
    public static void main(String[] args) {
        int[] arr = {1,8,10,89,1000,1234};
        System.out.println(fibSearch(arr, 1234));
    }

    // 因为后面我们mid = low+F(k - 1)-1. 需要使用斐波那契数列, 因此我们需要先获取到斐波那契数列
    // 用非递归的方法得到一个大小为20的斐波那契数列
    public static int[] fbn(){
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            f[i] = f[i-1] + f[i-2];
        }
        return f;
    }

    // 非递归的方式编写斐波那契数列查找算法
    /**
     *
     * @param a
     * @param key 需要查找的值
     * @return 返回对应的下标 没有则返回-1
     */
    public static int fibSearch(int[] a, int key){
        int low = 0;
        int high = a.length -1;
        int k = 0; // 表示斐波那契分割数值的下标  mid = low+F(k - 1)-1
        int mid = 0;
        int[] f = fbn(); // 获取斐波那契数列
        // 获取斐波那契分割数值的下标
        while (high > f[k]-1){
            k++;
        }
        // 因为 f[k] 可能大于数组a 的长度 需要一个Array类 构造一个新的数组 并指向temp[]
        int[] temp = Arrays.copyOf(a, f[k]); // 多出来的部分用0补起来
        // 实际上需要使用a最后的数填充temp
        // 举例 :
        // temp:{1,8,10,89,1000,1234,0,0} =>temp:{1,8,10,89,1000,1234,1234,1234}
        for (int i = high + 1;i < temp.length; i++){
            temp[i] = a[high];
        }

        // 使用while循环查找我们的key
        while (low <= high) {
            mid = low + f[k-1]-1;
            if (key < temp[mid]){//说明应该向数组的前一部分查找(左边)
                high = mid -1;
                // 为什么是k--
                // 1 全部元素 = 前面的元素 + 后边的元素
                // 2 f[k] = f[k-1] + f[k-2];
                // 因为前面有f[k-1]个元素,所以我们继续拆分   f[k-1] = f[k-2] + f[k-3];
                // 即在f[k-1] 的前面继续查找 k--
                // 即下次在循环的时候 mid = f[k-1-1] -1
                k--;
            }else if ( key > temp[mid]){ // 我们继续向数组的右边查找
                low = mid + 1;
                // 为什么是k-2
                // 1 全部元素 = 前面的元素 + 后边的元素
                // 2 f[k] = f[k-1] + f[k-2];
                // 3 因为后面有f[k-2] 所以加足拆分 f[k -2] = f[k-3] + f[k-4];
                // 4 即在f[k-2] 的前面继续查找
                // 下次循环mid = f[k-1-2] -1
                k -= 2;
            }else { // 找到
                // 需要确定返回的是哪个下标
                if (mid <= high){
                    return mid;
                }else {
                    return high;
                }
            }
        }
        return -1; // 没有找到

    }
}

哈希表

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.hashtab;
import java.util.Scanner;

/**
 * @author cbx
 * @create 2021/12/20
 */
public class HashTable {
	public static void main(String[] args) {
		// 创建一个哈希表
		HashTab hashTab = new HashTab(7);
		//写一个简单的菜单
		String key = "";
		Scanner scanner = new Scanner(System.in);
		while(true) {
			System.out.println("add: 添加雇员");
			System.out.println("list: 显示雇员");
			System.out.println("find: 查找雇员");
			System.out.println("exit: 退出系统");
			key = scanner.next();
			switch (key) {
				case "add":
					System.out.println("输入 id");
					int id = scanner.nextInt();
					System.out.println("输入名字");
					String name = scanner.next();
					//创建 雇员
					Emp emp = new Emp(id, name);
					hashTab.add(emp);
					break;
				case "list":
					hashTab.list();
					break;
				case "find":
					System.out.println("请输入要查找的 id");
					id = scanner.nextInt();
				 	hashTab.findEmpById(id);
					break;
				case "exit":
					scanner.close();
					System.exit(0);
				default:
					break;
			}
		}
	}
}
// 定义hash表
class HashTab{
	public EmployLinkedList[] employLinkedListArray;
	private int size; //有多少条链表

	public HashTab(int size) {
		// 初始化链表
		this.size = size;
		this.employLinkedListArray = new EmployLinkedList[size];
		// 不要忘了 分别初始化每个链表
		for (int i = 0; i < size; i++){
			employLinkedListArray[i] = new EmployLinkedList();
		}
	}

	// 添加顾员
	public void add (Emp emp){
		// 通过员工的Id 得到该员工应该添加到哪一个链表
		int empLinkedListNo = hashFun(emp.id);
		// 将emp 添加到链表自己中
		employLinkedListArray[empLinkedListNo].add(emp);
	}

	//遍历所有的链表
	public void list(){
		int index = 0;
		for (EmployLinkedList e :employLinkedListArray){
			e.list(index);
			index++;
		}
	}

	// 添加顾员
	public void findEmpById(int id){
		int empLinkedListNo = hashFun(id);
		System.out.println(employLinkedListArray[empLinkedListNo].findEmpById(id)==null?"没有该节点哦":
				employLinkedListArray[empLinkedListNo].findEmpById(id));
	}

	// 编写一个散列函数 使用简单的取模法
	public int hashFun(int id){
		return id % size;
	}

}

// 表示顾员
class Emp{
	public int id;
	public String name;
	public Emp next = null;

	public Emp(int id, String name) {
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "Emp{" +
				"id=" + id +
				", name='" + name + '\'' +
				'}';
	}
}

// 创建一个EmployLinkedList
class EmployLinkedList{
	// 头指针 指向第一个Emp ,这个头指针是直接指向第一个Emp
	private Emp head = null;

	// 添加顾员到链表
	// 假定添加顾员的时候 id自增  将该雇员插入链表的最后一个
	public void add(Emp emp){
		if (head == null){
			head = emp;
			return;
		}
		Emp temp = head;
		while (temp.next != null){
			temp = temp.next;
		}
		// 当前的temp为最后一个
		temp.next = emp;
	}

	// 遍历连表的顾员信息
	public void list(int i){
		if (head == null){
			System.out.println("第"+i+"条链表为空");
			System.out.println();
			return;
		}
		Emp temp = head;
		System.out.print("第"+i+"条链表内的数据为: ");
		while (temp != null){
			System.out.print(temp+" ");
			temp = temp.next;
		}
		System.out.println();
	}

	// 根据Id查找顾员
	// 如过找不到返回空
	public Emp findEmpById(int id) {
		if (head == null){
			return null;
		}
		Emp temp = head;
		while (temp != null){
			if (temp.id == id){
				return temp;
			}
			temp = temp.next;
		}
		return temp;
	}
}


二叉树

对数组 链表 树的比较

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

二叉树的概念

在这里插入图片描述

前中后序遍历图解

在这里插入图片描述

前中后序遍历实例

在这里插入图片描述

代码实现

package com.cbx.tree;

/**
 * @author cbx
 * @date 2021/12/24
 * @apiNote
 */
public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 先创建一个二叉树
        BinaryTree binaryTree = new BinaryTree();
        // 创建需要的节点
        HeroNode root = new HeroNode(1,"松江");
        HeroNode node2 = new HeroNode(2,"无用");
        HeroNode node3 = new HeroNode(3,"卢俊义");
        HeroNode node4 = new HeroNode(4,"林冲");

        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        binaryTree.setRoot(root);

        System.out.println("前序遍历");
        binaryTree.preOrder();

        System.out.println("中序遍历");
        binaryTree.infixOrder();

        System.out.println("后序遍历");
        binaryTree.postOrder();
    }
}

// 定义BinaryTree 二叉树
class BinaryTree{
    private HeroNode root;

    public void setRoot(HeroNode root) {
        this.root = root;
    }

    // 前序遍历
    public void preOrder(){
        if (this.root != null){
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }

    // 中序遍历
    public void infixOrder(){
        if (this.root != null){
            this.root.infixOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }


    // 后序遍历
    public void postOrder(){
        if (this.root != null){
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }


}

// 先创建HeroNode 节点
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;// 默认null
    private HeroNode right;// 默认null
    public HeroNode(int no,String name){
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public HeroNode getRight() {
        return right;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    // 编写前序遍历的方法
    public void preOrder(){
        System.out.println(this); // 先输出父节点
        // 递归向左子树前序遍历
        if (this.left != null){
            this.left.preOrder();
        }

        // 递归向右子树前序遍历
        if (this.right != null){
            this.right.preOrder();
        }
    }

    // 中序遍历
    public void infixOrder(){
        // 递归向左子树中序遍历
        if (this.left != null){
            this.left.infixOrder();
        }
        // 输出父节点
        System.out.println(this);
        // 递归向右子树中遍历
        if (this.right != null){
            this.right.infixOrder();
        }
    }

    // 后序遍历
    public void postOrder(){
        if (this.left != null){
            this.left.postOrder();
        }
        if (this.right != null){
            this.right.postOrder();
        }
        System.out.println(this);//输出父节点
    }
}

前中后序查找实例

在这里插入图片描述

package com.cbx.tree;

/**
 * @author cbx
 * @date 2021/12/24
 * @apiNote
 */
public class BinaryTreeDemo {
    public static void main(String[] args) {
        // 先创建一个二叉树
        BinaryTree binaryTree = new BinaryTree();
        // 创建需要的节点
        HeroNode root = new HeroNode(1,"松江");
        HeroNode node2 = new HeroNode(2,"无用");
        HeroNode node3 = new HeroNode(3,"卢俊义");
        HeroNode node4 = new HeroNode(4,"林冲");
        HeroNode node5 = new HeroNode(5,"关胜");

        root.setLeft(node2);
        root.setRight(node3);
        node3.setRight(node4);
        node3.setLeft(node5);
        binaryTree.setRoot(root);

        System.out.println("前序遍历");
        binaryTree.preOrder();

        System.out.println("中序遍历");
        binaryTree.infixOrder();

        System.out.println("后序遍历");
        binaryTree.postOrder();

        // 前序遍历查找
        // 前序遍历的此时
        // 比较了4次
        System.out.println("前序遍历查找");
        HeroNode heroNode = binaryTree.preOrderSearch(5);
        if (heroNode != null){
            System.out.println("找到该节点 :"+heroNode.toString());
        }else {
            System.out.println("没有该英雄哦");
        }

        // 中序遍历查找
        // 比较了3次
        System.out.println("中序遍历查找");
        heroNode = binaryTree.infixOrderSearch(5);
        if (heroNode != null){
            System.out.println("找到该节点 :"+heroNode.toString());
        }else {
            System.out.println("没有该英雄哦");
        }

        // 后序遍历查找
        // 比较了2次
        System.out.println("后序遍历查找");
        heroNode = binaryTree.postOrderSearch(5);
        if (heroNode != null){
            System.out.println("找到该节点 :"+heroNode.toString());
        }else {
            System.out.println("没有该英雄哦");
        }

    }
}

// 定义BinaryTree 二叉树
class BinaryTree{
    private HeroNode root;

    public void setRoot(HeroNode root) {
        this.root = root;
    }

    // 前序遍历
    public void preOrder(){
        if (this.root != null){
            this.root.preOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }

    // 中序遍历
    public void infixOrder(){
        if (this.root != null){
            this.root.infixOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }


    // 后序遍历
    public void postOrder(){
        if (this.root != null){
            this.root.postOrder();
        }else {
            System.out.println("二叉树为空,无法遍历");
        }
    }

    // 前序遍历查找
    public HeroNode preOrderSearch(int no){
        if (root != null){
            return root.preOrderSearch(no);
        }else {
            return null;
        }
    }

    // 中序遍历查找
    public HeroNode infixOrderSearch(int no){
        if (root != null){
            return root.infixOrderSearch(no);
        }else {
            return null;
        }
    }

    // 后序遍历查找
    public HeroNode postOrderSearch(int no){
        if (root != null){
            return root.postOrderSearch(no);
        }else {
            return null;
        }
    }


}

// 先创建HeroNode 节点
class HeroNode {
    private int no;
    private String name;
    private HeroNode left;// 默认null
    private HeroNode right;// 默认null
    public HeroNode(int no,String name){
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode getLeft() {
        return left;
    }

    public void setLeft(HeroNode left) {
        this.left = left;
    }

    public HeroNode getRight() {
        return right;
    }

    public void setRight(HeroNode right) {
        this.right = right;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    // 编写前序遍历的方法
    public void preOrder(){
        System.out.println(this); // 先输出父节点
        // 递归向左子树前序遍历
        if (this.left != null){
            this.left.preOrder();
        }

        // 递归向右子树前序遍历
        if (this.right != null){
            this.right.preOrder();
        }
    }

    // 中序遍历
    public void infixOrder(){
        // 递归向左子树中序遍历
        if (this.left != null){
            this.left.infixOrder();
        }
        // 输出父节点
        System.out.println(this);
        // 递归向右子树中遍历
        if (this.right != null){
            this.right.infixOrder();
        }
    }

    // 后序遍历
    public void postOrder(){
        if (this.left != null){
            this.left.postOrder();
        }
        if (this.right != null){
            this.right.postOrder();
        }
        System.out.println(this);
    }

    // 前序遍历查找
    /**
     *
     * @param no 查找的节点
     * @return 如果找到就返回该node,如果没有找到就返回null
     */
    public HeroNode preOrderSearch(int no){
        // 比较当前节点是不是
        if (this.no == no){
            return this;
        }
        // 1.判断当前节点的左子节点是否为空,如果不为空,则向左递归前序遍历查找
        // 2.如果左递归前序查找找到了节点,则返回
        HeroNode resNode = null;
        if (this.left != null){
            resNode = this.left.preOrderSearch(no);
        }
        if (resNode != null){ // 说明左子树找到
            return resNode;
        }
        // 1.判断当前节点的右子节点是否为空,如果不为空,则向右递归前序遍历查找
        // 2.如果右递归前序查找找到了节点,则返回
        if (this.right != null){
            resNode = this.right.preOrderSearch(no);
        }
        return resNode;
    }

    // 中序遍历查找
    public HeroNode infixOrderSearch(int no){
        // 判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
        HeroNode resNode = null;
        if (this.left != null){
            resNode = this.left.infixOrderSearch(no);
        }
        if (resNode != null){
            return resNode;
        }
        // 如果找到,则返回,如果没有找到,就和当前的节点比较,如果是则返回当前节点
        if (this.no == no){
            return this;
        }
        // 否则继续进行右递归的中序查找
        if (this.right != null){
            resNode = this.right.infixOrderSearch(no);
        }
        return resNode;
    }

    // 后序遍历查找
    public HeroNode postOrderSearch(int no){
        // 判断当前节点的左子节点是否为空,如果不为空,则向左递归后序遍历查找
        HeroNode resNode = null;
        if (this.left != null){
            resNode = this.left.postOrderSearch(no);
        }
        if (resNode != null){
            return resNode;
        }
        // 如果左子树没有找到,则向右子树递归进行后序遍历查找
        if (this.right != null){
            resNode = this.right.postOrderSearch(no);
        }
        if (resNode != null){
            return resNode;
        }
        // 如果左右子树都没有找到,就比较当前节点是不是
        if (this.no == no){
            return this;
        }
        return resNode;
    }
}


二叉树的删除

在这里插入图片描述

在这里插入图片描述

 // 递归删除节点
    // 1 如果删除的节点是叶子节点 ,则删除该节点
    // 2 如果是非叶子节点 则删除该子树
    public void delete(int no){
        if (this.left != null && this.left.no == no){
            this.left = null;
            return;
        }
        if (this.right != null && this.right.no == no){
            this.right = null;
            return;
        }
        if (this.left != null){
            this.left.delete(no);
        }
        if (this.right != null){
            this.right.delete(no);
        }
    }


 // 调用删除的方法
    public void delete(int no) {
        //考虑如果是空树则提示数位空
        if (this.root == null) {
            System.out.println("root为null,无法删除");
        } else {
            // 如果该树根节点就是要删除的树 直接将数变为空树
            if (this.root.getNo() == no) {
                root = null;
            } else {
                root.delete(no);
            }
        }
    }


 // 测试删除
        System.out.println("删除前");
        binaryTree.preOrder();
        binaryTree.delete(3);
        System.out.println("删除后");
        binaryTree.preOrder();

顺序存储二叉树

代码实现(前序中序后序遍历)

需求: 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序中序后序遍历的方式进行遍历。 前序遍历的结果应当为 1,2,4,5,3,6,7

中序遍历的结果应当为 4,2,5,1,6,3,7

后序遍历的结果应当为 4,5,2,6,7,3,1

package com.cbx.tree;

/**
 * @author cbx
 * @date 2021/12/25
 * @apiNote
 */
public class ArrayBinaryTreeDemo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        // 创建一个ArrayBinaryTree
        ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
        System.out.println("顺序存储二叉树的前序遍历为");
        arrayBinaryTree.preOrder();
        System.out.println();
        System.out.println("顺序存储二叉树的中序遍历为");
        arrayBinaryTree.infixOrder();
        System.out.println();
        System.out.println("顺序存储二叉树的后序遍历为");
        arrayBinaryTree.postOrder();
    }
}

// 编写一个ArrayBinaryTree , 实现顺序存储二叉树遍历
class ArrayBinaryTree {
    private int[] arr;// 存储数据节点的数据

    public ArrayBinaryTree(int[] arr){
        this.arr = arr;
    }

    public void preOrder(){
        this.preOrder(0);
    }

    public void infixOrder(){
        this.infixOrder(0);
    }

    public void postOrder(){
        this.postOrder(0);
    }

    // 编写一个方法 完成顺序存储二叉树的前序遍历
    public void preOrder(int index){ // index 表示数组的下标
        // 如果数组为空 或者 arr.length = 0
        if (arr == null || arr.length == 0){
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }
        // 输出当前的元素
        System.out.printf("%d\t",arr[index]);
        // 向左递归遍历,第n个元素的左节点为2*n+1
        if (2*index+1<arr.length){
            preOrder(2*index+1);
        }
        // 向右递归遍历,第n个元素的左节点为2*n+2
        if (2*index+2<arr.length){
            preOrder(2*index+2);
        }
    }

    // 编写一个方法 完成顺序存储二叉树的中序遍历
    public void infixOrder(int index){ // index 表示数组的下标
        // 如果数组为空 或者 arr.length = 0
        if (arr == null || arr.length == 0){
            System.out.println("数组为空,不能按照二叉树的中序遍历");
            return;
        }

        // 向左递归遍历,第n个元素的左节点为2*n+1
        if (2*index+1<arr.length){
            infixOrder(2*index+1);
        }
        // 输出当前的元素
        System.out.printf("%d\t",arr[index]);
        // 向右递归遍历,第n个元素的左节点为2*n+2
        if (2*index+2<arr.length){
            infixOrder(2*index+2);
        }
    }


    // 编写一个方法 完成顺序存储二叉树的后序遍历
    public void postOrder(int index){ // index 表示数组的下标
        // 如果数组为空 或者 arr.length = 0
        if (arr == null || arr.length == 0){
            System.out.println("数组为空,不能按照二叉树的后序遍历");
            return;
        }

        // 向左递归遍历,第n个元素的左节点为2*n+1
        if (2*index+1<arr.length){
            postOrder(2*index+1);
        }
        // 向右递归遍历,第n个元素的左节点为2*n+2
        if (2*index+2<arr.length){
            postOrder(2*index+2);
        }

        // 输出当前的元素
        System.out.printf("%d\t",arr[index]);
    }
}

在这里插入图片描述

线索化二叉树

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现 (中序线索化)

package com.cbx.tree.threadedbinarytree;

/**
 * @author cbx
 * @date 2021/12/26
 * @apiNote
 */
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        HeroNode1 root = new HeroNode1(1, "tom");
        HeroNode1 node1 = new HeroNode1(3, "jack");
        HeroNode1 node2 = new HeroNode1(6, "luyi");
        HeroNode1 node3 = new HeroNode1(8, "mary");
        HeroNode1 node4 = new HeroNode1(10, "ll");
        HeroNode1 node5 = new HeroNode1(14, "dim");

        // 二叉树 后面递归创建 现在手动创建
        root.setLeft(node1);
        root.setRight(node2);
        node1.setLeft(node3);
        node1.setRight(node4);
        node2.setLeft(node5);

        // 测试线索化
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

        // 以node4 10号 节点做测试
        HeroNode1 leftNode = node4.getLeft();
        HeroNode1 rightNode = node4.getRight();
        // 没有线索化之前 leftNode = rightNode = null;
        System.out.println("10号节点的前驱节点为 "+leftNode);// 10号节点的前驱节点为 HeroNode1{no=3, name='jack'}
        System.out.println("10号节点的后继节点为 "+rightNode);// 10号节点的后继节点为 HeroNode1{no=1, name='tom'}
    }
}

// 定义一个线索二叉树 ThreadedBinaryTree
class ThreadedBinaryTree {
    private HeroNode1 root;
    // 为了实现线索化 需要创建一个指向当前节点的前驱节点的指针
    // pre在递归进行线索化时 总是指向前一个节点
    private HeroNode1 pre = null;

    public void setRoot(HeroNode1 root) {
        this.root = root;
    }


    public void threadedNodes() {
        this.threadedNodes(root);
    }

    /**
     *  编写 对二叉树 进行中序线索化的方法
     * @param node 当前需要线索化的节点
     */
    public void threadedNodes(HeroNode1 node){
        //如果 node = null 不能线索化
        if(node == null) {
            return;
        }
        //(1) 线索化左子树
        if (node.getLeft() != null) {
            threadedNodes(node.getLeft());
        }
        //(2) 线索化当前节点
        //   先处理当前节点的前驱节点
        if (node.getLeft() == null){
            node.setLeftType(1);
            node.setLeft(pre);
        }
        //  处理后继后继节点
        if (pre != null && pre.getRight() == null){
            pre.setRight(node);
            pre.setRightType(1);
        }
        // 很重要 每次处理完当前节点后  让这个节点变成下一个节点的前驱节点
        pre = node;

        //(3) 线索化右子树
        if (node.getRight() != null) {
            threadedNodes(node.getRight());
        }


    }

    // 前序遍历
    public void preOrder(){
        if(this.root != null){
            root.preOrder();
        }else {
            System.out.println("二叉树为空 无法遍历");
        }
    }
    // 中序遍历
    public void infixOrder(){
        if(this.root != null){
            root.infixOrder();
        }else {
            System.out.println("二叉树为空 无法遍历");
        }
    }
    // 前序遍历
    public void postOrder(){
        if(this.root != null){
            root.postOrder();
        }else {
            System.out.println("二叉树为空 无法遍历");
        }
    }

    // 前序遍历查找
    public HeroNode1 preOrderSearch(int no){
        if (root != null){
            return root.preOrderSearch(no);
        }else return null;
    }

    // 中序序遍历查找
    public HeroNode1 infixOrderSearch(int no){
        if (root != null){
            return root.infixOrderSearch(no);
        }else return null;
    }

    // 后序序遍历查找
    public HeroNode1 postOrderSearch(int no){
        if (root != null){
            return root.postOrderSearch(no);
        }else return null;
    }
    public void delete(int no){
        //考虑如果是空树则提示数位空
        if (this.root == null){
            System.out.println("root为null,无法删除");
        }else {
            // 如果该树根节点就是要删除的树 直接将数变为空树
            if (this.root.getNo() == no){
                root = null;
            }else {
                root.delete(no);
            }
        }

    }
}


// 创建HeroNode1
class HeroNode1 {
    private int no;
    private String name;
    private HeroNode1 left; //  左子树
    private HeroNode1 right;  // 右子树

    // 说明
    // leftType = 0 表示指向左子树 leftType = 1 表示指向前驱节点
    // rightType = 0 表示指向右子树 rightType = 1 表示指向后继节点
    private int leftType;
    private int rightType;

    @Override
    public String toString() {
        return "HeroNode1{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }

    public HeroNode1(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HeroNode1 getLeft() {
        return left;
    }

    public void setLeft(HeroNode1 left) {
        this.left = left;
    }

    public HeroNode1 getRight() {
        return right;
    }

    public void setRight(HeroNode1 right) {
        this.right = right;
    }

    public int getLeftType() {
        return leftType;
    }

    public void setLeftType(int leftType) {
        this.leftType = leftType;
    }

    public int getRightType() {
        return rightType;
    }

    public void setRightType(int rightType) {
        this.rightType = rightType;
    }

    // 前序遍历的方法
    public void preOrder() {
        System.out.println(this); //先输出父节点
        // 递归遍历左子树
        if (this.left != null) {
            this.left.preOrder();
        }
        // 递归遍历右子树
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    // 中序遍历的方法
    public void infixOrder() {
        // 递归遍历左子树
        if (this.left != null) {
            this.left.infixOrder();
        }
        System.out.println(this); //输出父节点
        // 递归遍历右子树
        if (this.right != null) {
            this.right.infixOrder();
        }
    }

    // 后序遍历的方法
    public void postOrder() {
        // 递归遍历左子树
        if (this.left != null) {
            this.left.postOrder();
        }
        // 递归遍历右子树
        if (this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this); //输出父节点
    }


    /**
     * 前序查找的方法
     *
     * @param no
     * @return 找到返回节点  没有找到返回no
     */
    public HeroNode1 preOrderSearch(int no) {
        System.out.println("进入前序遍历查找");
        // 先比较当前节点
        if (this.no == no) {
            return this;
        }
        HeroNode1 resNode = null;
        if (this.left != null) {
            resNode = this.left.preOrderSearch(no);
        }
        if (resNode != null) {
            return resNode;
        }
        // 递归遍历右子树
        if (this.right != null) {
            resNode = this.right.preOrderSearch(no);
        }
        return resNode;
    }

    /**
     * 中序查找的方法
     *
     * @param no
     * @return 找到返回节点  没有找到返回no
     */
    public HeroNode1 infixOrderSearch(int no) {
        HeroNode1 resNode = null;
        if (this.left != null) {
            resNode = this.left.infixOrderSearch(no);
        }
        if (resNode != null) {
            return resNode;
        }
        System.out.println("进入中序遍历查找");
        // 比较当前节点
        if (this.no == no) {
            return this;
        }
        // 递归遍历右子树
        if (this.right != null) {
            resNode = this.right.infixOrderSearch(no);
        }
        return resNode;
    }

    /**
     * 后序查找的方法
     *
     * @param no
     * @return 找到返回节点  没有找到返回no
     */
    public HeroNode1 postOrderSearch(int no) {

        HeroNode1 resNode = null;
        if (this.left != null) {
            resNode = this.left.postOrderSearch(no);
        }
        if (resNode != null) {
            return resNode;
        }
        // 递归遍历右子树
        if (this.right != null) {
            resNode = this.right.postOrderSearch(no);
        }
        if (resNode != null) {
            return resNode;
        }
        System.out.println("进入后序遍历查找");
        // 比较当前节点
        if (this.no == no) {
            return this;
        }
        return resNode;
    }

    // 递归删除节点
    // 1 如果删除的节点是叶子节点 ,则删除该节点
    // 2 如果是非叶子节点 则删除该子树
    public void delete(int no) {
        if (this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        if (this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        if (this.left != null) {
            this.left.delete(no);
        }
        if (this.right != null) {
            this.right.delete(no);
        }
    }
}

遍历线索化二叉树

在这里插入图片描述

class ThreadedBinaryTree : 
// 遍历线索化二叉树的方法
    public void threadedList(){
        // 定义一个变量 存储当前遍历的节点 从root开始
        HeroNode1 node = root;
        while (node != null){
            // 循环找到 第一个 leftType = 1的节点 当前例子应该是8
            // 后面随着遍历 而变化 当 leftType == 1时 说明该节点是按照线索化处理后的有效节点
            while (node.getLeftType() == 0){
                node = node.getLeft();
            }
            System.out.println(node);
            // 如果当前节点的右指针指向的是后驱节点,则直接输出后驱节点
            while (node.getRightType() == 1){
                // 获取当前节点的后驱节点
                node = node.getRight();
                System.out.println(node);
            }
            // 循环结束,当前node.getRightType()!=1,替换遍历的节点
            node = node.getRight();
        }
    }

main:
 // 当线索化二叉树后 无法使用原来的遍历方法
        System.out.println("线索化的遍历方式");
        threadedBinaryTree.threadedList();

树结构的实际应用

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

我的理解:上图的数组变为无序树 再去找最后一个叶子节点i , i–循环到i =0 将当前无序树变成 一个大顶锥 把返回的数组的第一个元素(当前最大的数了)和最后一个元素交换 ,再把交换后的最大的数的之前的数据当做一个新数组 从根节点开始(上面的"最后一个叶子节点" 变成从根节点开始 因为下面已经都是堆排序的数组了 只需考虑根节点) 执行上面的生成大顶锥再交换的步骤 直到数组(“再把交换后的最大的数的之前的数据当做一个新数组”) 为空

package com.cbx.tree;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/1
 * @apiNote
 */
public class HeapSort {
    public static void main(String[] args) {
        // 数组升序排序
        int arr[] = {4, 6, 8, 5, 9};
        heapSort(arr);

    }

    // 编写一个堆排序的方法
    public static void heapSort(int[] arr) {
        /*System.out.println(" 将数组 变成大顶堆");
		System.out.println();
		// 将数组 变成大顶堆
		// 分布完成
		System.out.println(" 将数组 变成一个大顶堆 分布完成");
		adjustHeap(arr,1, arr.length);
		System.out.println("第一轮" + Arrays.toString(arr));
		adjustHeap(arr,0, arr.length);
		System.out.println("第二轮" + Arrays.toString(arr));
		*//**
         * 堆排序
         * 第一轮[4, 9, 8, 5, 6]
         * 第二轮[9, 6, 8, 5, 4]
         */
        System.out.println();
        System.out.println("  将数组(无序树)  变成一个大顶堆 一次完成");
        //  最后一叶子节点 = arr.length/2 - 1
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            adjustHeap(arr, i, arr.length);
        }
        System.out.println(Arrays.toString(arr));

        System.out.println("");
        // 将根节点(最大的数) 放到数组的最后位置 然后 将该数以前的数组再次构建成大顶锥 再交换 .... 再.. 在交换
        int temp = 0;
        for (int j = arr.length - 1; j > 0; j--) {
            // 交换
            temp = arr[j];
            arr[j] = arr[0];
            arr[0] = temp;
            adjustHeap(arr, 0, j);
        }
        System.out.println("排序结果:" + Arrays.toString(arr));
    }

    /**
     * 将无序数组(对应的二叉树) 调整成一个大顶堆
     * 完成将以 i 对应的非叶子节点的树 调整大顶堆   {4,6,8,5,9} => i=1时 {4,9,8,5,6}
     * 第二次 传入的是 i = 0 =>  {9,6,8,5,4}
     *
     * @param arr    待调整的数组
     * @param i      表示非叶子节点的索引
     * @param length 对多少个元素进行调整
     */
    public static void adjustHeap(int[] arr, int i, int length) {
        int temp = arr[i];//先取出当前元素的值, 保存在临时变量
        // 开始调整
        // k = i * 2 + 1 指向 i的左子几节点 下一个  k = k * 2 +1 指向当前节点的左子节点(i的左子节点的下一个左子节点)
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
            if (k + 1 < length && arr[k] < arr[k + 1]) {// 说明左子节点的值 小于右子节点的值
                k++;// k指向右子节点
            }
            if (arr[k] > temp) {// 如果子节点大于父节点
                arr[i] = arr[k];
                i = k;// 把较大的值 付给当前节点 然后 i指向k 继续循环比较
            } else {
                break;
            }
        }
        //for循环结束后 将 以 i 为父节点的树 的最大值 放到了最顶上(局部)
        arr[i] = temp;// 将temp放在调整后的位置
    }
}

堆排序的时间复杂度为O(nlogn) 8w 随机数的排序 秒杀 80w依旧秒杀 800w 4秒

哈夫曼树

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

哈夫曼树的代码实现

package com.luyi.huffman;

import java.util.ArrayList;
import java.util.Collections;

/**
 * @author cbx
 * @date 2022/1/2
 * @apiNote
 */
public class HuffmanTree {
	public static void main(String[] args) {
		int[] arr = {13,7,8,3,29,6,1};
		System.out.println("前序遍历");
		preOrder(createHuffmanTree(arr));
		System.out.println();
		/**
		 * 前序遍历
		 * 67 29 38 15 7 8 23 10 4 1 3 6 13 
		 */
	}
	// 创建 哈夫曼树的方法
	public static Node createHuffmanTree(int[] arr){
		// 第一步 为了操作方便 遍历arr数组 将arr的元素 构建成一个Node 将Node放入ArrayList
		ArrayList<Node> nodes = new ArrayList<>();
		for(int value : arr){
			nodes.add(new Node(value));
		}
		  // 排序 从小到大
		Collections.sort(nodes);
		while (nodes.size() >1) {
			// 第二步 取出根节点权值最小的两颗二叉树(一个节点也是一颗二叉树)
			Node left = nodes.get(0);
			Node right = nodes.get(1);

			// 构建一颗新的二叉树
			Node parent = new Node(left.value + right.value);
			parent.left = left;
			parent.right = right;
			nodes.remove(left);
			nodes.remove(right);
			nodes.add(parent);
			Collections.sort(nodes);
		}
		return nodes.get(0);
	}
	// 写一个前序遍历
	public static   void  preOrder(Node node){
		if (node == null){
			System.out.println("该树为空 无法遍历");
			return;
		}
		System.out.print(node.value+" ");
		if(node.left != null){
			preOrder(node.left);
		}
		if(node.right != null){
			preOrder(node.right);
		}
	}
}

// 创建节点类
// 为了让Node 支持Collections集合排序 让Node实现Comparable结构
class Node implements Comparable<Node>{
	int value; // 节点权值
	Node left; // 指向左子节点
	Node right; // 指向右子节点


	public Node(int value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return "Node{" +
				"value=" + value +
				'}';
	}

	@Override
	public int compareTo(Node o) {
		// 从小到大的排序
		// 从大到小 -(this.value - o.value)
		return this.value - o.value;
	}
}


哈夫曼编码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

在这里插入图片描述

最佳实践-数据压缩(创建赫夫曼树)

在这里插入图片描述

最佳实践-数据压缩(生成赫夫曼编码和赫夫曼编码后的数据)

在这里插入图片描述

最佳实践-数据解压(使用赫夫曼编码解码)

在这里插入图片描述

代码实现 (个人觉得面试真的用不到,)

package com.luyi.huffmancdoe;

import com.sun.beans.editors.ByteEditor;
import org.omg.CosNaming.IstringHelper;
import sun.security.util.Length;

import java.util.*;

/**
 * @author cbx
 * @date 2022/1/3
 * @apiNote
 */
public class HuffmanCode {
	public static void main(String[] args) {
		String str = "i like like like java do you like a java"; // 长度是40
		byte[] bytes = str.getBytes();
		List<Node> nodes = getNode(bytes);
		Node huffmanTree = createHuffmanTree(nodes);
		huffmanTree.preOrder();
		//	getCodes(huffmanTree,"" , stringBuilder);
		Map<Byte, String> codes = getCodes(huffmanTree);
		System.out.println("生成的哈夫曼编码表:" + codes);

		// 测试
		byte[] zip = zip(bytes, codes);
		System.out.println("哈夫曼编码压缩过后的bytes "+ Arrays.toString(zip)); // 长度是17

		// 封装类的测试
		System.out.println("封装类的测试");
		byte[] huffmanZip = huffmanZip(bytes);
		System.out.println("哈夫曼编码压缩过后的bytes "+ Arrays.toString(huffmanZip));

		// 解码测试
		byte[] source = decode(huffmanCode, huffmanZip);
		System.out.println("原来的字符串 "+new String(source));
	}

	/**
	 *       编写一个方法将字符串对应的byte[]数组 通过生成的哈夫曼编码表 返回一个哈夫曼编码 压缩后的byte数组
	 * @param bytes 原始的字符串对应的byte[]数组
	 * @param huffmanCode 生成得Huffman编码
	 * @return 返回哈夫曼树编码处理后的byte[]
	 * 当前例子将会返回(类似 因为生成得Huffman树的结构不同 但是长度会相同)1010100010111111110010001011111111001000101111111100100101001101110001110000011011101000111100101000
	 * 101111111100110001001010011011100
	 * =>对应的byte[] 数组 放入 8个数字 对应一个byte 放入数组 例如
	 * 10101000(补码) => byte (10101000 => 10101000 -1 => 10100111(反码) => 11011000(源码) =>-88)
	 */
	private static byte[] zip(byte[] bytes, Map<Byte,String> huffmanCode){
		// 1 先用Huffman编码表 将bytes 转成 Huffman编码对应的字符串
		StringBuilder stringBuilder = new StringBuilder();
		// 遍历byte数组
		for(byte b : bytes){
			stringBuilder.append(huffmanCode.get(b));
		}
		System.out.println(stringBuilder.toString());
		// 2 将01010001011111....转为一个byte数组
		// 如果被8乘除 就是stringBuilder.length()/8 没有没整除就是 (stringBuilder.length()+1)/8
		int num = (stringBuilder.length()+7) / 8;
		// 创建 一个存储压缩后的byte数组
		byte[] huffmanCodeBytes = new byte[num];
		int index = 0; // 第几个byte
		for (int i = 0; i < stringBuilder.length(); i+=8){// 步长为8
			String strByte;
			if(i + 8 > stringBuilder.length()) {// 不够8位
				strByte = stringBuilder.substring(i);  // i-结束
			}else {
				strByte = stringBuilder.substring(i, i + 8);
			}
			huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte,2); // 二进制
		}
		return huffmanCodeBytes;
	}

	// 使用一个方法 将前面的方法封装起来 ,便于调用

	/**
	 * @param bytes 原始的字符串对应的字节数组
	 * @return 返回的是经过哈夫曼编码处理后(压缩后)的数组
	 */
	private static byte[] huffmanZip(byte[] bytes) {
		List<Node> nodes = getNode(bytes);
		// 创建哈夫曼树
		Node huffmanTreeRoot = createHuffmanTree(nodes);
		// 根据哈夫曼树生成对应的哈夫曼编码
		Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
		// 根据生成的哈夫曼编码 对原来的数据进行封装 得到压缩后的哈夫曼树的编码
		byte[] zip = zip(bytes, huffmanCodes);
		return zip;

	}

	// 完成数据的解压
	// 思路
	// 1 将huffmanCodeBytes [40, 46, -56, 46, -56, 46, -55, 5, -123, 6, -88, -46, -126, -20, -124, -126, 24 ]
	// 重新转成赫夫曼编码对应的字符串 100100100...
	// 2 将赫夫曼对应的二进制字符串 100100100... 对照 哈夫曼编码转成 i like java......

	/**
	 * 将byte 转成一个二进制的字符串
	 * @param flag 标志当前是否需要补高位  如果是true 需要补高位
	 * @param b 传入的byte
	 * @return 返回  b 对应二进制的字符串(补码返回)
	 */
	private static String byteToBitString(boolean flag,byte b){
		int temp = b; // 向上强转 b变成int
		// 如果是整数 需要补高位
		if(flag) { // 如果当前长度 没有8位 不用高位补码
			temp |= 256; // 按位与 256 =1 0000 0000   1 = 0000 0001 => 1 0000 0001
		}
		String str = Integer.toBinaryString(temp); // 返回temp 对应二进制的补码
		if (flag) {
			return str.substring(str.length() - 8); // 取最后8位
		}else {
			return str;
		}
	}

	/**
	 * 编写有个方法  完成对压缩数据的编码
	 * @param huffmanCode 哈夫曼编码表
	 * @param huffmanBytes 哈夫曼编码的得到的字节数组
	 * @return  原理的字符串对应的数组
	 */
	private static byte[] decode(Map<Byte,String> huffmanCode, byte[] huffmanBytes){
		// 1 先得到 huffmanBytes 的二进制的字符串 形式为"10101010..."
		StringBuilder stringBuilder = new StringBuilder();
		// 将byte数组转成二进制的字符串
		boolean flag = false;
		for(int i = 0; i < huffmanBytes.length; i++){
			// 判断是不是最后一个字节
			if (huffmanBytes.length-1 == i){
				flag = true;
			}
			stringBuilder.append(byteToBitString(!flag,huffmanBytes[i]));
		}
		//	System.out.println("哈弗曼树对应的二进制字符串是 "+stringBuilder.toString());
		// 把字符串安装到指定的哈夫曼编码进行解码
		// 把哈夫曼树编码进行调换 因为反向查询 a -> 100 100->a
		Map<String, Byte> map = new HashMap<>();
		for (Map.Entry<Byte,String> entry : huffmanCode.entrySet()){
			map.put(entry.getValue(), entry.getKey());
		}
		//System.out.println(map);
		//创键一个集合 存放byte
		List<Byte> list = new ArrayList<>();
		// i理解成是一个索引 扫描stringBuilder
		for (int i = 0; i < stringBuilder.length();){
			int count = 1; // 小的计数器
			flag = true;
			Byte b = null;
			while (flag) {
				// 取出一个'1'或'0' 组建key 去map{000=108, 01=32, 001=105, 01000=100, 01011=118...中查找
				String key = stringBuilder.substring(i, i + count);// i 不动 让count移动// 直到匹配到一个字符

				b = map.get(key);
				if (b == null) {
					count++;
				} else {
					//匹配到了
					flag = false;

				}
			}
			list.add(b); i += count;
		}
		// for循环结束后 list就存放了所有字符
		// 把list中的数据放入byte数组中
		byte[] b = new byte[list.size()];
		for (int i = 0; i < b.length; i++){
			b[i] = list.get(i);
		}
		return b;
	}


	/**
	 *   构建字节Node数组
	 * @param bytes 字节数组
	 * @return  返回node数组
	 */
	public static List<Node> getNode(byte[] bytes){
		// 创建一个arrayList
		ArrayList<Node> nodes = new ArrayList<>();

		// 存储每一个byte出现的次数  map来做
		HashMap<Byte, Integer> map = new HashMap<Byte, Integer>();
		for (byte b : bytes){
			Integer count = map.get(b);
			if (count == null){
				map.put(b, 1);
			}else {
				map.put(b, ++count);
			}
		}
		// 每个键值对转成node对象  并加入nodes
		// 遍历map
		for(Map.Entry<Byte,Integer> entry : map.entrySet()){
			nodes.add(new Node(entry.getKey(), entry.getValue()));
		}
		return nodes;
	}

	// 构建哈夫曼树
	public static Node createHuffmanTree(List<Node> nodes){
		if (nodes.size() == 0){
			System.out.println("数组为空 无法构建哈夫曼树");
			return null;
		}
		Collections.sort(nodes);
		while (nodes.size() > 1){
			Collections.sort(nodes);
			Node left = nodes.get(0);
			Node right = nodes.get(1);
			Node node = new Node(null,left.weight + right.weight);
			node.left = left;
			node.right = right;
			nodes.remove(left);
			nodes.remove(right);
			nodes.add(node);
		}
		return nodes.get(0);
	}

	// 生成哈夫曼对应的哈夫曼编码
	// 1 将哈夫曼树放在Map<Byte,String> 形式大概为 a->100 d->11000 u->11001 e->1110 v->11011 i->101 y->11010 j->0010 k->1111 l->000 o->0011
	// 2 在生成哈夫曼编码表时 需要拼接路径  定义一个stringBuilder 存储某个叶子节点的路径
	static Map<Byte,String> huffmanCode = new HashMap<>();
	static StringBuilder stringBuilder = new StringBuilder();


	// 为了调用方便 重载getCodes
	private static Map<Byte, String> getCodes(Node root) {
		if (root == null){
			return null;
		}
		// 处理root的左子树
		getCodes(root.left, "0", stringBuilder);
		getCodes(root.right, "1", stringBuilder);
		return huffmanCode;

	}

	/**
	 *       将传入的node节点的所有叶子节点的哈夫曼编码得到 , 并放入到HuffmanCodes集合中
	 * @param node 传入节点 默认根节点
	 * @param code 路径 左子节点为 0 右子节点为 1
	 * @param stringBuilder 拼接路径
	 */
	private static void getCodes(Node node, String code, StringBuilder stringBuilder){
		StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
		stringBuilder2.append(code);
		if (node != null){
			if (node.data == null){ // 非叶子节点
				getCodes(node.left,"0",stringBuilder2);
				getCodes(node.right,"1",stringBuilder2);
			}else {
				huffmanCode.put(node.data, stringBuilder2.toString());
			}
		}

	}
}

// 创建node ,带数据和权值
class Node implements Comparable<Node>{
	Byte data; // 存放数据本身 比如'a' => 97 ' '=> 32
	int weight; // 权值 表示数据(字符) 出现的次数
	Node left;
	Node right;

	public Node(Byte data, int weight) {
		this.data = data;
		this.weight = weight;
	}


	@Override
	public int compareTo(Node o) {
		// weight 升序排列
		return this.weight - o.weight;
	}

	@Override
	public String toString() {
		return "Node{" +
				"data=" + data +
				", weight=" + weight +
				'}';
	}
	// 前序遍历
	public  void preOrder(){
		System.out.println(this);
		if(this.left != null){
			this.left.preOrder();
		}
		if(this.right != null){
			this.right.preOrder();
		}
	}
}

最佳实践-文件压缩

在这里插入图片描述

/**
	 * 编写一个方法 对文件进行压缩
	 * @param srcFile 传入文件的路径
	 * @param dsFile  压缩后的文件的存储位置
	 */
	public static void zipFile(String srcFile, String dsFile) {
		// 创建输入输出流
		// 创建文件的输入流
		FileInputStream is = null;
		OutputStream os = null;
		ObjectOutputStream oos = null;
		try {
			 is = new FileInputStream(srcFile);
			// 创建和源文件大小一样的数组byte[]
			byte[] b = new byte[is.available()];
			is.read(b);
			// 获取到文件对应的哈夫曼编码
			// 对源文件进行压缩
			byte[] huffmanBytes = huffmanZip(b);
			// 创建文件的输出流
			os = new FileOutputStream(dsFile);
			// 创建一个文件输出关联的ObjectOutputStream
			oos = new ObjectOutputStream(os);
			// 这里以对象流的方式写入哈夫曼编码后的字节数组
			oos.write(huffmanBytes);  // 先把huffmanBytes写入
			// 写入哈夫曼编码  用于数据的恢复 为了以后恢复源文件后使用
			oos.writeObject(huffmanCode);

		}catch (Exception e){
			e.printStackTrace();
		}finally {
			try {
				is.close();
				os.close();
				oos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

最佳实践-文件解压(文件恢复)

	//编写一个方法,完成对压缩文件的解压
	/**
	 *
	 * @param zipFile 准备解压的文件
	 * @param dstFile 将文件解压到哪个路径
	 */
	public static void unZipFile(String zipFile, String dstFile) {
//定义文件输入流
		InputStream is = null;
//定义一个对象输入流
		ObjectInputStream ois = null;
//定义文件的输出流
		OutputStream os = null;
		try {
//创建文件输入流
			is = new FileInputStream(zipFile);
//创建一个和 is 关联的对象输入流
			ois = new ObjectInputStream(is);
//读取 byte 数组 huffmanBytes
			byte[] huffmanBytes = (byte[])ois.readObject();
//读取赫夫曼编码表
			Map<Byte,String> huffmanCodes = (Map<Byte,String>)ois.readObject();
//解码
			byte[] bytes = decode(huffmanCodes, huffmanBytes);
//将 bytes 数组写入到目标文件
			os = new FileOutputStream(dstFile);
//写数据到 dstFile 文件
			os.write(bytes);
		} catch (Exception e) {
// TODO: handle exception
			System.out.println(e.getMessage());
		} finally {
			try {
				os.close();
				ois.close();
				is.close();
			} catch (Exception e2) {
// TODO: handle exception
				System.out.println(e2.getMessage());
			}
		}
	}

哈夫曼编码的注意事项

在这里插入图片描述

在这里插入图片描述

二叉排序树

在这里插入图片描述

在这里插入图片描述

二叉排序树的创建和遍历

在这里插入图片描述

代码实现

package com.cbx.binarysorttree;


/**
 * @author cbx
 * @date 2022/1/5
 * @apiNote
 */
public class BinarySortTreeDemo {
	public static void main(String[] args) {
		int[] arr = {7, 3, 10, 12, 5, 1, 9};
		BinarySortTree binarySortTree = new BinarySortTree();
		for (int a : arr) {
			binarySortTree.add(new Node(a));
		}
		binarySortTree.getRoot().infixOrder();
	}
}
// 创建二叉排序树
class BinarySortTree{
	private Node root;
	public void add(Node node){
		if (root == null){
			root = node;
		}else {
			root.add(node);
		}
	}

	public Node getRoot() {
		return root;
	}

	public void setRoot(Node root) {
		this.root = root;
	}

	// 中序遍历
	public void infixOrder(){
		if (root == null){
			System.out.println("当前二叉排序树为空 无法遍历");
		}else {
			root.infixOrder();
		}
	}
}

// 创建节点
class Node{
	int value;
	Node left;
	Node right;

	public Node(int value) {
		this.value = value;
	}

	public Node() {
	}


	// 添加节点的方法
	// 递归添加节点
	public void add(Node node){
		if (node == null){
			return;
		}
		if(this.value > node.value){
			if (this.left != null){
				this.left.add(node);
			}else {
				this.left = node;
			}
		}else {  // 如果value相等 也走右边节点
			if (this.right != null){
				this.right.add(node);
			}else {
				this.right = node;
			}
		}
	}

	// 中序遍历
	public void infixOrder(){
		if(this.left != null){
			this.left.infixOrder();
		}
		System.out.print(this.value+" ");
		if (this.right != null){
			this.right.infixOrder();
		}
	}
}


二叉排序树的删除

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

package com.cbx.binarysorttree;


/**
 * @author cbx
 * @date 2022/1/5
 * @apiNote
 */
public class BinarySortTreeDemo {
	public static void main(String[] args) {
		int[] arr = {7, 3, 10, 12, 5, 1, 9};
		BinarySortTree binarySortTree = new BinarySortTree();
		for (int a : arr) {
			binarySortTree.add(new Node(a));
		}
		binarySortTree.getRoot().infixOrder();
		System.out.println();
		// 测试删除
		System.out.println("删除节点后");
		// 测试删除叶子节点
		binarySortTree.delNode(5);
		binarySortTree.delNode(7);
		binarySortTree.delNode(3);
		binarySortTree.delNode(12);
		binarySortTree.delNode(9);
		// 测试删除 只有一个子节点的节点
		binarySortTree.delNode(10);

		// 测试删除 只有两个子节点的节点
		binarySortTree.delNode(1);
		binarySortTree.infixOrder();
	}
}
// 创建二叉排序树
class BinarySortTree{
	private Node root;
	public void add(Node node){
		if (root == null){
			root = node;
		}else {
			root.add(node);
		}
	}

	// 删除节点
	public void delNode(int value) {
		if (root ==  null){
			return;
		}else {
			// 1 先去找到要删除的节点 targetNode
			Node targetNode = search(value);
			if (targetNode == null){ //没有找到
				return;
			}
			// 如果这颗二叉树只有一个 targetNode 节点
			if (root.left == null && root.right == null){
				root = null;
				return;
			}

			// 去找到targetNode的父节点
			Node parent = searchParent(value);
			// 如果要删除的节点是叶子节点  左节点? 右节点
			if (targetNode.left == null && targetNode.right == null){
				if (parent.value > value){
					parent.left = null;
				}else {
					parent.right = null;
				}

			}else if(targetNode.right != null && targetNode.left != null){ // 要删除的节点左右都有子节点

				int min = delRightTreeMin(targetNode);
				targetNode.value = min;


			}else { // 删除只有一颗子树的接单
				// 如果要删除的这个节点有左子节点
				if (targetNode.left != null){
					if (parent != null) {
						if (parent.left.value == targetNode.value) {// 如果targetNode是parent的左子节点
							parent.left = targetNode.left;
						} else {//如果targetNode是parent的右子节点
							parent.right = targetNode.left;
						}
					}else {
						root = targetNode.left;
					}
				}else {
					if (parent != null) {
						if (parent.left.value == targetNode.value) {// 如果targetNode是parent的左子节点
							parent.left = targetNode.right;
						} else { //如果targetNode是parent的右子节点
							parent.right = targetNode.right;
						}
					}else {
						root = targetNode.right;
					}
				}
			}
		}
	}

	/**
	 *     找到要删除的节点的右子节点开始的最小节点(右子节点的向左走) 返回该节点的值  删除该节点
	 * @param node 传入要删除的节点
	 * @return 返回从要删除的节点的右子节点开始的最小节点(右子节点的向左走)
	 */
	public int delRightTreeMin(Node node){
		Node target = node.right;
		while ( target.left != null){
			target = target.left;
		}
		// 删除最小节点
		delNode(target.value);
		// 返回最小节点的值
		return target.value;
	}

	// 第二种,找到要删除的节点的左子节点开始的最大节点(左子节点的向右走) 返回该节点的值  删除该节点
	public int delLeftTreeMax(Node node){
		Node target = node.left;
		while ( target.right != null){
			target = target.right;
		}
		// 删除最大节点
		delNode(target.value);
		// 返回最大节点的值
		return target.value;
	}

	// 查找要删除的节点
	public Node search(int value){
		if (root == null){
			return null;
		}else {
			return root.search(value);
		}
	}

	//查找父节点
	public Node searchParent(int value){
		if (root == null){
			return null;
		}else {
			return root.searchParent(value);
		}
	}

	public Node getRoot() {
		return root;
	}

	public void setRoot(Node root) {
		this.root = root;
	}

	// 中序遍历
	public void infixOrder(){
		if (this.getRoot() == null){
			System.out.println("当前二叉排序树为空 无法遍历");
		}else {
			this.getRoot().infixOrder();
		}
	}
}

// 创建节点
class Node{
	int value;
	Node left;
	Node right;

	public Node(int value) {
		this.value = value;
	}

	public Node() {
	}

	/**
	 * 查找要删除的节点
	 * @param value 希望删除的节点
	 * @return 如果找到返回该节点 否则返回null
	 */
	public Node search (int value){
		if (value == this.value) { // 找到就是该节点
			return this;
		}else if (value < this.value){
			if(this.left != null){
				return left.search(value);
			}else {
				return null;
			}
		}else {
			if(this.right != null){
				return this.right.search(value);
			}else {
				return null;
			}
		}
	}

	/**
	 * 		查找要删除节点的父节点
	 * 	 @param value 希望删除的节点
	 */
	public Node searchParent(int value){
		if (this.left != null && this.left.value == value){
			return this;
		}else if(this.right != null && this.right.value == value){
			return this;
		}else {
			// 如果查找的值 小于当前节点的值 , 并且当前的节点左子节点不为空
			if(value < this.value && this.left != null){
				return this.left.searchParent(value);
			}else if(value >= this.value && this.right != null){
				return this.right.searchParent(value);
			}else {
				// 真的找不到了 没有父节点
				return null;
			}
		}
	}


	// 添加节点的方法
	// 递归添加节点
	public void add(Node node){
		if (node == null){
			return;
		}
		if(this.value > node.value){
			if (this.left != null){
				this.left.add(node);
			}else {
				this.left = node;
			}
		}else {  // 如果value相等 也走右边节点
			if (this.right != null){
				this.right.add(node);
			}else {
				this.right = node;
			}
		}
	}

	// 中序遍历
	public void infixOrder(){
		if(this.left != null){
			this.left.infixOrder();
		}
		System.out.print(this.value+" ");
		if (this.right != null){
			this.right.infixOrder();
		}
	}
}


二叉排序树的注意事

如果删除的数10这个根节点 走到这一步时 会报空指针异常 所以需要加一个判断

在这里插入图片描述

修改方案如果

parent 判空

在这里插入图片描述

	// 删除节点
	public void delNode(int value) {
		if (root ==  null){
			return;
		}else {
			// 1 先去找到要删除的节点 targetNode
			Node targetNode = search(value);
			if (targetNode == null){ //没有找到
				return;
			}
			// 如果这颗二叉树只有一个 targetNode 节点
			if (root.left == null && root.right == null){
				root = null;
				return;
			}

			// 去找到targetNode的父节点
			Node parent = searchParent(value);
			// 如果要删除的节点是叶子节点  左节点? 右节点
			if (targetNode.left == null && targetNode.right == null){
				if (parent.value > value){
					parent.left = null;
				}else {
					parent.right = null;
				}

			}else if(targetNode.right != null && targetNode.left != null){ // 要删除的节点左右都有子节点

				int min = delRightTreeMin(targetNode);
				targetNode.value = min;


			}else { // 删除只有一颗子树的接单
				// 如果要删除的这个节点有左子节点
				if (targetNode.left != null){
					if (parent != null) {
						if (parent.left.value == targetNode.value) {// 如果targetNode是parent的左子节点
							parent.left = targetNode.left;
						} else {//如果targetNode是parent的右子节点
							parent.right = targetNode.left;
						}
					}else {
						root = targetNode.left;
					}
				}else {
					if (parent != null) {
						if (parent.left.value == targetNode.value) {// 如果targetNode是parent的左子节点
							parent.left = targetNode.right;
						} else { //如果targetNode是parent的右子节点
							parent.right = targetNode.right;
						}
					}else {
						root = targetNode.right;
					}
				}
			}
		}
	}

平衡二叉树

在这里插入图片描述

在这里插入图片描述

单旋转 左旋转

在这里插入图片描述

在这里插入图片描述

package com.cbx.avl;

/**
 * @author cbx
 * @date 2022/1/8
 * @apiNote
 */
public class AVLTreeDemo {
    public static void main(String[] args) {
        int[] arr = {4,3,6,5,7,8};

        AVLTree avlTree = new AVLTree();

        for (int ar : arr) {
            avlTree.add(new Node(ar));
        }

        System.out.println("中序遍历");
        avlTree.infixOrder();

        System.out.println("在平衡处理后");
        System.out.println("树的高度="+avlTree.getRoot().height());
        System.out.println("树的左子树高度="+avlTree.getRoot().leftHeight());;
        System.out.println("树的右子树高度="+avlTree.getRoot().rightHeight());;
    }
}

class AVLTree{
    private Node root;
    public void add(Node node){
        if (root == null){
            root = node;
        }else {
            root.add(node);
        }
    }

    // 删除节点
    public void delNode(int value) {
        if (root ==  null){
            return;
        }else {
            // 1 先去找到要删除的节点 targetNode
            Node targetNode = search(value);
            if (targetNode == null){ //没有找到
                return;
            }
            // 如果这颗二叉树只有一个 targetNode 节点
            if (root.left == null && root.right == null){
                root = null;
                return;
            }

            // 去找到targetNode的父节点
            Node parent = searchParent(value);
            // 如果要删除的节点是叶子节点  左节点? 右节点
            if (targetNode.left == null && targetNode.right == null){
                if (parent.value > value){
                    parent.left = null;
                }else {
                    parent.right = null;
                }

            }else if(targetNode.right != null && targetNode.left != null){ // 要删除的节点左右都有子节点

                int min = delRightTreeMin(targetNode);
                targetNode.value = min;


            }else { // 删除只有一颗子树的接单
                // 如果要删除的这个节点有左子节点
                if (targetNode.left != null){
                    if (parent != null) {
                        if (parent.left.value == targetNode.value) {// 如果targetNode是parent的左子节点
                            parent.left = targetNode.left;
                        } else {//如果targetNode是parent的右子节点
                            parent.right = targetNode.left;
                        }
                    }else {
                        root = targetNode.left;
                    }
                }else {
                    if (parent != null) {
                        if (parent.left.value == targetNode.value) {// 如果targetNode是parent的左子节点
                            parent.left = targetNode.right;
                        } else { //如果targetNode是parent的右子节点
                            parent.right = targetNode.right;
                        }
                    }else {
                        root = targetNode.right;
                    }
                }
            }
        }
    }

    /**
     *     找到要删除的节点的右子节点开始的最小节点(右子节点的向左走) 返回该节点的值  删除该节点
     * @param node 传入要删除的节点
     * @return 返回从要删除的节点的右子节点开始的最小节点(右子节点的向左走)
     */
    public int delRightTreeMin(Node node){
        Node target = node.right;
        while ( target.left != null){
            target = target.left;
        }
        // 删除最小节点
        delNode(target.value);
        // 返回最小节点的值
        return target.value;
    }

    // 第二种,找到要删除的节点的左子节点开始的最大节点(左子节点的向右走) 返回该节点的值  删除该节点
    public int delLeftTreeMax(Node node){
        Node target = node.left;
        while ( target.right != null){
            target = target.right;
        }
        // 删除最大节点
        delNode(target.value);
        // 返回最大节点的值
        return target.value;
    }

    // 查找要删除的节点
    public Node search(int value){
        if (root == null){
            return null;
        }else {
            return root.search(value);
        }
    }

    //查找父节点
    public Node searchParent(int value){
        if (root == null){
            return null;
        }else {
            return root.searchParent(value);
        }
    }

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    // 中序遍历
    public void infixOrder(){
        if (this.getRoot() == null){
            System.out.println("当前二叉排序树为空 无法遍历");
        }else {
            this.getRoot().infixOrder();
        }
    }
}

// 创建节点
class Node{
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }

    public int leftHeight(){
        if (left == null){
            return 0;
        }
        return left.height();
    }

    public int rightHeight(){
        if (right == null){
            return 0;
        }
        return right.height();
    }


    public int height(){
        return Math.max(left == null ? 0 : left.height(), right == null? 0 : right.height())+1;
    }

    private void leftRotate(){
        Node newNode = new Node(value);

        newNode.left = left;

        newNode.right = right.left;

        value = right.value;

        right = right.right;

        left = newNode;
    }

    public Node() {
    }

    /**
     * 查找要删除的节点
     * @param value 希望删除的节点
     * @return 如果找到返回该节点 否则返回null
     */
    public Node search (int value){
        if (value == this.value) { // 找到就是该节点
            return this;
        }else if (value < this.value){
            if(this.left != null){
                return left.search(value);
            }else {
                return null;
            }
        }else {
            if(this.right != null){
                return this.right.search(value);
            }else {
                return null;
            }
        }
    }

    /**
     * 		查找要删除节点的父节点
     * 	 @param value 希望删除的节点
     */
    public Node searchParent(int value){
        if (this.left != null && this.left.value == value){
            return this;
        }else if(this.right != null && this.right.value == value){
            return this;
        }else {
            // 如果查找的值 小于当前节点的值 , 并且当前的节点左子节点不为空
            if(value < this.value && this.left != null){
                return this.left.searchParent(value);
            }else if(value >= this.value && this.right != null){
                return this.right.searchParent(value);
            }else {
                // 真的找不到了 没有父节点
                return null;
            }
        }
    }


    // 添加节点的方法
    // 递归添加节点
    public void add(Node node){
        if (node == null){
            return;
        }
        if(this.value > node.value){
            if (this.left != null){
                this.left.add(node);
            }else {
                this.left = node;
            }
        }else {  // 如果value相等 也走右边节点
            if (this.right != null){
                this.right.add(node);
            }else {
                this.right = node;
            }
        }

        if (rightHeight() - leftHeight() > 1){
            leftRotate();
        }
    }

    // 中序遍历
    public void infixOrder(){
        if(this.left != null){
            this.left.infixOrder();
        }
        System.out.print(this.value+" ");
        if (this.right != null){
            this.right.infixOrder();
        }
    }
}

单旋转 右旋转

在这里插入图片描述

在这里插入图片描述

// 右旋转
	private void rightRotate(){
		Node newNode = new Node(value);
		// 新节点的有节点指向当前节点的右节点
		newNode.right = right;
		// 当前节点的左节点指向当前节点的左节点的有节点
		newNode.left = left.right;
		// 当前节点的值指向当前节点的左子节点的值
		value = left.value;
		// 当前节点的左子节点 指向当前节点的左子节点的左子节点
		left = left.left;
		// 当前节点的右子节点 指向新的节点
		right = newNode;
	}

构造平衡二叉排序树的方法

	// 添加节点的方法
	// 递归添加节点
	public void add(Node node){
		if (node == null){
			return;
		}
		if(this.value > node.value){
			if (this.left != null){
				this.left.add(node);
			}else {
				this.left = node;
			}
		}else {  // 如果value相等 也走右边节点
			if (this.right != null){
				this.right.add(node);
			}else {
				this.right = node;
			}
		}

		// 当添加完一个节点后 如果 右子树的高度 - 左子树的高度>1 发生左旋转
		if (rightHeight() - leftHeight() > 1) {
			/*if (right != null && right.rightHeight() > right.leftHeight()){
				// 先对右子树进行旋转
			}*/
			leftRotate(); // 左旋转
		}

		// 当添加完一个节点后 如果 左子树的高度 - 有子树的高度>1 发生右旋转
		if (leftHeight() - rightHeight() > 1) {
			/*if (right != null && right.rightHeight() > right.leftHeight()){
				// 先对右子树进行旋转
			}*/
			rightRotate(); // 右旋转
		}
	}

双旋转

在这里插入图片描述

代码实现

主要是在生成平衡二叉树的时候进行操作

// 添加节点的方法
	// 递归添加节点
	public void add(Node node){
		if (node == null){
			return;
		}
		if(this.value > node.value){
			if (this.left != null){
				this.left.add(node);
			}else {
				this.left = node;
			}
		}else {  // 如果value相等 也走右边节点
			if (this.right != null){
				this.right.add(node);
			}else {
				this.right = node;
			}
		}

		// 当添加完一个节点后 如果 右子树的高度 - 左子树的高度>1 发生左旋转
		if (rightHeight() - leftHeight() > 1) {
			// 如果根节点的右子树的左子树的高度 > 根节点的右子树的右子树的高度
			if (right != null && right.rightHeight() > right.leftHeight()){
				// 对根节点的右子树进行右旋转
				right.rightRotate();
			}
			leftRotate(); // 左旋转
			return; // 必须要
		}

		// 当添加完一个节点后 如果 左子树的高度 - 有子树的高度>1 发生右旋转
		if (leftHeight() - rightHeight() > 1) {
			// 如果根节点的左子树的右子树的高度 > 根节点左子树的左子树的的高度
			if(left != null && left.rightHeight() > left.leftHeight()){
				// 对根节点的左子树进行左旋转
				left.leftRotate();
			}
			rightRotate(); // 右旋转
		}
	}


多路查找树

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

2-3树

在这里插入图片描述

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

B树 B+树 B*树

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

快速入门案例

在这里插入图片描述

代码实现

package com.cbx.graph;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/10
 * @apiNote
 */
public class Graph {

    private ArrayList<String> vertexList; // 存储顶点集合
    private int[][] edges; // 存储图对应的邻接矩阵
    private int numberOfEdges; // 表示边的数目

    public static void main(String[] args) {
// 节点的个数
        int n = 5;
        String[] vertexValue = {"A", "B", "C", "D", "E"};
        // 创建图对象
        Graph graph = new Graph(n);
        // 循环添加节点数据
        for (String item : vertexValue) {
            graph.insertVertex(item);
        }
        // 添加边 A-B A-C B-C B-D B-E
        graph.insertEdge(0, 1, 1);
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(1, 2, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 4, 1);

        graph.showGraph();
        
    }

    public Graph (int n) {
        // 初始化矩阵 和 arrayList
        edges = new int[n][n];
        vertexList = new ArrayList<>(n);
        numberOfEdges = 0;
    }

    // 图的常用方法

    // 1 返回节点的个数
    public int getNumberOfVertex() {
        return vertexList.size();
    }

    // 2 得到边的数目
    public int getNumberOfEdges() {
        return numberOfEdges;
    }

    // 3 返回节点i(下标) 对应的数据 0->"A" 1->"B"
    public String getValueByIndex(int i) {
        return vertexList.get(i);
    }
    // 4. 返回v1 和 v2 边的权值
    public int insertVertex(int v1, int v2) {
        return edges[v1][v2];
    }

    // 5. 显示图对应的矩阵
    public void showGraph() {
        System.out.println("遍历矩阵 ");
        for (int[] k : edges) {
            System.out.println(Arrays.toString(k));
        }
    }

    // 插入节点
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }

    // 添加边

    /**
     *
     * @param v1 第一个顶点的下标 第几个顶点
     * @param v2  第二个顶点的下标
     * @param weight 边的权值
     */
    public void insertEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numberOfEdges++;
    }
}



图的深度优先遍历

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

代码实现

package com.cbx.graph;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/10
 * @apiNote
 */
public class Graph {

    private ArrayList<String> vertexList; // 存储顶点集合
    private int[][] edges; // 存储图对应的邻接矩阵
    private int numberOfEdges; // 表示边的数目
    private boolean[] isVisited; // 记录节点是否被访问

    public static void main(String[] args) {
// 节点的个数
        int n = 5;
        String[] vertexValue = {"A", "B", "C", "D", "E"};
        // 创建图对象
        Graph graph = new Graph(n);
        // 循环添加节点数据
        for (String item : vertexValue) {
            graph.insertVertex(item);
        }
        // 添加边 A-B A-C B-C B-D B-E
        graph.insertEdge(0, 1, 1);
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(1, 2, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 4, 1);

        graph.showGraph();

        System.out.println("深度优先遍历");
        graph.dfs(); // A->B->C->D->E->
    }

    public Graph (int n) {
        // 初始化矩阵 和 arrayList
        edges = new int[n][n];
        vertexList = new ArrayList<>(n);
        numberOfEdges = 0;
        isVisited = new boolean[n];
    }

    // 得到第一个邻接结点的下标

    /**
     * 求该结点的第一个邻接矩阵下标
     * @param index 该结点的下标
     * @return 如果存在就返回对应的下标 否则返回-1
     */
    public int getFirstNeighbor(int index) {
        for (int j = 0; j < vertexList.size(); j++) {
            if (edges[index][j] > 0) {
                return j;
            }
        }
        return -1;
    }
    // 根据前一个邻接节点的下标 获取下一个邻接节点
    public int getNextNeighbor(int v1, int v2) {
        for (int j = v2 + 1; j < vertexList.size(); j++) {
            if (edges[v1][j] > 0) {
                return j;
            }
        }
        return -1;
    }

    // 深度优先遍历方法
    public void dfs(boolean[] isVisit, int i) {
        // 首先访问该节点
        System.out.print(getValueByIndex(i) + "->");
        // 将该节点设置已经访问过
        isVisit[i] = true;
        // 查找下标为i 的邻接节点
        int w = getFirstNeighbor(i);
        while (w != -1) {
            if (!isVisit[w]) { // 没有被访问过
                dfs(isVisit, w);
            }
            // 如果w 被访问过了 去找 下标为i 的节点的 下一个邻接节点的下一个邻接节点
            w = getNextNeighbor(i, w);
        }
    }

    // 对dfs 进行重载 遍历所有的节点 并进行 dfs
    public void dfs() {
        // 回溯遍历dfs
        for(int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                dfs(isVisited, i);
            }
        }
    }

    // 图的常用方法

    // 1 返回节点的个数
    public int getNumberOfVertex() {
        return vertexList.size();
    }

    // 2 得到边的数目
    public int getNumberOfEdges() {
        return numberOfEdges;
    }

    // 3 返回节点i(下标) 对应的数据 0->"A" 1->"B"
    public String getValueByIndex(int i) {
        return vertexList.get(i);
    }
    // 4. 返回v1 和 v2 边的权值
    public int insertVertex(int v1, int v2) {
        return edges[v1][v2];
    }

    // 5. 显示图对应的矩阵
    public void showGraph() {
        System.out.println("遍历矩阵 ");
        for (int[] k : edges) {
            System.out.println(Arrays.toString(k));
        }
    }

    // 插入节点
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }

    // 添加边

    /**
     *
     * @param v1 第一个顶点的下标 第几个顶点
     * @param v2  第二个顶点的下标
     * @param weight 边的权值
     */
    public void insertEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numberOfEdges++;
    }
}



图的广度优先遍历

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

代码实现

// 对一个结点进行广度优先遍历的方法
	private void bfs(boolean[] isVisited, int i) {
		int u; // 表示队列的头结点的下标
		int w; // 邻接节点的下标
		// 队列存放访问过的节点 (现进先出)
		LinkedList<Integer> queue = new LinkedList<Integer>();
		// 访问当前节点
		System.out.print(getValueByIndex(i) + "->");
		// 标记为已访问
		isVisited[i] = true;
		// 将节点加入队列
		queue.addLast(i);
		while (!queue.isEmpty()) {
			// 取出对列的头结点坐标
			 u = queue.removeFirst();
			 w = getFirstNeighbor(u);
			 while (w != -1) { //
			 	// 是否访问过
				 if (!isVisited[w]) {
				 	System.out.print(getValueByIndex(w) + "->");
				 	isVisited[w] = true;
				 	 // 入队列
					 queue.addLast(w);
				 }
				 // 已经访问过的话 以u为为前驱 找 w 的下一个邻接结点
				 w = getNextNeighbor(u, w); // 体现出广度优先
			 }
		}
	}

	// 遍历所有的节点 都进行广度优先搜索
	public void bfs() {
		for (int i = 0; i < vertexList.size(); i++) {
			if (!isVisited[i]) {
				bfs(isVisited, i);
			}
		}
	}


图的深度优先 VS 广度优先

在这里插入图片描述

总的代码实现

package com.cbx.graph;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

/**
 * @author cbx
 * @date 2022/1/10
 * @apiNote
 */
public class Graph {

    private ArrayList<String> vertexList; // 存储顶点集合
    private int[][] edges; // 存储图对应的邻接矩阵
    private int numberOfEdges; // 表示边的数目
    private boolean[] isVisited; // 记录节点是否被访问

    public static void main(String[] args) {
// 节点的个数
//        int n = 8;
//        String[] vertexValue = {"A", "B", "C", "D", "E"};
        String[] vertexValue = {"1", "2", "3", "4", "5", "6", "7", "8"};

        // 创建图对象
        Graph graph = new Graph(vertexValue.length);
        // 循环添加节点数据
        for (String item : vertexValue) {
            graph.insertVertex(item);
        }
        // 添加边 A-B A-C B-C B-D B-E
    /*    graph.insertEdge(0, 1, 1);
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(1, 2, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 4, 1);*/

        graph.insertEdge(0, 1, 1);
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 4, 1);
        graph.insertEdge(3, 7, 1);
        graph.insertEdge(4, 7, 1);
        graph.insertEdge(2, 5, 1);
        graph.insertEdge(2, 6, 1);
        graph.insertEdge(5, 6, 1);

        graph.showGraph();

        System.out.println("深度优先遍历");
        graph.dfs(); // A->B->C->D->E->
        System.out.println();
        System.out.println("广度优先遍历");
        graph.bfs();
    }

    public Graph (int n) {
        // 初始化矩阵 和 arrayList
        edges = new int[n][n];
        vertexList = new ArrayList<>(n);
        numberOfEdges = 0;
        isVisited = new boolean[n];
    }

    // 得到第一个邻接结点的下标

    /**
     * 求该结点的第一个邻接矩阵下标
     * @param index 该结点的下标
     * @return 如果存在就返回对应的下标 否则返回-1
     */
    public int getFirstNeighbor(int index) {
        for (int j = 0; j < vertexList.size(); j++) {
            if (edges[index][j] > 0) {
                return j;
            }
        }
        return -1;
    }
    // 根据前一个邻接节点的下标 获取下一个邻接节点
    public int getNextNeighbor(int v1, int v2) {
        for (int j = v2 + 1; j < vertexList.size(); j++) {
            if (edges[v1][j] > 0) {
                return j;
            }
        }
        return -1;
    }

    // 深度优先遍历方法
    public void dfs(boolean[] isVisit, int i) {
        // 首先访问该节点
        System.out.print(getValueByIndex(i) + "->");
        // 将该节点设置已经访问过
        isVisit[i] = true;
        // 查找下标为i 的邻接节点
        int w = getFirstNeighbor(i);
        while (w != -1) {
            if (!isVisit[w]) { // 没有被访问过
                dfs(isVisit, w);
            }
            // 如果w 被访问过了 去找 下标为i 的节点的 下一个邻接节点
            w = getNextNeighbor(i, w);
        }
    }

    // 对dfs 进行重载 遍历所有的节点 并进行 dfs
    public void dfs() {
        isVisited = new boolean[vertexList.size()];
        // 回溯遍历dfs
        for(int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                dfs(isVisited, i);
            }
        }
    }

    // 对一个结点进行广度优先遍历的方法
    private void bfs(boolean[] isVisited, int i) {
        int u; // 表示队列的头结点的下标
        int w; // 邻接节点的下标
        // 队列存放访问过的节点 (现进先出)
        LinkedList<Integer> queue = new LinkedList<Integer>();
        // 访问当前节点
        System.out.print(getValueByIndex(i) + "->");
        // 标记为已访问
        isVisited[i] = true;
        // 将节点加入队列
        queue.addLast(i);
        while (!queue.isEmpty()) {
            // 取出对列的头结点坐标
            u = queue.removeFirst();
            w = getFirstNeighbor(u);
            while (w != -1) { //
                // 是否访问过
                if (!isVisited[w]) {
                    System.out.print(getValueByIndex(w) + "->");
                    isVisited[w] = true;
                    // 入队列
                    queue.addLast(w);
                }
                // 已经访问过的话 以u为为前驱 找 下一个邻接结点
                w = getNextNeighbor(u, w); // 体现出广度优先
            }
        }
    }

    // 遍历所有的节点 都进行广度优先搜索
    public void bfs() {
        isVisited = new boolean[vertexList.size()];
        for (int i = 0; i < vertexList.size(); i++) {
            if (!isVisited[i]) {
                bfs(isVisited, i);
            }
        }
    }



    // 图的常用方法

    // 1 返回节点的个数
    public int getNumberOfVertex() {
        return vertexList.size();
    }

    // 2 得到边的数目
    public int getNumberOfEdges() {
        return numberOfEdges;
    }

    // 3 返回节点i(下标) 对应的数据 0->"A" 1->"B"
    public String getValueByIndex(int i) {
        return vertexList.get(i);
    }
    // 4. 返回v1 和 v2 边的权值
    public int insertVertex(int v1, int v2) {
        return edges[v1][v2];
    }

    // 5. 显示图对应的矩阵
    public void showGraph() {
        System.out.println("遍历矩阵 ");
        for (int[] k : edges) {
            System.out.println(Arrays.toString(k));
        }
    }

    // 插入节点
    public void insertVertex(String vertex) {
        vertexList.add(vertex);
    }

    // 添加边

    /**
     *
     * @param v1 第一个顶点的下标 第几个顶点
     * @param v2  第二个顶点的下标
     * @param weight 边的权值
     */
    public void insertEdge(int v1, int v2, int weight) {
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        numberOfEdges++;
    }
}



程序员常用10种算法

二分查找法 非递归

在这里插入图片描述

代码实现

package com.cbx.algorithm.binaryseearchnorecursion;

/**
 * @author cbx
 * @date 2022/1/11
 * @apiNote
 */
public class BinarySearchNoRecur {
    public static void main(String[] args) {

        int[] arr = {1,3, 8, 10, 11, 67, 100};
        int i = binarySearch(arr, 100);
        System.out.println(i);
    }

    /**
     * 二分查找的非递归实现
     * @param arr 带查找的数组  arr 默认是升序排列
     * @param target 要查找的目标
     * @return -1 表示没有找到
     */
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (arr[mid] == target) {
                return mid;
            }else if (arr[mid] > target) { // 需要向左边查找
                right = mid - 1;
            }else { // 需要向右边查找
                left = mid + 1;
            }
        }
        return -1;
    }
}



分治算法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

分治法解决汉诺塔问题 代码实现

package com.cbx.algorithm.dac;

/**
 * @author cbx
 * @date 2022/1/11
 * @apiNote
 */
public class Hanoitower {
    public static void main(String[] args) {
        hanoiTower(5,'A','B','C');
    }

    /**
     *
     * @param num 要移动的个数
     * @param a 起始位置
     * @param b 中转位置
     * @param c 目标位置
     */
    public static void hanoiTower(int num, char a, char b, char c) {
        // 如果只有一个盘
        if (num == 1) {
            System.out.println("第1个盘从" + a + "->" + c);
        } else {
            // 如果我们有n >= 2情况 我们总是可以看做是两个盘 1 最下面的盘 2 上面所有的盘
            // 1 先把上面的盘 a - > b  移动过程暂存到c
            hanoiTower(num- 1, a, c, b);
            // 2 最下面的盘 a - > c
            System.out.println("第"+num+"个盘从" + a + "->" + c);
            // 3 上面的盘 b - > c   移动过程暂存到a
            hanoiTower(num- 1, b, a, c);
        }

    }
}

动态规划算法

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

在这里插入图片描述

代码实现

package com.cbx.algorithm.dynamic;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/12
 * @apiNote
 */
public class DynamicKnapsackProblem {
    public static void main(String[] args) {
        int[] w = {1,4,3}; // 每个物品的重量
        int[] val = {1500, 3000, 2000}; // 物品的价值  val[i]
        int m = 4; // 背包的容量
        int n = w.length; // 物品的个数


        // 创建二维数组
        // v[i][j]  表示在前 i 个物品中能够装入容量为 j 的背包中的最大价值
        int[][] v = new int[n + 1][m + 1];

        // 为了记录放入商品情况 定义一个二维数组
        int[][] path = new int[n + 1][m + 1];


        // 初始化数组 初始化第一行和第一列, 这里在本程序中,可以不去处理,因为默认就是 0
        for (int i = 0; i < v.length; i++) {
            v[i][0] = 0;
        }
        for (int i = 0; i < m + 1; i++) {
            v[0][i] = 0;
        }

        // 根据前面的公式来动态规划处理
        for (int i = 1; i < v.length; i++) { // 不处理第一行
            for (int j = 1; j < v[0].length; j++) { // 不处理第一列
                // 公式
                // 1 当 w[i]> j 时:v[i][j]=v[i-1][j]
                if (w[i-1] > j ) { // i , j 从1 开始 要 -1
                    v[i][j] = v[i - 1][j];

                }else {
                    // 2  当 j>=w[i]时: v[i][j]=max{v[i-1][j], val[i]+v[i-1][j-w[i]]} // i 从 1开始
                    //v[i][j]=Math.max(v[i-1][j], val[i - 1]+v[i-1][j-w[i - 1]]); 为了记录商品存放的情况 不能简单判读大小
                    if (v[i - 1][j] < val[i - 1] + v[i - 1][j - w[i - 1]]) {
                        v[i][j] =  val[i - 1] + v[i - 1][j - w[i - 1]];
                        // 把 当前的情况记录到path
                        path[i][j] = 1;
                    }else {
                        v[i][j] =  v[i - 1][j];
                    }
                }

            }
        }

        //输出依一下 目前的情况
        for (int i = 0; i < v.length; i++) {
            System.out.println(Arrays.toString(v[i]));
        }
//		for (int i = 0; i < v.length; i++) {
//			System.out.println(Arrays.toString(path[i]));
//		}
//		for (int i = 0; i < path.length; i++) { // 这样输出会将所有的放入情况都会遍历 会有冗余
//			for (int j = 0; j < path[0].length; j++) {
//				if (path[i][j] == 1) {
//					System.out.println("第"+i+"个商品发布放入背包");
//				}
//			}
//		}

        for (int i = 0;i < path.length;i++){
            System.out.println(Arrays.toString(path[i]));
        }

        int i = path.length - 1; // 行的最大下标
        int j = path[0].length - 1; // 列的最大下标
        while (i > 0 && j > 0) { // 逆向遍历
            if (path[i][j] == 1) {
                System.out.println("第"+i+"个商品发布放入背包");
                j -= w[i -1]; // 相当于背包减去容量
            }
            i--;
        }





    }
}



KMP算法

在这里插入图片描述

暴力匹配法

在这里插入图片描述

暴力匹配算法 代码实现

package com.cbx.algorithm.kmp;

/**
 * @author cbx
 * @date 2022/1/13
 * @apiNote
 */
public class ViolenceMatch {
    public static void main(String[] args) {
        // 测试 暴力匹配算法
        String str1 = "硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好";
        String str2 = "尚硅谷你尚硅你";
        System.out.println(violenceMatch(str1, str2));
    }

    // 暴力匹配算法实现
    public static int violenceMatch(String str1, String str2) {
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();

        int s1Len = str1.length();
        int s2Len = str2.length();

        int i = 0; // i索引指向s1
        int j = 0; // j索引指向s2
        while (i < s1Len && j < s2.length) { // 保证不越界
            if(s1[i] == s2[j]) { // 匹配成功
                i++;j++;
            }else {
                i = i -( j - 1);
                j = 0;
            }
        }
        // 判断是否匹配成功
        if (j == s2Len) {
            return i - j;
        } else {
            return -1;
        }
    }
}

KMP算法

在这里插入图片描述

:https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html

在这里插入图片描述

在这里插入图片描述

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

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

KMP算法 代码实现

package com.cbx.algorithm.kmp;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/13
 * @apiNote
 */
public class KMPMatch {
	public static void main(String[] args) {
		String str1 = "BBC ABCDABABC DABCDABDE";
		String str2 = "ABCDABD";
		//String str2 = "BBC";
		int[] next = KMPNext(str2);
		System.out.println("部分匹配表" + Arrays.toString(next));
		System.out.println("匹配字段的起始位置" + KMPSearch(str1, str2, next)); // 15

	}


	/**
	 * 写出KMP搜索算法
	 * @param str1 原字符串
	 * @param str2 子串
	 * @param next 部分匹配表 是子串的部分匹配表
	 * @return -1 为没有匹配到 否则返回第一个匹配的位置
	 */
	public static int KMPSearch(String str1, String str2, int[] next) {
		// 遍历
		for (int i = 0, j = 0; i < str1.length(); i++) {
			// 需要处理 str1.charAt(i) != str2.charAt(j), 调整 j 的 大小
			// KMP代码的核心点
			while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
				j = next[j - 1];
			}

			if (str1.charAt(i) == str2.charAt(j)) {
				j++;
			}
			if (j == str2.length()) { //j==7
				return i - j + 1;
			}
		}
		return -1;
	}

	// 获取一个字符串(子串)的部分匹配
	public static int[] KMPNext (String str) {
		int[] next = new int[str.length()];
		next[0] = 0; // 如果字符传长度为1 部分匹配值为0
		for (int i = 1, j = 0; i < str.length(); i++) {
			// 当str.charAt(i) != str.charAt(j) 需要从next[j - 1]获取新得节点
			// 直到发现有str.charAt(i) == str.charAt(j)成立时推出
			// KMP算法的核心
			while (j > 0 && str.charAt(i) != str.charAt(j)) {
				j = next[j - 1];
			}

			// 满足str.charAt(i) == str.charAt(j)条件时 部分匹配值 + 1
			if (str.charAt(i) == str.charAt(j)) {
				j++;
			}
			next[i] = j;
		}
		return next;
	}
}


贪心算法

在这里插入图片描述

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

代码实现

package com.cbx.algorithm.greedy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
 * @author cbx
 * @date 2022/1/14
 * @apiNote
 */
public class GreedyAlgorithm {
    public static void main(String[] args) {
        // 创建广播电台 放入Map
        Map<String, HashSet<String>> broadcast = new HashMap<>();
        // 将各个电台放入HashSet
        HashSet<String> hashSet1 = new HashSet<>();
        hashSet1.add("北京");
        hashSet1.add("上海");
        hashSet1.add("天津");

        HashSet<String> hashSet2 = new HashSet<>();
        hashSet2.add("广州");
        hashSet2.add("上海");
        hashSet2.add("深圳");

        HashSet<String> hashSet3 = new HashSet<>();
        hashSet3.add("成都");
        hashSet3.add("上海");
        hashSet3.add("杭州");

        HashSet<String> hashSet4 = new HashSet<>();
        hashSet4.add("上海");
        hashSet4.add("天津");

        HashSet<String> hashSet5 = new HashSet<>();
        hashSet5.add("杭州");
        hashSet5.add("大连");

        broadcast.put("k1", hashSet1);
        broadcast.put("k2", hashSet2);
        broadcast.put("k3", hashSet3);
        broadcast.put("k4", hashSet4);
        broadcast.put("k5", hashSet5);

        // 存放所有地区
        HashSet<String> allArea = new HashSet<>();
        for (Map.Entry<String, HashSet<String>> entity : broadcast.entrySet()) {
            allArea.addAll(entity.getValue());
        }
        // 可以输出验证一波
//		for (String s : allArea) {
//			System.out.println(s);
//		}
        // 创建 ArrayList存放选择的电台集合
        ArrayList<String> selects = new ArrayList<>();

        // 定义一个临时集合 在遍历过程中 存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
        HashSet<String> tempSet = new HashSet<>();

        // 定义一个maxKey 保存在一次遍历过程中 能够覆盖最大覆盖的地区对应的电台key
        // 如果maxKey 不为null 则会加入selects里
        String maxKey = "";
        while (allArea.size() != 0) { // 如果allArea > 0 则还没有覆盖所有的地区
            maxKey = ""; // 每个循环需要对maxKey清空
            // 遍历broadcast. 取出对应的ekey
            for (String key : broadcast.keySet()) {
                tempSet.clear(); // tempSet 每个循环要清空
                // areas 当前的key(电台) 能够覆盖的地区
                HashSet<String> areas = broadcast.get(key);
                tempSet.addAll(areas);
                // 取出tempSet 与 allAreas 集合的交集  retainAll 方法 将交集的部分赋值给tempSet
                tempSet.retainAll(allArea);
                // 如果当前这个集合包含的未覆盖的地区的数量 比maxKey 指向集合为覆盖的地区的多 将 maxSize重新赋值
                if (tempSet.size() > 0 && ("".equals(maxKey) || tempSet.size() > broadcast.get(maxKey).size())) {
                    maxKey = key;
                }
            }
            // 如果maxKey的长度不为0 将 maxKey加入到selects
            if (!"".equals(maxKey)) {
                selects.add(maxKey);
                // 将maxKey指向的广播电台覆盖的地区, 从allAreas 去掉
                allArea.removeAll(broadcast.get(maxKey));
            }
        }

        System.out.println("得到的集合为:"+ selects); //得到的集合为:[k1, k2, k3, k5]
    }
}

普利姆算法 Prim

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

在这里插入图片描述

代码实现

package com.cbx.algorithm.prim;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/15
 * @apiNote
 */
public class PrimAlgorithm {
    public static void main(String[] args) {
        char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        int verxs = data.length;
        //邻接矩阵的关系使用二维数组表示,10000 这个大数,表示两个点不联通
        int [][]weight=new int[][]{
                {10000,5,7,10000,10000,10000,2}, // A
                {5,10000,10000,9,10000,10000,3}, // B
                {7,10000,10000,10000,8,10000,10000}, // C
                {10000,9,10000,10000,10000,4,10000}, // D
                {10000,10000,8,10000,10000,5,4}, // E
                {10000,10000,10000,4,5,10000,6}, // F
                {2,3,10000,10000,4,6,10000},}; // G
        // A  B  C  D  E  F  G
        MGraph mGraph = new MGraph(verxs);
        // 创建 MinTree 对象
        MinTree minTree = new MinTree();
        minTree.creatGraph(mGraph, verxs, data, weight);
        minTree.showGraph(mGraph);
        // 测试Prim算法
        minTree.prim(mGraph, 0);
    }
}


// 创建最小生成树 -> 村庄的通路问题
class MinTree {

    /**
     * 创建图的邻接矩阵
     * @param graph 图的对象
     * @param verxs 图的个数
     * @param data 图的各个顶点的值
     * @param weight 图的邻接矩阵
     */
    public void creatGraph(MGraph graph, int verxs, char[] data, int[][] weight) {
        for (int i = 0; i < verxs; i++) {
            graph.data[i] = data[i];
            System.arraycopy(weight[i], 0, graph.weight[i], 0, verxs);
            /*  for (int j = 0; j < verxs; j++) {
                graph.weight[i][j] = weight[i][j];
            }*/
        }
    }

    public void showGraph(MGraph graph) {
        for (int i = 0; i < graph.verx; i++) {
            System.out.println(Arrays.toString(graph.weight[i]));
        }
    }

    /**
     * 编写一个Prim算法 得到最小生成树
     * @param graph 图
     * @param v 表示从图的第几个顶点开始生成 'A'开始生成 传入 0   如果想从B开始生成 传入 1 ....
     */
    public void prim (MGraph graph, int v) {
        int[] visited = new int[graph.verx]; // 标记节点是否被访问   java默认都是 0 其他语言可能需要初始化一下;
        visited[v] = 1;
        // h1 和 h2 记录两个顶点的下标
        int h1 = -1;
        int h2 = -1;
        int minWeight = 10000; // 初始化成一个大数
        for (int k = 1; k < graph.verx; k++) { // 因为有 graph.verx 个顶点 结果会有 graph.verx-1 条边 所以直接从1 开始
            // 遍历每一次生成的子图 找到和哪个节点的距离最近
            for (int i = 0; i < graph.verx; i++) { // i 表示被访问过的节点
                for (int j = 0; j < graph.verx; j++) { // j 表示还没被访问过的节点
                    if (visited[i] == 1 && visited[j] == 0 && graph.weight[i][j] < minWeight) {
                        // 替换 minWeight (寻找已经访问过的节点间的权值最小的边) 看不懂去看图 卢意!!!
                        minWeight = graph.weight[i][j];
                        h1 = i;
                        h2 = j;
                    }
                }
            }
            // 找到一条边就是最小的
            System.out.println("边" + graph.data[h1] + "-----" + graph.data[h2] + " 距离= " + minWeight);
            minWeight = 10000;
            visited[h2] = 1;
        }

    }
}

class MGraph {
    int verx; // 表示图的节点的个数
    char[] data; //存放 节点的数据
    int[][] weight; // 存放边和权值 (邻接矩阵)

    public MGraph(int verx) {
        this.verx = verx;
        data = new char[verx];
        weight = new int[verx][verx];
    }

}

下面是 System.arrayCopy的源代码声明 :

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:
  Object src : 原数组
   int srcPos : 从元数据的起始位置开始
  Object dest : 目标数组
  int destPos : 目标数组的开始起始位置
  int length  : 要copy的数组的长度

比如 :我们有一个数组数据 byte[] srcBytes = new byte[]{2,4,0,0,0,0,0,10,15,50}; // 源数组

​ byte[] destBytes = new byte[5]; // 目标数组

我们使用System.arraycopy进行转换(copy)

System.arrayCopy(srcBytes,0,destBytes ,0,5)
上面这段代码就是 : 创建一个一维空数组,数组的总长度为 12位,然后将srcBytes源数组中 从0位 到 第5位之间的数值 copy 到 destBytes目标数组中,在目标数组的第0位开始放置.
那么这行代码的运行效果应该是 2,4,0,0,0,

克鲁斯卡尔算法 kruskal

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

代码实现

package com.cbx.algorithm.kruskal;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/16
 * @apiNote
 */
public class KruskalCase {
    private int edgeNum; // 边的个数
    private char[] vertex; // 顶点数组
    private int[][] matrix; // 邻接矩阵
    private static final int INF = Integer.MAX_VALUE; // 使用INF变量表示两个顶点不能联通

    public static void main(String[] args) {
        char[] vertex = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        //克鲁斯卡尔算法的邻接矩阵
        int matrix[][] = {  // 自己和自己为0
                /*A*//*B*//*C*//*D*//*E*//*F*//*G*/
                /*A*/ { 0, 12, INF, INF, INF, 16, 14},
                /*B*/ { 12, 0, 10, INF, INF, 7, INF},
                /*C*/ { INF, 10, 0, 3, 5, 6, INF},
                /*D*/ { INF, INF, 3, 0, 4, INF, INF},
                /*E*/ { INF, INF, 5, 4, 0, 2, 8},
                /*F*/ { 16, 7, 6, INF, 2, 0, 9},
                /*G*/ { 14, INF, INF, INF, 8, 9, 0}};

        // 创建克鲁斯卡尔的对象实例
        KruskalCase kruskalCase = new KruskalCase(vertex, matrix);
        kruskalCase.show();
        EData[] edges = kruskalCase.getEdges();
        System.out.println("未排序");
        System.out.println(Arrays.toString(edges));
		System.out.println("排序后");
		kruskalCase.sortEdges(edges);
        System.out.println(Arrays.toString(edges));
        kruskalCase.kruskal();
        System.out.println();

    }

    public KruskalCase(char[] vertex, int[][] matrix) {
        // 初始化顶点数和边的个数
        this.vertex = vertex;
        this.matrix = matrix;
        for (int i = 0; i < vertex.length; i++) {
            for (int j = i + 1; j < vertex.length; j++) {
                if (this.matrix[i][j] != INF) { // 判断可以联通
                    edgeNum ++;
                }
            }
        }
    }

    // 打印邻接矩阵
    public void show() {
        System.out.println("邻接矩阵为:");
        for (int[] arr : matrix) {
            System.out.println(Arrays.toString(arr));
        }
    }

   /* *//**
     * 对边进行排序处理  用我最不熟系的对排序进行排序
     *//*
    private void heapSort(EData[] eData) {
        for (int i = eData.length / 2 - 1; i >=0; i--) {
            adjustHeap(eData,i, eData.length);
        }
        EData temp;
        for (int i = eData.length - 1; i > 0; i--) {
            temp = eData[i];
            eData[i] = eData[0];
            eData[0] = temp;
            adjustHeap(eData, 0, i);
        }
        System.out.println(Arrays.toString(eData));
    }

    private void adjustHeap(EData[] eData, int i, int length) {
        EData temp = eData[i];
        for (int k = i * 2 +1; k < length; k = k * 2 + 1) {
            if (k + 1 < length && eData[k + 1].weight > eData[k].weight) {
                k++;
            }
            if (eData[k].weight > temp.weight) {
                eData[i] = eData[k];
                i = k;
            } else {
                break;
            }
        }
        eData[i] = temp;
    }
*/

    /**
     * 功能:对边进行排序处理,冒泡排序
     * @param edges 边的集合
     */
    private void sortEdges(EData[] edges){
        for (int i = 0; i < edges.length-1; i++) {
            for (int j = 0; j < edges.length - 1 - i; j++) {
                if (edges[j].weight > edges[j+1].weight){
                    EData tmp = edges[j];
                    edges[j] = edges[j+1];
                    edges[j+1] = tmp;
                }
            }
        }
    }



    /**
     * @param ch 顶点的值 'A'
     * @return 返回对应顶点的下标
     */
    private int getPosition(char ch) {
        for (int i = 0; i < vertex.length; i++) {
            if (vertex[i] == ch) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取图中的边 EData[]放入数组中 后面需要遍历该数组
     * 通过matrix邻接矩阵来获取
     * EData[] 形式 [['A', 'B', 12],['B', 'F', 7] .....]
     * @return
     */
    private EData[] getEdges() {
        int index = 0;
        EData[] edges = new EData[edgeNum];
        for (int i = 0; i < vertex.length; i++) {
            for (int j = i + 1; j < vertex.length; j++) {
                if (matrix[i][j] != INF) {
                    edges[index++] = new EData(vertex[i], vertex[j], matrix[i][j]);
                }
            }
        }
        return edges;
    }

    /**
     * 获取下标为i的顶点的终点 用于判断两个顶点的终点是否相同
     * @param ends 记录了各个顶点对应的终点的下标是哪个 动态生成的
     * @param i 传入的顶点对应的下标
     * @return 返回下标为i 的顶点的终点对应的下标
     */
    private int getEnd(int [] ends, int i) {
        while (ends[i] != 0) {
            i = ends[i];
        }
        return i;
    }

    // kruskal最小生成数算法
    public void kruskal() {
        int index = 0; // 最后结果数组的索引
        int[] ends = new int[edgeNum]; // 用于保存"已有最小生成数" 中的每个顶点 在最小生成数中的每个终点
        // 创建结果数组 保存最后的生成数
        EData[] res = new EData[edgeNum];

        // 获取图中所有所有边的集合 一个有12条边
        EData[] edges = getEdges();
        sortEdges(edges);

        // 按边的权值从小到大排序
        // 遍历edges 数组, 将边添加到最小生成树中, 判断是准备加入的边是否形成回路, 如果没有 则加入res
        int p1;
        int p2;
        for (int i = 0; i < edgeNum; i++) {
            // 获取到第i条边的第一个顶点 (起点)
            p1 = getPosition(edges[i].start);
            // 获取到第i条边的第二个顶点 (终点)
            p2 = getPosition(edges[i].end);

            // 获取 p1 这个顶点在已有的最小生成数中的终点是哪个
            int m = getEnd(ends, p1);
            // 获取 p2 这个顶点在已有的最小生成数中的终点是哪个
            int n = getEnd(ends, p2);
            // 判断是否构成回路
            if (m != n) { // 不构成回路
                ends[m] = n; // 设置m在"当前最小生成数" 中的终点为 n
                res[index++] = edges[i];
            }
        }
        for (int i = 0; i < index ; i++) {
            System.out.println(res[i]);
        }
    }

    // 打印和统计最小生成数
    public void showRes() {

    }

}

/**
 *  创建一个类EData 它的对象实例就表示一条边
 */
class EData {
    char start; // 边的一个点 可以理解为起点
    char end; // 边的另外一个顶点 可以理解为终点
    int weight; // 边的权值

    public EData(char start, char end, int weight) {
        this.start = start;
        this.end = end;
        this.weight = weight;
    }


    @Override
    public String toString() {
        return "EData{" +
                "start=" + start +
                ", end=" + end +
                ", weight=" + weight +
                '}';
    }

}

迪杰斯特拉算法 Dijkstra

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

代码实现

package com.cbx.algorithm.dijkstra;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/17
 * @apiNote
 */
public class DijkstraAlgorithm {
    public static final int INF = 65535; // 表示不可连接  不能Integer的最大值哦  超过了就变负数了 就有问题了
    public static void main(String[] args) {
        char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        // 邻接矩阵
        int[][] matrix = new int[vertex.length][vertex.length];

        matrix[0]=new int[]{INF,5,7,INF,INF,INF,2};
        matrix[1]=new int[]{5,INF,INF,9,INF,INF,3};
        matrix[2]=new int[]{7,INF,INF,INF,8,INF,INF};
        matrix[3]=new int[]{INF,9,INF,INF,INF,4,INF};
        matrix[4]=new int[]{INF,INF,8,INF,INF,5,4};
        matrix[5]=new int[]{INF,INF,INF,4,5,INF,6};
        matrix[6]=new int[]{2,3,INF,INF,4,6,INF};

        // 创建图的对象
        Graph graph = new Graph(vertex, matrix);
        // 测试 看看邻接矩阵是否ok
        graph.showGraph();

        // 测试迪杰斯特拉算法
        graph.dsj(6);
        graph.showDijkstra();
    }
}

// 创建一个图类
class Graph {
    private char[] vertex; // 存放顶点数组
    private int[][] matrix; // 邻接矩阵
    private VisitedVertex visitedVertex; // 已经访问顶点的集合

    public Graph(char[] vertex, int[][] matrix) {
        this.vertex = vertex;
        this.matrix = matrix;
    }

    // 显示图的方法
    public void showGraph() {
        for (int[] arr : matrix) {
            System.out.println(Arrays.toString(arr));
        }
    }

    // 显示结果
    public void showDijkstra() {
        visitedVertex.show();
    }

    /**
     *  迪杰斯特拉算法实现
     * @param index
     */
    public void dsj(int index) {
        this.visitedVertex = new VisitedVertex(vertex.length, index);
        update(index); // 更新index顶点 到周围顶点的距离和前驱顶点
        for (int i = 1; i < vertex.length; i++) {
            index = visitedVertex.updateArr(); // 选择并返回新的访问顶点
            update(index);  // 更新index顶点 到周围顶点的距离和前驱顶点
        }
    }


    // 更新index 下标顶点周围顶点的距离 和周围顶点的前驱顶点
    private void update(int index) {
        int length = 0;
        // 根据邻接矩阵的 index行
        for (int j = 0; j < matrix[index].length; j++) {
            // len 的含义是 出发顶点到index顶点的距离 + 从index顶点的距离到 j顶点的距离
            length = visitedVertex.getDis(index) + matrix[index][j];
            // 如果j的顶点没有被访问过 并 小于出发顶点到j顶点的距离就需要更新
            if (!visitedVertex.in(j) && length < visitedVertex.getDis(j)) {
                visitedVertex.updatePre(j, index); // 更新j顶点的前驱为index顶点
                visitedVertex.updateDis(j, length); // 更新出发顶点到j节点的距离
            }
        }

    }
}

// 已访问顶点的类
class  VisitedVertex {
    // 记录每个顶点是否访问过  1表示访问过,0表示未访问, 会动态更新
    public int[] already_arr;
    // 每个下标对应的值为第一个顶点的下标, 会动态更新
    public int[] pre_visited;
    // 记录出发顶点到洽谈所有顶点的距离 比如G为出发顶点 就会记录G到其他顶点的距离 会动态更新 求的最短距离会放到dis
    public int[] dis;

    /**
     * 构造器
     * @param length 数组的长度 也是节点的个数
     * @param index 出发顶点对应的下标
     */
    public VisitedVertex(int length, int index) {
        this.already_arr = new int[length];
        this.pre_visited = new int[length];
        this.dis = new int[length];
        // 初始化dis数组 都为不可到达的值
        Arrays.fill(dis, DijkstraAlgorithm.INF);
        // 设置出发顶点被访问过
        this.already_arr[index] = 1;
        // 自己的对应下标的值为0 也代表不可达
        this.dis[index] = 0;
    }

    /**
     * 判断index顶点是否被访问过
     * @param index
     * @return 如果访问过返回 true  否则 false
     */
    public boolean in(int index) {
        return already_arr[index] == 1;
    }

    /**
     * 更新出发顶点到index顶点的距离
     * @param index
     * @param len
     */
    public void updateDis(int index, int len) {
        dis[index] = len;
    }

    /**
     * 更新pre为索引的顶点的前驱为index索引的节点
     * @param pre
     * @param index
     */
    public void updatePre(int pre, int index) {
        pre_visited[pre] = index;
    }

    /**
     * 返回出发顶点到index顶点的距离
     * @param index
     */
    public int getDis(int index) {
        return dis[index];
    }

    /**
     * 继续选择滨返回新的访问节点, 比如这里的G 完后 就是A作为新的访问顶点 (注意不是出发点)
     * @return
     */
    public int updateArr() {
        int min = DijkstraAlgorithm.INF;
        int index = 0;
        for (int i = 0; i < already_arr.length; i++) {
            if (already_arr[i] == 0 && dis[i] < min) {
                min = dis[i];
                index = i;
            }
        }
        already_arr[index] = 1;
        return index;
    }

    // 显示最后的结果
    // 将三个数组的情况输出
    public void show() {
        System.out.println("==================================");
        System.out.println("array_arr:");
        for (int i : already_arr) {
            System.out.print(i + " " );
        }
        System.out.println();
        System.out.println("pre_visited:");
        for (int i : pre_visited) {
            System.out.print(i + " " );
        }
        System.out.println();
        System.out.println("dis:");
        for (int i : dis) {
            System.out.print(i + " " );
        }
        System.out.println();
        //为了好看最后的最短距离,我们处理
        char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
        int count = 0;
        for (int i : dis) {
            if (i != 65535) {
                System.out.print(vertex[count] + "("+i+") ");
            } else {
                System.out.println("N ");
            }
            count++;
        }
        System.out.println();
    }
}

弗洛伊德算法

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

代码实现

package com.cbx.algorithm.floyd;

import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/18
 * @apiNote
 */
public class FloydAlgorithm {
    public static void main(String[] args) {
        // 测试看看图是否创建成功
        char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };
        //创建邻接矩阵
        int[][] matrix = new int[vertex.length][vertex.length];
        final int N = 65535;
        matrix[0] = new int[] { 0, 5, 7, N, N, N, 2 };
        matrix[1] = new int[] { 5, 0, N, 9, N, N, 3 };
        matrix[2] = new int[] { 7, N, 0, N, 8, N, N };
        matrix[3] = new int[] { N, 9, N, 0, N, 4, N };
        matrix[4] = new int[] { N, N, 8, N, 0, 5, 4 };
        matrix[5] = new int[] { N, N, N, 4, 5, 0, 6 };
        matrix[6] = new int[] { 2, 3, N, N, 4, 6, 0 };

        //创建 Graph 对象
        GraphFloyd graph = new GraphFloyd(vertex.length, matrix, vertex);
        //调用弗洛伊德算法
        graph.floyd();
        graph.show();
    }
}


// 创建图
class GraphFloyd {
    private char[] vertex; // 存放顶点的数组
    private int[][] dis; // 记录从各个顶点出发 到其他顶点的距离, 最后的结果 , 也是流在该时数组
    private int[][] pre; // 保存到达目标结点的前驱节点

    /**
     *
     * @param length 顶点的个数
     * @param matrix 邻接表
     * @param vertex 顶点数组
     */
    GraphFloyd(int length, int[][] matrix, char[] vertex) {
        this.vertex = vertex;
        this.dis = matrix;
        this.pre = new int[length][length];
        // 对pre数组初始化, 存放的是前驱顶点的下标 初始化每一行都是当前节点的下标
        for (int i = 0; i < length; i++) {
            Arrays.fill(pre[i], i);
        }
    }

    // 显示pre数组 和 dis数组
    public void show() {
        char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
        for (int k = 0; k < dis.length; k++) {
            // 先将 pre 数组输出的一行
            for (int i = 0; i < dis.length; i++) {
                System.out.print(vertex[pre[k][i]] + " ");
            }
            System.out.println();
            // 输出 dis 数组的一行数据
            for (int i = 0; i < dis.length; i++) {
                System.out.print("(" + vertex[k] + "到" + vertex[i] + "的最短路径是" + dis[k][i] + ") ");
            }
            System.out.println();
            System.out.println();

        }
    }

    /**
     * 弗洛伊德算法
     */
    public void floyd() {
        // 保存变量
        int len;
        // 从中间顶点的变量 k是中间顶点的下标
        for (int k = 0; k < dis.length; k++) {
            // 从 i 顶点开始出发  'A', 'B', 'C', 'D', 'E', 'F', 'G'
            for (int i = 0; i < dis.length; i++) {
                // 到达j 顶点
                for (int j = 0; j < dis.length; j++) {
                    len = dis[i][k] + dis[k][j]; // 求出从i顶点出发 经过k 的中间顶点 到达j顶点的路离
                    if (len < dis[i][j]) { // 小于直连的值  取经过其他顶点的的值
                        dis[i][j] = len; // 更新距离表
                        pre[i][j] = pre[k][j]; // 更新前驱顶点
                    }
                }
            }
        }
    }

}

马踏棋盘算法

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

代码实现 (会很慢)

package com.cbx.algorithm.horse;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/19
 * @apiNote
 */
public class HorseChessboard {
    private static int x; // 棋盘的列
    private static int y; // 棋盘的行
    // 创建一个数组 标记各个位置是否被访问过
    private static boolean[] visited;
    // 使用一个属性 标记棋盘的所有位置是否被 访问过
    private static boolean finished;
//    static int count = 0;
    public static void main(String[] args) {
        // 测试
        x = 6;
        y = 6;
        int row = 2; // 马儿初始位置的行
        int clo = 4; // 马儿初始位置的列
        // 创建棋盘
        int [][] chessboard = new int[x][y];
        visited = new boolean[x * y]; // 初始值都是false
        // 测试一下花费时间
        long start = System.currentTimeMillis();
        System.out.println("开始时间 " + start);
        traversalChessBoard(chessboard, row - 1, clo - 1, 1);
        long end = System.currentTimeMillis();
        System.out.println("结束时间 " + end);
        System.out.println("功共耗时 " + (end - start) + "毫秒");

        System.out.println("输出结果");
        for (int[] arr : chessboard) {
            System.out.println(Arrays.toString(arr));
        }

    }


    /**
     * 完成马踏棋盘算法
     * @param chessboard 棋盘
     * @param row 马儿当前位置的行
     * @param col 马儿当前位置的列
     * @param step 表示马儿走的是第几步 初始位置是第一步
     */
    public static void traversalChessBoard(int[][] chessboard, int row, int col, int step) {
//        System.out.println("第"+(++count)+"次比较");
        chessboard[row][col] = step;
        // 若第四行 row=4  x为 8 clo = 4  4 * 8  = 4 = 36  (从1开始 所以 37 -1 = 36) 看图!!!
        visited[row * x + col] = true; // 标记已访问
        // 获取下一比的位置的集合
        ArrayList<Point> ps = next(new Point(col, row));
        // 遍历ps
        while (!ps.isEmpty()) {
            // 取出下一个可以走的位置
            Point p = ps.remove(0);
            // 判断该点 是否已经访问过
            if (!visited[p.y * x + p.x]) {
                // 没有访问过
                traversalChessBoard(chessboard, p.y, p.x , step + 1);
            }
        }
        // 判断是否完成任务
        // 1 step < x * y 情况有两种 棋盘到目前位置 棋盘为走完
        // 2 棋盘处于回溯过程
        if (step < x * y && !finished) {
            chessboard[row][col] = 0;
            visited[row * x + col] = false;
        }else {
            finished = true;
        }

    }

    /**
     * 根据当前的位置(根据point对象) 计算马儿还能走哪些位置 并放入到一个集合中(arrayList),最多有8个位置
     * @param curPoint 当前位置
     * @return
     */
    public static ArrayList<Point> next(Point curPoint) {
        // 创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<>();
        /// 创建一个point对象
        Point p1 = new Point();
        // 判断可不可以走可以走 图中'5'的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'6'的位置
        if ((p1.x = curPoint.x -1 ) >= 0 && (p1.y = curPoint.y - 2) >=0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'7'的位置
        if ((p1.x = curPoint.x + 1) < x && (p1.y = curPoint.y - 2) >=0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'0'的位置
        if ((p1.x = curPoint.x + 2) < x && (p1.y = curPoint.y - 1) >=0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'1'的位置
        if ((p1.x = curPoint.x + 2) < x && (p1.y = curPoint.y + 1) < y) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'2'的位置
        if ((p1.x = curPoint.x + 1) < x && (p1.y = curPoint.y + 2) < y) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'3'的位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < y) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'4'的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < y) {
            ps.add(new Point(p1));
        }
        return ps;

    }
}

贪心法优化

package com.cbx.algorithm.horse;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * @author cbx
 * @date 2022/1/19
 * @apiNote
 */
public class HorseChessboard {
    private static int x; // 棋盘的列
    private static int y; // 棋盘的行
    // 创建一个数组 标记各个位置是否被访问过
    private static boolean[] visited;
    // 使用一个属性 标记棋盘的所有位置是否被 访问过
    private static boolean finished;
//    static int count = 0;
    public static void main(String[] args) {
        // 测试
        x = 8;
        y = 8;
        int row = 1; // 马儿初始位置的行
        int clo = 1; // 马儿初始位置的列
        // 创建棋盘
        int [][] chessboard = new int[x][y];
        visited = new boolean[x * y]; // 初始值都是false
        // 测试一下花费时间
        long start = System.currentTimeMillis();
        System.out.println("开始时间 " + start);
        traversalChessBoard(chessboard, row - 1, clo - 1, 1);
        long end = System.currentTimeMillis();
        System.out.println("结束时间 " + end);
        System.out.println("功共耗时 " + (end - start) + "毫秒");

        System.out.println("输出结果");
        for (int[] arr : chessboard) {
            System.out.println(Arrays.toString(arr));
        }

    }


    /**
     * 完成马踏棋盘算法
     * @param chessboard 棋盘
     * @param row 马儿当前位置的行
     * @param col 马儿当前位置的列
     * @param step 表示马儿走的是第几步 初始位置是第一步
     */
    public static void traversalChessBoard(int[][] chessboard, int row, int col, int step) {
//        System.out.println("第"+(++count)+"次比较");
        chessboard[row][col] = step;
        // 若第四行 row=4  x为 8 clo = 4  4 * 8  = 4 = 36  (从1开始 所以 37 -1 = 36) 看图!!!
        visited[row * x + col] = true; // 标记已访问
        // 获取下一比的位置的集合
        ArrayList<Point> ps = next(new Point(col, row));
        sort(ps);
        // 遍历ps
        while (!ps.isEmpty()) {
            // 取出下一个可以走的位置
            Point p = ps.remove(0);
            // 判断该点 是否已经访问过
            if (!visited[p.y * x + p.x]) {
                // 没有访问过
                traversalChessBoard(chessboard, p.y, p.x , step + 1);
            }
        }
        // 判断是否完成任务
        // 1 step < x * y 情况有两种 棋盘到目前位置 棋盘为走完
        // 2 棋盘处于回溯过程
        if (step < x * y && !finished) {
            chessboard[row][col] = 0;
            visited[row * x + col] = false;
        }else {
            finished = true;
        }

    }

    /**
     * 根据当前的位置(根据point对象) 计算马儿还能走哪些位置 并放入到一个集合中(arrayList),最多有8个位置
     * @param curPoint 当前位置
     * @return
     */
    public static ArrayList<Point> next(Point curPoint) {
        // 创建一个ArrayList
        ArrayList<Point> ps = new ArrayList<>();
        /// 创建一个point对象
        Point p1 = new Point();
        // 判断可不可以走可以走 图中'5'的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y - 1) >= 0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'6'的位置
        if ((p1.x = curPoint.x -1 ) >= 0 && (p1.y = curPoint.y - 2) >=0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'7'的位置
        if ((p1.x = curPoint.x + 1) < x && (p1.y = curPoint.y - 2) >=0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'0'的位置
        if ((p1.x = curPoint.x + 2) < x && (p1.y = curPoint.y - 1) >=0) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'1'的位置
        if ((p1.x = curPoint.x + 2) < x && (p1.y = curPoint.y + 1) < y) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'2'的位置
        if ((p1.x = curPoint.x + 1) < x && (p1.y = curPoint.y + 2) < y) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'3'的位置
        if ((p1.x = curPoint.x - 1) >= 0 && (p1.y = curPoint.y + 2) < y) {
            ps.add(new Point(p1));
        }
        // 判断可不可以走可以走 图中'4'的位置
        if ((p1.x = curPoint.x - 2) >= 0 && (p1.y = curPoint.y + 1) < y) {
            ps.add(new Point(p1));
        }
        return ps;

    }

    // 根据当前这一步的左右的下一步的选择位置 进行非递减排序(前后两个数组可能相同的递增排序) 1,2,3,3,4,5,8,8:非递减排列,选择第一个 减少回溯次数
    public static void sort(ArrayList<Point> ps) {
        ps.sort((o1, o2) -> {
            // 现获取到o1的下一步的所有位置的个数
            ArrayList<Point> arr1 = next(o1);
            ArrayList<Point> arr2 = next(o2);
            if (arr1.size() < arr2.size()) {
                return -1;
            } else if (arr1.size() > arr2.size()) {
                return 1;
            }else {
                return 0;
            }
        });
    }
}

end
  • 作者:AWhiteElephant(联系作者)
  • 发表时间:2022-03-11 20:54
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 评论