二叉查找树C语言实现

今天练习一下二叉查找树的东东,感觉这个东西,找前驱和后继结点有些绕,再通过后继结点实现删除,更需要好好想一想流程。参照算法导论经典,经过自己的思考,将其中提到的一些基本操作实现了。说明都在注释里呢,这边迷糊的童鞋可以看看,好好想一下。 (我写的都是naive版本的代码,仅供理解算法本身)
/*一个关键字元素不重复的二叉查找树及其典型操作的实现,记录关键字出现次数*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>

#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<len;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<N;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<N;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实现。

http://baike.baidu.com/view/389459.htm

http://www.cnblogs.com/xiaosuo/archive/2010/02/10/1656183.html

http://blog.sina.com.cn/s/blog_76871c5e0100s5xj.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值