树是一种非线性数据结构,这种数据结构要比线性数据结构复杂的多,因此分为三篇博客进行讲解:
第二篇:二叉查找树
第三篇:红黑树
本文目录
4、二叉查找树的删除操作 —- 重要
第二篇:二叉查找树BST
1、写在前面:二叉查找树最大的特点就是:支持动态数据集合的快速插入、删除和查找操作。
2、文末附:二叉查找树的常用操作。
3、这里需要进行说明下,前面讲二叉查找树的时候,我们默认都是树中结点存储的都是数字。很多时候,在实际的软件开发中,我们在二叉查找树中存储的,是一个包含很多字段的对象。我们利用对象的某个字段作为键值(key)来构建二叉查找树。我们把对象中的其他字段叫做卫星数据。
1、二叉查找树的基本概念
二叉查找树BST(Binary Sort Tree):又称为二叉排序树/二叉搜索树。顾名思义,二叉查找树是为了实现快速查找而生的。不过它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。
具有以下性质的二叉树称为二叉查找树:
1、若左子树不为空,则左子树上所有结点的值均小于它的根结点的值;
2、若右子树不为空,则右子树上所有的结点的值均大于或等于它的根结点的值;
3、左、右子树也分别为二叉查找树。
从上图可以看出来,二叉查找树中,左子树都比父结点小,右子树都比父结点大,递归定义。
所以,根据这个特点,二叉查找树的中序遍历的结果肯定是有序的,上图中中序遍历结果为:1、3、4、6、7、8、10、13、14。
2、二叉查找树的查找操作
根据二叉查找树的定义,我们可以将二叉查找树的查找操作大概分为以下几个步骤:
1、先比较它与根节点,相等就返回;或者根节点为空,说明树为空,也返回;
2、如果它比根节点小,就从根节点的左子树中进行递归查找;
3、如果它比根节点大,就从根节点的右子树中进行递归查找。
【可以看出来,二叉查找树的查找操作与二分查找十分相似】
public class BinarySearchTree {
private Node root; // 根节点
// 二叉查找树的查找操作
public Node find(int data){
Node node = root;
while(node != null){
if(data < node.data) node = node.leftChild;
else if(data > node.data) node = node.rightChild;
else return node;
}
return null;
}
// Node内部类
public static class Node{
private int data;
private Node leftChild;
private Node rightChild;
public Node(int data){
this.data = data;
}
}
}
3、二叉查找树的插入操作
二叉查找树的插入操作有点类似于查找操作。先查找到合适的位置再插入,新插入的数据一般都在叶子结点上,所以我们只需要从根结点开始,依次比较要插入的数据和结点的大小关系。
二叉查找树的插入操作的关键几个步骤如下:
1、如果要插入的数据比根结点的数据大,并且根结点的右子树为空,就将新数据直接插入到右子结点的位置;如果不为空,就再递归遍历右子树,查找插入位置;
2、同理,如果要插入的数据比根结点数值小,并且结点的左子树为空,就将新数据插入到左子结点的位置;如果不为空,则遍历左子树,查找插入位置。
// 二叉查找树的插入操作
public void insert(int data){
if(root == null){
root = new Node(data);
}
Node node = root;
while(node != null){
if(data > node.data){
if(node.rightChild == null){
node.rightChild = new Node(data); // 右子结点为空,直接将新结点设置为当前结点的右子结点
}
node = node.rightChild; // 右子结点不为空,则继续遍历当前结点的右子树
}else{ // data < node.data
if(node.leftChild == null){
node.leftChild = new Node(data); // 左子结点为空,直接将新结点设置为当前结点的左子结点
}
node = node.leftChild; // 左子结点不为空,则继续遍历当前结点的左子树
}
}
}
4、二叉查找树的删除操作
二叉查找树的删除操作不是很好理解,该章节出自:https://blog.csdn.net/isea533/article/details/80345507
二叉查找树的删除操作相对于它的查找和插入操作来说复杂的多。针对要删除的结点的子结点个数的不同,我们需要分成三种情况来处理:
1、要删除结点没有左右子结点,可以直接删除;
2、存在左结点或者右结点中的一个,删除后需要对子结点移动;
3、同时存在左右子结点时,不能简单地删除,但是可以通过和后继结点交换后转换为前两种情况。
说明:实际上,还有一个特例,就是删除根节点,实现代码中会体现。
下面就正式对删除操作的三种情况进行说明:
一棵查找二叉树的初始状态如下图所示:
4.1 待删除结点没有子结点
如图中值为20的结点。这种情况是最简单的,我们只需要删除该结点和它父结点的关系即可。删除的时候需要判断自己和父结点的关系是左侧还是右侧,代码如下:
// 这里忽略了父结点不存在的情况,三种情况整合时会处理这种情况
if(node.parent.left == node){
node.parent.left = null; // 如果父结点的左子结点是自己,就将其设置为null
}else{
node.parent.right = null; // 如果父结点的右子结点是自己,就将其设置为null
}
4.2 待删除结点存在左子结点或右子结点时
如图中值为70的结点,删除它时,需要断开两个关系:70与它子结点的关系已经70与它父结点的关系,同时建立70子结点与70父结点之间的父子结点关系。
// 先找到要删除结点的子结点,不需要管他是左还是右
BSTNode<T> child = null; // 表示待删除结点的子结点
if(node.left != null){
child = node.left;
}else{
child = node.right;
}
// 这里忽略了父结点不存在的情况,三种情况整合的时候,会处理
// 将代删除结点的父结点和待删除结点的子结点建立父子结点关系
if(node.parent.left == node){
// 如果待删除结点node是它父结点的左子结点,则将待删除结点node的子结点设置为node父结点的左子结点,即取代它的位置
node.parent.left = child;
}else{
// 如果待删除结点node是它父结点的右子结点,则将待删除结点node的子结点设置为node父结点的右子结点,即取代它的位置
node.parent.right = child;
}
child.parent = node.parent; // 建立node的子结点child中的父结点指针
4.3 待删除结点同时存在左右子结点时
如上图中值为30的结点。如上图中二叉查找树中序遍历的结果,当我们要删除30结点时,整个中序遍历结果从32位都要往前移动一位。而32是30的后继结点(即比30大的结点中最小的结点)。当某个结点存在右结点时,则它的后继结点就是它右子树中最小值,由于左侧结点总比右侧结点和父结点小,所以后继结点一定不会是左子树中的。从这一特点也能想到,待删除结点的后继结点可能存在右结点或者没有任何子结点。30的后继结点还有一个特点就是,他比30的左子树中的结点大,比30的右子树中的结点都小,因此删除30的时候,可以之间将后继结点32转移到30结点上,然后再删除后继结点32。又因为后继结点最多只有一个子结点(最多只有一个比它大的右子结点),因此删除后继结点的时候,就转换成了3种情况的前两种情况。
因此,当待删除结点同时存在左子结点和右子结点时,删除操作被分为了两步:
1、将待删除结点的后继节点“转移”到待删除结点上;
2、删除待删除结点的后继结点。【将删除指定结点转化为了删除它的后继结点】
// 获取当前结点的后继结点
Node<T> successor = successor(ndoe); // successor()函数的具体实现,后文中会给出
// 1、转移值
node.data = successor.data
// 2、删除后继结点successor
node = successor;
4.4 三种情况代码整合 —- 重要【这样写比较容易理解】
代码说明:为了体现出其他二叉查找树的操作,抽象出很多功能函数,相互配合调调用。后面会贴上二叉查找树的所有常用操作的代码。
public class BSTree<T extends Comparable<T>>{
BSTNode<T> root; // 根节点
/**
* 删除指定的data结点
*/
public void delete(T data){
// 先查找到要删除的结点
BSTNode<T> node = search(root, data);
// 如果存在就删除
if(node != null){
delete(node);
}
}
/**
* 删除指定的node
*/
public BSTNode<T> delete(BSTNode<T> node){
// 第3种情况,如果同时存在左右子结点
if(node.left != null && node.right != null){
// 获取待删除结点的后继结点
BSTNode<T> successor = successor(node);
// 转移后继节点值到当前待删除结点上
node.data = successor.data;
// 把要删除的当前结点设置为它的后继结点
node = successor;
}
// 经过前面一步的处理,下面只有两种情况,即:待删除结点有一个子结点或者没有子结点
// 不管有没有子结点,都获取子结点
BSTNode<T> child = null;
if(node.left != null){
child = node.left; // 待删除结点存在左子结点
}else{
child = node.right; // 待删除结点存在右子结点
}
// 如果child != null,就说明待删除结点存在一个子结点
if(child != null){
// 将待删除结点的子结点和待删除结点的父结点关联上
child.parent = node.parent;
}
// 如果当前待删除结点没有父结点,说明要删除的结点是根结点
if(node.parent == null){
// 将根节点的子节点设置为根节点,按照前面的逻辑下来,根节点只有一个子结点或者没有子结点
root = child;
}
// 如果待删除结点有父结点,并且待删除结点是它父结点的左结点时
else if(node == node.parent.left){
// 将父结点的左子结点设置为child
node.parent.left = child;
}
// 如果待删除结点有父结点,并且待删除结点是它父结点的右结点时
else{
// 将父结点的右子结点设置为child
node.parent.right = child;
}
// 返回被删除的结点
return node;
}
/**
* 找到结点node的后继节点。即:查找二叉树中数据大于该结点的最小结点
*/
private BSTNode<T> successor(BSTNode<T> node) {
BSTNode<T> successor = null;
// 如果存在右子结点,则node的后继结点为以其右子结点为根的右子树中的最小结点
if(node.right != null){
while(node.right.left != null){
successor = node.right.left; // 最小结点肯定在右子树中的左子结点中
}
}
// 如果node没有右子结点,则node有以下两种情况
BSTNode<T> nodeParent = node.parent;
// 1.node是“一个左子结点”,则node的后继结点就为node的父结点
if(nodeParent != null && node == node.parent.left){
successor = node.parent;
}
// 2.node是“一个右子结点”,则查找node的“最低”父结点,并且该父结点要有左子结点,那么这个“最低”的父结点就是node的后继结点
// 2.1 如果nodeParent是nodeParent.parent的左子结点,则nodeParent.parent就是它的后继节点
// 2.2如果nodeParent是nodeParent.parent的右子结点,则还需要往上面找,直到node作为它的左子结点时,此时的nodeParent.parent就是它的后继节点
while(nodeParent != null && node == node.parent.right){
node = node.parent;
nodeParent = nodeParent.parent;
successor = nodeParent;
}
return successor;
}
/**
* 递归实现查找以node为根节点的二叉树中值为data的结点
*/
private BSTNode<T> search(BSTNode<T> node, T data) {
if(node == null){
return node;
}
int cmp = data.compareTo(node.data);
if(cmp < 0) return search(node.left, data); // 在左子树中找
else if(cmp > 0) return search(node.left, data); // 在右子树中找
else return node;
}
/**
* BSTNode:结点内部类
*/
public class BSTNode<T extends Comparable<T>>{
T data; // 值
BSTNode<T> left; // 左子结点
BSTNode<T> right; // 右子结点
BSTNode<T> parent; // 父结点
public BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right){
this.data= data;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getData(){
return this.data;
}
@Override
public String toString(){
return "data:" + data;
}
}
}
5、支持重复数据的二叉查找树
前面我们讲的二叉查找树的操作,针对的都是不存在键值相同的情况。如果存在存储两个对象的键值相同的情况,我们可以有两种解决办法:
方法1:通过链表和支持动态扩容的数组等数据结构实现二叉树中的结点,把值相同的数据都存储在同一个结点上;
方法2:每个结点仍然只存储一个数据。在查找插入位置的过程中,如果碰到一个结点的值与要插入数据的值相同,那么就将这个要插入的数据放到这个结点的右子树中,也就是说把这个新插入的数据当作大于这个结点的值来处理。
第1种方法比较容易理解,下面对第2种方法,进行下说明:
当我们查找数据的时候,遇到值相同的结点,我们并不停止查找操作,而是继续在右子树中查找,直到遇到叶子节点,才停止。这样就可以把键值等于要查找值得所有结点都找出来了。
对于删除操作,我们也需要先查找到每个要删除得结点,然后再按照前面讲得删除操作的方法,依次删除。
6、二叉查找树的时间复杂度分析
此章节内容出自于:https://time.geekbang.org/column/article/68334
二叉查找树的形态多种多样。比如下图中,对于同一组数据,我们构造了三种二叉查找树。它们的查找、插入和删除操作的执行效率都不一样的。图中第一种二叉查找树,根节点的左右子树极不平衡,已经退化成了链表,所以查找的时间复杂度为O(n)。这是最糟糕的情况。
上面分析了最糟糕的情况,下面来分析下最理想的情况,即二叉查找树是一棵满二叉树。
其实从前面的讲解中,我们不难发现,不管操作是查找、插入还是删除,时间复杂度都跟树的高度成正比,也就是O(height)。所以,求解二叉查找树操作的时间复杂度就转化为了如何求一棵包含n个结点的完全二叉树的高度。
树的高度就等于最大层数减1,为了方便计算,我们转换为层来表示。从图中可以看出,包含n个结点的完全二叉树中,下面一层的结点个数是上面一层的2倍,即第k层包含的结点个数就是2^(k – 1)。不过对于完全二叉树的最后一层的结点个数就没有规律了,它包含的结点个数在1~2^(L – 1)个之间(假设最大层数为L)。如果我们把每一层的结点个数加起来就是总的结点个数n。那么可以得出如下关系:
1 + 2 + 4 + 8 + … + 2^(L-2) + 1 <= n <= 1 + 2 + 4 + 8 + … + 2^(L-2) + 2^(L-1) // L-2:不包含最后一层
借助等比数列的求和公式,我们可以计算出L的范围是[ , ]。完全二叉树的层数小于等于 ,也就是说,完全二叉树的高度小于等于。即理想状态下的完全二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。
从上面的最坏和最好的二叉查找树的时间复杂度分析来看,极度不平衡的二叉查找树的查找性能肯定不能满足我们的需求。我们需要构建出一种不管怎么删除、插入和查找数据都能保持任意结点的左、右子树比较平衡的二叉查找树,这就是下一篇文章要讲的一种特殊的二叉查找树,即平衡二叉查找树(红黑树)。平衡二叉查找树的高度接近logn,所以它的查找、插入和删除操作的时间复杂度都比较稳定,是O(logn)。
7、二叉查找树和散列表的对比
散列表的讲解:https://blog.csdn.net/pcwl1206/article/details/83582986
在前面的散列表文章中讲过,散列表的查找、插入和删除操作的时间复杂度都可以做到常量级的O(1),非常的高效。那么二叉查找树在比较平衡的情况下,查找、插入和删除操作的时间复杂度才是O(logn),相对散列表好像没有什么优势,那么我们为什么还要用二叉查找树呢?
主要原因有以下几个方面:
1、散列表中的数据是无序存储的,如果要输出有序的数据,需要先进行排序。对于二叉查找树来说,只需要中序遍历,就可以在O(n)的时间复杂度内输出所有的有序数据;
2、散列表扩容耗时很多,而且当遇到散列冲突的时候,性能不稳定。在工程中,我们常用的平衡二叉查找树的性能非常稳定,时间复杂度稳定在O(logn);
3、笼统的来说,尽管散列表的查找等操作的时间复杂度是常量级的,但是因为哈希冲突的存在,这个常量不一定比logn小,所以实际的查找速度可能不一定比O(logn)快。再加上哈希函数的耗时,也不一定比平衡二叉查找树的效率高;
4、散列表的构造要比二叉查找树要复杂的多,需要考虑的东西很多。比如散列函数的设计、冲突解决办法、扩容、缩容等。平衡二叉查找树只需要考虑平衡性这一个问题,而且这个问题的解决方案比较成熟、固定。
5、散列表为了避免过多的散列冲突,散列表装载因子不能太大,特别是基于开放寻址法解决散列冲突的散列表,不然会浪费一定的存储空间。
综合以上几点:平衡二叉查找树在某些方面还是由于散列表的,所以,这两者之间并不存在冲突。我们在实际的开发中,需要结合实际的需求再来选择使用哪一个。
8、二叉查找树的常用操作全代码实现
package com.zju.tree;
/**
* @author 作者 pcwl
* @date 创建时间:2018年11月17日 上午9:04:51
* @version 1.0
* @comment 二叉查找树的操作
*/
public class BSTree<T extends Comparable<T>>{
BSTNode<T> root; // 根节点
public BSTree(){
root = null;
}
/**
* BSTNode:结点内部类
*/
public class BSTNode<T extends Comparable<T>>{
T data; // 值
BSTNode<T> left; // 左子结点
BSTNode<T> right; // 右子结点
BSTNode<T> parent; // 父结点
public BSTNode(T key, BSTNode<T> parent, BSTNode<T> left, BSTNode<T> right){
this.data= data;
this.parent = parent;
this.left = left;
this.right = right;
}
public T getData(){
return this.data;
}
@Override
public String toString(){
return "data:" + data;
}
}
/**
* 前序遍历指定结点下的所有结点
*/
public void preOrder(BSTNode<T> node){
if(node != null){
System.out.println(root.data + " ");
preOrder(node.left);
preOrder(node.right);
}
}
/**
* 前序遍历二叉查找树下的所有结点
*/
public void preOrder(){
preOrder(root);
}
/**
* 中序遍历指定结点下的所有结点
*/
public void inOrder(BSTNode<T> node){
if(node != null){
inOrder(node.left);
System.out.println(node.data + " ");
inOrder(node.right);
}
}
/**
* 中序遍历二叉查找树下的所有结点
*/
public void inOrder(){
inOrder(root);
}
/**
* 后序遍历指定结点下的所有结点
*/
public void lastOrder(BSTNode<T> node){
if(node != null){
lastOrder(node.left);
lastOrder(node.right);
System.out.println(node.data + " ");
}
}
/**
* 后序遍历二叉查找树下的所有结点
*/
public void lastOrder(){
lastOrder(root);
}
/**
* 递归实现查找以node为根节点的二叉树中值为data的结点
*/
private BSTNode<T> search(BSTNode<T> node, T data) {
if(node == null){
return node;
}
int cmp = data.compareTo(node.data);
if(cmp < 0) return search(node.left, data); // 在左子树中找
else if(cmp > 0) return search(node.left, data); // 在右子树中找
else return node;
}
/**
* 递归实现查找根节点下值为data的结点
*/
public BSTNode<T> search(T data){
return search(root, data);
}
/**
* 删除指定的data结点
*/
public void delete(T data){
// 先查找到要删除的结点
BSTNode<T> node = search(root, data);
// 如果存在就删除
if(node != null){
delete(node);
}
}
/**
* 删除指定的node
*/
public BSTNode<T> delete(BSTNode<T> node){
// 第3种情况,如果同时存在左右子结点
if(node.left != null && node.right != null){
// 获取待删除结点的后继结点
BSTNode<T> successor = successor(node);
// 转移后继节点值到当前待删除结点上
node.data = successor.data;
// 把要删除的当前结点设置为它的后继结点
node = successor;
}
// 经过前面一步的处理,下面只有两种情况,即:待删除结点有一个子结点或者没有子结点
// 不管有没有子结点,都获取子结点
BSTNode<T> child = null;
if(node.left != null){
child = node.left; // 待删除结点存在左子结点
}else{
child = node.right; // 待删除结点存在右子结点
}
// 如果child != null,就说明待删除结点存在一个子结点
if(child != null){
// 将待删除结点的子结点和待删除结点的父结点关联上
child.parent = node.parent;
}
// 如果当前待删除结点没有父结点,说明要删除的结点是根结点
if(node.parent == null){
// 将根节点的子节点设置为根节点,按照前面的逻辑下来,根节点只有一个子结点或者没有子结点
root = child;
}
// 如果待删除结点有父结点,并且待删除结点是它父结点的左结点时
else if(node == node.parent.left){
// 将父结点的左子结点设置为child
node.parent.left = child;
}
// 如果待删除结点有父结点,并且待删除结点是它父结点的右结点时
else{
// 将父结点的右子结点设置为child
node.parent.right = child;
}
// 返回被删除的结点
return node;
}
/**
* 查找以node为根节点下的最小结点
*/
public BSTNode<T> minNode(BSTNode<T> node){
if(node == null){
return null;
}
// 其实就是找ndoe下的最后一个左子结点
while(node.left != null){
node = node.left;
}
return node;
}
/**
* 查找整棵二叉查找树下的最小结点
*/
public BSTNode<T> minNode(){
return minNode(root);
}
/**
* 查找以node为根节点下的最大结点
*/
public BSTNode<T> maxNode(BSTNode<T> node){
if(node == null){
return null;
}
// 其实就是找ndoe下的最后一个右子结点
while(node.right != null){
node = node.right;
}
return node;
}
/**
* 查找整棵二叉查找树下的最大结点
*/
public BSTNode<T> maxNode(){
return maxNode(root);
}
/**
* 找到结点node的后继节点。即:查找二叉树中数据大于该结点的最小结点
*/
private BSTNode<T> successor(BSTNode<T> node) {
BSTNode<T> successor = null;
// 如果存在右子结点,则node的后继结点为以其右子结点为根的右子树中的最小结点
if(node.right != null){
while(node.right.left != null){
successor = node.right.left; // 最小结点肯定在右子树中的左子结点中
}
}
// 如果node没有右子结点,则node有以下两种情况
BSTNode<T> nodeParent = node.parent;
// 1.node是“一个左子结点”,则node的后继结点就为node的父结点
if(nodeParent != null && node == node.parent.left){
successor = node.parent;
}
// 2.node是“一个右子结点”,则查找node的“最低”父结点,并且该父结点要有左子结点,那么这个“最低”的父结点就是node的后继结点
// 2.1 如果nodeParent是nodeParent.parent的左子结点,则nodeParent.parent就是它的后继节点
// 2.2如果nodeParent是nodeParent.parent的右子结点,则还需要往上面找,直到node作为它的左子结点时,此时的nodeParent.parent就是它的后继节点
while(nodeParent != null && node == node.parent.right){
node = node.parent;
nodeParent = nodeParent.parent;
successor = nodeParent;
}
return successor;
}
/**
* 找到结点node的前驱节点。即:查找二叉树中数据小于该结点的最小结点
*/
private BSTNode<T> predecessor(BSTNode<T> node) {
BSTNode<T> predecessor = null;
// 如果存在左子结点,则node的前驱结点为以其左子结点为根的左子树中的最大结点
if(node.left != null){
while(node.left.right != null){
predecessor = node.left.right; // 最大结点肯定在左子树中的右子结点中
}
}
// 如果node没有左子结点,则node有以下两种情况
BSTNode<T> nodeParent = node.parent;
// 1.node是“一个右子结点”,则node的前驱结点就为node的父结点
if(nodeParent != null && node == node.parent.left){
predecessor = node.parent;
}
// 2.node是“一个左子结点”,则查找node的“最低”父结点,并且该父结点要有右子结点,那么这个“最低”的父结点就是node的前驱结点
// 2.1 如果nodeParent是nodeParent.parent的右子结点,则nodeParent.parent就是它的前驱节点
// 2.2如果nodeParent是nodeParent.parent的左子结点,则还需要往上面找,直到node作为它的右子结点时,此时的nodeParent.parent就是它的前驱节点
while(nodeParent != null && node == node.parent.left){
node = node.parent;
nodeParent = nodeParent.parent;
predecessor = nodeParent;
}
return predecessor;
}
/**
* 将结点插入到查找二叉树中
*/
public void insert(BSTree<T> bst, BSTNode<T> node){
int cmp;
BSTNode<T> node1 = null;
BSTNode<T> root = bst.root;
// 先查找node的插入位置
while(root != null){
node1 = root;
cmp = node.data.compareTo(root.data);
if(cmp < 0){
root = root.left;
}else{
root = root.right;
}
}
node.parent = node1;
if(node1 == null){
bst.root = node;
}else{
cmp = node.data.compareTo(node1.data);
if(cmp < 0){
node1.left = node;
}else{
node1.right = node;
}
}
}
/**
* 销毁二叉树
*/
public void destroy(BSTNode<T> node){
if(node == null){
return ;
}
if(node.left != null){
destroy(node.left);
}
if(node.right != null){
destroy(node.right);
}
node = null;
}
/**
* 打印node作为根节点的二叉树
* direction: 0:表示该结点是根结点
* -1:表示该结点是它父结点的左子结点
* 1:表示该结点是它父结点的右子结点
*/
public void print(BSTNode<T> node, T data, int direction){
if(node != null){
if(direction == 0){
System.out.printf("%2d is root\n", node.data);
}else{
System.out.printf("%2d is %2d's %6s child\n", node.data, data, direction == 1 ? "right" : "left");
}
print(node.left, node.data, -1);
print(node.right, node.data, 1);
}
}
/**
* 打印二叉树
*/
public void print(){
if(root != null){
print(root, root.data, 0);
}
}
}
推荐及参考:
1、重温数据结构:二叉排序树的查找、插入、删除:https://blog.csdn.net/u011240877/article/details/53242179
2、《数据结构与算法之美》:https://time.geekbang.org/column/article/68334
3、二叉树的删除操作详解:https://blog.csdn.net/isea533/article/details/80345507【推荐阅读】