第二十五篇,二叉树的相关操作。

一、二叉树。
1、为什么要学习二叉树?
前面我们都已经学习过链表,大家都知道,如果我们需要检索出来一个数据,有可能检索一次,就可以找到数据,有可能检索到最后一个节点才能找到数据,这就是链表的特点。也就是说,寻找一条长度为N个节点的链表,搜索次数处于1~N-1(头节点无效),二叉树的出现,就是为了减少检索数据时次数。

2、什么是二叉树?
二叉树也是一种存储结构,也就是说跟链表一样,都是用来存储数据的,但是链表是线性规则结构,而二叉树是非线性的存储结构。二叉树有一个根节点作为起点,这个根节点也是用来存放数据。
二叉树结构就是把小于根节点的数据存储到根节点的左边,把大于根节点的数据存储到根节点的右边在,这样就实现了检索时,不需要更多的次数。
例如:根是节点20,根的左边是18,根的右边是22,当我们寻找节点数据为22时,就不需要考虑去跟的左边寻找,只需要去根节点的右边寻找即可,这样每次对比寻找,都可以节点一半节点不需要对比。

3、介绍二叉树概念。
1)双亲与孩子。                             A就是B与C的双亲,  B与C就是A的孩子。
2)兄弟:拥有共同的双亲的两个节点称之为兄弟  B与C是兄弟,D与E也是兄弟
3)度:形容一个节点孩子的个数                A的度是2  D是度是1  H的度是0
4)层:                                     A的层是1  B/C的层是2

二、二叉树的增删改查。
1、设计节点。
经过分析,每一个节点存储数据,所以有数据域,还要有两个指针,分别指向左孩子与右孩子。
struct node{
    int data;
    struct node *lchild;
    struct node *rchild;
};

2、初始化一个新节点。
struct node *init_new_node(int num)
{
    //1. 为新节点申请空间
    struct node *root = malloc(sizeof(struct node));
    if(root == NULL)
        printf("malloc root error!\n");
    
    //2. 为新节点赋值
    root->data = num;
    root->lchild = NULL;
    root->rchild = NULL;
    
    return root;    
}

3、插入节点。
struct node *insert_node(struct node *new,struct node *root)
{
    //1. 判断二叉树中有没有根节点。如果没有,则new作为根节点。
    if(root == NULL)
    {
        return new;  //那么新节点就作为根节点
    }
    
    //2. 如果数根节点,则正常插入节点。
    if(new->data > root->data)
    {
        root->rchild = insert_node(new,root->rchild);
    }
    else if(new->data < root->data)
    {
        root->lchild = insert_node(new,root->lchild);
    }
    else{
        printf("%d node already exists!\n",new->data);
    }
    
    return root;
}

4、搜索节点。

struct node *find_node(struct node *root,int num)
{
    if(root == NULL) //找到底都没有找到你啊
    {
        return NULL;
    }
    
    if(num < root->data) //如果这个数字比root数字要小,则去root的左边寻找。
    {
        return find_node(root->lchild,num);
    }
    else if(num > root->data)  //如果这个数字比root数字要小,则去root的右边寻找。
    {
        return find_node(root->rchild,num);
    }
    else{
        return root;
    }    
}


5、遍历二叉树。
1)先序遍历。
顺序:根  -->  左   --> 右

void show_node_first(struct node *root)
{
    if(root == NULL)
        return;
    
    //1. 打印根节点
    printf("%d\n",root->data); //根
    
    //2. 左
    show_node_first(root->lchild);
    
    //3. 右
    show_node_first(root->rchild);
    
    return;
}

2)中序遍历。
顺序: 左  --> 根  -->  右

void show_node_middle(struct node *root)
{
    if(root == NULL)
        return;
    
    //1. 左
    show_node_middle(root->lchild);
    
    //2. 打印根节点
    printf("%d\n",root->data);
    
    //3. 右
    show_node_middle(root->rchild);
    
    return;
}


3)后序遍历。
顺序:  左   -->  右   -->  根

void show_node_last(struct node *root)
{
    if(root == NULL)
        return;
    
    //1. 左
    show_node_last(root->lchild);
    
    //2. 右
    show_node_last(root->rchild);
    
    //3. 打印根节点
    printf("%d\n",root->data);
    
    return;
}


4)按层遍历。
步骤如下:
1、初始化一条空队列。
2、将根节点入队。
3、出队,如果出队失败,则结束程序,如果成功,则打印该节点。
4、在二叉树中找到出队的那个节点。
5、判断刚刚出队的那个节点有没有左孩子,如果有,则将左孩子入队。
6、判断刚刚出队的那个节点有没有右孩子,如果有,则将右孩子入队。
7、重复第3步。


struct q_node{
    int data;          //节点的数据域
    struct q_node *next; //节点的指针域
};

struct queue{
    struct q_node *head; //指向队头
    struct q_node *tail; //指向队尾
    int size;   //统计当前队列中节点的个数
};

struct queue *init_queue()
{
    //1. 为管理结构体申请空间
    struct queue *q = malloc(sizeof(struct queue));
    if(q == NULL)
        printf("malloc q error!\n");
    
    //2. 为管理结构体赋值
    q->head = NULL;
    q->tail = NULL;
    q->size = 0;
    
    return q;
}

    
int in_queue(struct queue *q,int num)
{
    //1. 为新节点申请空间
    struct q_node *new = malloc(sizeof(struct q_node));
    if(new == NULL)
        printf("malloc new error!\n");
    
    //2. 为新节点赋值
    new->data = num;
    new->next = NULL;
    
    //3. 分情况讨论
    if(q->size == 0)  //如果插入的节点是第一个节点,则需要特殊处理
    {
        //如果队列中只有你一个人,那么队头队尾都是你。
        q->head = new;
        q->tail = new;
    }
    else{
        q->tail->next = new;  //原来的那个队尾的指针域指向这个新节点
        q->tail = new;        //队尾就是指向这个新节点
    }
    
    q->size++;
    
    return 0;
}


int out_queue(struct queue *q,int *ret)
{
    //1. 如果是空队,就不用再出队了
    if(q->size == 0)
        return -1;
    
    //2. 不为空,正常出队
    struct q_node *tmp = q->head;  //这个tmp指向的节点就是待会要出队的节点
    
    //3. 分情况讨论
    if(q->size == 1)  //如果整个队列中只有一个节点
    {
        q->head = NULL;
        q->tail = NULL;
    }
    else{
        q->head = q->head->next;
    }
    
    //4. 保存以下将要释放的节点的数据
    *ret = tmp->data;
    
    //5. 释放tmp
    free(tmp);
    
    //6. 节点个数-1
    q->size--;
    
    return 0;
}

int show_node_level(struct node *root)
{
    //0. 判断二叉树是否为NULL
    if(root == NULL)
        return -1;
    
    //1、初始化一条空队列。
    struct queue *q = NULL;
    q = init_queue();
    
    //2、将根节点入队。
    in_queue(q,root->data);
    
    int a;
    struct node *tmp = NULL;
    while(1)
    {
        //3、出队,如果出队失败,则结束程序,如果成功,则打印该节点。
        if(out_queue(q,&a) == -1)  //a就是出队的元素
        {
            break;  //跳出循环
        }
        printf("%d\n",a);
        
        //4、在二叉树中找到出队的那个节点。
        tmp = find_node(root,a);
        
        //5、判断刚刚出队的那个节点有没有左孩子,如果有,则将左孩子入队。
        if(tmp->lchild != NULL)
            in_queue(q,tmp->lchild->data);
        
        //6、判断刚刚出队的那个节点有没有右孩子,如果有,则将右孩子入队。
        if(tmp->rchild != NULL)
            in_queue(q,tmp->rchild->data);
        
        //7、重复第3步。    
    }
    
    free(q);

    return 0;
}

6、删除节点。
1)如果需要删除的节点有左子树(不管有没有右子树),其方法是将左子树中最大值替换掉该节点。

第一步:通过递归寻找到需要删除的节点。
第二步:找到删除的那个节点的左子树的最大值。
第三步:将这个最大值替换需要删除的节点。
第四步:通过调用删除函数,递归地去删除这个最大值。(不能直接删除,需要递归删除,因为这个节点虽然肯定没有右子树(因为如果有右子树,该节点就不是最大的),但是有可能有左子树)

2)如果需要删除的节点只有右子树,其方法就是把右子树中最小值替换该节点。

第一步:通过递归寻找到需要删除的节点。
第二步:找到删除的那个节点的右子树的最小值。
第三步:将这个最小值替换需要删除的节点。
第四步:通过调用删除函数,递归地去删除这个最小值。(不能直接删除,需要递归删除,因为这个节点虽然肯定没有左子树(因为如果有左子树,该节点就不是最小的),但是有可能有右子树)

3)如果需要删除的节点的度为0,则直接删除。

第一步:通过递归寻找到需要删除的节点。
第二步:直接调用free()释放该节点的空间。


------------------------------------------------------
struct node *delete_node(struct node *root,int num)
{
    //1. 找到底都没有找到该节点,则直接返回NULL。
    if(root == NULL)
        return NULL;
    
    //2. 通过递归寻找到需要删除的节点。
    if(num < root->data)  //应该继续去左边找
    {
        root->lchild = delete_node(root->lchild,num);
    }
    else if(num > root->data)  //应该继续去右边找
    {
        root->rchild = delete_node(root->rchild,num);
    }
    else{  //找到你想删除的节点,这个节点就是root
        //3. 判断需要删除的节点有没有左子树
        struct node *tmp = NULL;
        if(root->lchild != NULL)  //不为NULL,说明有左子树
        {
            //4. 寻找该节点左子树的最大值。
            for(tmp=root->lchild;tmp->rchild!=NULL;tmp=tmp->rchild);
            
            //从循环出来时,tmp就是左子树的最大值。
            
            //5. 将tmp指向数据赋值给需要删除的节点的数据域。
            root->data = tmp->data;
            
            //6. 递归删除这个tmp
            root->lchild = delete_node(root->lchild,tmp->data);    
        }
        else if(root->rchild!=NULL)  //不为NULL,说明有右子树
        {
            //4. 寻找该节点右子树的最小值。
            for(tmp=root->rchild;tmp->lchild!=NULL;tmp=tmp->lchild);
            
            //从循环出来时,tmp就是右子树的最小值。
            
            //5. 将tmp指向的数据赋值给需要删除的节点的数据域
            root->data = tmp->data;
            
            //6. 递归删除这个tmp
            root->rchild = delete_node(root->rchild,tmp->data);
        }
        else{
            free(root);
            return NULL;  //最后的节点释放掉之后,一定要返回一个NULL给指向这个最后的节点的左/右指针。    
        }
    }
    
    return root;    
}
----------------------------------------------------

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肖爱Kun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值