目前为止,我们学的数据结构分为两种,数组和链表
数组存储分析
- 优点:访问速度快,还可以通过二分查找增加速度
- 缺点:如果要插入值需要整体移动,效率较低
链表存储分析
- 优点:在一定程度上对数组存储上进行优化,插入删除变得十分遍历
- 缺点:检索效率低,需要检索值的时候每次都要从头开始遍历。
现在我们既想插入的快点一点,又想遍历的快一点怎么办?
接下来我们引入一个新的数据结构:树,来解决这个问题

上边的树是一个有序树,7是树的根节点,3是7的左子节点,10是7的右子节点,7是10和3的父节点。
而有序树是什么呢,左子节点比父节点小,右子节点比父节点大。
仔细看上图会发现每一个节点都是如此。
而无序树的话,就随意存放了。
现在这个树是有序树,我们分析一下,如果我们需要找到5这个节点。
首先5比7小,那么5一定在7的左边,5再和3比较,比3大,那么5一定在3的右边,这样是不是有种二分查找的感觉了?没错,这样我们遍历起来也很快。而插入和删除的时候,我们只需要改变链接指向就可以了。这样我们删除和插入也会很快。
树的基础概念

树有很多种,最多只能有两个节点的树称为二叉树
- 二叉树不存在度大于2的结点
- 左右子树是有顺序的,即使某结点只有一棵子树,也要区分它是左子树还是右子树
- 二叉树具有五种基本形态:空二叉树、只有一个根节点、只有左子树、只有右子树、左右子树都有
- 二叉树第i层上至多有2^(i-1)个结点(i≥1)
- 深度为k的二叉树至多有2^k - 1个结点(k≥1),此时为满二叉树
- 结点拥有的子树数称为结点的度,度为0的结点称为叶子结点
- 对任何一棵二叉树T,如果其叶子结点数为n0,度为2的结点数为n2,则n0 = n2 + 1。这个的推导:设结点数为n,可以知道结点间连接线数为n-1。于是有两个式子:n-1 = n1 + 2*n2 和 n = n0 + n1 +n2,联合解出n0 = n2 + 1
- 若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这就是完全二叉树。

- 对于完全二叉树,在有左右子结点的情况下,设根结点的编号是n(这个编号从1开始),则左孩子的编号是2n,右孩子的编号是2n+1
二叉树的前中后序遍历

对图中的树进行分析
前序遍历:12354
中序遍历:21534
后序遍历:25431
代码实现
public class Node {int id;Node left;Node right;@Overridepublic String toString() {return "Node{" +"id=" + id +'}';}public Node(int id) {this.id = id;}}
public class BinaryTreeDemo {public static void main(String[] args) {BinaryTree binaryTree = new BinaryTree();Node root = new Node(1);Node node2 = new Node(2);Node node3 = new Node(3);Node node4 = new Node(4);Node node5 = new Node(5);root.left = node2;root.right = node3;node3.left = node5;node3.right = node4;binaryTree.setRoot(root);// binaryTree.preOrder(root);// binaryTree.midOrder(root);binaryTree.postOrder(root);}}class BinaryTree {//根节点private Node root;public void setRoot(Node root) {this.root = root;}//前序遍历public void preOrder(Node root) {//首先输出父节点System.out.println(root);//判断左节点是否为空if (root.left != null) {preOrder(root.left);}if (root.right != null) {preOrder(root.right);}}//中序遍历public void midOrder(Node root) {//判断左节点是否为空if (root.left != null) {midOrder(root.left);}//输出父节点System.out.println(root);//判断右节点if (root.right != null) {midOrder(root.right);}}//后序遍历public void postOrder(Node root) {//判断左节点是否为空if (root.left != null) {postOrder(root.left);}//判断右节点if (root.right != null) {postOrder(root.right);}//输出父节点System.out.println(root);}}
线索化二叉树

图中我们可以发现,它的中序遍历顺序为 8,3,10,1,14,6
但叶子节点8,10,14的左右指针和 节点6的右指针都没用上。
为了不让它空闲,充分利用空间
我们可以让它们的 左指针指向前驱节点,右指针指向后继节点
这样我们可以快速得到遍历顺序。
这样的二叉树,我们称之为线索化二叉树。
代码实现
为了能够区分一个节点的左指针到底指向的是前驱节点还是左子节点,我们有必要用一个变量来区分一下
public class Node {int id;Node left;Node right;//为了在线索化二叉树中区分//0表示子节点,1表示前驱或后继结点int leftType;int rightType;@Overridepublic String toString() {return "Node{" +"id=" + id +'}';}public Node(int id) {this.id = id;}}
首先我们要对二叉树进行线索化。
以中序为例,不明白代码的,debug或者画图就会了解。

然后我们之前的中序遍历方式毫无疑问已经不能用了,所以我们必须重新相出一个遍历方法从根节点开始
- 先找到被线索化的节点curNode,遍历循环 curNode = curNode.left;
- 如果找不到线索化的节点,输出当前节点
- 如果 curNode.rightType = 1的话,一直遍历输出右指针类型为0
- 现在rightType = 0了,我们让curNode = curNode.right ,再次回到第一步循环,直到curNode = null
我们对着图先来一遍过程:
首先找到了节点8,发现这个是被线索化的节点,curNode=8,那么我们一直遍历它的后继节点->3,这个时候我们发现3并不是被线索化的节点。那么我们只能让curNode = 3了,我们发现它没指向后继节点,那么我们就让curNode = curNode.right
现在curNode = 10了,我们继续来循环,遍历left发现,leftType = 1,那没事了,我们来遍历rightType = 1的时候。输出了1,发现1也没后继,那么curNode = curNode.right,
curNode = 6,发现6也不是被线索化的节点,我们就遍历left找到了14.然后就完事了。
public class ThreadedBinaryTreeDemo {//线索化二叉树的实现public static void main(String[] args) {ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();Node root = new Node(1);Node node3 = new Node(3);Node node8 = new Node(8);Node node10 = new Node(10);Node node14 = new Node(14);Node node6 = new Node(6);root.left = node3;root.right = node6;node3.left = node8;node3.right = node10;node6.left = node14;threadedBinaryTree.setRoot(root);// threadedBinaryTree.midOrder(root);threadedBinaryTree.threadedNodes();threadedBinaryTree.threadedList();}}class ThreadedBinaryTree {//根节点private Node root;//为了实现线索化,需要创建一个指向前驱结点的一个指针private Node pre = null;public void setRoot(Node root) {this.root = root;}public void threadedNodes() {this.threadedNodes(root);}/*** 对当前节点进行线索化* 中序线索化为例* @param node*/public void threadedNodes(Node node) {//如果node == null,无法线索化if (node == null){return;}//先线索化左子树threadedNodes(node.left);//线索化当前节点//处理前驱节点if (node.left == null) {//指向前驱节点node.left = pre;//修改当前节点的左指针类型node.leftType = 1;}//指向后继节点(实际上是在第一次回溯的时候才开始进行)//第一次 pre = null,当第一次回溯的时候,这里pre变成了前驱节点if (pre != null && pre.right == null) {pre.right = node;pre.rightType = 1;}//最最重要的一条pre = node;//线索化右子树threadedNodes(node.right);}//遍历线索化二叉树的方法(中序为例)public void threadedList() {//定义一个变量,存储当前遍历的节点Node curNode = root;while (curNode != null) {//循环找到被线索化的节点 如果节点的leftType == 1,说明是线索化的有效节点while (curNode.leftType == 0) {curNode = curNode.left;}System.out.println(curNode);//如果当前节点的右指针指向的是后继节点,那么就一直输出。while (curNode.rightType == 1) {curNode = curNode.right;System.out.println(curNode);}curNode = curNode.right;}}}
哈夫曼树(最优二叉树)
哈夫曼树,也被叫做最优二叉树,它的带权路径的值(WPL)为最小。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度。权值越大的节点,离根节点越近。
这样说可能有点晦涩
举个例子:
wpl = 13 2 + 7 2 + 8 2 + 3 2
13 为叶子节点的值,2为路径长度(根节点到该叶子节点的长度)。但是这并不是哈夫曼树
权值越大的节点,离根节点越近
这样才是最优二叉树
现在一组数据,我们将它变为二叉树
13, 7, 8, 3, 29, 6, 1
从中找出两个最小的 值(可以一开始先排好序)组成一个二叉树,根节点的值为 两个最小值的和,然后将这个值放进数组中
现在数组变为了 13, 7, 8, 29, 6, 4
再次从中找两个最小值,4,6,组成一个二叉树。将4 + 6 =10放入到数组中。
现在数组变为13, 7, 8, 29,10
再次找出最小值 7 和 8,我们发现与我们上边组成的树毫无关系,那么我们只能新组一个树。
然后将15放入数组
现在数组变为13,29,10,15
两个最小值为13和10,现在我们将13和10组成树,23放入到数组中
现在数组变为 29,15,23
两个最小值为15和23,38放进数组
数组只剩下29和38了,我们放入树中,哈夫曼树完成!
代码实现
public class HuffmanTree {//哈夫曼树public static void main(String[] args) {int[] arr = {13, 7, 8, 3, 29, 6, 1};//创建哈夫曼树HuffmanNode root = createHuffman(arr);preOrder(root);}//创建哈夫曼树private static HuffmanNode createHuffman(int[] arr) {//首先转化成list方便操作List<HuffmanNode> data = new LinkedList<>();for (int i = 0; i < arr.length; i++) {data.add(new HuffmanNode(arr[i]));}while (data.size() > 1) {//进行排序(也可以自己手写)data = data.stream().sorted((o1, o2) -> o1.val - o2.val).collect(Collectors.toList());//去除两个最小的值HuffmanNode leftNode = data.get(0);HuffmanNode rightNode = data.get(1);//生成树HuffmanNode parent = new HuffmanNode(leftNode.val + rightNode.val);parent.left = leftNode;parent.right = rightNode;data.remove(leftNode);data.remove(rightNode);data.add(parent);}//返回根节点。return data.get(0);}public static void preOrder(HuffmanNode root) {//首先输出父节点System.out.println(root);//判断左节点是否为空if (root.left != null) {preOrder(root.left);}if (root.right != null) {preOrder(root.right);}}}class HuffmanNode {int val;HuffmanNode left;HuffmanNode right;public HuffmanNode(int val) {this.val = val;}@Overridepublic String toString() {return "HuffmanNode{" +"val=" + val +'}';}}
二叉排序树(BST)

二叉排序树的定义和创建都非常的简单,他的麻烦之处在于删除。
仔细观察二叉排序树,会发现它在查找的时候就有着二分的性质,这点让它既能快速的增删,也能快速的查找。
创建就没什么好说的了。比根节点大就放右边,小就放左边。
我们来说一下删除。
删除分三种情况
一种是删除叶子节点。比如说上图的2,5,9,12
删除叶子节点好说,我们只需要找到它的父节点,然后判断它是父节点的左子树还是右子树,让对应的left和right指针为null就行了。
删除只有一个子节点的节点。
比如说上图中的节点 1
我们首先要判断要删除的节点 (这里我们叫做targetNode) 是有左子树还是右子树
有左子树的情况
我们再判断targetNode是父节点(parentNode)的左子树还是右子树,然后对应的
parentNode.left = targetNode.left 或 parentNode.right = targetNode.left;
有右子树的情况则反之。
parentNode.left = targetNode.right 或 parentNode.right = targetNode.right;
最后一种情况就是,被删除的节点targetNode 有两个子节点。比如,3,7,10
这个时候,我们的做法应该是。
找出右子树中值最小的那个节点
也就是一直遍targetNode.right 的左边,直到找到 targetNode.right.left = null,targetNode的变成这个最小节点的值。
代码实现
public class BinarySortTreeDemo {public static void main(String[] args) {int[] arr = {7, 3, 10, 12, 5, 1, 9, 2};BinarySortTree tree = new BinarySortTree();for (int i = 0; i < arr.length; i++) {Node node = new Node(arr[i]);tree.add(node);}tree.midOrder(tree.root);tree.delNode(2);tree.delNode(5);tree.delNode(9);tree.delNode(12);tree.delNode(7);tree.delNode(3);tree.delNode(10);tree.delNode(1);tree.midOrder(tree.root);}}class BinarySortTree {Node root;public void add(Node node) {if (root == null) {root = node;}else {if (node == null) {return;}Node curNode = root;while (curNode != null) {if (curNode.id > node.id) {//说明curNode比新增节点大,新增节点应该在当前节点的左边if (curNode.left == null) {//如果当前节点的左子树为null,那么我们可以直接加入到curNode左边。curNode.left = node;return;}curNode = curNode.left;}else {if (curNode.right == null) {//如果当前节点的右子树为null,那么我们可以直接加入到curNode右边。curNode.right = node;return;}curNode = curNode.right;}}}}//中序遍历public void midOrder(Node node) {if (root == null) {return;}if (node.left != null) {midOrder(node.left);}System.out.println(node);if (node.right != null) {midOrder(node.right);}}/*** 查找要删除的节点** @param value 希望删除的节点的值*/public Node search(int value) {return this.search(root, value);}public Node search(Node node,int value) {if (node.id == value) {return node;} else if (value < node.id) {if (node.left != null) {return search(node.left, value);}return null;} else {if (node.right != null) {return search(node.right, value);}return null;}}//查找要删除节点的父节点public Node searchParent(Node node, int value) {//左子树不为null,并且左子树值相等 or(右子树不为null,右子树值相等)if ((node.left != null && node.left.id == value) ||(node.right != null && node.right.id == value)){return node;}else {//比当前节点值小,去递归左子树找,否则去右子树if (value < node.id && node.left != null) {return searchParent(node.left, value);}else if (value > node.id && node.right != null) {return searchParent(node.right, value);}//都不满足返回nullreturn null;}}public void delNode(int value) {if (root != null) {//当前树只有根节点的情况,我们可以直接置空if (root.left == null && root.right ==null) {root = null;return;}Node targetNode = search(value);//没找到要和删除的节点if (targetNode == null) {return;}//找到要删除节点的父节点Node parentNode = searchParent(root,value);//如果当前节点为叶子节点if (targetNode.left == null && targetNode.right == null){//判断当前节点是父节点左子树还是右子树if (parentNode.left != null && value == parentNode.left.id) {parentNode.left = null;}else if (parentNode.right != null && value == parentNode.right.id){parentNode.right = null;}}else if (targetNode.left != null && targetNode.right != null) {//当前节点有两个子节点,即有两个左右子树int min = delRightTreeMin(targetNode.right);targetNode.id = min;}else {//当前节点有一个子节点//如果要删除的节点有左子树if (targetNode.left != null) {//如果父节点为null,那么该节点为根节点if (parentNode != null) {//并且当前节点 为父节点的左子树if (value == parentNode.left.id) {parentNode.left = targetNode.left;}else {//当前节点为父节点的右子节点parentNode.right = targetNode.left;}}else {root = targetNode.left;}}else {if (parentNode != null) {//要删除的节点有右子节点//并且当前节点 为父节点的左子树if (value == parentNode.left.id) {parentNode.left = targetNode.right;}else {//当前节点为父节点的右子节点parentNode.right = targetNode.right;}}else {root = targetNode.right;}}}}}/*** 查找右子树中的最小节点* @param node* @return*/public int delRightTreeMin(Node node) {Node target = node;//循环的查找左子节点,就会找到最小值while (target.left != null) {target = target.left;}//这时target已经指向了最小节点,我们需要把这个节点给删掉delNode(target.id);return target.id;}}
现在我们已经很清楚如何对一个二叉排序树进行基本操作了。
但是如果现在有一组数据为1,2,3,4,5,那么我们来变成一个二叉排序树看看
这个时候我们会发现,这个树变回单链表了对吧?它的查找速度毫无疑问变慢了,甚至因为一些多余的操作,比原来的单链表还要慢,完全没有发挥出我们原有的二叉排序树的特性,我们该这么办?
为了解决这个问题,我们要引入二叉平衡树。
二叉平衡树(AVL)

AVL是对二叉排序树的一种改进。
当它的两个子树的高度差值大于1的时候,就会出现旋转。
左旋
如果右子树的高度 - 左子树的高度 > 1,那么会进行左旋转,降低右子树的高度。
我们来分析一波数据,4, 3, 6, 5, 7, 8
毫无疑问,根节点左子树的高度为3 - 根节点左子树高度为1 = 2 > 1,这个时候我们要进行左旋转。
左旋转步骤
- 1-首先创建一个新节点,值为当前节点的值
- 2-新节点left 指向当前节点的left
- 3-新节点的right 指向当前节点的右子树的左子树
- 4-当前节点的值替换成右子树的值
- 5-当前节点右子树设置成当前节点的右子树的右子树
- 6-当前节点的左子树设置成新的节点
步骤如下图:
右旋
如果左子树的高度 - 右子树的高度 > 1,那么会进行右旋转,降低左子树的高度。
一组数据:10, 12, 8, 9, 7, 6
毫无疑问,左子树高度3 - 右子树高度1 = 2 > 1,要进行右旋转
右旋转的步骤:
- 1-首先创建一个新节点,值为当前节点的值
- 2-新节点right 指向当前节点的right
- 3-新节点left 指向当前节点的left的right
- 4-当前节点的值替换成左子树的值
- 5-当前节点左子树设置成当前节点的左子树的左子树
- 6-当前节点的右子树设置成新的节点
步骤如图:
我们我们在进行转为平衡树的时候,只需要一次左旋转和一次右旋转,这样就能解决问题了吗?
并不,有的时候,我们需要双旋转,即一次左旋加右旋(或一次右旋加左旋)
双旋
一组数据:10, 11, 7, 6, 8, 9
我们来看看它的树结构,会发现确实左子树高度 > 右子树高度,于是我们进行了一次右旋,发现变成了右子树高度大于左子树高度了,这并不是我们所期望的二叉树
这种情况怎么办呢?
我们在进行右旋之前,先要判断根节点的左子树的情况。
如果左子树的右子树高度 > 左子树的左子树高度,那么我们要对根节点的左子树进行一次左旋。
同理在进行左旋之前
我们要判断根节点右子树的情况
如果根节点右子树的左子树的高度 > 右子树的右子树的高度,那么我们要先对根节点的右子树右旋
这里就不画图了,根上边步骤差不多。
代码实现
public class AVLTreeDemo {public static void main(String[] args) {//左旋转//int[] arr = {4, 3, 6, 5, 7, 8};//右旋转//int[] arr = {10, 12, 8, 9, 7, 6};//这组数据会发现,单旋转并不能让我们的树变为平衡二叉树int[] arr = {10, 11, 7, 6, 8, 9};AVLTree avlTree = new AVLTree();for (int i = 0; i < arr.length; i++) {avlTree.add(new AVLNode(arr[i]));}avlTree.midOrder(avlTree.root);System.out.println("当前根节点为" + avlTree.root);System.out.println("树的高度" + avlTree.root.height());System.out.println("左子树的高度" + avlTree.root.leftHeight());System.out.println("右子树的高度" + avlTree.root.rightHeight());}}class AVLTree {AVLNode root;/*** 因为右子树高度 - 左子树高度 > 1* 左旋转 变为平衡二叉树** @param node 在非双旋(左右都要旋转)的情况下,为root*/public void leftRotate(AVLNode node) {//首先创建一个新节点,值为当前节点的值AVLNode newNode = new AVLNode(node.id);//新节点left 指向当前节点的leftnewNode.left = node.left;//新节点的right 指向当前节点的右子树的左子树newNode.right = node.right.left;//当前节点的值替换成右子树的值node.id = node.right.id;//当前节点右子树设置成当前节点的右子树的右子树node.right = node.right.right;//当前节点的左子树设置成新的节点node.left = newNode;}/*** 左子树高度 - 右子树高度 > 1* 右旋转 变为平衡二叉树* @param node 在非双旋的情况下,为root*/public void rightRotate(AVLNode node) {//首先创建一个新节点,值为当前节点的值AVLNode newNode = new AVLNode(node.id);//新节点right 指向当前节点的rightnewNode.right = node.right;//新节点left 指向当前节点的left的rightnewNode.left = node.left.right;//当前节点的值替换成左子树的值node.id = node.left.id;node.left = node.left.left;node.right = newNode;}//以下代码就是二叉排序数的代码,add方法要修改public void add(AVLNode node) {if (root == null) {root = node;return;}if (node == null) {return;}AVLNode curNode = root;while (curNode != null) {if (curNode.id > node.id) {//说明curNode比新增节点大,新增节点应该在当前节点的左边if (curNode.left == null) {//如果当前节点的左子树为null,那么我们可以直接加入到curNode左边。curNode.left = node;break;}curNode = curNode.left;}else {if (curNode.right == null) {//如果当前节点的右子树为null,那么我们可以直接加入到curNode右边。curNode.right = node;break;}curNode = curNode.right;}}int leftHeight = root.leftHeight();int rightHeight = root.rightHeight();//判断是否需要左旋转if (rightHeight - leftHeight > 1) {System.out.println("左旋转");//如果右子树的左子树高度大于右子树的右子树高度的话,那么我们还要进行一次右旋转if (root.right != null && root.right.leftHeight() > root.right.rightHeight()) {//右节点System.out.println("右子树右旋转");leftRotate(root.right);}leftRotate(root);return;}//判断是否需要右旋转if (leftHeight - rightHeight > 1) {System.out.println("右旋转");//如果左子树的右子树高度大于左子树的左子树高度的话,那么我们还要进行一次左旋转if (root.left != null && root.left.rightHeight() > root.left.leftHeight()) {//左节点System.out.println("左子树左旋转");leftRotate(root.left);}rightRotate(root);}}//中序遍历public void midOrder(AVLNode node) {if (root == null) {return;}if (node.left != null) {midOrder(node.left);}System.out.println(node);if (node.right != null) {midOrder(node.right);}}/*** 查找要删除的节点** @param value 希望删除的节点的值*/public AVLNode search(int value) {return this.search(root, value);}public AVLNode search(AVLNode node,int value) {if (node.id == value) {return node;} else if (value < node.id) {if (node.left != null) {return search(node.left, value);}return null;} else {if (node.right != null) {return search(node.right, value);}return null;}}//查找要删除节点的父节点public AVLNode searchParent(AVLNode node, int value) {//左子树不为null,并且左子树值相等 or(右子树不为null,右子树值相等)if ((node.left != null && node.left.id == value) ||(node.right != null && node.right.id == value)){return node;}else {//比当前节点值小,去递归左子树找,否则去右子树if (value < node.id && node.left != null) {return searchParent(node.left, value);}else if (value > node.id && node.right != null) {return searchParent(node.right, value);}//都不满足返回nullreturn null;}}public void delNode(int value) {if (root != null) {//当前树只有根节点的情况,我们可以直接置空if (root.left == null && root.right ==null) {root = null;return;}AVLNode targetNode = search(value);//没找到要和删除的节点if (targetNode == null) {return;}//找到要删除节点的父节点AVLNode parentNode = searchParent(root,value);//如果当前节点为叶子节点if (targetNode.left == null && targetNode.right == null){//判断当前节点是父节点左子树还是右子树if (parentNode.left != null && value == parentNode.left.id) {parentNode.left = null;}else if (parentNode.right != null && value == parentNode.right.id){parentNode.right = null;}}else if (targetNode.left != null && targetNode.right != null) {//当前节点有两个子节点,即有两个左右子树int min = delRightTreeMin(targetNode.right);targetNode.id = min;}else {//当前节点有一个子节点//如果要删除的节点有左子树if (targetNode.left != null) {//如果父节点为null,那么该节点为根节点if (parentNode != null) {//并且当前节点 为父节点的左子树if (value == parentNode.left.id) {parentNode.left = targetNode.left;}else {//当前节点为父节点的右子节点parentNode.right = targetNode.left;}}else {root = targetNode.left;}}else {if (parentNode != null) {//要删除的节点有右子节点//并且当前节点 为父节点的左子树if (value == parentNode.left.id) {parentNode.left = targetNode.right;}else {//当前节点为父节点的右子节点parentNode.right = targetNode.right;}}else {root = targetNode.right;}}}}}/*** 查找右子树中的最小节点* @param node* @return*/public int delRightTreeMin(AVLNode node) {AVLNode target = node;//循环的查找左子节点,就会找到最小值while (target.left != null) {target = target.left;}//这时target已经指向了最小节点,我们需要把这个节点给删掉delNode(target.id);return target.id;}}class AVLNode {int id;AVLNode left;AVLNode right;@Overridepublic String toString() {return "AVLNode{" +"id=" + id +'}';}public AVLNode(int id) {this.id = id;}//返回以当前节点为根节点的树的高度(当前节点+子树有多高)public int height() {return Math.max(left == null ? 0 : left.height(),right == null ? 0 : right.height()) + 1;}//返回左子树的高度public int leftHeight() {if (left == null) {return 0;}return left.height();}public int rightHeight() {if (right == null) {return 0;}return right.height();}}
