数据结构与算法

在IDE中编写源代码(.java文件),通过Java编译器(javac)将源代码编译为字节码文件并存储在外存/辅存中

运行Java程序时,JVM将字节码文件从外存/辅存加载至内存,JVM再通过解释器(Interpreter)或者即时编译器(JIT compiler)执行字节码

解释执行:逐条解释字节码并执行

JIT编译:将部分字节码编译为本地机器码(Native Machine Code针对特定硬件架构如x86,ARM编写的指令集,不需要进一步翻译或解释,是可直接被计算机硬件理解并执行的二进制指令),以提高执行效率

数据结构(data structure)是组织数据的方式  算法(algorithm):解决问题的方法

线性数据结构(linear data structure)

数据元素之间是一对一的线性关系,除第一个和最后一个元素外,都有一个前驱和一个后继元素

顺序存储结构(sequential storage structure)

数据元素按顺序存储在一块连续的空间,通过下标或索引直接访问,一旦定义了顺序存储结构的大小,例如定义了数组的长度,其大小就固定不变,无法动态扩展

可以通过下标快速访问数据;插入和删除元素时需要移动其他元素,性能较低;数组定义的大小大于实际需要的大小,可能会浪费空间,如果大小不足,则需要重新分配更大的空间并复制数据

例如:

数组(Array):用于存储一组类型相同的数据

动态数组(如C++中的vector,Java中的ArrayList):允许在运行时自动调整数组的大小,在数据量动态变化时非常有用

链式存储结构(Linked storage structure)

数据元素存储在不连续的空间中,每个元素称为结点(Node),结点包含数据域(Data)和一个或多个指向其他元素位置的指针(引用)(指针域Pointer,或曰next域),通过指针将多个结点连接在一起

对比顺序存储结构那样需要连续的空间,无需提前知道大小,可动态增删结点;在插入和删除操作只涉及到指针的改变,性能更好;缺点是增加了指针域,增加了存储开销;访问一个结点时,需要从头结点开始遍历,随机访问速度较慢

例如:

单向链表(singly linked list):每个结点只有一个指针指向下一个结点,最后一个结点的指针指向NULL

缺点是只能单向遍历,不能反向访问

双向链表(double linked list):每个结点有两个指针,一个指向下一个结点,一个指向上一个结点,支持双向遍历,相比单向链表,每个结点需要额外一个指针域,增加了存储开销

循环链表(circular linked list):链表最后一个结点指向头结点,可以是单向的或双向的

非线性数据结构(Non-linear data structure)

数据元素间没有线性关系,元素间可以是一对多的关系

树(Tree) 图(Map) 哈希表(Hash Table)

稀疏数组(Sparse Array)

是一种用于存储稀疏数据(Sparse Data大部分元素是零或者某个值,只有少部分元素是有意义的非零值)的数据结构

在存储稀疏数据的情景中,传统数组会浪费大量空间存储零值元素,而稀疏数组则通过只存储非零元素来压缩数组,节省空间

稀疏数组操作比普通数组复杂,可能需要维护更多的索引信息

如果需要频繁随机访问数组,由于需要解压缩元素的位置,稀疏数组可能比普通数组更慢

稀疏数组的基本结构

行号(Row Index):元素所在的行位置

列号(Column Index):元素所在的列位置

值(Value):元素的实际值(非零值)

稀疏数组的存储方式

三元组表示法(Triplet)

每个非零元素使用一个三元组(行号,列号,值)来表示,并将所有三元组存储在一个数组或其他结构中

压缩行存储(Compressed Row Storage,CRS)

将数据按行进行压缩,记录每一行中非零元素的位置和值

压缩列存储(Compressed Column Storage,CCS)

将数据按列进行压缩,记录每一列中非零元素的位置和值

应用场景

稀疏矩阵(Sparse Matrix)/二维数组,比如保存棋盘

    public void SparseArrayDemo() {
        //创建原始二维数组,除了[1][2]和[2][3],其他元素皆为0
        int[][] arr = new int[11][11];
        arr[1][2] = 1;
        arr[2][3] = 2;

        //遍历原始二维数组,获得非零数据个数
//        通过arr.length获得行数,通过arr[i].length获得每一行的列数
//        int[][] arry = {
//                {1, 2, 3},
//                {1, 2},
//                {1}
//        };
        int sum = 0;
        for (int i = 0; i < arr.length; i++)
            for (int j = 0; j < arr[i].length; j++)
                if (arr[i][j] != 0) sum++;

        //创建对应的稀疏数组,共sum+1行,sum行用于保存非零数据,第一行用于保存原始数组行数,列数及非零元素个数
        //共三列,第一列用于保存非零元素在原始数组中的行号,第二列用于保存列号,第三列用于保存value
        int[][] sparseArr = new int[sum + 1][3];
        sparseArr[0][0] = arr.length;
        sparseArr[0][1] = arr[0].length;
        sparseArr[0][2] = sum;

        int count = 1;
        for (int i = 0; i < 11; i++)
            for (int j = 0; j < 11; j++)
                if (arr[i][j] != 0) {
                    sparseArr[count][0] = i;
                    sparseArr[count][1] = j;
                    sparseArr[count][2] = arr[i][j];
                    count++;
                }

        //稀疏数组解压缩
        int[][] arr2 = new int[sparseArr[0][0]][sparseArr[0][1]];
        for (int i = 1; i < sparseArr.length; i++)
            arr2[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
    }

队列(Queue)

一种遵循先进先出(FIFO,first in first out)的线性数据结构,最早插入队列的元素最先被删除,插入操作发生在队尾(rear),删除操作发生在队头(front)(入队enqueue  出队dequeue)

用数组实现普通队列

用数组实现循环队列

class CircleArray {
    //表示数组最大容量,即循环队列最大容量
    private int maxSize;
    //初始化为0;指向队列第一个元素
    private int front = 0;
    //初始化为0;指向队列最后一个元素的后一个位置
    private int rear = 0;
    private int[] arr;

    public CircleArray(int maxSize) {
        this.maxSize = maxSize;
        arr = new int[maxSize + 1];
    }

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

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

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

    //添加数据
    public void addQueue(int addedData) {
        //判断数组为满与否
        if (isFull()) {
            System.out.println("数组已满");
            return;
        }
        //添加数据
        arr[rear] = addedData;
        //rear指针移位
        rear = (rear + 1) % maxSize;
    }

    //出队列
    public int getQueue() {
        //判断为空与否
        if (isEmpty()) {
            throw new RuntimeException("队列为空");
        }
        //将将要出队列的第一个元素保存至temp中
        int temp = arr[front];
        //指针移位
        front = (front + 1) % maxSize;
        return temp;
    }
}

链表(Linked List)

有头结点的链表:头结点不存储数据,只用于链表的管理

无头结点的链表:链表的第一个结点即为存储数据的结点

单向链表(Single Linked List)

class Node {
    //数据域
    public int id;
    public String name;
    //指针域
    public Node next;

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

    @Override
    public String toString() {
        return "Node{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}
class singleLinkedList {
    //初始化一个头结点,不存放数据,头结点永远不动
    private Node head = new Node(0, "");

    //获得头结点,即获得链表
    public Node getHead(){
        return head;
    }

    //根据id删除对应结点,找到需要删除的结点的前一个结点
    public void deleteById(int id) {
        //判断链表为空与否
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为head不动,所以使用一个辅助指针tempPtr用于遍历链表
        Node tempPtr = head.next;
        //flag用以标识链表中是否存在对应id
        boolean flag = false;
        while (true) {
            if (tempPtr.next == null) break;
            if (tempPtr.next.id == id) {
                flag = true;
                break;
            }
            tempPtr = tempPtr.next;
        }

        if (flag) {
            tempPtr.next = tempPtr.next.next;
        } else {
            System.out.println("不存在对应结点");
        }
    }


    //根据targetNode.id修改结点信息,将对应id的结点的name修改为targetNode.name
    public void updateById(Node targetNode) {
        //判断链表为空与否
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为head不动,所以使用一个辅助指针tempPtr用于遍历链表
        Node tempPtr = head.next;
        //flag用以标识链表中是否存在对应id
        boolean flag = false;
        while (true) {
            if (tempPtr == null) break;
            if (tempPtr.id == targetNode.id) {
                flag = true;
                break;
            }
            tempPtr = tempPtr.next;
        }

        if (flag) {
            tempPtr.name = targetNode.name;
        } else {
            System.out.println("没有找到对应结点");
        }
    }

    //向链表里添加结点,不考虑排序,直接添加至链表尾部
    //这个method没考虑添加时id不重复
    public void add(Node addedNode) {
        //因为head不动,所以使用一个辅助指针tempPtr用于遍历链表
        Node tempPtr = head;
        //遍历寻找链表最后一个元素
        while (true) {
            if (tempPtr.next == null) {
                break;
            }
            //tempPtr指针移位
            tempPtr = tempPtr.next;
        }
        //跳出循环后,tempPtr即指向链表的最后一个元素
        tempPtr.next = addedNode;
    }

    //向链表里添加结点,以id进行排序
    public void addById(Node addedNode) {
        //因为head不动,所以使用一个辅助指针tempPtr用于遍历链表
        Node tempPtr = head;
        //flag用以标识addedNode.id在链表中是否存在,true->存在
        boolean flag = false;
        //遍历寻找addedNode的插入位置,向tempPtr指针指向的结点后面添加
        while (true) {
            if (tempPtr.next == null) break;
            if (tempPtr.next.id > addedNode.id) {
                break;
            } else if (tempPtr.next.id == addedNode.id) {
                flag = true;
                break;
            }
            tempPtr = tempPtr.next;
        }
        if (flag) {
            System.out.println("id已经存在");
        } else {
            addedNode.next = tempPtr.next;
            tempPtr.next = addedNode;
        }


    }

    //显示链表
    public void showLinkedList() {
        //判断链表为空与否
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }

        //因为head不动,所以使用一个辅助指针tempPtr用于遍历链表
        Node tempPtr = head.next;
        //遍历并输出链表
        while (true) {
            //链表为空时,直接break
            if (tempPtr == null) break;

            System.out.println(tempPtr);
            //指针移位
            tempPtr = tempPtr.next;
        }
    }
}

例题1

求出单链表中有效数据个数

解:遍历+计数器

例题2

寻找单链表中倒数第k个结点

解:先求出单链表中有效数据个数size,则正数size-k个结点即为所求

例题3

反转单链表

解:

//反转链表
public void reverseLinkedList(Node head) {
    //原链表为空,或者只有一个结点,则无需操作原链表
    //先判断head.next,否则先判断head.next.next可能直接报空指针
    if (head.next == null || head.next.next == null) {
        return;
    }

    //currentPtr用以遍历原链表
    Node currentNode = head.next;
    //nextNode指向currentNode在原始链表中的下一个结点
    Node nextNode;
    //tempHead临时头结点用以存储反转链表
    //将遍历到的原链表上的每一个结点依次拼接到tempHead临时链表上
    Node reverseHead = new Node(0, "");

    while (currentNode != null) {
        nextNode = currentNode.next;
        currentNode.next = reverseHead.next;
        reverseHead.next = currentNode;
        currentNode = nextNode;
    }
    head.next = reverseHead.next;
}

例题4

逆序打印单链表

思路一:先反转,再打印,但破环了原始链表

思路二:将各个节点压入栈,利用栈先进后出的特性,即可逆序打印

public void reversePrint(Node head) {
    if (head.next == null) {
        System.out.println("链表为空");
    }

    //创建栈
    Stack<Node> stack = new Stack<>();

    //currentNode用于遍历原始链表
    Node currentNode = head.next;

    //将链表所有结点压入栈
    while (currentNode != null) {
        stack.push(currentNode);
        currentNode = currentNode.next;
    }

    //依次出栈并打印即可
    while (stack.size() > 0)
        System.out.println(stack.pop());
}

例题5

合并两个有序单链表,合并后依旧有序

//参考题83的思路,合并两个有序数组,采用双指针法
//我们同样申请一个新链表用来存储最终结果
//代码为:ListNode resultNode = new resultNode();
//list1 和 list2作为指针分别指向两个原始链表,比较两个指针指向的结点之val以确定将哪个结点存入结果链表
//我们还需要一个指针用来告诉我们应该往新链表的哪一个位置上存(后面为了方便描述,就把这个指针叫做指示指针,命名为showNode)
//初始时,当然给resultNode(指向结果链表的头结点,且始终指向)里存
//所以指示指针一开始应该指向结果链表的头结点
//代码实现为:ListNode showNode = resultNode
//假设第一次填充的是list1指针指向的结点
//则代码实现为:
//首先把list1指针指向的结点存入结果链表:showNode.next = list1;
//然后移动list1指针,list1 = list1.next
//最后移动指示指针至下一次应当存入的位置
//showNode = showNode.next;
//以此类推,遍历尽两个原始链表
//若某个链表为空了,则直接把另一个链表拼入结果链表即可
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
    //防御性编程,如果哪一个链表为空,直接返回另一个链表即可
    //另外:为什么要叫防御性编程?因为一旦为空,后面调用方法之类的就很可能报空指针异常
    if (list1 == null) return list2;
    if (list2 == null) return list1;
    //创建结果链表
    ListNode resultNode = new ListNode();
    //创建并初始化指示指针
    ListNode showNode = resultNode;

    while (list1 != null && list2 != null) {//一旦哪一个链表为空,就跳出循环
        if (list1.val < list2.val) {//判断大小,以决定把哪个存入结果链表
            showNode.next = list1;
            list1 = list1.next;
        } else {
            showNode.next = list2;
            list2 = list2.next;
        }
        //移动指示指针:
        showNode = showNode.next;
    }
    //判断哪一个链表空了.然后把另一个链表拼到结果链表里
    if (list1 == null) showNode.next = list2;
    if (list2 == null) showNode.next = list1;
    //返回结果链表,这里实际上的结果链表的头结点应该是被resultNode.next指向的,所以返回resultNode.next
    return resultNode.next;
}
//法二:递归写法
//考虑一个黑箱方法mergeTwoLists,其作用为传入两个原始链表(两个原始链表为了方便叙述,记作list1链表和list2链表),返回拼接后的链表
//递归需要缩小问题的规模,我们采用如下的缩小方法:
//比较list1链表和list2链表头结点的val,哪个头结点va小,则说明那个头结点的val是最小的
//我们可以把那个头结点去除,然后把list1链表和list2链表(其中一个去头了)再次传入黑箱方法
//代码实现为:mergeTwoLists(list1.next,list2)
//再把返回的链表和那个被去除的头结点进行拼接整体作为返回值
//代码实现为:list1.next = mergeTwoLists(list1.next,list2)
//返回值为:return list1;
//边界条件为:传入的某个原始链表为空,则直接返回另一个链表即可
//总的代码如下:
// public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//     //边界条件
//     if(list1 == null) return list2;
//     if (list2 == null) return  list1;

//     if (list1.val < list2.val){
//         list1.next = mergeTwoLists(list1.next,list2);
//         return list1;
//     }else {
//         list2.next = mergeTwoLists(list2.next,list1);
//         return list2;
//     }
// }

双向链表(Double Linked List)

class DNode {
    //数据域
    public int id;
    public String name;
    //指针域
    public DNode next;
    public DNode pre;

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

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

class DoubleLinkedList {
    //头结点
    private DNode head = new DNode(0, "");

    //获取头结点
    public DNode getHead() {
        return head;
    }

    //遍历双向链表
    public void list() {
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }

        DNode ptrNode = head.next;

        while (true) {
            if (ptrNode == null) break;

            System.out.println(ptrNode);
            ptrNode = ptrNode.next;
        }
    }

    //添加结点,默认添加至尾部
    public void add(DNode addedDNode) {
        DNode ptrNode = head;

        //遍历链表,找到最后一个结点
        while (true) {
            if (ptrNode.next == null) break;
            //指针移位
            ptrNode = ptrNode.next;
        }

        //添加结点
        ptrNode.next = addedDNode;
        addedDNode.pre = ptrNode;
        //addedDNode.next = null;
    }

    //向链表里添加结点,以id进行排序
    public void addById(DNode addedNode) {
        //因为head不动,所以使用一个辅助指针tempPtr用于遍历链表
        DNode tempPtr = head;
        //flag用以标识addedNode.id在链表中是否存在,true->存在
        boolean flag = false;
        //遍历寻找addedNode的插入位置,向tempPtr指针指向的结点后面添加
        while (true) {
            tempPtr = tempPtr.next;

            if (tempPtr == null) break;

            if (tempPtr.id > addedNode.id) {
                break;
            } else if (tempPtr.id == addedNode.id) {
                flag = true;
                break;
            }
        }
        if (flag) {
            System.out.println("id已经存在");
        } else {
            tempPtr.pre.next = addedNode;
            addedNode.next = tempPtr;
            addedNode.pre = tempPtr.pre;
            tempPtr.pre = addedNode;
        }
    }

    //修改某一个链表的内容(即name属性)
    public void upDateById(DNode targetDNode) {
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }

        DNode ptrNode = head.next;

        //标识是否找到相应结点
        boolean flag = false;

        while (true) {
            if (ptrNode == null) break;

            if (ptrNode.id == targetDNode.id) {
                flag = true;
                break;
            }

            ptrNode = ptrNode.next;
        }

        if (flag) {
            ptrNode.name = targetDNode.name;
        } else {
            System.out.println("未找到对应结点");
        }
    }

    //根据Id删除结点
    public void deleteById(int id) {
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }

        DNode ptrNode = head.next;

        //用于标识是否存在对应Id的结点
        boolean flag = false;

        while (true) {
            if (ptrNode == null) break;
            //上下两个if,先判断ptrNode为空与否,否则先判断ptrNode.id==id会报空指针异常
            if (id == ptrNode.id) {
                flag = true;
                break;
            }
            //指针移位
            ptrNode = ptrNode.next;
        }

        if (flag) {
            ptrNode.pre.next = ptrNode.next;
            //ptrNode可能指向链表的最后一个结点,所以先加以判断,否则直接ptrNode.next.pre会报空指针异常
            if (ptrNode.next != null)
                ptrNode.next.pre = ptrNode.pre;
        } else {
            System.out.println("未找到对应结点");
        }
    }
}

环形链表

约瑟夫问题

n个人围成一圈,编号为1~n, 编号为k的人从1开始报数,数到m的人出列,出列的人的下一个人再从1开始报数, 以此类推, 求出列的人的编号序列

class Josephus {
    //first结点用于管理环形链表,但亦存储数据
    private Node first = null;

    //初始化环形链表的大小,注意每次初始化得到的都是一个新的约瑟夫环
    public void initializeCircleList(int nodeNums) {
        //对nodeNums进行校验
        if (nodeNums < 1) {
            System.out.println("输入大于0的值");
            return;
        }
        //辅助指针,指向上一次添加的结点
        //这里可以不初始化的,只是IDEA比较智能,会检测出潜在的空指针异常的风险,实际上这个风险不会发生
        Node currentNode = first;
        for (int i = 1; i <= nodeNums; i++) {
            //构建环形链表
            Node addednode = new Node(i, "");
            //第一个结点比较特殊
            //让first指向第一个结点,之后,first始终指向第一个结点
            if (i == 1) {
                first = addednode;
                first.next = first;
                currentNode = first;
            } else {
                currentNode.next = addednode;
                addednode.next = first;
                currentNode = addednode;
            }

        }
    }

    //显示上次初始化的约瑟夫环形链表
    public void showList() {
        if (first == null) {
            System.out.println("链表为空");
            return;
        }

        Node ptrNode = first;

        while (true) {
            System.out.println(ptrNode.id);

            ptrNode = ptrNode.next;

            if (ptrNode == first) break;
        }
    }

    //出列序列
    //startId:开始数数的人   countNum:一轮数几下   nodeNums:一开始总共有多少人
    public void outSequence(int startId, int countNum, int nodeNums) {
        //校验参数
        if (first == null || startId < 1 || startId > nodeNums) {
            System.out.println("参数输入有误");
            return;
        }

        //创建辅助指针helper,ptrNode
        Node helper = first;
        Node ptrNode = first;

        //先让helper指针移至first结点的前一个结点
        while (true) {
            if (helper.next == first) break;
            helper = helper.next;
        }

        //再让两个辅助指针移动到起始位置上
        for (int i = 1; i <= startId - 1; i++) {
            helper = helper.next;
            ptrNode = ptrNode.next;
        }

        //开始报数
        while (true) {
            //说明圈中只剩一个结点了
            if (helper == ptrNode) break;

            for (int i = 1; i <= countNum - 1; i++) {
                //让两个指针同时移动countNum-1次,此时ptrNode即指向应出列之结点
                helper = helper.next;
                ptrNode = ptrNode.next;
            }

            //输出出列的人的id
            System.out.println(ptrNode.id);

            ptrNode = ptrNode.next;
            helper.next = ptrNode;
        }

        //输出最后出列的人的id
        System.out.println(ptrNode.id);
    }
}

栈(Stack)

用数组模拟栈

class Stack {
    //栈的大小
    private int maxSize;
    //用以模拟栈的数组
    private int[] arr;
    //表示栈顶,初始化为-1,表示不存在数据
    private int top = -1;

    public Stack(int maxSize) {
        this.maxSize = maxSize;
        arr = new int[this.maxSize];
    }

    //返回栈顶元素,但不是pop
    public int peek() {
        return arr[top];
    }

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

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

    //入栈
    public void push(int pushedValue) {
        if (isFull()) {
            System.out.println("栈满");
            return;
        }

        top++;
        arr[top] = pushedValue;
    }

    //出栈
    public int pop() {
        if (isEmpty()) {
            throw new RuntimeException("栈空");
        }

        int poppedValue = arr[top];
        top--;
        return poppedValue;
    }

    //遍历显示栈
    public void list() {
        if (isEmpty()) {
            System.out.println("栈空");
            return;
        }

        for (int i = top; i >= 0; i--)
            System.out.println(arr[i]);
    }

    //返回运算符的优先级,优先级使用数字表示,数字越大,优先级越高
    public int priority(int operator) {
        if (operator == '*' || operator == '/') {
            return 1;
        } else if (operator == '+' || operator == '-') {
            return 0;
        } else {
            return -1;
        }
    }

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

    //计算方法
    public int calculate(int operand1, int operend2, int operator) {
        //初始化为0,这个不严谨,但是不初始化会报错,因为后面return result了
        int result = 0;

        switch (operator) {
            case '+':
                result = operend2 + operand1;
                break;
            case '-':
                result = operend2 - operand1;
                break;
            case '*':
                result = operend2 * operand1;
                break;
            case '/':
                result = operend2 / operand1;
                break;
            default:
                break;
        }

        return result;
    }
}

三种表达式

前缀表达式(prefix expression):运算符位于操作数之前

中缀表达式(infix expression):常见式子

后缀表达式(suffix expression):又称逆波兰表达式,运算符位于操作数之后

例题1:用栈计算中缀表达式

这里用的栈是上面自定义的栈,因为需要自己定义一些方法,比如priority

//这个计算器只有+-*/,没有()
//创建一个数栈,一个符号栈
//对targetExpression进行扫描
//如果是数字,则入数栈
//如果是符号:
//若符号栈为空,则直接入栈;
//若符号栈非空,则若当前扫描到的符号之优先级小于等于栈顶符号之优先级,
//                  则从数栈中pop出两个数,再从符号栈中pop出一个符号,进行运算,将结果入数栈
//                  最后将当前扫描到的符号入符号栈
//           若当前扫描到的符号之优先级大于栈顶符号之优先级
//                  则直接入符号栈
//表达式扫描完毕后,从数栈pop出两个数,符号栈中pop出一个符号,并运算
//注意减法/除法中,后出栈的数是被减数/被除数
//最后数栈只剩下一个数字,即为最终结果
public int calculator(String targetExpression) {
    //创建数栈和符号栈
    Stack operandStack = new Stack(10);
    Stack operatorStack = new Stack(10);

    //用于扫描targetExpression的指针
    int ptr = 0;
    //用于存储扫描到的符号
    char character;
    //用于接收operand和operator和中间运算结果
    int operand1;
    int operand2;
    int operator;
    int result;
    //用于处理多位数,不初始化会报错
    String multiDigit = "";

    while (true) {
        character = targetExpression.substring(ptr, ptr + 1).charAt(0);
        //判断character是数还是运算符
        if (operatorStack.isOperator(character)) {
            //判断符号栈为空与否
            if (!operatorStack.isEmpty()) {
                //判断当前扫描到的运算符和符号栈栈顶运算符的优先级
                if (operatorStack.priority(character) <= operatorStack.priority(operatorStack.peek())) {
                    operand1 = operandStack.pop();
                    operand2 = operandStack.pop();
                    operator = operatorStack.pop();
                    result = operandStack.calculate(operand1, operand2, operator);
                    //result入数栈
                    operandStack.push(result);
                    //当前扫描到的运算符入符号栈
                    operatorStack.push(character);
                } else {
                    operatorStack.push(character);
                }
            } else {
                //符号栈为空,直接入栈
                operatorStack.push(character);
            }
        } else {//如果是数,直接入数栈,但要处理多位数的情况
            multiDigit += character;
            if (ptr == targetExpression.length() - 1) {//如果character已经是targetExpression的最后一个字符
                //则直接入栈
                operandStack.push(Integer.parseInt(multiDigit));
            } else {
                //判断下一个字符是否为数字
                if (operatorStack.isOperator(targetExpression.substring(ptr + 1, ptr + 2).charAt(0))) {
                    operandStack.push(Integer.parseInt(multiDigit));
                    //要重置multiDigit!!
                    multiDigit = "";
                }
            }
        }
        //ptr移位,并判断是否扫描完毕
        ptr++;
        if (ptr >= targetExpression.length()) break;
    }

    //表达式扫描完毕后,从数栈pop出两个数,符号栈中pop出一个符号,并运算
    while (true) {
        //如果符号栈为空,则计算完毕,数栈中仅有一个数字,即为最终结果
        if (operatorStack.isEmpty()) {
            break;
        } else {
            operand1 = operandStack.pop();
            operand2 = operandStack.pop();
            operator = operatorStack.pop();
            result = operandStack.calculate(operand1, operand2, operator);
            operandStack.push(result);
        }
    }
    return operandStack.peek();
}

中缀表达式转后缀表达式

虽然会出不存在该运算符,但无伤大雅

//1)初始化两个栈,s1和s2
//2)从左向右扫描中缀表达式
//3)遇到操作数,则入栈s2
//4)遇到运算符
//4.1若s1为空,或s1栈顶元素为左括号(,或优先级比s1栈顶运算符高则直接入栈
//4.2若不满足4.1,则将s1栈顶元素弹出并压入s2,再次回到4.1
//5)
//5.1遇到左括号(,则直接入栈s1
//5.2遇到右括号),则依次弹出s1的元素并压入s2,直至遇到左括号,这对括号被丢弃
//怎么丢弃见代码
//6)重复步骤2~5,直至中缀表达式扫描完毕
//7)将s1中剩余的元素依次弹出并压入s2
//8)依次弹出s2中元素并输出,结果的逆序即为所求的后缀表达式
public List<String> infixToSuffix(String infixExpression) {
    //先将infixExpression存储至list集合中,这里的infixExpression并无空格间隔
    List<String> list = new ArrayList<>();
    //用于遍历infixExpression
    int ptr = 0;
    //存储遍历到的字符
    char character;
    //用于处理多位数
    String multiDigit;
    do {
        //如果character是一个非数字则直接加入list集合
        if ((character = infixExpression.charAt(ptr)) < 48 ||
                (character = infixExpression.charAt(ptr)) > 57) {
            list.add("" + character);
            ptr++;
        } else {//如果是一个数,则需要考虑多位数
            multiDigit = "";
            while (ptr < infixExpression.length() && (character = infixExpression.charAt(ptr)) >= 48
                    && (character = infixExpression.charAt(ptr)) <= 57) {
                multiDigit += character;
                ptr++;
            }
            list.add(multiDigit);
        }
    } while (ptr <= infixExpression.length() - 1);

    //infixExpression存入list集合完毕

    //定义两个栈s1,s2,因为s2栈没有出栈操作,所以s2使用集合List代替
    //s2使用集合,就没有栈先入先出的限制,直接正常打印集合即是对应的后缀表达式
    Stack<String> s1 = new Stack<>();
    List<String> s2 = new ArrayList<>();

    //遍历list
    for (String item : list) {
        if (item.matches("\\d+")) {//多位数
            s2.add(item);
        } else if (item.equals("(")) {//如果是左括号
            s1.push(item);
        } else if (item.equals(")")) {//如果是右括号,注意右括号没加入s2
            while (!s1.peek().equals("(")) {//未见到左括号则s1不断pop
                s2.add(s1.pop());
            }
            s1.pop();//将左括号弹出栈,左括号亦未加入s2
        } else {
            //4)遇到运算符
            //4.1若s1为空,或s1栈顶元素为左括号(,或优先级比s1栈顶运算符高则直接入栈
            //4.2若不满足4.1,则将s1栈顶元素弹出并压入s2,再次回到4.1
            while (s1.size() != 0 && Operator.getPriority(s1.peek()) >= Operator.getPriority(item)) {
                s2.add(s1.pop());
            }
            //出while循环说明满足4.1了,将item入栈s1
            s1.push(item);
        }
    }
    //7)将s1中剩余的元素依次弹出并压入s2
    while (s1.size() != 0) {
        s2.add(s1.pop());
    }
    return s2;
}

逆波兰计算器

//后缀表达式计算器,逆波兰计算器
//从左向右扫描字符串
//遇到数字则将数字入栈
//遇到运算符则pop两个数字进行相应的运算,并将结果入栈
//直至字符串扫描完毕,栈中唯一数字即为结果
//注意这里的suffixExpression中的数字符号之间用空格分开
public int polandCalculator(String suffixExpression) {
    //将suffixExpression以space分割开,用list集合接收结果
    String[] split = suffixExpression.split(" ");
    List<String> list = new ArrayList<>();
    for (String element : split) list.add(element);

    Stack<String> stack = new Stack<>();

    for (String item : list) {
        //使用正则表达式取出数
        if (item.matches("\\d+")) {//匹配多位数
            stack.push(item);
        } else {
            //pop出两个数并运算,再将结果入栈
            int operand2 = Integer.parseInt(stack.pop());
            int operand1 = Integer.parseInt(stack.pop());
            //存储运算结果
            int result = 0;
            if (item.equals("+")) {
                result = operand1 + operand2;
            } else if (item.equals("-")) {
                result = operand1 - operand2;
            } else if (item.equals("*")) {
                result = operand1 * operand2;
            } else if (item.equals("/")) {
                result = operand1 / operand2;
            } else {
                throw new RuntimeException("运算符有误");
            }

            //将result入栈
            stack.push("" + result);
        }
    }
    return Integer.parseInt(stack.pop());
}

排序(Sort)

内部排序:数据量较小时,将数据全部加载至内存完成排序,速度较快

外部排序:数据量较大时,借助外存完成排序,涉及内外存的频繁交互,速度较慢

时间频度T(n),表示算法执行的基本操作总数,n是输入规模

渐进时间复杂度,简称时间复杂度,对操作执行次数忽略系数,忽略低阶...

最坏时间复杂度/平均时间复杂度

空间复杂度,算法运行时占用的空间大小的度量

相比空间复杂度,速度更重要,一些缓存产品(redis)和算法采取以空间换时间的策略

冒泡排序(Bubble Sort)

//O(n²)
public void bubbleSort(int[] arr) {
    //第一轮确定前n个数里最大的数,头指针从索引0移动到索引n-2
    //第二轮确定前n-1个数里最大的数,指针从索引0移动索引n-3
    //第n-1轮确定前2个数里最大的数,指针从索引0移动索引0
    //所以共进行n-1轮冒泡
    //第i轮确定前n-i+1个数里最大的数,指针从索引0移动索引n-i-1次
    int temp;
    //用来标识如果在某轮冒泡排序中,没有进行过交换,则说明已经排序完成
    boolean flag = false;
    for (int i = 1; i <= arr.length - 1; i++) {
        for (int j = 0; j <= arr.length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                flag = true;
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        if (!flag) {
            break;
        } else {
            //重置flag
            flag = false;
        }
    }
}

选择排序

//O(n²)
public void selectSort(int[] arr) {
    //共进行n-1轮选择排序
    //第一轮确定后n个数(索引从0至n-1)里最小的数,和第1个数(索引为0)交换,假定最小的数是第1个(索引为0),指针从第1个数(索引为0)移动到最后一个数(索引为n-1)
    //第二轮确定后n-1个数(索引从1至n-1)里最小的数,和第2个数(索引为1)交换,假定最小的数是第2个(索引为1),指针从第2个数(索引为1)移动到最后一个数(索引为n-1)
    //第i轮确定后n-i+1个数(索引从i-1至n-1)里最小的数,和第i个数(索引为i-1)交换,假定最小的数是第i个(索引为i-1),指针从第i个数(索引为i-1)移动到最后一个数(索引为n-1)
    int temp;    
    for (int i = 1; i <= arr.length - 1; i++) {
        int minIndex = i - 1;
        for (int j = i - 1; j <= arr.length - 1; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        temp = arr[minIndex];
        arr[minIndex] = arr[i-1];
        arr[i-1] = temp;
    }
}

插入排序

//O(n²) but 优势在于1+2+3+...而非倒序相加
public void insertSort(int[] arr) {
    //共进行n-1轮插入排序
    //第一轮将前一个数看作有序表,其后看作无序表,将无序表第一个数(索引为1)插入有序表中
    //第二轮将前二个数看作有序表,其后看作无序表,将无序表第一个数(索引为2)插入有序表中
    //第i轮将前i个数看作有序表,其后看作无序表,将无序表第一个数(索引为i)插入有序表中

    //用temp保存无序表第一个数
    int temp;
    //用于遍历有序表的指针
    int ptr;
    for (int i = 1; i <= arr.length - 1; i++) {
        temp = arr[i];
        ptr = i - 1;
        //先判断temp < arr[ptr]得话可能arr[ptr]就报数组越界了
        while (ptr >= 0 && temp < arr[ptr]) {
            arr[ptr + 1] = arr[ptr];
            ptr--;
        }
        //ptr+1即是应该插入的位置
        arr[ptr + 1] = temp;
    }
}

希尔排序

交换式

public void shellSort(int[] arr) {
    //temp作为完成交换操作的中间变量
    int temp;
    //arr.length不断/2作为步长(或曰增量)gap,一共要进行log轮,直至gap为0
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        for (int i = gap; i <= arr.length - 1; i++) {
            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;
                }
            }
        }
    }
}

采用插入排序式

public void shellSort(int[] arr) {
    //arr.length不断/2作为步长(或曰增量)gap,一共要进行log轮,直至gap为0
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        for (int i = gap; i <= arr.length - 1; i++) {
            //temp用于保存待插入元素
            int temp = arr[i];
            //ptr用以遍历分组
            int ptr = i - gap;
            while (ptr >= 0 && temp < arr[ptr]) {
                arr[ptr + gap] = arr[ptr];
                ptr -= gap;
            }
            //ptr+gap即为应该插入的位置
            arr[ptr + gap] = temp;
        }
    }
}

快速排序(Quick Sort)

public void quickSort(int[] arr, int left, int right) {
    int l = left;
    int r = right;
    int pivot = arr[(left + right) / 2];
    int temp;
    while (l < r) {
        while (arr[l] < pivot) l += 1;

        while (arr[r] > pivot) r -= 1;

        if (l >= r) break;

        temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
        //防止出现如  pivot和其左右一个元素三者相等(pivot和其左或有一个原二者相等也会如此),结果l和r指针停着不动,出现死循环
        if (arr[l] == pivot) r-=1;
        if (arr[r] == pivot) l+=1;
    }

    if (l==r){
        l+=1;
        r-=1;
    }

    //向左递归
    if (left<r){
        quickSort(arr,left,r);
    }

    //向右递归
    if (right>l){
        quickSort(arr,l,right);
    }
}

归并排序(Merge Sort)

//temp临时数组和arr长度相同
public 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, right, mid, temp);//分解后即合并
    }
}

//以mid为分界线,将arr划分为左右两个有序序列
public void merge(int[] arr, int left, int right, int mid, int[] temp) {
    //指向temp数组的指针
    int tempPtr = 0;
    //i作为左边有序序列的指针
    int i = left;
    //j作为右边有序序列的指针
    int j = mid + 1;

    //while循环用于将左右两侧有序数组按序填充至temp数组
    //直至左右某个序列填充完毕
    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[tempPtr] = arr[i];
            i++;
            tempPtr++;
        } else {
            temp[tempPtr] = arr[j];
            j++;
            tempPtr++;
        }
    }

    //将尚未被填充完毕的序列填充至temp数组
    while (i <= mid) {//i<=mid说明左序列仍未被填充完毕
        temp[tempPtr] = arr[i];
        i++;
        tempPtr++;
    }
    while (j <= right) {//j<=right说明右序列仍未被填充完毕
        temp[tempPtr] = arr[j];
        j++;
        tempPtr++;
    }

    //将temp数组拷贝至arr
    tempPtr = 0;//重置tempPtr指针
    int arrPtr = left;//arrPtr指向arr
    while (arrPtr <= right) {
        arr[arrPtr] = temp[tempPtr];
        tempPtr++;
        arrPtr++;
    }

}

查找

线性查找(Linear Search)

//O(n)
public List<Integer> linearSearch(int[] arr, int target) {
    List<Integer> reasultList = new ArrayList<>();
    
    for (int i = 0; i <= arr.length - 1; i++)
        if (arr[i] == target) reasultList.add(i);
    
    return reasultList;
}

二分查找(Binary Search)

//O(logn)
public List<Integer> binarySearch(int[] arr, int target, int left, int right) {
    //递归边界条件,触发该条件,则说明target不存在
    if (left > right || target < arr[0] || target > arr[arr.length - 1]) return new ArrayList<>();

    int middle = (left + right) / 2;
    if (arr[middle] < target) {
        return binarySearch(arr, target, middle + 1, right);
    } else if (arr[middle] > target) {
        return binarySearch(arr, target, left, middle - 1);
    } else {
        //结果集合
        List<Integer> reasult = new ArrayList<>();

        //用于遍历数组,将所有等于target的元素都找出来
        int temp;
        //先向左遍历
        temp = middle - 1;
        while (true) {
            if (temp < 0 || arr[temp] != target) break;
            reasult.add(temp);
            temp--;
        }

        reasult.add(middle);

        //向右遍历
        temp = middle + 1;
        while (true) {
            if (temp > arr.length - 1 || arr[temp] != target) break;
            reasult.add(temp);
            temp++;
        }

        return reasult;
    }
}

插值查找(Interpolation Search)

Interpolate: insert something of different nature into something else

插值查找是对二分查找的改进,适用于数据分布均匀时

//O(logn)
public List<Integer> interpolationSearch(int[] arr, int target, int left, int right) {
    //递归边界条件,触发该条件,则说明target不存在
    //target < arr[0] || target > arr[arr.length - 1]必须要有,否则可能数组越界
    if (left > right || target < arr[0] || target > arr[arr.length - 1]) return new ArrayList<>();

    int middle = left + (right - left) * (target - arr[left]) / (arr[right] - arr[left]);
    if (arr[middle] < target) {
        return interpolationSearch(arr, target, middle + 1, right);
    } else if (arr[middle] > target) {
        return interpolationSearch(arr, target, left, middle - 1);
    } else {
        //结果集合
        List<Integer> reasult = new ArrayList<>();

        //用于遍历数组,将所有等于target的元素都找出来
        int temp;
        //先向左遍历
        temp = middle - 1;
        while (true) {
            if (temp < 0 || arr[temp] != target) break;
            reasult.add(temp);
            temp--;
        }

        reasult.add(middle);

        //向右遍历
        temp = middle + 1;
        while (true) {
            if (temp > arr.length - 1 || arr[temp] != target) break;
            reasult.add(temp);
            temp++;
        }

        return reasult;
    }
}

斐波那契查找(Fibonacci Search)

public int fibonacciSearch(int[] arr, int target) {
    //获得Fibonacci数列,这里聊取前20项Fibnacci
    int[] fibSequence = new int[50];
    fibSequence[0] = 0;
    fibSequence[1] = 1;
    for (int i = 2; i < 50; i++) {
        fibSequence[i] = fibSequence[i - 1] + fibSequence[i - 2];
    }

    int low = 0;
    int high = arr.length - 1;
    int k = 0;
    int middle;

    while (fibSequence[k] <= arr.length) k++;
    int[] tempArray = Arrays.copyOf(arr, fibSequence[k]);
    for (int i = arr.length; i < tempArray.length; i++) {
        tempArray[i] = arr[arr.length - 1];
    }

    while (low <= high) {
        middle = low + fibSequence[k - 1] - 1;
        if (target < tempArray[middle]) {
            high = middle - 1;
            k -= 1;
        } else if (target > tempArray[middle]) {
            low = middle + 1;
            k -= 2;
        } else {
            if (middle <= high) {
                return middle;
            } else {
                return high;
            }
        }
    }
    return -1;
}

哈希表(Hash Table,或曰散列表)

基于键值对(Key-Value Pair)的数据结构,Key是唯一的,通过哈希函数(Hash Function,或曰散列函数)计算出哈希值(或曰散列值),对应数组索引(哈希函数设计不当,会导致冲突增加,影响性能)

增删查很高效,平均TC为O(1),最坏情况下(如冲突严重时)可能退化为O(n)

哈希冲突(Collision):

拉链法(Chaining):在冲突位置存储一个链表或其他数据结构

链表越短越高效

开放地址法(Open Addressing):发生冲突时在其他空闲位置存储数据

线性探查(Linear Probing)

二次探查(Quadratic Probing)

双重哈希(Double Hashing)

动态扩展:

哈希表负载因子(元素数量和数组容量之比)超过一定阈值时,则触发动态扩容

Java中的哈希表:

HashMap:线程不安全

HashTable:线程安全

ConcurrentHashMap:支持高并发的哈希表

代码实现哈希表

class HashTableDef {
    private EmpLinkedList[] empLinkedListsArray;
    private int size;

    //初始化empLinkedListsArray
    public HashTableDef(int size) {
        this.size = size;
        empLinkedListsArray = new EmpLinkedList[this.size];
        //初始化每一条链表,否则empLinkedListsArray中的每条链表皆为null
        for (int i = 0; i < size; i++)
            empLinkedListsArray[i] = new EmpLinkedList();
    }

    //用去模法,编写散列函数,根据id求应该添加到哪条链表
    public int hashFunc(int id) {
        return id % size;
    }

    //添加员工
    public void add(Emp emp) {
        //根据员工id,确定应添加至哪条链表
        int addedLinkedListNum = hashFunc(emp.id);
        empLinkedListsArray[addedLinkedListNum].add(emp);
    }

    //遍历哈希表,即遍历所有链表
    public void list() {
        for (int i = 0; i < size; i++)
            empLinkedListsArray[i].list(i);
    }

    //根据id查找员工
    public void findEmpById(int id) {
        //使用散列函数确定在哪一条链表中寻找
        int targetLinkedListNum = hashFunc(id);
        Emp targetEmp = empLinkedListsArray[targetLinkedListNum].findEmpById(id);

        if (targetEmp != null) {
            System.out.println("在第" + (targetLinkedListNum + 1) + "条链表中找到id为" + id + "的员工");
        } else {
            System.out.println("未找到相应id的员工");
        }
    }
}

//员工类
class Emp {
    public int id;
    public String name;
    public Emp next;

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

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

//链表
class EmpLinkedList {
    //这里的head亦代表一个员工,而非仅用于管理链表
    private Emp head;//默认null

    //假定添加员工时,id自增,即id的分配总是从小而大
    //故直接将员工添加至链表尾部即可
    public void add(Emp emp) {
        //添加第一个员工时
        if (head == null) {
            head = emp;
            return;
        }

        //并非添加第一个员工时
        //用指针寻找链表最后一个结点
        Emp ptr = head;
        while (true) {
            if (ptr.next == null) break;
            ptr = ptr.next;
        }

        //将emp添加至链表尾部
        ptr.next = emp;
    }

    //遍历链表信息
    public void list(int num) {
        //链表为空
        if (head == null) {
            System.out.println("第" + (num + 1) + "条链表为空");
            return;
        }

        System.out.println("第" + (num + 1) + "条链表信息为");

        Emp ptr = head;
        while (true) {
            if (ptr == null) break;
            System.out.println(ptr);
            ptr = ptr.next;
        }
    }

    //根据id查找员工
    public Emp findEmpById(int id) {
        //判断链表为空与否
        if (head == null) {
            System.out.println("链表为空");
            return null;
        }

        Emp ptr = head;
        while (true) {
            if (ptr == null) break;
            if (ptr.id == id) break;
            ptr = ptr.next;
        }

        return ptr;
    }
}

树(Tree)

数组的优缺点:

可以通过下标快速访问数据,有序数组还可以通过二分查找等算法提高查找性能;插入和删除元素时需要移动其他元素,性能较低;数组定义的大小大于实际需要的大小,可能会浪费空间,如果大小不足,则需要重新分配更大的空间并复制数据

而链表的增删,无需移动其他元素,效率较高,但查找时需要从头至尾遍历,性能低

基本概念

而树增删改查的性能都不错

非线性数据结构

根结点(Root Node)

子结点(Child Node)

父结点(Parent Node)

叶子结点(Leaf Node):无子结点

结点的度(Degree):一个结点的子结点个数

树的度(Degree of Tree):所有结点的最大度数

层级(Level):根结点为第0层,以此类推

路径(Path):两结点间的连接关系

深度(Depth):某结点到根结点的路径长度

高度(Height):根节点到叶子结点的最长路径长度(最大层数/最大深度)

子树(Subtree)

森林(Forest):若干互不相交的树组成的集合

分类

普通树(General Tree):

每个结点可以有任意数量的子结点,如文件系统

二叉树(Binary Tree):

每个结点最多有两个结点,左子节点和右子节点

完全二叉树(Compele Bianry Tree):

除最后一层,其他层节点数必须达到最大值,最后一层如果不满,则结点必须从左至右排列,不能出现空隙

满二叉树(Full Binary Tree)

叶子结点均在最后一层,非叶子结点都有两个子节点anyway 顾名思义,满!!

二叉树(Binary Tree)

三种遍历

前序遍历(Pre-order Traversal)

根左右

中序遍历(In-order Traversal)

左根右

后序遍历(Post-order Traversal)

左右根

class BinaryTree {
    //根节点
    private Node root;

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

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


class Node {
    private int id;
    private String name;
    private Node left;
    private Node right;


    //对结点的前序遍历
    public void preOrder() {
        //输出当前节点
        System.out.println(this);
        //若左子树不为空,则递归左子树
        if (this.left != null)
            this.left.preOrder();
        //若右子树不为空,则递归右子树
        if (this.right != null)
            this.right.preOrder();
    }

    //对结点的中序遍历
    public void inOrder() {
        //若左子树不为空,则递归左子树
        if (this.left != null)
            this.left.inOrder();
        //输出当前结点
        System.out.println(this);
        //若右子树不为空,则递归右子树
        if (this.right != null)
            this.right.inOrder();
    }

    //对结点的后序遍历
    public void postOrder() {
        //若左子树不为空,则递归左子树
        if (this.left != null)
            this.left.postOrder();
        //若右子树不为空,则递归右子树
        if (this.right != null)
            this.right.postOrder();
        //输出当前节点
        System.out.println(this);
    }

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

    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 Node getLeft() {
        return left;
    }

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

    public Node getRight() {
        return right;
    }

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值