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的值。