二叉搜索树在集合类中对应到TreeSet,TreeMap的底层实现,它属于一种特殊的二叉树,对比于普通的二叉树,它的特殊之处在于左子树的所有值都小于根节点,而右子树的所有值都大于根节点。所以我们根据中序遍历二叉搜索树就可以得到一个有序的序列。顾名思义,二叉搜索树的最大的用处在于查找,如果我们要查找一个元素,就把它和根节点进行比较,如果小于根节点,就去左边找,如果大于根节点,就去右边找。大大提高了查找效率,一般情况下时间复杂度可达O(logN),除非这颗树二叉搜索树特别的不一般,那就是只有单边树的情况,这样的查找类似于遍历链表,时间复杂度为O(N)。为了防止这种特殊情况,进一步又引入了两种特殊的二叉搜索树——AVL树和红黑树。
AVL树是一种严格平衡的二叉搜索树,要求就是必须平衡,但是这样也就导致了插入删除操作之后会涉及到重新调整树的结构,会很麻烦
红黑树在AVL树的基础上又进一步做了改善,要求不是那么严格,只需要比较平衡就可以了,实际上TreeMap和TreeSet的底层实现就是红黑树。
二叉搜索树的基本操作
- 插入元素
思路就是先找到待插入元素的合适位置,然后再进行插入
public boolean insertkey(int key){
//首先判断是否是空树
if(root == null){
root = new Node(key);//让root指向一个新的节点,这个新节点的key就是插入的这个元素
return true;
}
//当root不为null的时候有也就是不是空树的时候
Node parent = null;//首先定义一个parent来保留cur的父节点的记录,因为cur为null了代表找到
//插入位置了,需要定义一个parent来进行插入
Node cur = root;
while(cur!=null){
if(key>cur.key){
parent = cur;
cur = cur.right;//代表需要把这个元素往右插
}else if(key<cur.key){
parent = cur;
cur = cur.left;
}else{//此时代表当前key = cur.key.重复了,就直接返回false
return false;
}
}
//上面循环走完cur已经为null了,代表此时找到了插入位置,也就是parent的子树
if(key>parent.key){
parent.right = new Node(key);//创建一个新节点插入到parent的右边,这个新节点也就是key
}else{
parent.left = new Node(key);
}
return true;
}
- 查找元素
public Node find(int key){//1.查找元素,如果找到则返回当前节点的地址
Node cur = root;//让cur从根节点出发
while (cur!=null){
if(key>cur.key){//说明在当前节点的右边
cur = cur.right;
}else if(key<cur.key){//说明在当前节点的左侧
cur = cur.left;
}else{//此时说明找到了
return cur;
}
}
//循环走完cur已经为null了,此时还没找到,就返回null
return null;
}
- 删除元素
删除元素比较复杂。
①.待删除元素的左子树不为null,右子树为null的时候
此时又要判断待删除节点是它的父节点的左子树还是右子树,如果是左子树的话就让它的父节点的left= 待删除节点的left。如果是右子树的话就让它的父节点的right = 待删除节点的left
②.待删除节点的左子树为null,右子树不为null
此时同样判断待删除节点是它的父节点的左子树还是右子树,具体操作和上面一样
③.待删除节点的左右子树都不为null
仍然需要判断是它的父节点的左子树还是右子树,具体操作也一样,以左子树为例,就是先找到待删除节点右子树中的最小值,或者左子树中的最大值,然后覆盖到待删除节点的位置,最后再删除这个最小值或者最大值就可以了,因为如果在右子树中找到了最小值,那么此时这个最小值一定没有左子树,所以删除方法和上面的情况一样。在左子树中找最大值也是一样。
public boolean remove(int key){
//如果key在树中存在,就删除成功,不存在就删除失败
//先找到要删除的节点的位置,再进行删除
//找到待删除元素之后,再去判断是哪种情况
Node cur = root;
Node parent = null;
while(cur!=null){
if(key>cur.key){
parent = cur;
cur = cur.right;
}else if(key<cur.key){
parent = cur;
cur = cur.left;
}else{//此时说明找到了待删除节点
removekey(parent,cur);//这个方法就用来判断是哪种情况并且进行删除
return true;
}
}
return false;
//上面循环走完说明cur为null了也没有找到待删除元素,就返回false
}
private void removekey(Node parent, Node cur) {
//1.待删除元素的左子树为null的时候
if(cur.left==null){
if(cur==root){//说明要删除的节点就是根节点的时候
root = cur.right;
}else if(cur == parent.left){//待删除元素是它的父节点的左子树
parent.left = cur.right;
}else{//待删除节点是它的父节点的右子树
parent.right = cur.right;
}
}else if(cur.right==null){//待删除元素的右子树为null的时候
if(cur==root){//说明待删除节点就是根节点
root = cur.left;
}else if(cur == parent.left){//说明待删除节点是它的父节点的左子树
parent.left=cur.left;
}else{//说明待删除节点是它的父节点的右子树
parent.right=cur.left;
}
}else{//说明待删除节点的左右子树都不为null的时候
//先找到待删除元素的右子树中最小的那个
//再把找到的这个最小值复制给待删除节点
//然后删除这个最小值就可以了
Node scapeGoat = cur.right;//用它来保存最小值节点,cur.right是一定存在的,如果不存在已经执行了上面
Node goatparent = cur;//用它来保存最小值节点的父节点,以便删除
while(scapeGoat.left!=null){
goatparent = scapeGoat;//先让替罪羊的父节点暂时保存替罪羊节点的信息
scapeGoat = scapeGoat.left;//让替罪羊一直往左走,因为最小值都是在左边
//当左边为null的时候此时scape就不走了,scape此时就保留着最小值的信息。s
}
//循环结束时scape就指向了右子树中的最小值
cur.key = scapeGoat.key;//将这个最小值复制到待删除节点
//接下来删除替罪羊节点,替罪羊节点就一定没有左子树,只需要判断是替罪羊父亲的左子树还是右子树即可
if(scapeGoat==goatparent.left){//这是左子树的情况
goatparent.left=scapeGoat.right;
}else{
goatparent.right = scapeGoat.right;
}
}
}
红黑树和AVL树我还没学。