数据结构与算法--二叉树

178 阅读13分钟

在聊二叉树之前,我们先来了解一下什么是树

树的定义

  1. 树是我们计算机中非常重要的一种数据结构,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构、等等。

  2. 树是由n ( n>=1)个有限结点组成一个具有层次关系的集合。把它叫做"树"是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

树的图示

image.png

树的特点

  1. 每个结点有零个多个子结点;
  2. 没有父结点的结点为根结点;
  3. 每一个非根结点只有一个父结点;
  4. 每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;

树的相关术语

结点的度:

一个结点含有的子树的个数称为该结点的度;

叶结点:

度为0的结点称为叶结点,也可以叫做终端结点

分支结点:

度不为0的结点称为分支结点,也可以叫做非终端结点

结点的层次:

从根结点开始,根结点的层次为1 ,根的直接后继层次为2 ,以此类推

结点的层序编号:

将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列 ,把他们编成连续的自然数。

树的度:

树中所有结点的度的最大值

树的高度(深度) :

树中结点的最大层次

森林:

m ( m>=0 )个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树

image.png

孩子结点︰

一个结点的直接后继结点称为该结点的孩子结点,如(H是D的孩子结点)

双亲结点(父结点)∶

一个结点的直接前驱称为该结点的双亲结点,如(D是H的父节点)

兄弟结点∶

同一双亲结点的孩子结点间互称兄弟结点,如(I和J是兄弟结点)

了解完这些,相信你对树有了一个初步的定义,接下来,让我们来看看树中比较特殊的一种类型:二叉树

二叉树

定义

二叉树就是度不超过2的树(即每个结点最多有两个子节点)

图示

image.png

两种特别的二叉树

满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。

image.png

每一层的结点数从根结点开始依次为1,2,4,8,16.... 规律就是2^(n-1)

完全二叉树

叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

image.png

需要注意满二叉树和完全二叉树定义和判断

普通二叉树的创建

  1. 我们使用链表的形式进行创建
  2. 我们需要一个结点类来记录当前结点的左子树,右子树,键,值
private class Node {
        public Key key;//键
        public Value value;//值
        public Node left;//左子结点
        public Node right;//右子结点

        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
  1. 需要两个变量分别来记录根节点树中的元素个数
private Node root;//根节点
private int N;//树的元素个数
  1. 定义一些方法来实现,二叉树的创建

向二叉树中插入数据

  • public void put(Key key, Value value):向树中插入一个键值对(调用下面重载的put方法
  • public Node put(Node x,Key key, Value value):给指定树x填加一个键值对,并返回添加后的新树 思路:
1. 树为空,新结点作为根结点节点,元素个数加一
2. 树不为空,从根节点开始,判断新结点的key和当前结点的key。
    2.1 新节点的key比当前结点的key大的话,就继续找当前结点的右子节点
    2.2 新节点的key比当前结点的key小的话,就继续找当前结点的左子节点
    2.3 新节点的key和当前结点的key相等的话,就替换当前结点的value

代码实现:

public Node put(Node x, Key key, Value value) {
        if (x == null) {//树为空
            N++;
            return new Node(key, value, null, null);
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//新节点的key比当前结点的key大的话,就继续找当前结点的右子节点
            x.right = put(x.right, key, value);
        } else if (cmp < 0) {//新节点的key比当前结点的key小的话,就继续找当前结点的左子节点
            x.left = put(x.left, key, value);
        } else {//新节点的key和当前结点的key相等的话,就替换当前结点的value
            x.value = value;
        }
        return x;
    }

从树中找出key对应的值

  • public Value get(Key key) :从树中找出key对应的值(调用下面重载的get方法
  • public Value get(Node x, Key key):在指定树x中,查找key对应的值 思路:
1. 树为空,则找不到索要查找的key
2. 树不为空,就从根结点开始遍历,判断要查找的结点的key和当前结点的key的大小
    2.1 要查找的结点的key比当前结点的key大的话,就继续找当前结点的右子节点
    2.2 要查找的结点的key比当前结点的key小的话,就继续找当前结点的左子节点
    2.3 要查找的结点的key和当前结点的key相等的话,就返回当前结点的value

代码实现:

    public Value get(Node x, Key key) {
        // 树为空时
        if (x == null) {
            return null;
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//要查找的key比当前结点的key大的话,就继续找当前结点的右子节点
            return get(x.right, key);
        } else if (cmp < 0) {//要查找的key比当前结点的key小的话,就继续找当前结点的左子节点
            return get(x.left, key);
        } else {//要查找的key和当前结点的key相等的话,就替换当前结点的value
            return x.value;
        }

    }

从树中删除key对应的值

  • public void delete(Key key):从树中删除key对应的值(调用下面重载的delete方法
  • public Node delete(Node x, Key key):在指定树x中,删除key对应的值 思路:
1. 找到被删除结点(同get方法的思路),元素个数减一
2. 找到被删除结点右子树中的最小结点minNode
    2.1 考虑要删除的结点可能存在的位置
    2.1.1 要删除的结点没有左子节点
    2.1.2 要删除的结点没有右子节点
    2.1.3 要删除的结点有左子节点和右子节点
3. 删除右子树中的最小结点
4. 让被删除结点的左子树成为最小结点minNode的左子树,让被删除结点的右子树成为最小结点minNode的右子树
5. 让被删除结点的父节点指向最小结点minNode

代码实现:

 public Node delete(Node x, Key key) {
        //树为空
        if (x == null) {
            return null;
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//要查找的key比当前结点的key大的话,就继续找当前结点的右子节点
            x.right = delete(x.right, key);
        } else if (cmp < 0) {//要查找的key比当前结点的key小的话,就继续找当前结点的左子节点
            x.left = delete(x.left, key);
        } else {//要查找的key和当前结点的key相等的话,就找到了要删除的结点
            /*
               找到要删除的结点后,要进行的删除操作
               0. 元素个数减一
               1. 找到被删除结点右子树中的最小结点minNode
                  1.1 考虑要删除的结点可能存在的位置
                       1.1.1 要删除的结点没有左子节点
                       1.1.2 要删除的结点没有右子节点
                       1.1.3 要删除的结点有左子节点和右子节点
               2. 删除右子树中的最小结点
               3. 让被删除结点的左子树成为最小结点minNode的左子树,
               4. 让被删除结点的右子树成为最小结点minNode的右子树
               5. 让被删除结点的父节点指向最小结点minNode
             */
            // 元素个数减一
            N--;
            // 要删除的结点没有左子节点
            if (x.left == null) {
                return x.right;
            }
            // 要删除的结点没有右子节点
            if (x.right == null) {
                return x.left;
            }
            // 找到要删除的结点的右子树中的最小结点
            Node minNode = x.right;
            //循环结束,就找到了要删除的结点的右子树的最小结点minNode
            while (minNode.left != null) {
                minNode = minNode.left;
            }
            //删除右子树中的最小结点
            // 从要删除的结点的右子树开始遍历,一直向左子树找
            Node temp = x.right;
            while (temp.left != null) {
                if (temp.left.left == null) {
                    temp.left = null;//这里实现了删除
                } else {
                    temp = temp.left;
                }
            }
            // 让被删除结点的左子树成为最小结点minNode的左子树
            minNode.left = x.left;
            // 让被删除结点的右子树成为最小结点minNode的右子树
            minNode.right = x.right;
            // 让被删除结点的父节点指向最小结点minNode
            x = minNode;// 将minNode的内容全部给x,相当于将minNode替换到了x结点的位置

        }

        return x;

获取树中元素的个数

  • public int size(): 获取树中元素的个数 代码实现:
    // 获取树的元素个数
    public int size() {
        return N;
    }

完整的二叉树创建的API

package com.study.tree;

import com.study.queue.queueAPI;

public class BinaryTreeAPI<Key extends Comparable<Key>, Value> {
    // 结点类
    private class Node {
        public Key key;//键
        public Value value;//值
        public Node left;//左子结点
        public Node right;//右子结点

        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }
    private Node root;//根节点
    private int N;//树的元素个数

    // 获取树的元素个数
    public int size() {
        return N;
    }

    // 向整个树中添加元素key-value
    // 调用put的重载方法
    public void put(Key key, Value value) {
        root = put(root, key, value);
    }

    /**
     * 向指定树中添加key-value,并返回添加元素后的新的树
     * 1. 树为空,新结点作为根结点节点
     * 2. 树不为空,从根节点开始,判断新结点的key和当前结点的key。
     * 2.1 新节点的key比当前结点的key大的话,就继续找当前结点的右子节点
     * 2.2 新节点的key比当前结点的key小的话,就继续找当前结点的左子节点
     * 2.3 新节点的key和当前结点的key相等的话,就替换当前结点的value
     */
    public Node put(Node x, Key key, Value value) {
        if (x == null) {//树为空
            N++;
            return new Node(key, value, null, null);
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//新节点的key比当前结点的key大的话,就继续找当前结点的右子节点
            x.right = put(x.right, key, value);
        } else if (cmp < 0) {//新节点的key比当前结点的key小的话,就继续找当前结点的左子节点
            x.left = put(x.left, key, value);
        } else {//新节点的key和当前结点的key相等的话,就替换当前结点的value
            x.value = value;
        }
        return x;
    }

    // 查询整个树中指定key对应的value
    public Value get(Key key) {
        return get(root, key);
    }

    // 在指定树x中,查找key对应的值
    public Value get(Node x, Key key) {
        // 树为空时
        if (x == null) {
            return null;
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//要查找的key比当前结点的key大的话,就继续找当前结点的右子节点
            return get(x.right, key);
        } else if (cmp < 0) {//要查找的key比当前结点的key小的话,就继续找当前结点的左子节点
            return get(x.left, key);
        } else {//要查找的key和当前结点的key相等的话,就替换当前结点的value
            return x.value;
        }

    }


    // 删除树中的key对应的value
    public void delete(Key key) {
        delete(root, key);
    }

    /**
     * 删除指定树中的key对应的value,并返回删除后的新树
     * 1.找到被删除结点;
     * 2.找到被删除结点右子树中的最小结点minNode
     * 3.删除右子树中的最小结点
     * 4.让被删除结点的左子树成为最小结点minNode的左子树,
     * 让被删除结点的右子树成为最小结点minNode的右子树
     * 5.让被删除结点的父节点指向最小结点minNode
     */
    public Node delete(Node x, Key key) {
        //树为空
        if (x == null) {
            return null;
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//要查找的key比当前结点的key大的话,就继续找当前结点的右子节点
            x.right = delete(x.right, key);
        } else if (cmp < 0) {//要查找的key比当前结点的key小的话,就继续找当前结点的左子节点
            x.left = delete(x.left, key);
        } else {//要查找的key和当前结点的key相等的话,就找到了要删除的结点
            /*
               找到要删除的结点后,要进行的删除操作
               0. 元素个数减一
               1. 找到被删除结点右子树中的最小结点minNode
                  1.1 考虑要删除的结点可能存在的位置
                       1.1.1 要删除的结点没有左子节点
                       1.1.2 要删除的结点没有右子节点
                       1.1.3 要删除的结点有左子节点和右子节点
               2. 删除右子树中的最小结点
               3. 让被删除结点的左子树成为最小结点minNode的左子树,
               4. 让被删除结点的右子树成为最小结点minNode的右子树
               5. 让被删除结点的父节点指向最小结点minNode
             */
            // 元素个数减一
            N--;
            // 要删除的结点没有左子节点
            if (x.left == null) {
                return x.right;
            }
            // 要删除的结点没有右子节点
            if (x.right == null) {
                return x.left;
            }
            // 找到要删除的结点的右子树中的最小结点
            Node minNode = x.right;
            //循环结束,就找到了要删除的结点的右子树的最小结点minNode
            while (minNode.left != null) {
                minNode = minNode.left;
            }
            //删除右子树中的最小结点
            // 从要删除的结点的右子树开始遍历,一直向左子树找
            Node temp = x.right;
            while (temp.left != null) {
                if (temp.left.left == null) {
                    temp.left = null;//这里实现了删除
                } else {
                    temp = temp.left;
                }
            }
            // 让被删除结点的左子树成为最小结点minNode的左子树
            minNode.left = x.left;
            // 让被删除结点的右子树成为最小结点minNode的右子树
            minNode.right = x.right;
            // 让被删除结点的父节点指向最小结点minNode
            x = minNode;// 将minNode的内容全部给x,相当于将minNode替换到了x结点的位置

        }

        return x;
    }

}

二叉树中的最大键和最小键

思路

因为我们在创建的时候,就是根据,根结点的左子树比根结点小,根结点的右子树比根节点大来创建的,所以我们找最大键就去找右子树,最小键就去找左子树

方法实现(这些我也放在了二叉树创建的API中)

    // 查找树中最大的键
    public Key max() {
        Node minNode = root; // 用一个新的节点的目的,防止root被我们修改了
        //树为空
        if (minNode == null) {
            return null;
        }
        //树不为空
        while (minNode.right != null) {
            if (minNode.right.right == null) {
                return minNode.right.key;
            } else {
                minNode = minNode.right;
            }
        }
        return minNode.key;
    }

    // 查找树中最小的键
    public Key min() {
        Node maxNode = root;
        //树为空
        if (maxNode == null) {
            return null;
        }
        //树不为空
        while (maxNode.left != null) {
            if (maxNode.left.left == null) {
                return maxNode.left.key;
            } else {
                maxNode = maxNode.left;
            }
        }
        return maxNode.key;
    }

二叉树的遍历

以下面这个二叉树的遍历来说明三种普通的遍历方式和一种高级的遍历方式

image.png

前序遍历

遍历顺序:根节点->左子树->右子树 思路:

  1. 把当前结点的key放入到队列中
  2. 找到当前结点的左子树,如果不为空,递归遍历左子树
  3. 找到当前结点的右子树,如果不为空,递归遍历右子树
    // 前序遍历:先访问根结点,再访问左子树,再访问右子树
    // 获取整棵树的所有键
    public queueAPI<Key> preErgodic() {
        queueAPI<Key> queue = new queueAPI<>();
        preErgodic(root, queue);
        return queue;
    }

    // 获取指定树的所有键,将这些键放到队列中
    public void preErgodic(Node x, queueAPI<Key> queue) {
        if (x == null) {
            return;
        }
        // 将x结点的key放到队列中
        queue.enqueue(x.key);

        // 递归遍历x的左子树
        if (x.left != null) {
            preErgodic(x.left, queue);
        }
        // 递归遍历x的右子树
        if (x.right != null) {
            preErgodic(x.right, queue);
        }
    }

中序遍历

遍历顺序:左子树->根节点->右子树 思路:

  1. 找到当前结点的左子树,如果不为空,递归遍历左子树
  2. 把当前结点的key放入到队列中;
  3. 找到当前结点的右子树,如果不为空,递归遍历右子树
     // 中序遍历:先访问左子树,再访问根结点,再访问右子树
    public queueAPI<Key> midErgodic() {
        queueAPI<Key> queue = new queueAPI<>();
        midErgodic(root, queue);
        return queue;
    }

    public void midErgodic(Node x, queueAPI<Key> queue) {
        if (x == null) {
            return;
        }
        // 递归遍历左子树,一直找最左边的元素
        if (x.left != null) {
            midErgodic(x.left, queue);
        }
        // 当x.left等于null时,当前结点x就是最左边的元素,直接加入到队列中
        queue.enqueue(x.key);
        //递归遍历右子树
        if (x.right != null) {
            midErgodic(x.right, queue);
        }
    }

后序遍历

遍历顺序:左子树->右子树->根节点 思路:

  1. 找到当前结点的左子树,如果不为空,递归遍历左子树
  2. 找到当前结点的右子树,如果不为空,递归遍历右子树
  3. 把当前结点的key放入到队列中;
    // 后序遍历:先访问左子树,再访问右子树,再访问根结点
    public queueAPI<Key> afterErgodic() {
        queueAPI<Key> queue = new queueAPI<>();
        afterErgodic(root, queue);
        return queue;
    }

    public void afterErgodic(Node x, queueAPI<Key> queue) {
        if (x == null) {
            return;
        }
        // 递归遍历左子树
        if (x.left != null) {
            afterErgodic(x.left, queue);
        }
        // 递归遍历右子树
        if (x.right != null) {
            afterErgodic(x.right, queue);
        }
        // 当x.left等于null,是,当前结点x就是最左边的元素,直接加入到队列中
        queue.enqueue(x.key);

    }

层序遍历

遍历顺序:就是从根节点(第一层)开始,依次向下,获取每一层所有结点的值, 思路:

  1. 创建一个队列用来存储结点,先将头节点放进入
  2. 每次弹出队列中的一个结点,获取结点的key
  3. 看弹出的结点是否有左子节点,有就将它放进去
  4. 看弹出的结点是否有右子节点,有就将它放进去

图解

image.png

image.png

public queueAPI<Key> layerErgodic() {
        queueAPI<Key> queue = new queueAPI<>();// 存放排序好的元素
        queueAPI<Node> nodes = new queueAPI<>();//存放结点
        // 先将头节点放到结点队列中
        nodes.enqueue(root);
        // 遍历nodes队列
        while (!nodes.isEmpty()){
            // 每次弹出结点队列的头一个结点
            Node node = nodes.dequeue();
            // 将弹出的结点的key放入到排序的队列中
            queue.enqueue(node.key);
            // 看弹出的结点是否有左子节点,有就将它到结点队列中
            if (node.left!=null){
                nodes.enqueue(node.left);
            }
            // 看弹出的结点是否有右子节点,有就将它到结点队列中
            if (node.right!=null){
                nodes.enqueue(node.right);
            }
        }
        return queue;
    }

二叉树的深度

深度:树的根节点到最远叶子结点的最长路径上的结点数

思路:

  1. 如果根结点为空,则最大深度为0;
  2. 计算左子树的最大深度;
  3. 计算右子树的最大深度;
  4. 当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1 代码实现:
 public int maxDepth(){
        return maxDepth(root);
    }
    // 求结点所在的树的最大深度
    public int maxDepth(Node x){
        if (x==null){
            return 0;
        }
        int left = 0; // 左子树的最大深度
        int right = 0; // 右子树的最大深度
        int max = 0; // 左右子树中的较大者
        if (x.left!=null){//计算左子树的最大深度;
            left=maxDepth(x.left);
        }
        if (x.right!=null){//计算右子树的最大深度;
            right = maxDepth(x.right);
        }
        max = left>right?left+1:right+1;
        return max;

    }

二叉树总结

将上面的方法实现归结到一个二叉树实现的API中,总代码为:

package com.study.tree;

import com.study.queue.queueAPI;

public class BinaryTreeAPI<Key extends Comparable<Key>, Value> {
    private Node root;//根节点
    private int N;//树的元素个数

    // 获取树的元素个数
    public int size() {
        return N;
    }

    // 向整个树中添加元素key-value
    // 调用put的重载方法
    public void put(Key key, Value value) {
        root = put(root, key, value);
    }

    /**
     * 向指定树中添加key-value,并返回添加元素后的新的树
     * 1. 树为空,新结点作为根结点节点
     * 2. 树不为空,从根节点开始,判断新结点的key和当前结点的key。
     * 2.1 新节点的key比当前结点的key大的话,就继续找当前结点的右子节点
     * 2.2 新节点的key比当前结点的key小的话,就继续找当前结点的左子节点
     * 2.3 新节点的key和当前结点的key相等的话,就替换当前结点的value
     */
    public Node put(Node x, Key key, Value value) {
        if (x == null) {//树为空
            N++;
            return new Node(key, value, null, null);
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//新节点的key比当前结点的key大的话,就继续找当前结点的右子节点
            x.right = put(x.right, key, value);
        } else if (cmp < 0) {//新节点的key比当前结点的key小的话,就继续找当前结点的左子节点
            x.left = put(x.left, key, value);
        } else {//新节点的key和当前结点的key相等的话,就替换当前结点的value
            x.value = value;
        }
        return x;
    }

    // 查询整个树中指定key对应的value
    public Value get(Key key) {
        return get(root, key);
    }

    // 在指定树x中,查找key对应的值
    public Value get(Node x, Key key) {
        // 树为空时
        if (x == null) {
            return null;
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//要查找的key比当前结点的key大的话,就继续找当前结点的右子节点
            return get(x.right, key);
        } else if (cmp < 0) {//要查找的key比当前结点的key小的话,就继续找当前结点的左子节点
            return get(x.left, key);
        } else {//要查找的key和当前结点的key相等的话,就替换当前结点的value
            return x.value;
        }

    }


    // 删除树中的key对应的value
    public void delete(Key key) {
        delete(root, key);
    }

    /**
     * 删除指定树中的key对应的value,并返回删除后的新树
     * 1.找到被删除结点;
     * 2.找到被删除结点右子树中的最小结点minNode
     * 3.删除右子树中的最小结点
     * 4.让被删除结点的左子树成为最小结点minNode的左子树,
     * 让被删除结点的右子树成为最小结点minNode的右子树
     * 5.让被删除结点的父节点指向最小结点minNode
     */
    public Node delete(Node x, Key key) {
        //树为空
        if (x == null) {
            return null;
        }
        // 树不为空
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {//要查找的key比当前结点的key大的话,就继续找当前结点的右子节点
            x.right = delete(x.right, key);
        } else if (cmp < 0) {//要查找的key比当前结点的key小的话,就继续找当前结点的左子节点
            x.left = delete(x.left, key);
        } else {//要查找的key和当前结点的key相等的话,就找到了要删除的结点
            /*
               找到要删除的结点后,要进行的删除操作
               0. 元素个数减一
               1. 找到被删除结点右子树中的最小结点minNode
                  1.1 考虑要删除的结点可能存在的位置
                       1.1.1 要删除的结点没有左子节点
                       1.1.2 要删除的结点没有右子节点
                       1.1.3 要删除的结点有左子节点和右子节点
               2. 删除右子树中的最小结点
               3. 让被删除结点的左子树成为最小结点minNode的左子树,
               4. 让被删除结点的右子树成为最小结点minNode的右子树
               5. 让被删除结点的父节点指向最小结点minNode
             */
            // 元素个数减一
            N--;
            // 要删除的结点没有左子节点
            if (x.left == null) {
                return x.right;
            }
            // 要删除的结点没有右子节点
            if (x.right == null) {
                return x.left;
            }
            // 找到要删除的结点的右子树中的最小结点
            Node minNode = x.right;
            //循环结束,就找到了要删除的结点的右子树的最小结点minNode
            while (minNode.left != null) {
                minNode = minNode.left;
            }
            //删除右子树中的最小结点
            // 从要删除的结点的右子树开始遍历,一直向左子树找
            Node temp = x.right;
            while (temp.left != null) {
                if (temp.left.left == null) {
                    temp.left = null;//这里实现了删除
                } else {
                    temp = temp.left;
                }
            }
            // 让被删除结点的左子树成为最小结点minNode的左子树
            minNode.left = x.left;
            // 让被删除结点的右子树成为最小结点minNode的右子树
            minNode.right = x.right;
            // 让被删除结点的父节点指向最小结点minNode
            x = minNode;// 将minNode的内容全部给x,相当于将minNode替换到了x结点的位置

        }

        return x;
    }

    // 查找树中最大的键
    public Key max() {
        Node minNode = root; // 用一个新的节点的目的,防止root被我们修改了
        //树为空
        if (minNode == null) {
            return null;
        }
        //树不为空
        while (minNode.right != null) {
            if (minNode.right.right == null) {
                return minNode.right.key;
            } else {
                minNode = minNode.right;
            }
        }
        return minNode.key;
    }

    // 查找树中最小的键
    public Key min() {
        Node maxNode = root;
        //树为空
        if (maxNode == null) {
            return null;
        }
        //树不为空
        while (maxNode.left != null) {
            if (maxNode.left.left == null) {
                return maxNode.left.key;
            } else {
                maxNode = maxNode.left;
            }
        }
        return maxNode.key;
    }

    // 前序遍历:先访问根结点,再访问左子树,再访问右子树
    // 获取整棵树的所有键
    public queueAPI<Key> preErgodic() {
        queueAPI<Key> queue = new queueAPI<>();
        preErgodic(root, queue);
        return queue;
    }

    // 获取指定树的所有键,将这些键放到队列中
    public void preErgodic(Node x, queueAPI<Key> queue) {
        if (x == null) {
            return;
        }
        // 将x结点的key放到队列中
        queue.enqueue(x.key);

        // 递归遍历x的左子树
        if (x.left != null) {
            preErgodic(x.left, queue);
        }
        // 递归遍历x的右子树
        if (x.right != null) {
            preErgodic(x.right, queue);
        }
    }

    // 中序遍历:先访问左子树,再访问根结点,再访问右子树
    public queueAPI<Key> midErgodic() {
        queueAPI<Key> queue = new queueAPI<>();
        midErgodic(root, queue);
        return queue;
    }

    public void midErgodic(Node x, queueAPI<Key> queue) {
        if (x == null) {
            return;
        }
        // 递归遍历左子树,一直找最左边的元素
        if (x.left != null) {
            midErgodic(x.left, queue);
        }
        // 当x.left等于null时,当前结点x就是最左边的元素,直接加入到队列中
        queue.enqueue(x.key);
        //递归遍历右子树
        if (x.right != null) {
            midErgodic(x.right, queue);
        }
    }

    // 后序遍历:先访问左子树,再访问右子树,再访问根结点
    public queueAPI<Key> afterErgodic() {
        queueAPI<Key> queue = new queueAPI<>();
        afterErgodic(root, queue);
        return queue;
    }

    public void afterErgodic(Node x, queueAPI<Key> queue) {
        if (x == null) {
            return;
        }
        // 递归遍历左子树
        if (x.left != null) {
            afterErgodic(x.left, queue);
        }
        // 递归遍历右子树
        if (x.right != null) {
            afterErgodic(x.right, queue);
        }
        // 当x.left等于null,是,当前结点x就是最左边的元素,直接加入到队列中
        queue.enqueue(x.key);

    }

    // 层序遍历
    // 1. 创建一个队列用来存储结点,先将头节点放进入
    // 2. 每次弹出队列中的一个结点,
    // 3. 看弹出的结点是否有左子节点,有就将它放进去
    // 4. 看弹出的结点是否有右子节点,有就将它放进去
    public queueAPI<Key> layerErgodic() {
        queueAPI<Key> queue = new queueAPI<>();// 存放排序好的元素
        queueAPI<Node> nodes = new queueAPI<>();//存放结点
        // 先将头节点放到结点队列中
        nodes.enqueue(root);
        // 遍历nodes队列
        while (!nodes.isEmpty()){
            // 每次弹出结点队列的头一个结点
            Node node = nodes.dequeue();
            // 将弹出的结点的key放入到排序的队列中
            queue.enqueue(node.key);
            // 看弹出的结点是否有左子节点,有就将它到结点队列中
            if (node.left!=null){
                nodes.enqueue(node.left);
            }
            // 看弹出的结点是否有右子节点,有就将它到结点队列中
            if (node.right!=null){
                nodes.enqueue(node.right);
            }
        }
        return queue;
    }
    // 求整颗树的最大深度
    // 1. 求左子树的最大深度
    // 2. 求右子树的最大深度
    // 3. 取左子树和右子树的最大值+1即为深度
    public int maxDepth(){
        return maxDepth(root);
    }
    // 求结点所在的树的最大深度
    public int maxDepth(Node x){
        if (x==null){
            return 0;
        }
        int left = 0; // 左子树的最大深度
        int right = 0; // 右子树的最大深度
        int max = 0; // 左右子树中的较大者
        if (x.left!=null){
            left=maxDepth(x.left);
        }
        if (x.right!=null){
            right = maxDepth(x.right);
        }
        max = left>right?left+1:right+1;
        return max;

    }
    // 结点类
    private class Node {
        public Key key;//键
        public Value value;//值
        public Node left;//左子结点
        public Node right;//右子结点

        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

}

再来一下测试代码:

package com.study.tree;

import com.study.queue.queueAPI;

public class BinaryTreeTest {
    public static void main(String[] args) {
        BinaryTreeAPI<Integer, String> bt = new BinaryTreeAPI<>();
        bt.put(5,"张三");
        bt.put(2,"李四");
        bt.put(7,"王五");
        bt.put(1,"赵六");
        bt.put(4,"吴昕");
        bt.put(6,"赵四");
        bt.put(8,"吴磊");
        bt.put(3,"小林");
        System.out.println("添加元素后,元素总数为:"+bt.size());
        System.out.println(bt.get(2));
        bt.delete(2);
        System.out.println("删除元素后,元素总数为:"+bt.size());
        System.out.println("删除元素后,键为2的元素的值为:"+bt.get(2));
        bt.put(2,"李四");
        Integer min = bt.min();
        System.out.println("键最小的元素是:"+min);
        Integer max = bt.max();
        System.out.println("键最大的元素是:"+max);
        System.out.println("前序遍历:");
        queueAPI<Integer> prequeue = bt.preErgodic();
        for (Integer i : prequeue) {
            System.out.println(i+"---"+bt.get(i));
        }
        System.out.println("中序遍历:");
        queueAPI<Integer> queue = bt.midErgodic();
        for (Integer i : queue) {
            System.out.println(i+"---"+bt.get(i));
        }
        System.out.println("后序遍历:");
        queueAPI<Integer> afqueue = bt.afterErgodic();
        for (Integer i : afqueue) {
            System.out.println(i+"---"+bt.get(i));
        }
        System.out.println("层序遍历:");
        queueAPI<Integer> layerqueue = bt.layerErgodic();
        for (Integer i : layerqueue) {
            System.out.println(i+"---"+bt.get(i));
        }
        int maxDepth = bt.maxDepth();
        System.out.println("树的深度为:"+maxDepth);
    }
}

添加元素到树中,删除树中的元素,得到树中具体key的值

image.png

前序遍历

image.png

中序遍历

image.png

后序遍历

image.png

层序遍历

image.png

树的深度

image.png