BST二叉查找树

BST(Binary Search Tree)

二叉查找树

满足的性质:

设x为二叉查找树中的一个结点。如果y是x的左子树中的一个结点,则key[y]≤key[x]。如果y是x的右子树中的一个结点,则key[x]≤key[y]。

根据上述性质,对一棵BST,可以通过中序遍历按序输出所有关键字。

/*中序遍历*/
INORDER_TREE_WALK(x)
if x!= NULL
    then INORDER_TREE_WALK(left[x])
         print key[x]
         INORDER_TREE_WALK(right[x])


查找指定元素

给定指向树根的指针x和关键字k,过程TREE_SEARCH返回指向包含关键字k的节点(如果存在的话)的指针;否则,返回NULL。

/*查找*/
TREE_SEARCH(x,k)
if x = NULL or k = key[x]
     then return x
if x < key[x]
     then return TREE_SEARCH(left[x],k)
     else return TREE_SEARCH(right[x],k)


查找最小元素

/*返回最小*/
TREE-MINIMUM(x)
while left[x] != NULL
    do x = left[x]
return x


查找最大元素

/*返回最大*/
TREE-MAXIMUM(x)
while right[x] != NULL
    do x = right[x]
return x

查找后继

/*返回后继*/
TREE_SUCCESSOR(x)
if right[x] != NULL
    then return TREE_MINIMUM(right[x])
y = parent[x]
while y != NULL and x = right[y]
   do x = y
      y = parent[y]
return y
分析:

①3-4 x有右孩子的话。我们知道,x的左孩子比x->key都要小,右孩子比x->key都要大。后继就是要找一个比x->key都要大里最小的那个。于是在右子树里查找最小的,通过TREE_MINIMUN(right[x]),以x的右孩子为根节点查找,返回的就是我们要找的后继。

②5 - 9  如果x没有右孩子。那么p令为x的父亲。

如果不满足while里面的条件,有两种情况:

一,y = NULL,x的没有父节点,x就是根节点。这是的情况是,x为根节点,x没有右孩子,只有左孩子,或者连左孩子都没有。而左子树都是小于x->key的,所以没有符合要求的后继,所以直接返回空的y。

二,x != right[y],即x == left[y]。x是y的左孩子,此时x没有右孩子。



于是要找比x大的,只有x->parent满足,即y。

再看while循环:

什么情况下,一直循环呢?x现在没有右孩子,只有左孩子,后继的key值要比x的大,那么肯定不在左左子树,只有向上找。向上找,有两种情况,第一就是上面说的x是y的左孩子,如图a,b,X的左子树都小于X,不考虑。Y和Y的右子树都大于X,符合。

(a)中,Y的父亲>Y>X,那么(a)里满足的集合{Y,Y的右子树,Y的父亲},其中排序:Y的父亲>Y的右子树>Y。所以返回的就是Y。

(b)中,Y的右子树>Y>X>Y的父亲,满足的集合{Y,Y的右子树},当然返回Y就是后继。

(c)中,一直while循环,循环停止的条件是y == NULL,也就是一直寻找到了根节点,寻找的路径一直是往上从右孩子,也就是x一直是y的右孩子,图中可见,X是最大的元素,因此后继为NULL。

(d)中,循环了若干次,满足while停止的条件,也就是X是Y的左孩子,找大于X的元素:X的左子树首先排除;Y和Y的左子树也都小于X;由B<Y,Y<X推出,B<X,当然B的左子树也不考虑了;X在A的左子树部分,所以有A>X,并且X在左子树部分里是最大的;A的右子树>A,所以也大于X;X在A父亲的左子树里,所以X<A的父亲。

综上满足集合{A,A的父亲,A的右子树},A的父亲>A的右子树>A,里面A较小,所以返回A。

(e)中,与(d)相比,A变成了右子树,因为X在A父亲的右子树,所以X>A的父亲。所以满足集合的只有{A,A的右子树},显然A。


从整体上说,X只有左孩子情况下,要找大的只可能是父辈。于是用y代表x的父亲,在往上查找过程中:

两种情况:

①x是y的左孩子,那么正好y>x,且y就是x的后继。

②x是y的右孩子,继续搜索,相当于递归,直到遇到根节点或者x成为y的左孩子为止,否则就代表无后继,返回空指针。


插入

/*插入元素*/
TREE_INSERT(T,z)
y = NULL
x = root(T)
while x != NULL
     do y = x
       if key[z] < key[x]
          then x = left[x]
          else x = right[x]
parent[z] = y
if y = NULL
   then root(T) = z
   else if key[z] < key[y]
           then left[y] = z
           else right[y] = z
y 代表的是,要插入点的父亲; x是根节点;z是插入的点。

分两种情况:

①空树

那么x = NULL,不执行while循环,z->parent = NULL,满足if判断语句,那么把插入点z赋给树T的root,插入点成为根节点。

②非空树

while循环是为了查找合适的插入点,y总保持是x的父亲的状态。当找到合适的插入点时,x必为NULL,此时y就是要插入z的父亲。此时将z的parent指针指向y,下面的判断语句,判断是y的左孩子还是右孩子。


删除

/*删除*/
TREE_DELETE(T,z)
if left[z] = NULL or right[z] = NULL
   then y = z
   else y = TREE_SUCCESSOR(z)
if left[y] != NULL
   then x = left[y]
   else x = right[y]
if x != NULL
   then parent[x] = parent[y]
if parent[y] = NULL
   then root(T) = x
   else if y = left[parent[y]]
           then left[parent[y]] = x
           else right[parent[y]] = x
if y != z
   then key[z] = key[y]
return y 

3--5行, 确定要删除的结点 y。该结点或者是输入结点z(如果z至多只有一个子女),或者是z的后继(如果z有两个子女)。


z只有一个子树的情况下,删去z,然后将z的子树与y连接,并不影响树的性质。

(a)删除前:Z的左子树<Z<Y,删除后:Z的左子树<Y。

(b)删除前:Z<Z的左子树<Y,删除后:Z的左子树<Y

(c)z没有子树的情况下,更加明显。

删除只要保证局部满足性质,那么整体就不会影响。


假如Z有两个子女呢?

(a)第一步,找到z的后继。对应代码:

y = TREE_SUCCESSOR(z);



(b)第二步,找到要删除y的孩子。要知道,既然y是z的后继,那么y不可能有左子树,因为如果有的话,那么y>左子树部分>z,那么与y是x的后继矛盾。

这里为什么还要判断y的左子树是否存在呢?因为是在x至多只有一个子女的情况下,并且是左子树将。

这里x = right[y]或者x = NULL


(c)

①判断x是否为空

if x != NULL 

then parent[x] = parent[y]

x非空,即(b1)情况:将x的parent指针指向10;(b2)图暂时不动。

②判断y是否是根节点

if parent[y] = NULL  //如果y是根节点的话,也就是说如果删除节点y是根节点,那么用x来作为新的根节点。

    then root(T) = x

else if y = left[parent[y]] //如果y是父亲的左孩子,y父亲的左孩子指针指向x

    then left[parent[y]] = x

else right[parent[y]] = x  //如果y是父亲的右孩子,y父亲的右孩子指针指向x

如果y是根节点的话,也就是说如果删除节点y是根节点,那么用x来作为新的根节点。



③注意,我们明明要删的是红色的z结点,为何删除了6呢?接着看:

if y != z

   then key[z] = key[y]

return y


完成删除操作,看一看,树的性质没有变化吧。

总结一下:

①先确定删除的节点y。注意我们要删除的确实是z,但是我们可以先删除y,然后在将z的值赋为y,那么等效于删除了z。

②x置为y的非NULL子女(优先置为左孩子),或当y无子女时置为NULL。

③通过修改parent[y]和x中的指针将y踢出树,也就是删除掉。考虑边界条件时,x=NULL或y为根节点时,对y的删除就优点复杂了。

④如果我们删除的y不是我们要删除的z的时候,那么就要把z赋值为y的值。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值