今天练习一下二叉查找树的东东,感觉这个东西,找前驱和后继结点有些绕,再通过后继结点实现删除,更需要好好想一想流程。参照算法导论经典,经过自己的思考,将其中提到的一些基本操作实现了。说明都在注释里呢,这边迷糊的童鞋可以看看,好好想一下。 (我写的都是naive版本的代码,仅供理解算法本身)
/*一个关键字元素不重复的二叉查找树及其典型操作的实现,记录关键字出现次数*/
#include
#include
#include
#include
#define N 20
#define N_RAND 200
typedef struct node{
int key; //关键字
int num; //出现次数
struct node *l; //左结点
struct node *r; //右结点
struct node *p; //父结点
}Node,*Pnode;
//以关键字初始化结点
Pnode IniNode(int key){
Pnode p=(Pnode)malloc(sizeof(Node));
p->key=key;
p->num=1;
p->l=NULL;
p->r=NULL;
p->p=NULL;
}
//中序遍历输出二叉树,由小变大
void BSTreeWalk(Pnode h){
if(h!=NULL){
BSTreeWalk(h->l);
printf("key=%d,num=%d\n",h->key,h->num);
BSTreeWalk(h->r);
}
}
//查找含有关键字key的结点
Pnode Search(Pnode h, int key){
/*
//递归版本
if (h==NULL || key == h->key)
return h;
if(key < h->key) return Search(h>l, int key);
else return Search(h>r, int key);
*/
//非递归版本
while(h!=NULL && key!=h->key){
if(key < h->key) h=h->l;
else h=h->r;
}
return h;
}
//返回最大结点指针
Pnode MaxElem(Pnode h){
//一直向左查找左子树
Pnode p;
for(p=h;p->r!=NULL;p=p->r);
return p;
}
//返回最小结点指针
Pnode MinElem(Pnode h){
//一直向右查找右子树
Pnode p;
for(p=h;p->l!=NULL;p=p->l);
return p;
}
//插入一个结点,如果有key值相等,出现数量加1
void Insert(Pnode h, Pnode p){
Pnode q=h;
Pnode pre=q;
while(q!=NULL && q->key != p->key){
if(p->key < q->key){
pre=q;
q=q->l;
}else if(p->key > q->key){
pre=q;
q=q->r;
}
}
if(q!=NULL) q->num ++; //结点已经存在 ,出现数量加1
else{
p->p=pre;
if(pre->key < p->key) pre->r=p;
else pre->l=p;
}
}
//查找结点p的后前驱结点
Pnode Predecessor(Pnode h, Pnode p){
assert(h!=NULL || p!=NULL);
//左子树不为空,则为左子树的最大元素
if(p->l != NULL) return MaxElem(p->l);
//右子树为空的情况,则该结点p的后继y为p的最低祖先结点,且y的右儿子也是x的祖先。
Pnode y=p->p;
while(y!=NULL && y->l==p){ //如果是右子树,还要向上查找,支到遇到左子树。
p=y;
y=p->p;
}
return y;
}
//查找结点p的后继结点
Pnode Successor(Pnode h, Pnode p){
assert(h!=NULL || p!=NULL);
//右子树不为空,则为右子树的最小元素
if(p->r != NULL) return MinElem(p->r);
//右子树为空的情况,则该结点p的后继y为p的最低祖先结点,且y的左儿子也是x的祖先。
Pnode y=p->p;
while(y!=NULL && y->r==p){ //如果是右子树,还要向上查找,支到遇到左子树。
p=y;
y=p->p;
}
return y;
}
/*删除p结点 ,可能会有删除根节点情况,跟节点地址改变,所以需要*h,取根节点指针地址
* delete可能不太好理解,基于的思想为先找到实际要删除的结点是哪个,可能是该结点本身也可能是后继,然后根据不同的情况改变连接该结点的
* 指针指向,如果删除的是结点后继,则将结点后继信息替换过来,相当于用结点后继替换了该结点。
*/
Pnode Delete(Pnode *h, Pnode p){
assert(*h!=NULL || p!=NULL);
Pnode x=NULL,y=NULL;
//分三种情况,即删除结点没有子女,只有一个子女,有两个子女的情况
if(p->l==NULL || p->r==NULL) y=p; //p有一个子女或者没有子女,则直接删除p
else y=Successor(*h,p); //p有两个子女,则删除其后继结点,然后将后继结点信息拷贝到p的位置
//x被置为y的非NULL子女,或者当y无子女时,x为空
if(y->l != NULL) x=y->l;
else x=y->r;
//通过修改y->p和x的指针将y删除。
if(x!=NULL) x->p=y->p;
if(y->p==NULL) *h=x; //此为删除的为根节点情况
else if(y == y->p->l) y->p->l=x;
else y->p->r=x;
//交换信息
if(y!=p){
p->key=y->key;
p->num=y->num;
}
return y;
}
//根据输入数据构建一个二叉查找树
int BuildBSTree(Pnode *h, int *a, int len){
int i;
*h=IniNode(a[0]);
for(i=1;i
Pnode p=IniNode(a[i]);
Insert(*h,p);
}
return 1;
}
int main(){
int i, a[N];
Pnode head = NULL;
srand((unsigned)time(NULL));
for(i=0;i
a[i]=rand()%N_RAND;
printf("%d ",a[i]);
}
printf("\n");
if(BuildBSTree(&head, a, N) == 1) printf("构建二叉查找树成功!\n");
else{
printf("构建二叉查找树失败!\n");
return 0;
}
BSTreeWalk(head);
printf("最小元素是:%d\n",MinElem(head)->key);
printf("最大元素是:%d\n",MaxElem(head)->key);
for(i=0;i
Pnode find=Search(head,a[i]);
if(find == NULL) printf("未找到该节点!\n");
else{
Pnode pre= Predecessor(head,find);
// if(pre!=NULL) printf("前驱节点为:key=%d,num=%d\n",pre->key,pre->num);
printf("删除结点为:key=%d,num=%d\n",find->key,find->num);
if(Delete(&head,find) == NULL) printf("删除失败\n");
if(head == NULL){
printf("二叉查找树已空!\n");
}else{
BSTreeWalk(head);
}
}
}
system("pause");
}
相关链接:前两个原理,后面是另外的一个C实现。