数据结构与算法

1.数组模拟循环队列

1.1. 实现的关键步骤:

1、front:指向队列的第一个元素
2、rear:指向队列的最后一个元素的后一个位置
3、maxSize:队列的最大容量,可以在初始化时定义;如果没定义,可设置默认值。
4、front 和 rear 的初始值为0
5、判空条件:(rear + 1)% maxSize == front
6、判满条件:rear == front
7、入队:判满、rear指针后移、赋值
8、出队:判空、front指针后移
9、获取队首元素:获取 front 指向的元素
10、打印队列:从 front指向的元素到 front + 队列元素的个数
11、队列元素的个数:(rear + maxSize - front) % maxSize

1.2. 实现代码


class ArrayQueue{
    // 表示数组的最大容量
    private int maxSize;
    //front 指向队列的第一个元素
    private int front;
    //rear 指向队列的最后一个元素的后一个位置
    private int rear;
    // 模拟队列的数组,用于存放元素
    private int[] arr;

    public ArrayQueue(int arrMaxSize) {
        // 初始化时,front 和 rear的初始值都为0,但由于int 本身默认值为 0 ,因此这里可以不用写
        //最大值由外部指定(由于牺牲掉一个位置,所以例如传入4实际上只有3个位置)
        maxSize = arrMaxSize;
        // 创建数组存储
        arr = new int[maxSize];
    }

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

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

    // 添加数据到队列
    public void addQueue(int n) {
        // 判断队列是否满
        if (isFull()) {
            System.out.println("队列满,不能加入数据~");
            return;
        }
        //直接将数据加入
        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.printf("arr[%d]=%d\n", i % maxSize, arr[i % maxSize]);
        }
    }

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

    // 显示队列的头数据, 注意不是取出数据
    public int headQueue() {
        // 判断
        if (isEmpty()) {
            throw new RuntimeException("队列空的,没有数据~~");
        }
        return arr[front];
    }
}

2. 单链表

2.1. 实现的关键步骤:

1、需要创建一个节点类(属性包括 数据域 和 指针域 next )
2、需要创建一个链表类(属性包括头节点,初始化链表时,需要创建一个头节点,头节点没有实际意义)
3、增加–找到要插入位置的节点的前一个节点,然后先将节点与后一个节点连接,再将前面的节点连接。关键代码:node.next = temp.next;temp.next = node;
4、删除–找到要删除的节点的前一个节点,然后将引用修改为下下个节点。关键代码:temp.next = temp.next.next;
5、查看–找到要查看的节点并返回。关键代码:return temp;
6、修改–找到要修改的节点,然后修改其值即可。关键代码:temp.值 = 新值;
7、打印链表:先跳过头节点,然后遍历链表直到 temp 指向空时,打印链表的信息。

2.2. 实现代码

package com.cs.testt;

import java.util.Scanner;

/**
 * @ClassName SingleLinkedListDemo
 * @Description TODO
 * @Author jiaqi
 * @Date 2022/2/22 14:27
 * @Version 1.0
 **/
public class SingleLinkedListDemo {
    public static void main(String[] args) {
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("------------------------");
            System.out.println("请输入以下字母:");
            System.out.println("a(add):添加");
            System.out.println("f(find):查找");
            System.out.println("u(update):更新");
            System.out.println("d(delete):删除");
            System.out.println("p(print):打印");
            System.out.println("o(out):退出程序");
            System.out.println("------------------------");
            String next = scanner.next();
            if (next.charAt(0) == 'o') {
                break;
            }
            switch (next.charAt(0)) {
                case 'a':
                    System.out.println("请输入要添加的值:");
                    int item = scanner.nextInt();
                    singleLinkedList.add(item);
                    break;
                case 'f':
                    System.out.println("请输入要查找的值:");
                    int item1 = scanner.nextInt();
                    Node node = singleLinkedList.find(item1);
                    if (node == null) {
                        System.out.println("没有找到对应的值");
                    } else {
                        System.out.println("找到的值为:" + node.num);
                    }
                    break;
                case 'u':
                    System.out.println("请输入要旧的值:");
                    int oldN = scanner.nextInt();
                    System.out.println("请输入要新的值:");
                    int newN = scanner.nextInt();
                    boolean update = singleLinkedList.update(oldN, newN);
                    String s = update ? "更新成功" : "要更新的值根本不存在";
                    System.out.println(s);
                    break;
                case 'd':
                    System.out.println("请输入要删除的值:");
                    int item3 = scanner.nextInt();
                    Node delete = singleLinkedList.delete(item3);
                    if (delete == null) {
                        System.out.println("没有找到对应的值");
                    } else {
                        System.out.println("已删除的值为:" + delete.num);
                    }
                    break;
                case 'p':
                    String s1 = singleLinkedList.printList();
                    System.out.println(s1);
                    break;
                default:
                    System.out.println("-------------------------------");
                    System.out.println("|您的输入格式有误,请重新输入: |");
                    System.out.println("-------------------------------");
                    break;

            }
        }
        System.out.println("程序已退出...");
    }

}

//链表类
class SingleLinkedList {
    private Node head;

    public SingleLinkedList() {
        this.head = new Node(0, null);
    }

    //再链表的尾部添加一个节点
    public void add(int num) {
        Node temp = head;
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        Node node = new Node(num, null);
        temp.next = node;
    }

    //    //根据编号添加一个节点(根据num从小到大排序)
//    public void addByOrder(Node node){
//        Node temp = head;
//        while (true){
//            if (temp.next == null){
//                break;
//            }
//            if (temp.next.num >= node.num){
//                break;
//            }
//            temp = temp.next;
//        }
//
//        node.next = temp.next;
//        temp.next = node;
//    }
    public Node find(int num) {
        Node temp = head.next;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.num == num) {
                break;
            }
            temp = temp.next;
        }
        return temp;
    }

    public boolean update(int oldnum, int newNum) {
        Node temp = head.next;
        boolean flag = false;//是否找到旧值
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.num == oldnum) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.num = newNum;
        }
        return flag;
    }

    public Node delete(int num) {
        Node del = null;
        Node temp = head;
        while (true) {
            if (temp.next == null) {//没有要找的节点
                break;
            }
            if (temp.next.num == num) {
                del = temp.next;
                temp.next = temp.next.next;
                break;
            }
            temp = temp.next;
        }
        return del;

    }

    public String printList() {
        StringBuffer stringBuffer = new StringBuffer();
        Node temp = head.next;
        int count = 1;
        while (true) {
            if (temp == null) {
                break;
            }
            stringBuffer.append("第" + count + "个值: " + temp.num + "\n");
            temp = temp.next;
            count++;
        }
        return stringBuffer.toString();
    }
    //计算节点的个数
    public int NodeNum(){
        int count = 0;
        Node temp = head.next;
        while (true){
            if (temp == null){
                break;
            }
            temp = temp.next;
            count++;
        }
        return count;
    }
    //查找单链表中的倒数第k个节点(双指针法)
    public Node findOrderByLast(int n){
        //设置两个指针
        Node temp1 = head.next;
        Node temp2 = head.next;
        while (true){
            if (temp1 == null){
                break;
            }
            if (n<1){
                break;
            }
            temp1 = temp1.next;
            n--;
        }
        while (true){
            if (temp1 == null){
                break;
            }
            temp1 = temp1.next;
            temp2 = temp2.next;
        }
        return temp2;
    }
    //反转链表
    public void reserve(Node head){
        Node reserveHead = new Node(0,null);
        Node temp = head.next;
        Node cur;
        while (true){
            if (temp == null){
                break;
            }
            cur = temp;
            temp = temp.next;
            cur.next = reserveHead.next;
            reserveHead.next = cur;
        }
        head.next = reserveHead.next;
    }

}


//节点类
class Node {
    public int num;
    public Node next;

    public Node(int num, Node next) {
        this.num = num;
        this.next = next;
    }
}

2.3. 拓展:

题目1:求一个单链表中节点的个数
方法:先创建一个变量作为计数器存储个数,初始值为 0 ,接着创建临时指针,循环遍历直到指针指向的节点为 null ,计数不断增加,最后跳出循环返回计数器。

题目2:查找单链表中的倒数第k个节点
方法1:双指针法。先让指针1和指针2指向第一个节点,接着指针1先走k个节点,然后让两个指针同时走,知道指针1走到null之后,指针2所指的节点即为所求。
方法2:先求出节点的个数(题目1),然后用节点个数减掉k得到需要走的步数,接着让指针走这么多步,即可。

题目3:单链表的反转。
方法:先创建一个新的头节点,然后遍历链表,当遍历到一个节点时将其放到新的头节点的后面的那个位置(每一个都如此),这样到最后的 temp == null 时停止,此时除了头节点外,其余都为所求,最后将旧的头节点指向新的头节点的下一个节点即可(即:head.next = nowHead.next;),然后head。

题目4:从头到尾打印单链表(不可破坏原先链表的结构)
方法:用栈结构 Stack ,遍历链表,并将每个节点压入栈(stack.push),接着循环栈,当栈不为空时,出栈(stack.pop),打印出栈的元素即可。

题目5:合并两个有序的单链表,并且合并之后还是有序的
方法:先创建一个新的头节点,然后创建两个指针指向两个链表的第一个节点(非头节点),然后再创建一个指针记录指向新的头节点(用户每次添加),循环直到有两个链表有一个指针为空为止,当哪个值比较小时,则让新链表的指针指向那个节点,并且新链表向后移动一个,指向的那个节点的指针也要向右移动,退出循环后,要将新链表的指针指向未结束的链表的指针的节点。最后返回头节点。

3. 双链表

3.1. 实现的关键步骤

1、需要创建一个节点类(属性包括 数据域 和指针域 next 和 pre )
2、需要创建一个链表类(属性包括头节点,与单链表类似)
3、增加–节点到链表尾部:与单链表类似,通过遍历,然后连接
4、增加–节点到指定的位置:与单链表类似,找到前一个节点,然后改变指针域
5、删除–可以自我删除,这个是双链表的特点,指针 temp 可以直接指向待删除的节点,然后通过改变指针指向,即可。
6、查看和修改–与单链表类似,直接找到指定的节点进行操作
7、打印链表–与单链表一样

3.2. 实现代码:

package com.cs.testt;

/**
 * @ClassName DoubleLinkedListDemo
 * @Description TODO
 * @Author jiaqi
 * @Date 2022/2/23 14:27
 * @Version 1.0
 **/
//双向链表
public class DoubleLinkedListDemo {

}

class DoubleLinkedList{
    private TNode head;
    public DoubleLinkedList(){
        this.head = new TNode(0,null,null);
    }
    //添加节点到尾部
    public void addLast(TNode tNode){
        TNode temp = head;
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        temp.next = tNode;
        tNode.pre = temp;
    }
    //根据值的大小(从小到大)添加节点
    public void addOrder(TNode tNode){
        TNode temp = head;
        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.next.num >=tNode.num){
                break;
            }
            temp = temp.next;
        }
        if (temp.next == null){
            temp.next = tNode;
            tNode.pre = temp;
        }else {
            //没名字的先来
            temp.next.pre = tNode;
            tNode.next = temp.next;
            temp.next = tNode;
            tNode.pre = temp;
        }
        
    }
    //删除指定值的节点(双向链表删除可以自删除)
    public boolean delete(int num){
        TNode temp = head.next;
        //记录是否找到要删除的值
        boolean flag = false;
        while (true){
            if (temp == null){
                break;
            }
            if (temp.num == num){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.pre.next = temp.next;
            if (temp.next != null){
                temp.next.pre = temp.pre;
            }
        }
        return flag;
        
    }
    //修改节点
    public boolean update(int oNum,int nNum){
        //记录是否找到旧值
        boolean flag = false;
        TNode temp = head.next;
        while (true){
            if (temp == null){
                //找不到
                break;
            }
            if (temp.num == oNum){
                //找到了
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.num = nNum;
        }
        return flag;
    } 
    //查看
    public TNode select(int num){
        TNode temp = head.next;
        while (true){
            if (temp == null || temp.num == num){
                return temp;
            }
            temp = temp.next;
        }
    }
    //打印链表
    public String printList(){
        StringBuffer sb = new StringBuffer();
        TNode temp = head.next;
        int count = 1;
        while (true){
            if (temp == null){
                break;
            }
            //打印节点
            sb.append("第"+count+"个节点:"+temp.num+"\n");
            //指针移动1
            temp = temp.next;
            count++;
        }
        return sb.toString();
    }
    
}

//(双向)节点类
class TNode{
    public int num;
    public TNode next;
    public TNode pre;
    public TNode(int num,TNode next,TNode pre){
        this.num = num;
        this.next = next;
        this.pre = pre;
    }
}

4. 单向环形链表

分为有头节点和无头节点。
经典问题:约瑟夫问题(可以用不带头节点单向环形链表实现,用链表模拟,然后记录出圈顺序,然后改变节点的指向。)
在这里插入图片描述

5. 数组模拟栈(stack)

5.1. 实现的关键步骤

1、自己实现一个栈类,属性包括(栈的最大空间,栈顶指针(指向栈顶),模拟栈的数组)
2、初始化时,传入一个最大空间,然后创建一个这么大的数组,栈顶指针值为 -1
3、出栈:先 判断栈是否为空,再 int value = arr[top]; top --; return value;
4、入栈:先 判断栈是否为满,再 top++;arr[top] = value;
5、判满:top == maxSize - 1
6、判空:top == -1
7、打印栈:从栈顶打印到栈底,因此 for 循环从 位置 top 打印到 位置 0,即可。

5.2. 实现代码

5.3. 拓展

题目一:实现综合计算器题目。传入一个表达式字符串,返回表达式的计算结果。例如:传入字符串 7*2+5+1*5+3-4 ,返回结果为4
方法:
1、通过一个 index 指针,遍历这个字符串表达式
2、创建一个数字栈存数字,一个符号栈存符号
3、如果遍历到是一个数字,就直接入数栈
4、如果遍历到是一个符号,就分情况:
4.1、如果符号栈为空,直接入栈
4.2、如果符号栈不为空,就将当前符号的优先级与栈顶符号的优先级比较
4.2.1、如果当前操作符的优先级小于或等于栈顶操作符,则从数栈pop出两个数,符号栈pop出一个符号,进行运算,将运算结果存到数栈中,然后当前操作符入符号栈
4.2.2、如果当前操作符的优先级大于栈顶操作符,则直接入符号栈
5、当表达式扫描完毕,就顺序从数栈和符号栈中pop出相应的数和符号进行运算
6、最后,符号栈为空,数栈剩下一个数,这个数就是表达式的结果。

代码实现:(一位数的加减乘除)

package com.cs.testt;
/**
 * @ClassName Test1
 * @Description TODO
 * @Author jiaqi
 * @Date 2022/2/28 11:24
 * @Version 1.0
 **/
public class Test1 {
    public static void main(String[] args) throws Exception {
        //给你一个字符串表达式,返回一个表达式结果。
        String s = "3+4*5+4-9-6*5";
        ArrayStack numStcak = new ArrayStack(10);
        ArrayStack operStack = new ArrayStack(10);
        int index = 0;
        while (true){
            //判断index是否结束,结束就退出
            if (index == s.length()){
                break;
            }
            //先判断是否是操作符
            if (isoper(s.charAt(index))){
                //是操作符判断栈是否为空
                if (operStack.isEmpty()){
                    //直接入栈
                    operStack.push(s.charAt(index));
                }else {
                    //判断操作符的优先级
                    if (operf(s.charAt(index))>operf(operStack.peek())){
                        operStack.push(s.charAt(index));
                    }else {
                        int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
                        numStcak.push(suan);
                        operStack.push(s.charAt(index));
                    }
                }
            }else{
                //是数字直接进数字栈
                numStcak.push(s.charAt(index) -'0');
            }
            index++;

        }
        //index 走完之后
        while (true){
            if (operStack.isEmpty()){
                break;
            }
            int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
            numStcak.push(suan);
        }
        System.out.printf("%s = %d",s,numStcak.pop());
    }
    //判断传进来的是运算符还是数字
    public static boolean isoper(int a){
        if (a == '+' || a == '-' || a== '*'||a == '/'){
            return true;
        }else {
            return false;
        }
    }
    //传入两个数字和一个运算符,返回结果
    public static int suan(int num1,int num2,int oper){
        if (oper == '+'){
            return num1 + num2;
        }else if (oper == '-'){
            return num2 - num1;
        }else if (oper == '*'){
            return num1 * num2;
        }else if (oper == '/'){
            return num2 / num1;
        }else {
            return -1;
        }
    }
    //判断操作符的优先级
    public static int operf(int oper){
        if (oper == '*' || oper == '/'){
            return 1;
        }else if (oper == '+' || oper == '-'){
            return 0;
        }else {
            return -1;
        }
    }

}

class ArrayStack{
    private int top;
    private int[] arr;
    private int maxSize;
    //初始化
    public ArrayStack(int maxSize){
        arr = new int[maxSize];
        this.maxSize = maxSize;
        top = -1;
    }
    //判断满
    public boolean isFull(){
        return top == maxSize - 1;
    }
    public boolean isEmpty(){
        return top == -1;
    }
    //入栈
    public void push(int num) throws Exception {
        if (isFull()){
            throw new Exception("栈满");
        }
        top++;
        arr[top] = num;
    }
    //出栈
    public int pop() throws Exception {
        if (isEmpty()){
            throw new Exception("栈空");
        }
        int temp = arr[top];
        top--;
        return temp;
    }
    //查看栈顶元素
    public int peek() throws Exception {
        if (isEmpty()){
            throw new Exception("栈空");
        }
        return arr[top];
    }

}

代码实现:(多位数的加减乘除)

package com.cs.testt;
/**
 * @ClassName Test1
 * @Description TODO
 * @Author jiaqi
 * @Date 2022/2/28 11:24
 * @Version 1.0
 **/
public class Test1 {
    public static void main(String[] args) throws Exception {
        //给你一个字符串表达式,返回一个表达式结果。
        String s = "30+4*5+4-9-6*5";
        ArrayStack numStcak = new ArrayStack(10);
        ArrayStack operStack = new ArrayStack(10);
        int index = 0;
        while (true){
            //判断index是否结束,结束就退出
            if (index == s.length()){
                break;
            }
            //先判断是否是操作符
            if (isoper(s.charAt(index))){
                //是操作符判断栈是否为空
                if (operStack.isEmpty()){
                    //直接入栈
                    operStack.push(s.charAt(index));
                }else {
                    //判断操作符的优先级
                    if (operf(s.charAt(index))>operf(operStack.peek())){
                        operStack.push(s.charAt(index));
                    }else {
                        int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
                        numStcak.push(suan);
                        operStack.push(s.charAt(index));
                    }
                }
                index++;
            }else{
                //是数字直接进数字栈
                //numStcak.push(s.charAt(index) -'0');
                int sum = 0;
                while (true){
                    if (s.length() == index || isoper(s.charAt(index))){
                        break;
                    }
                    sum = sum * 10 + (s.charAt(index) - '0');
                    index++;
                }
                numStcak.push(sum);
            }


        }
        //index 走完之后
        while (true){
            if (operStack.isEmpty()){
                break;
            }
            int suan = suan(numStcak.pop(), numStcak.pop(), operStack.pop());
            numStcak.push(suan);
        }
        System.out.printf("%s = %d",s,numStcak.pop());
    }
    //判断传进来的是运算符还是数字
    public static boolean isoper(int a){
        if (a == '+' || a == '-' || a== '*'||a == '/'){
            return true;
        }else {
            return false;
        }
    }
    //传入两个数字和一个运算符,返回结果
    public static int suan(int num1,int num2,int oper){
        if (oper == '+'){
            return num1 + num2;
        }else if (oper == '-'){
            return num2 - num1;
        }else if (oper == '*'){
            return num1 * num2;
        }else if (oper == '/'){
            return num2 / num1;
        }else {
            return -1;
        }
    }
    //判断操作符的优先级
    public static int operf(int oper){
        if (oper == '*' || oper == '/'){
            return 1;
        }else if (oper == '+' || oper == '-'){
            return 0;
        }else {
            return -1;
        }
    }

}

class ArrayStack{
    private int top;
    private int[] arr;
    private int maxSize;
    //初始化
    public ArrayStack(int maxSize){
        arr = new int[maxSize];
        this.maxSize = maxSize;
        top = -1;
    }
    //判断满
    public boolean isFull(){
        return top == maxSize - 1;
    }
    public boolean isEmpty(){
        return top == -1;
    }
    //入栈
    public void push(int num) throws Exception {
        if (isFull()){
            throw new Exception("栈满");
        }
        top++;
        arr[top] = num;
    }
    //出栈
    public int pop() throws Exception {
        if (isEmpty()){
            throw new Exception("栈空");
        }
        int temp = arr[top];
        top--;
        return temp;
    }
    //查看栈顶元素
    public int peek() throws Exception {
        if (isEmpty()){
            throw new Exception("栈空");
        }
        return arr[top];
    }

}

前缀表达式(波兰表达式,从右向左扫描)- * + 3 4 5 6 计算机容易理解的形式,人类不容易理解
中缀表达式(3 + 4)*5-6 人类容易理解的形式,计算机不容易理解
后缀表达式(逆波兰表达式,从左向右扫描)3 4 + 5 * 6 -计算机容易理解的形式,人类不容易理解

前面的扩展就是我们将中缀表达式表达给计算机,很复杂,又得考虑优先级,计算机很难理解,因此编码难度大。因此一般我们是先将中缀表达式转换成其它表达式(一般是后缀表达式,因为最容易转换)的,然后再给计算机计算。

一般是先将中缀表达式转换为后缀表达式,然后再从左到右进行计算后缀表达式,计算的规矩是:从左到右扫描表达式,遇到数字就压入栈,遇到运算符就弹出两个数字,用运算符对他们进行运算,并将结果入栈,重复上诉操作直到表达式的最右端,最后运算得到的值则为结果。

6. 递归

6.1. 递归在实际开发中可以解决什么问题?

八皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子问题,快排,归并排序,二分查找,分治查找 等等。

6.2. 递归的常见形式

个人理解:通常的递归都是 链式 或者 发散式。例如:打印 1- n 就是链式,打印二叉树就是 发散式。一般来说,一个方法包含链式或发散式中的一个节点(发散式只是多了可以选择的路径而已)。

链式:

public 返回值 方法名(第一个节点需要的数据){
	//退出条件,一般是结束条件
	if(结束的标志){
		return xx;
	}
	返回值 = 方法名(一般是第一个节点的后续);
	return 返回值
}

发散式:

public 返回值 方法名(第一个节点需要的数据){
	//退出条件,一般是结束条件
	if(结束的标志){
		return xx;
	}
	if(满足链路1){
		返回值 = 方法名(一般是第一个节点的后续);
		return 返回值
	}
	if(满足链路2){
		返回值 = 方法名(一般是第一个节点的后续);
		return 返回值
	}
	
}

6. 排序算法

6.1. 冒泡排序

package com.atguigu.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;




public class BubbleSort {

	public static void main(String[] args) {
//		int arr[] = {3, 9, -1, 10, 20};
//		
//		System.out.println("排序前");
//		System.out.println(Arrays.toString(arr));
		
		//为了容量理解,我们把冒泡排序的演变过程,给大家展示
		
		//测试一下冒泡排序的速度O(n^2), 给80000个数据,测试
		//创建要给80000个的随机的数组
		int[] arr = new int[80000];
		for(int i =0; i < 80000;i++) {
			arr[i] = (int)(Math.random() * 8000000); //生成一个[0, 8000000) 数
		}
		
		Date data1 = new Date();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date1Str = simpleDateFormat.format(data1);
		System.out.println("排序前的时间是=" + date1Str);
		
		//测试冒泡排序
		bubbleSort(arr);
		
		Date data2 = new Date();
		String date2Str = simpleDateFormat.format(data2);
		System.out.println("排序后的时间是=" + date2Str);
		
		//System.out.println("排序后");
		//System.out.println(Arrays.toString(arr));
		
		
		/*
		
		// 第二趟排序,就是将第二大的数排在倒数第二位
		
		for (int j = 0; j < arr.length - 1 - 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 - 1 - 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));
		
		// 第四趟排序,就是将第4大的数排在倒数第4位

		for (int j = 0; j < arr.length - 1 - 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)); */
		
	}
	
	// 将前面额冒泡排序算法,封装成一个方法
	public static void bubbleSort(int[] arr) {
		// 冒泡排序 的时间复杂度 O(n^2), 自己写出
		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!!!, 进行下次判断
			}
		}
	}

}

6.2. 插入排序

package com.atguigu.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class InsertSort {

	public static void main(String[] args) {
		//int[] arr = {101, 34, 119, 1, -1, 89}; 
		// 创建要给80000个的随机的数组
		int[] arr = new int[80000];
		for (int i = 0; i < 80000; i++) {
			arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
		}

		System.out.println("插入排序前");
		Date data1 = new Date();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date1Str = simpleDateFormat.format(data1);
		System.out.println("排序前的时间是=" + date1Str);
		
		insertSort(arr); //调用插入排序算法
		
		Date data2 = new Date();
		String date2Str = simpleDateFormat.format(data2);
		System.out.println("排序前的时间是=" + date2Str);
		
		//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
			// 举例:理解不了,我们一会 debug
			//这里我们判断是否需要赋值
			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}
		
		
		//{101, 34, 119, 1}; => {101,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
		//举例:理解不了,我们一会 debug
		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)); */
		
	}

}

6.3. 选择排序

package com.atguigu.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
//选择排序
public class SelectSort {

	public static void main(String[] args) {
		//int [] arr = {101, 34, 119, 1, -1, 90, 123};
		
		//创建要给80000个的随机的数组
		int[] arr = new int[80000];
		for (int i = 0; i < 80000; i++) {
			arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
		}
		
		System.out.println("排序前");
		//System.out.println(Arrays.toString(arr));
		
		Date data1 = new Date();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date1Str = simpleDateFormat.format(data1);
		System.out.println("排序前的时间是=" + date1Str);
		
		selectSort(arr);
		
		
		Date data2 = new Date();
		String date2Str = simpleDateFormat.format(data2);
		System.out.println("排序前的时间是=" + date2Str);
		
		//System.out.println("排序后");
		//System.out.println(Arrays.toString(arr));
		
		
	}
	
	//选择排序
	public static void selectSort(int[] arr) {
		
		
		
		//在推导的过程,我们发现了规律,因此,可以使用for来解决
		//选择排序时间复杂度是 O(n^2)
		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, 34, 119, 101
		}
		
		
		/*
		
		//使用逐步推导的方式来,讲解选择排序
		//第1轮
		//原始的数组 : 	101, 34, 119, 1
		//第一轮排序 :   	1, 34, 119, 101
		//算法 先简单--》 做复杂, 就是可以把一个复杂的算法,拆分成简单的问题-》逐步解决
		
		//第1轮
		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));// 1, 34, 119, 101
		
		
		//第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));// 1, 34, 119, 101
		
		//第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));// 1, 34, 101, 119 */

		

		
	}

}

6.4. 快速排序

package com.atguigu.sort;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class QuickSort {

	public static void main(String[] args) {
		//int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};
		
		//测试快排的执行速度
		// 创建要给80000个的随机的数组
		int[] arr = new int[8000000];
		for (int i = 0; i < 8000000; i++) {
			arr[i] = (int) (Math.random() * 8000000); // 生成一个[0, 8000000) 数
		}
		
		System.out.println("排序前");
		Date data1 = new Date();
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date1Str = simpleDateFormat.format(data1);
		System.out.println("排序前的时间是=" + date1Str);
		
		quickSort(arr, 0, arr.length-1);
		
		Date data2 = new Date();
		String date2Str = simpleDateFormat.format(data2);
		System.out.println("排序前的时间是=" + date2Str);
		//System.out.println("arr=" + Arrays.toString(arr));
	}

	public static void quickSort(int[] arr,int left, int right) {
		int l = left; //左下标
		int r = right; //右下标
		//pivot 中轴值
		int pivot = arr[(left + right) / 2];
		int temp = 0; //临时变量,作为交换时使用
		//while循环的目的是让比pivot 值小放到左边
		//比pivot 值大放到右边
		while( l < r) { 
			//在pivot的左边一直找,找到大于等于pivot值,才退出
			while( arr[l] < pivot) {
				l += 1;
			}
			//在pivot的右边一直找,找到小于等于pivot值,才退出
			while(arr[r] > pivot) {
				r -= 1;
			}
			//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
			//小于等于pivot值,右边全部是大于等于pivot值
			if( l >= r) {
				break;
			}
			
			//交换
			temp = arr[l];
			arr[l] = arr[r];
			arr[r] = temp;
			
			//如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
			if(arr[l] == pivot) {
				r -= 1;
			}
			//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
			if(arr[r] == pivot) {
				l += 1;
			}
		}
		
		// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
		if (l == r) {
			l += 1;
			r -= 1;
		}
		//向左递归
		if(left < r) {
			quickSort(arr, left, r);
		}
		//向右递归
		if(right > l) {
			quickSort(arr, l, right);
		}
		
		
	}
}

7.查找

7.1. 二分查找

package com.atguigu.search;

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

//注意:使用二分查找的前提是 该数组是有序的.
public class BinarySearch {

	public static void main(String[] args) {
		//int arr[] = { 1, 8, 10, 89,1000,1000, 1234 };
		int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 12, 13,14,15,16,17,18,19,20 };
		

		//
//		int resIndex = binarySearch(arr, 0, arr.length - 1, 1000);
//		System.out.println("resIndex=" + resIndex);
		
		List<Integer> resIndexList = binarySearch2(arr, 0, arr.length - 1, 1);
		System.out.println("resIndexList=" + resIndexList);
	}

	// 二分查找算法
	/**
	 * 
	 * @param arr
	 *            数组
	 * @param left
	 *            左边的索引
	 * @param right
	 *            右边的索引
	 * @param findVal
	 *            要查找的值
	 * @return 如果找到就返回下标,如果没有找到,就返回 -1
	 */
	public static int binarySearch(int[] arr, int left, int right, int findVal) {
		

		// 当 left > right 时,说明递归整个数组,但是没有找到
		if (left > right) {
			return -1;
		}
		int mid = (left + right) / 2;
		int midVal = arr[mid];

		if (findVal > midVal) { // 向 右递归
			return binarySearch(arr, mid + 1, right, findVal);
		} else if (findVal < midVal) { // 向左递归
			return binarySearch(arr, left, mid - 1, findVal);
		} else {
			
			return mid;
		}

	}
	
	//完成一个课后思考题:
	/*
	 * 课后思考题: {1,8, 10, 89, 1000, 1000,1234} 当一个有序数组中,
	 * 有多个相同的数值时,如何将所有的数值都查找到,比如这里的 1000
	 * 
	 * 思路分析
	 * 1. 在找到mid 索引值,不要马上返回
	 * 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
	 * 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
	 * 4. 将Arraylist返回
	 */

	public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {

		System.out.println("hello~");
		// 当 left > right 时,说明递归整个数组,但是没有找到
		if (left > right) {
			return new ArrayList<Integer>();
		}
		int mid = (left + right) / 2;
		int midVal = arr[mid];

		if (findVal > midVal) { // 向 右递归
			return binarySearch2(arr, mid + 1, right, findVal);
		} else if (findVal < midVal) { // 向左递归
			return binarySearch2(arr, left, mid - 1, findVal);
		} else {
//			 * 思路分析
//			 * 1. 在找到mid 索引值,不要马上返回
//			 * 2. 向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
//			 * 3. 向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
//			 * 4. 将Arraylist返回
			
			List<Integer> resIndexlist = new ArrayList<Integer>();
			//向mid 索引值的左边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
			int temp = mid - 1;
			while(true) {
				if (temp < 0 || arr[temp] != findVal) {//退出
					break;
				}
				//否则,就temp 放入到 resIndexlist
				resIndexlist.add(temp);
				temp -= 1; //temp左移
			}
			resIndexlist.add(mid);  //
			
			//向mid 索引值的右边扫描,将所有满足 1000, 的元素的下标,加入到集合ArrayList
			temp = mid + 1;
			while(true) {
				if (temp > arr.length - 1 || arr[temp] != findVal) {//退出
					break;
				}
				//否则,就temp 放入到 resIndexlist
				resIndexlist.add(temp);
				temp += 1; //temp右移
			}
			
			return resIndexlist;
		}

	}
}

8. 哈希表

8.1. 模拟HashTable代码

package com.cs.testt;


/**
 * @ClassName HashDemo
 * @Description TODO
 * @Author jiaqi
 * @Date 2022/3/7 0:03
 * @Version 1.0
 **/
public class HashDemo {
    public static void main(String[] args) {
        MyHashTable myHashTable = new MyHashTable(7);
        System.out.println(myHashTable.print());
        myHashTable.add(new Student(1,"jack",21));
        myHashTable.add(new Student(2,"jack",21));
        myHashTable.add(new Student(3,"oo",21));
        myHashTable.add(new Student(4,"jack",21));
        myHashTable.add(new Student(5,"jack",21));
        myHashTable.add(new Student(6,"jack",21));
        myHashTable.add(new Student(7,"jack",21));
        myHashTable.add(new Student(8,"jack",21));
        myHashTable.deleteById(7);
        myHashTable.updete(new Student(4,"tom",11));
        System.out.println(myHashTable.get(3));
        System.out.println(myHashTable.print());
    }
}

//数组类
class MyHashTable{
    private LinkedList[] linkedLists;
    private int size;//多少条链表

    public MyHashTable(int size){
        this.size = size;
        linkedLists = new LinkedList[size];
    }
    //添加元素
    public void add(Student stu){
        //根据id获取到散列的位置
        int i = myHash(stu.getId());
        if (linkedLists[i] == null){
            linkedLists[i] = new LinkedList();
        }
        linkedLists[i].add(stu);
    }
    //根据id获取到学生的值(默认id是不重复的)
    public Student get(int id){
        int i = myHash(id);
        if (linkedLists[i] == null){
            return null;
        }
        Student student = linkedLists[i].get(id);
        return student;
    }
    //遍历元素
    public String print(){
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < size; i++) {
            if (linkedLists[i] == null){
                continue;
            }
            stringBuffer.append(linkedLists[i].print());
        }
        return stringBuffer.toString();
    }
    //散列方法
    private int myHash(int id){
        return id % size;
    }
    //根据id删除一个学生
    public Student deleteById(int id){
        int i = myHash(id);
        if (linkedLists[i] == null){
            return null;
        }
        Student delete = linkedLists[i].delete(id);
        return delete;
    }
    //修改一个学生的数据
    public boolean updete(Student stu){
        //根据id找到数据并更新,找到返回true,找不到返回false
        int i = myHash(stu.getId());
        if (linkedLists[i] == null){
            return false;
        }
        boolean upd = linkedLists[i].upd(stu);
        return upd;
    }
}
//链表类
class LinkedList{
    //头节点:表示第一个数据,默认为空
    private Student head;

    public LinkedList(){
        head = null;
    }

    //添加数据:根据一个值,添加到链表的尾部
    public void add(Student stu){
        //临时指针
        Student temp = head;
        //头节点都为空
        if (temp == null){
            head = stu;
            return;
        }
        while (true){
            if (temp.next == null){
                temp.next = stu;
                break;
            }
            temp = temp.next;
        }
    }
    //根据id获取数据
    public Student get(int id){
        //临时指针
        Student temp = head;
        while (true){
            if (temp == null || temp.getId() == id){
                return temp;
            }
            temp = temp.next;
        }
    }
    public StringBuffer print(){
        StringBuffer sb = new StringBuffer();
        Student temp = head;
        while (true){
            if (temp == null){
                break;
            }
            sb.append(temp.toString());
            temp = temp.next;
        }
        return sb;
    }
    //根据id删除一个学生
    public Student delete(int id){
        Student temp = head;
        //如果头节点为空
        if (temp == null){
            return temp;
        }
        //如果删除的是头节点
        if (temp.getId() == id){
            Student result = temp;
            head = temp.next;
            return result;
        }
        while (true){
            if (temp.next == null){
                return temp.next;
            }
            if (temp.next.getId() == id){
                Student result = temp.next;
                temp.next =temp.next.next;
                return result;
            }
            temp = temp.next;
        }
    }
    public boolean upd(Student stu){
        Student temp = head;
        while (true){
            if (temp == null){
                return false;
            }
            if (temp.getId() == stu.getId()){
                temp.setName(stu.getName());
                temp.setAge(stu.getAge());
                return true;
            }
            temp = temp.next;
        }
    }

}

//封装学生数据
class Student{
    private int id;
    private String name;
    private int age;
    public Student next;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

9. 二叉树

9.1. 代码实现二叉树/中前后序遍历/打印二叉树

package com.cs.testt;

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

/**
 * @ClassName BinaryTree
 * @Description TODO 二叉树
 * @Author jiaqi
 * @Date 2022/3/7 15:26
 * @Version 1.0
 **/
public class BinaryTree {
    private TreeNode root;  //根节点

    public BinaryTree(){
        root = null;
    }

    //按关键字查找节点
    public TreeNode find(int key){
        TreeNode cur = root;  //从根节点开始查找

        if(cur == null){  //如果树为空,直接返回null
            return null;
        }

        while(cur.age != key){
            if(key < cur.age){
                cur = cur.leftChild;  //如果关键字比当前节点小,转向左子节点
            }else{
                cur = cur.leftChild;  //如果关键字比当前节点大,转向右子节点
            }

            if(cur == null){  //没有找到结果,搜索结束
                return null;
            }
        }
        return cur;
    }

    //插入新节点
    public void insert(TreeNode node){
        if(root == null){
            root = node;  //如果树为空,则新插入的节点为根节点
        }else{
            TreeNode cur = root;

            while(true){
                if(node.age < cur.age){
                    if(cur.leftChild == null){  //找到了要插入节点的父节点
                        cur.leftChild = node;
                        return;
                    }
                    cur = cur.leftChild;
                }else{
                    if(cur.rightChild == null){  //找到了要插入节点的父节点
                        cur.rightChild = node;
                        return;
                    }
                    cur = cur.rightChild;
                }
            }
        }
    }

    //删除指定节点
    public boolean delete(TreeNode node){
        if(root == null){
            return false;  //如果为空树,直接返回false
        }

        boolean isLeftChild = true;  //记录目标节点是否为父节点的左子节点
        TreeNode cur= root;  //要删除的节点
        TreeNode parent = null; //要删除节点的父节点

        while(cur.age != node.age){  //确定要删除节点和它的父节点
            parent = cur;
            if(node.age < cur.age){  //目标节点小于当前节点,跳转左子节点
                cur = cur.leftChild;
            }else{//目标节点大于当前节点,跳转右子节点
                isLeftChild = false;
                cur = cur.rightChild;
            }
            if(cur == null){
                return false;  //没有找到要删除的节点
            }
        }

        if(cur.leftChild == null && cur.rightChild == null){  //目标节点为叶子节点(无子节点)
            if(cur == root){  //要删除的为根节点
                root = null;
            }else if(isLeftChild){
                //要删除的不是根节点,则该节点肯定有父节点,该节点删除后,需要将父节点指向它的引用置空
                parent.leftChild = null;
            }else{
                parent.rightChild = null;
            }
        }else if(cur.leftChild == null){  //只有一个右子节点
            if(cur == root){
                root = cur.rightChild;
            }else if(isLeftChild){
                parent.leftChild = cur.rightChild;
            }else{
                parent.rightChild = cur.rightChild;
            }
        }else if(cur.rightChild == null){  //只有一个左子节点
            if(cur == root){
                root = cur.leftChild;
            }else if(isLeftChild){
                parent.leftChild = cur.leftChild;
            }else{
                parent.rightChild = cur.leftChild;
            }
        }else{  //有两个子节点
            //第一步要找到欲删除节点的后继节点
            TreeNode successor = cur.rightChild;
            TreeNode successorParent = null;
            while(successor.leftChild != null){
                successorParent = successor;
                successor = successor.leftChild;
            }
            //欲删除节点的右子节点就是它的后继,证明该后继无左子节点,则将以后继节点为根的子树上移即可
            if(successorParent == null){
                if(cur == root){  //要删除的为根节点,则将后继设置为根,且根的左子节点设置为欲删除节点的做左子节点
                    root = successor;
                    root.leftChild = cur.leftChild;
                }else if(isLeftChild){
                    parent.leftChild = successor;
                    successor.leftChild = cur.leftChild;
                }else{
                    parent.rightChild = successor;
                    successor.leftChild = cur.leftChild;
                }
            }else{ //欲删除节点的后继不是它的右子节点
                successorParent.leftChild = successor.rightChild;
                successor.rightChild = cur.rightChild;
                if(cur == root){
                    root = successor;
                    root.leftChild = cur.leftChild;
                }else if(isLeftChild){
                    parent.leftChild = successor;
                    successor.leftChild = cur.leftChild;
                }else{
                    parent.rightChild = successor;
                    successor.leftChild = cur.leftChild;
                }
            }
        }

        return true;
    }

    public static final int PREORDER = 1;   //前序遍历
    public static final int INORDER = 2;    //中序遍历
    public static final int POSTORDER = 3;  //中序遍历

    //遍历
    public void traverse(int type){
        switch(type){
            case 1:
                System.out.print("前序遍历:\t");
                preorder(root);
                System.out.println();
                break;
            case 2:
                System.out.print("中序遍历:\t");
                inorder(root);
                System.out.println();
                break;
            case 3:
                System.out.print("后序遍历:\t");
                postorder(root);
                System.out.println();
                break;
        }
    }

    //前序遍历
    public void preorder(TreeNode currentRoot){
        if(currentRoot != null){
            System.out.print(currentRoot.age+"\t");
            preorder(currentRoot.leftChild);
            preorder(currentRoot.rightChild);
        }
    }

    //中序遍历,这三种遍历都用了迭代的思想
    public void inorder(TreeNode currentRoot){
        if(currentRoot != null){
            inorder(currentRoot.leftChild);  //先对当前节点的左子树对进行中序遍历
            System.out.print(currentRoot.age+"\t"); //然后访问当前节点
            inorder(currentRoot.rightChild);  //最后对当前节点的右子树对进行中序遍历
        }
    }

    //后序遍历
    public void postorder(TreeNode currentRoot){
        if(currentRoot != null){
            postorder(currentRoot.leftChild);
            postorder(currentRoot.rightChild);
            System.out.print(currentRoot.age+"\t");
        }
    }

    //私有方法,用迭代方法来获取左子树和右子树的最大深度,返回两者最大值
    private int getDepth(TreeNode currentNode,int initDeep){
        int deep = initDeep;  //当前节点已到达的深度
        int leftDeep = initDeep;
        int rightDeep = initDeep;
        if(currentNode.leftChild != null){  //计算当前节点左子树的最大深度
            leftDeep = getDepth(currentNode.leftChild, deep+1);
        }
        if(currentNode.rightChild != null){  //计算当前节点右子树的最大深度
            rightDeep = getDepth(currentNode.rightChild, deep+1);
        }

        return Math.max(leftDeep, rightDeep);
    }

    //获取树的深度
    public int getTreeDepth(){
        if(root == null){
            return 0;
        }
        return getDepth(root,1);
    }

    //返回关键值最大的节点
    public TreeNode getMax(){
        if(isEmpty()){
            return null;
        }
        TreeNode cur = root;
        while(cur.rightChild != null){
            cur = cur.rightChild;
        }
        return cur;
    }

    //返回关键值最小的节点
    public TreeNode getMin(){
        if(isEmpty()){
            return null;
        }
        TreeNode cur = root;
        while(cur.leftChild != null){
            cur = cur.leftChild;
        }
        return cur;
    }

    //以树的形式打印出该树
    public void displayTree(){
        int depth = getTreeDepth();
        ArrayList<TreeNode> currentLayerNodes = new ArrayList<TreeNode> ();
        currentLayerNodes.add(root);  //存储该层所有节点
        int layerIndex = 1;
        while(layerIndex <= depth){
            int NodeBlankNum = (int)Math.pow(2, depth-layerIndex)-1;  //在节点之前和之后应该打印几个空位
            for(int i = 0;i<currentLayerNodes.size();i++){
                TreeNode node = currentLayerNodes.get(i);
                printBlank(NodeBlankNum);   //打印节点之前的空位

                if(node == null){
                    System.out.print("*\t");  //如果该节点为null,用空位代替
                }else{
                    System.out.print("*  "+node.age+"\t");  //打印该节点
                }

                printBlank(NodeBlankNum);  //打印节点之后的空位
                System.out.print("*\t");   //补齐空位
            }
            System.out.println();
            layerIndex++;
            currentLayerNodes = getAllNodeOfThisLayer(currentLayerNodes);  //获取下一层所有的节点
        }
    }

    //获取指定节点集合的所有子节点
    private ArrayList getAllNodeOfThisLayer(List parentNodes){
        ArrayList list = new ArrayList<Node>();
        TreeNode parentNode;
        for(int i=0;i<parentNodes.size();i++){
            parentNode = (TreeNode)parentNodes.get(i);
            if(parentNode != null){
                if(parentNode.leftChild != null){  //如果上层的父节点存在左子节点,加入集合
                    list.add(parentNode.leftChild);
                }else{
                    list.add(null);  //如果上层的父节点不存在左子节点,用null代替,一样加入集合
                }
                if(parentNode.rightChild != null){
                    list.add(parentNode.rightChild);
                }else{
                    list.add(null);
                }
            }else{  //如果上层父节点不存在,用两个null占位,代表左右子节点
                list.add(null);
                list.add(null);
            }
        }
        return list;
    }

    //打印指定个数的空位
    private void printBlank(int num){
        for(int i=0;i<num;i++){
            System.out.print("*\t");
        }
    }

    //判空
    public boolean isEmpty(){
        return (root == null);
    }

    //判断是否为叶子节点
    public boolean isLeaf(TreeNode node){
        return (node.leftChild != null || node.rightChild != null);
    }

    //获取根节点
    public TreeNode getRoot(){
        return root;
    }
}

//树节点的封装类
class TreeNode{
    int age;
    String name;
    TreeNode leftChild;  //左子节点的引用
    TreeNode rightChild; //右子节点的引用

    public TreeNode(int age,String name){
        this.age = age;
        this.name = name;
    }

    //打印该节点的信息
    public void displayNode(){
        System.out.println("name:"+name+",age:"+age);
    }
}

参考自:https://www.jianshu.com/p/bb0c12b34f2a

9.2. 树的相关操作

树的前中后序遍历
树的前中后序查找
打印树
二叉排序树
霍夫曼树
AVL树
B树
B+树
顺序存储二叉树(求他的前中后序的数组)

10. 分治算法

在这里插入图片描述

经典的:汉诺塔问题、快排、二分查找等等。

11.动态规划

0 - 1背包问题(限制每个物品只能一个)
完全背包问题(每个物品不限个数)
在这里插入图片描述
区别:分治算法分解得到的小问题是相互独立的,而动态规划算法分解的问题是相互不独立的。

12. 字符串匹配问题

12.1. 暴力匹配

package Algorithm;

public class ViolenceMatch {

	public static void main(String[] args) {
		String str1="addgadfhfgsfgs";
		String str2="fhf";
		int index = violenceMatch(str1, str2);
		System.out.println("index="+index);
	}
	//暴力匹配算法实现
	public static int violenceMatch(String str1,String str2){
		char[] s1 = str1.toCharArray();
		char[] s2 = str2.toCharArray();
		
		int s1len = s1.length;
		int s2len = s2.length;
		//两个索引
		int i = 0;//指向字符串str1
		int j = 0;//指向字符串str2
		while (i<s1len && j<s2len) {//保证匹配不越界
			if (s1[i] == s2[j]) {
				i++;
				j++;
			}else {
				i = i-(j-1);
				j=0;
			}
		}
		//判断是否匹配成功
		if (j == s2len) {
			return i-j;
		}else {
			return -1;
		}
	}
}

12.2. KMP 算法

在这里插入图片描述

13. 贪心算法

13.1. 集合覆盖问题

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

在这里插入图片描述

动态规划贪心回溯、递归、分治、深度优先/广度优先搜索
几个排序算法(冒泡、插入、选择、快排)、二分查找、双指针、滑动窗口、数据结构

五大常用算法之一:分治算法
五大常用算法之二:动态规划算法
五大常用算法之三:贪心算法
五大常用算法之四:回溯法
五大常用算法之五:分支限界法

常见排序算法
二分查找

分治算法和动态规划的区别

14. 额外

如何理解「分治算法」?
比如说二分查找这个问题的思路是如下:
1、我们的目的是什么:找到一个数组中的某个指定的值,那么方法的参数和返回值我们就可以确定了(参数是数组,返回值是找到的值)
2、如何分解:每个分治题目的分解方式都不太一样,在这道题的分治是通过对半分(原因是数组有序),那么对半分后的数组需要做什么呢?答案是重复 1 的操作,即我们的目的还是一样的:找到对半分的数组的指定值,那么方法还是第1中的方法,那么这里就是写递归方法的地方。
3、递归方法写完之后,我们需要获取其返回值,根据左右两个半数组的返回值,择优返回一个最优的返回。
4、当然在方法之初,需要一个退出条件(配合return使用),这个条件一般是「不可再分的情况」。

基本上所有的分治算法都遵循上面的思路,不同的地方主要是在第 2 点,难点也是在第 2 点,因为你不知道对于一道陌生的题,你到底应该怎么分解(对半?还是去一?等等)而且第 3 有时候某些分支也不需要递归(因为分解的足够小后已经成为了特殊情况,可以直接处理)。

如何理解「动态规划」?
动态规划和分治的区别:分治是把大问题「分裂」成若干个小问题,直到小问题不可再分,直接处理后返回,所以,思想上是自大到小处理,用到递归。动态规划也是把大问题「分裂」成若干个小问题,但是它是从小问题处理到大问题的过程,会有一个状态方程和一个维护的数组(可能一维或者二维)以及几个初始条件,状态方程反映了前后步骤的关系,数组存储各个步骤的数据。

冒泡排序:让第k个和第k+1个进行比较,然后交换,一直交换,到最后一个就是最大值(最小值),然后再重新比较。(联想:最后一个就像泡泡一样冒出来了)。
选择排序:先假设第一个就是最小的,接着遍历后面,找到一个比第一个还小的,就交换,这样,第一个就是最小的。接着第二个。。
插入排序:假定第一个是排好的,接着从第二个开始,往前找,看要插入到哪里。
快速排序

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执章学长

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值