二叉搜索树:
二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:
设x是二叉搜索树的一个结点。如果y是x左子树中的一个结点,那么y.key<=x.key 如果y是x右子树中的一个结点,那么y.key>=x.key
遍历一棵有n个结点的二叉搜索树需要耗费fai(n)的时间,因为初次调用之后,对于树中的每个结点这个过程恰好要自己调用两次:一次是他的左子树,另一次是他的右子树。
对于二叉搜索树的相关操作“
插入和删除:
要将一个新值v插入到一棵二叉搜索树T中,需要调用过程Tree-Insert。该过程以结点z作为输入,其中z . key = v , z . left = NIL , z . right = NIL.这个过程要修改T和z的某些属性,来把z插入到树中的相应位置上。
Tree-Instert(T , z)
语言描述:将根指针传进来,进行遍历搜索找到z应该属于的地方,然后将z的父指定到找到的节点,然后进行z值得判断看z的值符不符合条件,实在搜索树结果的左边还是右边。
算法导论中的伪代码:
Tree-Insert(T , z)
y= NIL
x=T.root
while x!=NIL
y = x
if z.key<x.key
x = x.left
else x = x.right
z.p = y
if y = = NIL
T.root = z
elseif z.key<y.key
y.left = z
else y.right = z
C语言实现法:
void insert_TreeNode(node * Tree_root, node* z){
node *p;
node *p2;
p = Tree_root;
p2 = NULL;
//遍历寻找z应该插的位置,并且找到其定位的父节点
while(p!=NULL){
p2 = p;//保留上一个的父节点指针
if(z->key < p->key){//开始遍历寻找z的位置
p = p->left;
}else{
p = p->right;
}
}
z->parent = p2;//遍历完成之后,将z的父指针指向p2
//下面判断z在其父节点的左边、右边还是空直接变成根结点
if(p2==NULL){
Tree_root = z;
}else if(z->key < p2->key){
p2->left = z;
}else {
p2->right = z;
}
}
二叉平衡树中结点删除操作:
从一颗二叉搜索树T中删除一个结点z的整个策略分为三种情况:
1 .如果z没有孩子结点,那么只是简单地将它删除,并修改它的父结点,用NULL作为孩子来替换z。
2.如果z只有一个孩子,那么将这个孩子提升到树中z的位置上,并修改z的父结点,用z的孩子来替换z 。
3.如果z有两个孩子,那么找 z 的后继 y(一定在z的右子树中),并让y占据树中z的位置。z的原来右子树部分成为y的新的右子树,并且z的左子树将成为y的新的左子树。这种情况比较麻烦,因为还与y是否为z的右孩子相关。
(何为后继:如果所有的关键字互不相同,则一个结点x的后继是大于x.key的最小关键字的结点。一棵二叉搜索树的结构允许我们通过没有任何关键字的比较来确定一个结点的后缀。)
用图片解释一下如何进行既有左子树又有右子树的情况:
刚开始的情况:准备删除z结点,且z的后继为y != r 位于以r为根的子树中。
开始删除z:
下面就要决定谁来代替z的位置,由于二叉搜索树必须满足--->(y是x左子树中的一个结点,那么y.key<=x.key 如果y是x右子树中的一个结点,那么y.key>=x.key)这种关系,那么当代替的时候就得找到可以满足二叉搜索树性质的节点。
解释:我们必须找到右子树中最小的那个值,也即z节点的后继,如果不找后继只取Z的直接右节点,作为替代Z的位置的节点的话,则会出现该结点的右侧有比该结点值还小的节点的情况,这样就不符合搜索二叉树的性质了,所以必须寻找该右子树中最小值所对应的那个节点,也即z的后继节点。本例子中z的后继为Y那么应该取Y 来代替Z的位置,在取代Z的过程中应该以以下的这个方法进行变换:
算法导论中的伪代码:
为了在二叉搜索树内移动子树,定义一个子过程Transplant,它是用一棵子树替换一棵子树并成为其双亲的孩子的结点,当Transplant用一棵以v为根的子树来替换一棵以u为根的子树时,结点u的双亲就变成v的双亲,并且最后v成为u的双亲的相应孩子。
Transplant(T , u , v)
if u.p == NIL
T.root = v
elseif u == u.p.left
u.p.left = v
else u.p.right = v
if v!=NIL
v.p = u. p
void Transplant(node *Tree_root,node *u,node *v){
//如果原结点的父节点为空
if(u->parent == NULL){
Tree_root = v;//则V就被替换成根节点
}else if(u == u->parent->left){//如果要替换的节点为他的父节点的左分支处
u->parent->left = v;//则将v放在该父节点的左侧替换u
}else{
u->parent->right = v;//相反则放在父节点的右侧替换u
}
//如果要进行替换的V不为空,则将原来的u的父节点,由v来指引
if(v!=NULL){
v->parent = u->parent;
}
}
从二叉搜索树中删除结点Z的过程伪码:
Tree-Delete(T , z)
if z.left == NIL
Transplant(T , z , z.right)
elseif z.right == NIL
Transplant(T,z, z.left)
else y = Tree-Minimum(z.right)
if y.p!=z
Transplant(T , y , y.right)
y.right = z.right
y.right.p = y
Transplant(T , z , y)
y.left = z.left
y.left.p = y
C语言实现:
void Tree_Delete(node * Tree_root,node * z){
node* y ;
if(z->left == NULL){
//如果z的左子树为空则直接将z删除将其右子树节点替换过来即可
Transplant(Tree_root,z,z->right);
}else if(z->right == NULL){
//如果z的右子树为空则直接将z删除将其左子树节点替换过来即可
Transplant(Tree_root,z,z->left);
}else{
y = z->right;
//搜索z子树中最小值,即后继
while(y!=NULL){
y = y ->left;
}
//如果z的父节点不为z
if(y->parent!=z){
//那么将y(后继)的右子树节点替换y的位置
}
//然后用y(后继)来替换z的位置
Transplant(Tree_root,z ,y);
y->left = z->left;
y->right = z->right;
}
}