二叉搜索树是基于二叉树的,树中的每个结点代表一个对象,每个结点有以下属性:key、value、p(指向父结点)、left(指向左结点)、right(指向右结点),如果某个子节点或父节点不在,则对应的属性值为NIL。同时这棵二叉树还满足以下性质:对于某个结点来说,其左子树上的所有结点的值都小于或等于该结点的值,其右子树的值都大于或等于该结点的值。
顺序访问
二叉搜索树的中序遍历也就是按属性变量了。
getByOrder(x){//x为根结点
if(x != NIL){
getByOrder(x.left);
print x.key;
getByOrder(x.rigth);
}
}
查询
查询也可以通过递归实现,或者通过循环展开递归。
search1(x,K){ //x是根节点,k是需要查找的键
if(x==NIL || x.key == K)
return x;
if(x.k < key)
return search1(x.left);
else
return search2(x.rigth);
}
search2(x,k){
while(x !=null && x.key != k){
if(k < x.key)
x = x.left;
else
x = x.right;
}
return x;
}
最小结点和最大结点
最小结点就是二叉搜索树的最左边的结点,最大结点就是二叉搜索树的最右边结点。
minNode(x){//x是根结点
if x == NIL
return x;
while(x.left != NIL)
x = x.left;
return x;
}
maxNode(x){
if x == NIL
return x;
while(x.right != NIL)
x = x.right;
return x;
}
前驱和后继
对于x前驱结点,如果x的左子树存在,就是左子树的最大结点;如果左子树不存在,x的前驱结点是父辈中第一个作为右结点的结点的父结点,如果不存在这样的父辈结点,那x就是整个数中最小的结点,前驱结点为NIL。
// 查询x前驱结点
predecessor(x){
if(x.left != NIL){
x = x.left;
while(x.right!= null)
x = x.left;
return x;
}
y = x.p;
while(y != NIL && x != y.right){
x = y;
y = x.p;
}
return y;
}
查找x的后继结点,如果x存在右结点,那个x的后驱结点就是x的右子树的最小结点。如果不存在右子树,那么x的后驱结点就是父辈结点中第一个作为左结点的结点的父节点,不过不存在这样的结点,那么x就是整个树的最大结点,不存在右结点。
//查询 y 的后继结点
successor(x){
if(x.right != NIL){
x = x.right;
while(x.left != null)
x = x.left;
return x;
}
y = x.p
while(y!=NIL || x != y.left){
x = y;
y = x.p;
}
return y;
}
看到有人问如果不用递归和栈遍历二叉树,网上有答案可以通过不停查找结点的后继结点遍历,这样的做法是可以的,但是这样遍历的时间和递归遍历的时间是有差别的。有n个结点的二叉搜索树,像上面所说的中序遍历时间是O(n),对于一棵高度为h的二叉搜索树,找到后继或前驱结点的时间复杂度是O(h),那么对于一棵理想的二叉搜索树,通过后继结点的方法遍历树,时间复杂度是nO(logn)。
插入
一棵非空的二叉搜索树,插入一个值时一定可以作为叶子节点插入,对于任何一个结点小于等于的都可以插入它的左子树,大于的都可以插入其右子树,这样搜索下来会找到一个叶子结点的左右结点。插入是O(h),h是书的高度。插入可能会导致更加不平衡。
insert(T,x){ //x.key是要插入的值
if(T.root = NIL) //如果是空树,x就是根结点
T.root = x;
return;
y = NIL;
z = T.root;
while(z != NIL){ // while循环中查找x的父节点
y = z;
if(x.key <= z.key)
z = z.left;
else
z = z.right;
}
if(x.key <= y.key) //这种情况 y.left 为空
y.left = x;
else
y.right = x; //这种情况 y.right为空
x.p = y;
}
删除
删除有三种情况,假设x是要删除的结点:
- x没有子节点,直接删除;
- x有只有一个结点y,用y替x结点,y子树保持不变;
- 如果x有两个结点,找到x的后继结点(前驱结点也是一样),用后继节点替换y,相当删除y,删除y可以使用前两种情况。
replace(T,x,y){ // 用y为根的子树替换x为根的子树(替换整个子树,x子树中的结点都不在T树中)
if(x.p == NIL)
T.root = y;
else x == x.p.right
x.p.right = y;
y.p = x.p;
else
x.p.left = y;
y.p = x.p;
}
delete(T,x){ // 删除x结点
if(x.left == NIL && x.right == NIL)
x.p = NIL; //情况1
if(x.left == NIL)
replace(T,x, x.right); //情况2
if(x.right == NIL)
replace(T,x, x.left); //情况2
else
y = MIN(x.right) //其实就是找到y的后继结点,有右子树情况下
if(y.p != z)
replace(T, y, y.right)
y.right = z.right; //若y.p == z,这两句赋值导致y.right = y,y.p = y
z.right.p = y;
y.left = z.left;
z.left.p = y;
replace(T,z,y)
}
上面是通过找到该结点的后继结点删除该结点,也可以通过找到前驱结点删除。