(网上讲二叉排序树删除的资料很少,这篇很不错!转自:http://bbs.csdn.net/topics/110010437)
二叉排序树的删除:
对于一般的二叉树来说,删去树中的一个结点是没有意义的,因为它将使以被删除的结点为根的子树变成森林,破坏了整棵树的结构
但是,对于二叉排序树,删去树上的一个结点相当于删去有序序列中的一个记录,只要在删除某个结点后不改变二叉排序树的特性即可。
在二叉排序树上删除一个结点的算法如下:
btree * DeleteBST(btree *b, ElemType x)
{
if (b)
{
if (b->data == x)
b = DelNode(b);
else if (b->data > x)
b->lchild = DeleteBST(b->lchild, x);
else
b->rchild = DeleteBST(b->rchild, x);
}
return b;
}
其中删除过程有两种方法。
第一种过程如下:
1。若p有左子树,找到其左子树的最右边的叶子结点r,用该叶子结点r来替代p,把r的左孩子
作为r的父亲的右孩子。
2。若p没有左子树,直接用p的右孩子取代它。
第二种过程如下:
1。若p有左子树,用p的左孩子取代它;找到其左子树的最右边的叶子结点r,把p的右子树作为r
的右子树。
2。若p没有左子树,直接用p的右孩子取代它。
两种方法各有优劣,第一种操作简单一点点,但均衡性不如第二种,因为它将结点p的右子树
全部移到左边来了。下面将分别以两种种思路编写代码。
第一种:
btree * DelNode(btree *p)
{
if (p->lchild)
{
btree *r = p->lchild; //r指向其左子树;
while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r
{
r = r->rchild;
}
r->rchild = p->rchild;
btree *q = p->lchild; //q指向其左子树;
free(p);
return q;
}
else
{
btree *q = p->rchild; //q指向其右子树;
free(p);
return q;
}
}
第二种:
btree * DelNode(btree *p)
{
if (p->lchild)
{
btree *r = p->lchild; //r指向其左子树;
btree *prer = p->lchild; //prer指向其左子树;
while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r
{
prer = r;
r = r->rchild;
}
if(prer != r)//若r不是p的左孩子,把r的左孩子作为r的父亲的右孩子
{
prer->rchild = r->lchild;
r->lchild = p->lchild; //被删结点p的左子树作为r的左子树
}
r->rchild = p->rchild; //被删结点p的右子树作为r的右子树
free(p);
return r;
}
else
{
btree *q = p->rchild; //q指向其右子树;
free(p);
return q;
}
}
但是上面这种方法,把r移来移去,很容易出错,其实在这里我们删除的只是p的元素值,而不是它的地址,所以完全没有必要移动指针。仔细观察,发现我们删除的地址实际上是p的左子树的最右边的叶子结点r的地址,所以我们只要把r的数据填到p中,然后把r删除即可。
算法如下:
btree * DelNode(btree *p)
{
if (p->lchild)
{
btree *r = p->lchild; //r指向其左子树;
btree *prer = p->lchild; //prer指向其左子树;
while(r->rchild != NULL)//搜索左子树的最右边的叶子结点r
{
prer = r;
r = r->rchild;
}
p->data = r->data;
if(prer != r)//若r不是p的左孩子,把r的左孩子作为r的父亲的右孩子
prer->rchild = r->lchild;
else
p->lchild = r->lchild; //否则结点p的左子树指向r的左子树
free(r);
return p;
}
else
{
btree *q = p->rchild; //q指向其右子树;
free(p);
return q;
}
}
http://www.programfan.com/
——————————————————————————————————————————————————————————————————
1.二叉排序树的概念:
二叉排序树是一种动态树表。
二叉排序树的定义:二叉排序树或者是一棵空树,
或者是一棵具有如下性质的二叉树:
⑴ 若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
⑵ 若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
⑶ 左、右子树本身又各是一棵二叉排序树。二叉排序树的性质: 按中序遍历二叉排序树,所得到的中序遍历序列是一个递增有序序列
2.二叉排序树的插入:
在二叉排序树中插入新结点,要保证插入后的二叉树仍符合二叉排序树的定义。
插入过程:若二叉排序树为空,则待插入结点*S作为根结点插入到空树中;
当非空时,将待插结点关键字S->key和树根关键字t->key进行比较,
若s->key = t->key,则无须插入,若s->key< t->key,则插入到根的左子树中,
若s->key> t->key,则插入到根的右子树中。而子树中的插入过程和在树中的插入过程相同,
如此进行下去,直到把结点*s作为一个新的树叶插入到二叉排序树中,或者直到发现树已有相同关键字的结点为止。
3. 二叉排序树生成:
从空的二叉排序树开始,经过一系列的查找插入操作以后,生成了一棵二叉排序树。
说明:
① 每次插入的新结点都是二叉排序树上新的叶子结点。
② 由不同顺序的关键字序列,会得到不同二叉排序树。
③ 对于一个任意的关键字序列构造一棵二叉排序树,其实质上对关键字进行排序。
4.二叉排序树查找的程序实现:
#include<malloc.h>
#include<iostream.h>
#include<stdio.h>
typedef struct BiTNode{
int data;
int flag;
struct BiTNode *lchild,*rchild;
}BTNode,BTree;
//二叉排序树的查找非递归算法
//在二叉排序树T中查找关键字为key的元素,若找到返回true,
//否则返回false.
bool SearchBST(BTree *T,int key){
BTree *p=T;
while(p){
if(p->data==key)
return true;
p=(key<p->data)? p->lchild:p->rchild;
}
return false;
}
//二叉排序树的查找递归算法
//在二叉排序树T中查找关键字为key的元素,若找到返回true,
//否则返回false.
bool SearchBST2(BTree *T,int key){
BTree *p=T;
if(!p)
return false;
else
if(p->data==key)
return true;
else
if(key>p->data)
return SearchBST2(p->rchild,key);
else
return SearchBST2(p->lchild,key);
}
//建立二叉排序树
//当二叉排序树T中不存在关键字为key的元素时,插入key,并返回树的根,
//否则不插入,并返回树的根。
BTree* InsertBST(BTree *T,int key){
BTree *f=T,*p=T;
while(p){
if(p->data==key) return T;
f=p;//用f记下查找路径上的最后一个访问的节点
p=(key<p->data)? p->lchild:p->rchild;
}
p=(BTNode*)malloc(sizeof(BTNode));
p->data=key;
p->lchild=p->rchild=NULL;
if(T==NULL)
T=p;
else
if (key<f->data)
f->lchild=p;
else
f->rchild=p;
return T;
}
//递归中序遍历
void InOrderDisplay(BTree *T){
if(T){
InOrderDisplay(T->lchild);
cout<<T->data;
InOrderDisplay(T->rchild);
}
}
//test:
int main(){
int i;
int data;
BTree *tree=NULL;
for(i=0;i<4;i++){
cout<<"input data"<<endl;
cin>>data;
tree=InsertBST(tree,data);
}
InOrderDisplay(tree);
bool find=SearchBST2(tree,24);
cout<<find<<endl;
return 0;
}
5. 二叉排序树的删除:
假设被删结点是*p,其双亲是*f,不失一般性,设*p是*f的左孩子,下面分三种情况讨论:
⑴ 若结点*p是叶子结点,则只需修改其双亲结点*f的指针即可。
⑵ 若结点*p只有左子树PL或者只有右子树PR,则只要使PL或PR 成为其双亲结点的左子树即可。
⑶ 若结点*p的左、右子树均非空,先找到*p的中序前趋结点*s(注意*s是*p的左子树中的最右下的结点,它的右链域为空),然后有两种做法:
① 令*p的左子树直接链到*p的双亲结点*f的左链上,而*p的右子树链到*p的中序前趋结点*s的右链上。
② 以*p的中序前趋结点*s代替*p(即把*s的数据复制到*p中),将*s的左子树链到*s的双亲结点*q的左(或右)链上。(在下面的演示算法中用的就是此方法)
6. 删除算法演示 :
//删除二叉排序树中的一个节点
//在二叉排序树T中删除关键字为key的结点
void DelBST(BTree *T,int key){
BTree *p=T,*f,*q,*s,*root=T;
while(p){
if(p->data==key) break; //找到关键字为key的结点
f=p;//记下关键字key节点的父节点
p=(key<p->data)?p->lchild:p->rchild;//分别在*p的左、右子树中查找
}
if(!p) return;//二叉排序树中无关键字为key的结点
if(p->lchild==NULL&&p->rchild==NULL){//p没有左右子树
if(p==T) T=NULL;//删除的是根节点
else
if(p==f->lchild)
f->lchild=NULL;
else
f->rchild=NULL;
free(p);
}
else if(p->lchild==NULL&&p->rchild!=NULL)//p无左子树有右子树
{
if(f->lchild==p)
f->lchild=p->rchild; //将p的右子树链接到其父结点的左链上
else
f->rchild=p->rchild; //将p的右子树链接到其父结点的右链上
free(p);
}
else if(p->rchild==NULL&&p->lchild!=NULL)//p有左子树无右子树
{
if (f->lchild==p)
f->lchild=p->lchild; //将p的左子树链接到其父结点的左链上
else
f->rchild=p->lchild; //将p的左子树链接到其父结点的右链上
free(p);
}
else if(p->lchild!=NULL&&p->rchild!=NULL)//p既有左子树又有右子树
{
q=p;
s=p->lchild;//转左
while(s->rchild){//然后向右到尽头
q=s;
s=s->rchild;//s指向被删节点的“前驱”(中序前驱)
}
p->data=s->data;//以p的中序前趋结点s代替p(即把s的数据复制到p中)
if(q!=p) q->rchild=s->lchild;//重接q的右子树
else q->lchild=s->lchild;//重接q的左子树。
free(s);
}
}
7. 二叉排序树的查找:
在二叉排序树中进行查找的过程和二分查找类似,也是一个逐步缩小查找范围的过程。若查找成功,则是走了一条从根结点到待查结点的路径;若查找失败,则是走了一条根结点到某个叶子结点的路径。因此,查找过程中和关键字比较的次数不超过树的深度。
由于含有n个结点的二叉排序树不唯一,形态和深度可能不同。故含有n个结点的二叉排序树的平均查找长度和树的形态有关。
最好的情况是: 二叉排序树和二叉判定树形态相同。
最坏的情况是: 二叉排序树为单支树,这时的平均查找长度和顺序查找时相同。
最坏情况示例
就平均性能而言,二叉排序树上的查找和二分查找相差不大,并且二叉排序树上的插入和删除结点十分方便,无须大量移动结点。