Skip to content

树结构应用

一、堆排序

1、堆排序概述

  1. 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏、最好、平均时间复杂度均为 O(nlogn),它也是不稳定排序
  2. 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆,每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
  3. 结点的左孩子的值和右孩子的值的大小关系无要求

2、堆排序思路

  1. 将待排序序列构造成一个大顶堆(数组形式)
  2. 整个序列的最大值就是堆顶的根节点
  3. 将其与末尾元素进行交换,此时末尾就为最大值
  4. 然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n-1 个元素的次小值。如此反复执行,便能得到一个有序序列了

3、堆排序代码实现

java
package work.rexhao.tree;

import java.util.Arrays;

/**
 * 堆排序
 *
 * @author 王铭颢
 * @Date 2022/7/5 21:36
 */
public class HeapSort {
    public static void main(String[] args) {
        int[] num = new int[]{3, 2, 4, 1, 5};
        System.out.println(Arrays.toString(num));
        heapSort(num, num.length);
        System.out.println(Arrays.toString(num));
    }

    /**
     * 维护大顶堆
     *
     * @param num   数组
     * @param len   数组长度
     * @param index 待维护元素的下表
     */
    public static void heapify(int[] num, int len, int index) {
        int max = index;
        // 找到最大值
        int leftSon = index * 2 + 1;
        int rightSon = index * 2 + 2;
        if (leftSon < len && num[max] < num[leftSon]) {
            max = leftSon;
        }
        if (rightSon < len && num[max] < num[rightSon]) {
            max = rightSon;
        }
        // 最大的不是父节点 => 交换
        if (max != index) {
            // 1.交换
            int temp = num[max];
            num[max] = num[index];
            num[index] = temp;
            // 2.继续维护下面的子堆
            heapify(num, len, max);
        }

    }


    /**
     * 堆排序入口
     *
     * @param num 待排序数组
     * @param len 数组长度
     */
    public static void heapSort(int[] num, int len) {
        // 建堆
        for (int i = len / 2 - 1; i >= 0; i--) {
            heapify(num, len, i);
        }
        // 交换首尾元素
        for (int i = len - 1; i > 0; i--) {
            int temp = num[i];
            num[i] = num[0];
            num[0] = temp;
            heapify(num, i, 0);
        }
    }
}

二、赫夫曼树

1、赫夫曼树基本介绍

赫夫曼树(Hufriman Tree):给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树

赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近

2、赫夫曼树的概念

1)路径和路径长度

在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。

通路中分支的数目称为路径长度。

若规定根结点的层数为1,则从根结点到第工 层结点的路径长度为L-1

2)结点的权及带权路径长度

若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。

结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积

3)树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为 WPL(weighted path length)

权值越大的结点离根结点越近的二叉树才是最优二叉树。

4)WPL

WPL 最小的就是赫夫曼树

3、赫夫曼树的代码实现

java
package work.rexhao.huffmantree;

import org.jetbrains.annotations.NotNull;

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

/**
 * 哈夫曼树的生成
 *
 * @author 王铭颢
 * @Date 2022/7/6 12:49
 */
public class HuffmanTreeDemo {
    public static void main(String[] args) {
        int num[] = {13, 7, 8, 3, 29, 6, 1};
        initHuffmanTree(num);

    }

    public static void initHuffmanTree(int[] num) {
        ArrayList<TreeNode> tnodes = new ArrayList<>();
        for (int i : num) {
            tnodes.add(new TreeNode(i));
        }
        while (tnodes.size() != 1) {
            Collections.sort(tnodes);
            TreeNode leftNode = tnodes.get(0);
            TreeNode rightNode = tnodes.get(1);
            tnodes.add(new TreeNode(leftNode.getData() + rightNode.getData(), leftNode, rightNode));
            tnodes.remove(leftNode);
            tnodes.remove(rightNode);
            Collections.sort(tnodes);
        }
        preOrder(tnodes.get(0));


    }

    /**
     * 先序遍历
     */
    public static void preOrder(TreeNode tn) {
        if (tn != null) {
            System.out.print(tn.getData() + " ");
            preOrder(tn.leftNode);
            preOrder(tn.rightNode);
        }
    }


}

/**
 * 二叉树的节点
 */
class TreeNode implements Comparable<TreeNode> {
    private int data;
    TreeNode leftNode;
    TreeNode rightNode;

    TreeNode(int data) {
        this.data = data;
    }

    public TreeNode(int data, TreeNode leftNode, TreeNode rightNode) {
        this.data = data;
        this.leftNode = leftNode;
        this.rightNode = rightNode;
    }

    public int getData() {
        return data;
    }

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

    @Override
    public int compareTo(@NotNull TreeNode o) {
        return this.data - o.data;
    }
}

三、赫夫曼编码

1、赫夫曼编码的基本介绍

  1. 赫夫曼编码是赫哈大曼树在电讯通信中的经典的应用之一
  2. 赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~-90%之间
  3. 赫夫曼码是可变字长编码(VLC)的一种
  4. Huffman于1952年提出一种编码方法,称之为最佳编码
  5. 宇符的编码都不能是其他字符编码的前缀,符合此要求的编码叫做前缀编码,即不能匹配到重复的编码

2、赫夫曼编码的注意事项

  1. 赫夫曼树根据排序方法不同,也可能不太一样,这样对应的赫夫曼编码也不完全一样,但是wpl是一样的,都是最小的,最后生成的赫夫曼编码的长度是一样
  2. 如果文件本身就是经过压缩处理的,那么使用赫夫曼编码再压缩效率不会有明显变化,比如视频、ppt 等等文件
  3. 赫夫曼编码是按字节来处理的,因此可以处理所有的文件(二进制文件、文本文件)
  4. 如果一个文件中的内容,重复的数据不多,压缩效果也不会很明显

3、代码实现

未解决的Bug:

哈夫曼字节编码8个二进制数一组。 对于最后一组,若0开头,转换后因为不去补零,丢失前面的0;如果补零,最后一组不一定是8位,不能补零。 我的做法是:将最后一组后面补零至8位,但是会造成解码后产生多余字符。

java
package work.rexhao.huffmantree;

import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * 哈夫曼编码与解码
 *
 * @author 王铭颢
 * @Date 2022/7/10 16:08
 */
public class HuffmanCode {
    // 存储哈夫曼编码表
    static Map<Byte, String> HuffmanCodes = new HashMap<>();

    public static void main(String[] args) {
        String str1 = "i like java java java do you like java?";
        System.out.println("str1:" + str1);
        byte[] huffmanBytes = huffmanZip(str1.getBytes());
        System.out.println("huffmanBytes:" + Arrays.toString(huffmanBytes));
        String str2 = huffmanUnzip(huffmanBytes);
        System.out.println("str2:" + str2);
    }

    /**
     * 哈夫曼解码
     *
     * @param huffmanBytes 哈夫曼编码
     * @return 解码后字符串
     */
    private static String huffmanUnzip(byte[] huffmanBytes) {
        StringBuilder code = new StringBuilder();
        for (byte b : huffmanBytes) {
            code.append(Integer.toBinaryString(b | 256).substring(
                    Integer.toBinaryString(b | 256).length() - 8));
        }
        Map<String, Byte> unzipCodes = new HashMap<>();
        for (Map.Entry<Byte, String> entry : HuffmanCodes.entrySet()) {
            unzipCodes.put(entry.getValue(), entry.getKey());
        }
        List<Byte> bytesList = new ArrayList<>();
        for (int i = 0; i < code.length(); ) {
            int count = 1;
            while (true) {
                if (i + count >= code.length()) {
                    i = code.length();
                    break;
                }
                String key = code.substring(i, i + count);
                Byte value = unzipCodes.get(key);
                if (value == null) {
                    count++;
                } else {
                    bytesList.add(value);
                    i += count;
                    break;
                }
            }
        }
        byte[] bytes = new byte[bytesList.size()];
        for (int i = 0; i < bytesList.size(); i++) {
            bytes[i] = bytesList.get(i);
        }
        return new String(bytes);
    }

    /**
     * 哈夫曼编码
     *
     * @param bytes 编码前
     * @return 编码后
     */
    public static byte[] huffmanZip(byte[] bytes) {
        // 1.创建哈夫曼树
        Map<Byte, Integer> huffmanMap = byteCounter(bytes);
        HuffmanCodeNode root = initHuffmanTree(huffmanMap);
        // 2.获得哈夫曼编码表
        toHuffmanCodes(root);
        // 3.进行转码
        return toHuffmanBytes(bytes, HuffmanCodes);
    }

    /**
     * 通过哈夫曼编码表转化成哈弗曼编码
     *
     * @param bytes        原字符数组
     * @param huffmanCodes 哈夫曼编码表
     * @return 编码后的哈夫曼编码
     */
    private static byte[] toHuffmanBytes(byte[] bytes, Map<Byte, String> huffmanCodes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(huffmanCodes.get(b));
        }
        byte[] code = new byte[(sb.length() + 7) / 8];
        int index = 0;
        for (int i = 0; i < sb.length(); i += 8) {
            if (i + 8 < sb.length()) {
                code[index++] = (byte) Integer.parseInt(sb.substring(i, i + 8), 2);
            } else {
                StringBuilder temp = new StringBuilder(sb.substring(i));
                for (int j = temp.length(); j < 8; j++) {
                    temp.append("0");
                }
                code[index++] = (byte) Integer.parseInt(temp.toString(), 2);
            }
        }
        return code;
    }

    /**
     * 将哈夫曼树转换为哈夫曼编码表(重载)
     *
     * @param root 根节点
     */
    private static void toHuffmanCodes(HuffmanCodeNode root) {
        toHuffmanCodes(root, "");
    }

    /**
     * 将哈夫曼树转换为哈夫曼编码表
     *
     * @param node 节点
     * @param s    代拼接编码
     */
    private static void toHuffmanCodes(HuffmanCodeNode node, String s) {
        if (node == null) {
            return;
        }
        // node有值的话 -> 将b放进去
        if (node.b != null) {
            HuffmanCodes.put(node.b, s);
        }
        toHuffmanCodes(node.leftNode, s + "0");
        toHuffmanCodes(node.rightNode, s + "1");
    }

    /**
     * 创建哈夫曼树
     *
     * @param huffmanMap 字符权重
     * @return 根节点
     */
    private static HuffmanCodeNode initHuffmanTree(Map<Byte, Integer> huffmanMap) {
        List<HuffmanCodeNode> list = new ArrayList<>();
        for (Byte b : huffmanMap.keySet()) {
            list.add(new HuffmanCodeNode(b, huffmanMap.get(b)));
        }
        while (list.size() != 1) {
            Collections.sort(list);
            HuffmanCodeNode hcn1 = list.get(0);
            HuffmanCodeNode hcn2 = list.get(1);
            list.remove(0);
            list.remove(0);
            list.add(new HuffmanCodeNode(null, hcn1.weight + hcn2.weight, hcn1, hcn2));
        }
        return list.get(0);
    }

    /**
     * 统计字符出现的次数
     *
     * @param bytes 待统计字符
     * @return 字符权重map
     */
    public static Map<Byte, Integer> byteCounter(byte[] bytes) {
        Map<Byte, Integer> map = new HashMap<>();
        for (byte b : bytes) {
            map.merge(b, 1, Integer::sum);
        }
        return map;
    }
}

/**
 * 哈夫曼编码的哈夫曼树节点
 */
class HuffmanCodeNode implements Comparable<HuffmanCodeNode> {
    Byte b;
    int weight;
    HuffmanCodeNode rightNode;
    HuffmanCodeNode leftNode;

    public HuffmanCodeNode(Byte b, int weight) {
        this.b = b;
        this.weight = weight;
    }

    public HuffmanCodeNode(Byte b, int weight, HuffmanCodeNode rightNode, HuffmanCodeNode leftNode) {
        this.b = b;
        this.weight = weight;
        this.rightNode = rightNode;
        this.leftNode = leftNode;
    }

    @Override
    public int compareTo(@NotNull HuffmanCodeNode o) {
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "HuffmanCodeNode{" + "b=" + b + ", weight=" + weight + ", rightNode=" + rightNode + ", leftNode=" + leftNode + '}';
    }
}

四、二叉排序树(BST)

1、二叉排序树概述

二叉排序树:BST(Binary Sort(Search) Tree),对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。

如果有相同的值,可以将该节点放在左子节点或右子节点

2、二叉排序树删除节点

1)叶子节点

  1. 找到该节点
  2. 找到该节点的父节点
  3. 判断该节点与其父节点的位置
    1. 左子树:parentNode.left == null
    2. 右子树:parentNode.right == null

2)度为一的节点

  1. 找到该节点
  2. 找到该节点的父节点
  3. 判断该节点与其父节点的位置
    1. 该节点是父节点的左子树
      1. 该节点存在左子树:parentNode.left == node.left
      2. 该节点存在右子树:parentNode.left == node.right
    2. 该节点是父节点的右子树
      1. 该节点存在左子树:parentNode.right == node.left
      2. 该节点存在右子树:parentNode.right == node.right

3)度为二的节点

  1. 找到该节点
  2. 找到该节点右子树的最小节点(递归其右子树的左节点的左节点...)
  3. 保存最小节点的值temp
  4. 删除最小节点(叶子结点)
  5. 将该节点的的值改为temp

3、二叉排序树代码实现

java
package work.rexhao.binarySortTree;

/**
 * 二叉排序树BST
 *
 * @author 王铭颢
 * @Date 2022/7/11 10:05
 */
public class BinarySortTreeDemo {
    public static void main(String[] args) {
        BinarySortTree bst = new BinarySortTree();
        int[] num = {7, 3, 10, 1, 6, 9, 12, 2};
        for (int i : num) {
            bst.add(new Node(i));
        }
        BinarySortTree.infixOrder(bst.root);
        System.out.println();

        bst.delete(10);
        BinarySortTree.infixOrder(bst.root);
        System.out.println();

    }
}

/**
 * 二叉排序树
 */
class BinarySortTree {
    Node root;

    /**
     * 添加节点
     *
     * @param next 待添加节点
     */
    public void add(Node next) {
        if (root == null) {
            root = next;
        } else {
            root.add(next);
        }
    }

    /**
     * 前序遍历
     *
     * @param node 根节点
     */
    public static void infixOrder(Node node) {
        if (node == null) {
            return;
        }
        infixOrder(node.left);
        System.out.print(node.data + " ");
        infixOrder(node.right);
    }

    /**
     * 删除节点
     *
     * @param target 待删除节点的值
     */
    public void delete(int target) {
        Node node = search(root, target);
        Node nodeParent = searchParent(root, node);
        // 经典判空
        if (node == null || nodeParent == null) {
            System.out.println("节点不存在或节点无父节点!");
            return;
        }
        // 判断待删除节点的形式
        if (node.left == null && node.right == null) {
            // 叶子节点
            if (nodeParent.right == node) {
                // 右节点
                nodeParent.right = null;
            } else {
                // 左节点
                nodeParent.left = null;
            }
        } else if (node.left != null && node.right != null) {
            // 两个子树的节点
            // 1. 找右子树的最小值(右子树的左子树的叶子结点)
            Node min = searchRightMinNode(node.right);
            // 2. 记录最小值
            int temp = min.data;
            // 3. 删除原来的最小值节点
            delete(min.data);
            // 4. 将最小值提到(覆盖)节点位置
            node.data = temp;
        } else {
            // 一个子树的节点
            if (node.left == null) {
                // 存在右子树
                if (nodeParent.right == node) {
                    // 右节点
                    nodeParent.right = node.right;
                } else {
                    // 左节点
                    nodeParent.left = node.right;
                }
            } else {
                // 存在左子树
                if (nodeParent.right == node) {
                    // 右节点
                    nodeParent.right = node.left;
                } else {
                    // 左节点
                    nodeParent.left = node.left;
                }
            }
        }


    }

    /**
     * 找 node 节点右子树的最小节点
     *
     * @param node 开始寻找的节点
     * @return 右子树的最小节点
     */
    private Node searchRightMinNode(Node node) {
        // 找到叶子结点(最小的点)
        return node.left == null ? node : searchRightMinNode(node.left);
    }

    /**
     * 找 data 为 i 的节点
     *
     * @param root 根节点
     * @param i    待查找数据
     * @return 查找到的节点,不存在返回null
     */
    public static Node search(Node root, int i) {
        // 1.经典判空
        if (root == null) {
            return null;
        }
        // 2.找节点
        if (i == root.data) {
            return root;
        } else if (i < root.data) {
            return search(root.left, i);
        } else {
            return search(root.right, i);
        }
    }

    /**
     * 找到 target 的父节点
     *
     * @param root   根节点
     * @param target 待寻找节点
     * @return 待寻找节点的父节点,不存在返回null
     */
    public static Node searchParent(Node root, Node target) {
        // 经典判空
        if (root == null || target == null) {
            return null;
        }
        // 判左右的节点
        if ((root.right != null && root.right.data == target.data) || (root.left != null && root.left.data == target.data)) {
            return root;
        }
        // 没找到 -> 递归往下找
        if (root.data > target.data) {
            return searchParent(root.left, target);
        } else {
            return searchParent(root.right, target);
        }
    }
}

/**
 * 节点
 */
class Node {
    int data;
    Node left;
    Node right;

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

    /**
     * 添加节点
     *
     * @param next 待添加节点
     */
    public void add(Node next) {
        if (next == null) {
            return;
        }
        if (this.data > next.data) {
            if (this.left == null) {
                this.left = next;
            } else {
                this.left.add(next);
            }
        } else {
            if (this.right == null) {
                this.right = next;
            } else {
                this.right.add(next);
            }
        }
    }

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

五、平衡二叉树(AVL树)

1、BST可能的问题

若BST左子树or右子树为空 -> 单链表

查询速度明显降低(因为需要依次比较),不能发挥BST的优势

2、平衡二叉树概述

定义:平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为 AVL 树(AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis)

特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

3、平衡二叉树的转换

1)左旋转

  1. 取右子节点
  2. 将根节点的值挂在 newRoot 上
  3. 将 root 的左子节点挂在 newRoot.left.left
  4. 将原 newRoot 的左子节点挂在 newRoot.left.right
  5. 将原 newRoot 的右子节点挂在 newRoot.right
  6. 用 newRoot 替换 root

2)右旋转

  1. 取左子节点
  2. 将根节点的值挂在 newRoot 上
  3. 将 root 的右子节点挂在 newRoot.right.right
  4. 将原 newRoot 的右子节点挂在 newRoot.right.left
  5. 将原 newRoot 的左子节点挂在 newRoot.left
  6. 用 newRoot 替换 root

3)双旋转

左子树 > 右子树:右旋转

左子树 < 右子树:左旋转

4、AVL树的代码实现

java
package work.rexhao.AVLTree;

import java.util.Arrays;

/**
 * 平衡二叉树
 */
public class AVLDemo {
    public static void main(String[] args) {
        // 案例1:左旋转
        int[] num0 = {4, 3, 6, 5, 7, 8};
        System.out.println(Arrays.toString(num0));
        AVLTree avl0 = new AVLTree();
        for (int i : num0) {
            avl0.add(new Node(i));
        }
        avl0.infixOrder();
        System.out.println("left = " + avl0.findLeftHeight());
        System.out.println("right = " + avl0.findRightHeight());
        System.out.println("是否为AVL树:" + avl0.isAVL());
        avl0.leftRotate();
        avl0.infixOrder();
        System.out.println("left = " + avl0.findLeftHeight());
        System.out.println("right = " + avl0.findRightHeight());
        System.out.println("是否为AVL树:" + avl0.isAVL());

        System.out.println("-------------");

        // 案例2:右旋转
        int[] num1 = {7, 5, 8, 4, 6, 3};
        System.out.println(Arrays.toString(num1));
        AVLTree avl1 = new AVLTree();
        for (int i : num1) {
            avl1.add(new Node(i));
        }
        avl1.infixOrder();
        System.out.println("left = " + avl1.findLeftHeight());
        System.out.println("right = " + avl1.findRightHeight());
        System.out.println("是否为AVL树:" + avl1.isAVL());
        avl1.rightRotate();
        avl1.infixOrder();
        System.out.println("left = " + avl1.findLeftHeight());
        System.out.println("right = " + avl1.findRightHeight());
        System.out.println("是否为AVL树:" + avl1.isAVL());

        System.out.println("-------------");

        // 案例3:双旋转
        int[] num2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(Arrays.toString(num2));
        AVLTree avl2 = new AVLTree();
        for (int i : num2) {
            avl2.add(new Node(i));
        }
        avl2.infixOrder();
        System.out.println("left = " + avl2.findLeftHeight());
        System.out.println("right = " + avl2.findRightHeight());
        System.out.println("是否为AVL树:" + avl2.isAVL());
        avl2.rotate();
        avl2.infixOrder();
        System.out.println("left = " + avl2.findLeftHeight());
        System.out.println("right = " + avl2.findRightHeight());
        System.out.println("是否为AVL树:" + avl2.isAVL());

        System.out.println("-------------");
    }
}

/**
 * 二叉排序树
 */
class AVLTree {
    Node root;

    /**
     * 双旋转
     */
    public void rotate() {
        while (true) {
            if (isAVL()) {
                break;
            } else {
                if (findLeftHeight() > findRightHeight()) {
                    rightRotate();
                } else {
                    leftRotate();
                }
            }
        }
    }

    /**
     * 左旋转
     */
    public void leftRotate() {
        // 0. 经典判空
        if (root == null) return;
        // 1. 取右子节点
        Node newRoot = new Node(root.right.data);
        // 2. 将根节点的值挂在 newRoot 上
        newRoot.left = new Node(root.data);
        // 3. 将 root 的左子节点挂在 newRoot.left.left
        newRoot.left.left = root.left;
        // 4. 将原 newRoot 的左子节点挂在 newRoot.left.right
        newRoot.left.right = root.right.left;
        // 5. 将原 newRoot 的右子节点挂在 newRoot.right
        newRoot.right = root.right.right;
        // 6. 用 newRoot 替换 root
        this.root = newRoot;
    }

    /**
     * 右旋转
     */
    public void rightRotate() {
        // 0. 经典判空
        if (root == null) return;
        // 1. 取左子节点
        Node newRoot = new Node(root.left.data);
        // 2. 将根节点的值挂在 newRoot 上
        newRoot.right = new Node(root.data);
        // 3. 将 root 的右子节点挂在 newRoot.right.right
        newRoot.right.right = root.right;
        // 4. 将原 newRoot 的右子节点挂在 newRoot.right.left
        newRoot.right.left = root.left.right;
        // 5. 将原 newRoot 的左子节点挂在 newRoot.left
        newRoot.left = root.left.left;
        // 6. 用 newRoot 替换 root
        this.root = newRoot;
    }

    /**
     * 判断是否为平衡二叉树
     */
    public boolean isAVL() {
        return Math.abs(findLeftHeight() - findRightHeight()) <= 1;
    }

    /**
     * 找右子树深度
     *
     * @return 右子树深度
     */
    public int findRightHeight() {
        if (root == null) {
            return 0;
        }
        int[] r = {1};
        int[] l = {1};
        findHeight(root.right, r, l);
        return Math.max(r[0], l[0]);
    }

    /**
     * 找左子树深度
     *
     * @return 左子树深度
     */
    public int findLeftHeight() {
        if (root == null) {
            return 0;
        }
        int[] r = {1};
        int[] l = {1};
        findHeight(root.left, r, l);
        return Math.max(r[0], l[0]);
    }

    /**
     * 求深度
     */
    private void findHeight(Node node, int[] r, int[] l) {
        // 经典判空
        if (node == null) {
            return;
        }
        // 判叶子节点
        if (node.left == null && node.right == null) {
            return;
        }
        if (node.left != null) {
            r[0]++;
            findHeight(node.left, r, l);
        }
        if (node.right != null) {
            l[0]++;
            findHeight(node.right, r, l);
        }
    }


    /**
     * 添加节点
     *
     * @param next 待添加节点
     */
    public void add(Node next) {
        if (root == null) {
            root = next;
        } else {
            root.add(next);
        }
    }

    /**
     * 中序遍历
     */
    public void infixOrder() {
        Node.infixOrder(root);
        System.out.println();
    }

}

/**
 * 节点
 */
class Node {
    int data;
    Node left;
    Node right;

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

    /**
     * 添加节点
     *
     * @param next 待添加节点
     */
    public void add(Node next) {
        if (next == null) {
            return;
        }
        if (this.data > next.data) {
            if (this.left == null) {
                this.left = next;
            } else {
                this.left.add(next);
            }
        } else {
            if (this.right == null) {
                this.right = next;
            } else {
                this.right.add(next);
            }
        }
    }

    /**
     * 中序遍历
     *
     * @param node 根节点
     */
    public static void infixOrder(Node node) {
        if (node == null) {
            return;
        }
        infixOrder(node.left);
        System.out.print(node.data + " ");
        infixOrder(node.right);
    }

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

Released under the MIT License.