小白学C语言数据结构之二叉树

结构

一个节点,有两个向左子树指和右子树指的指针。

typedef struct Node{   //二叉树节点
    int value;    //自己储存的值
    struct Node* left;   //指向左子树的指针
    struct Node* right;  //指向右子树的指针 
}

最顶的点叫头结点Head,没有左右子节点的节点叫叶节点。

递归序遍历

先上代码

void bianli(Node* head){
    if(head == NULL){
        return;
    }
    bianli(head->left);
    bianli(head->right);
}

如名字所起的一样,用递归的方式来进行遍历。

从头结点开始,到左树的尽头

如 1

2 3

4 5 6 7

遍历的过程就是

1->2->4->4(因为4的左树为NULL,所以返回4)->4>2->5->5->5->2->1->3->6->6->6->3->7->7->7->3->1

神奇的来了

如果取每个数第一次出现的顺序,就是

1 2 4 5 3 6 7

如果取每个数第二次出现的顺序,就是

4 2 5 1 6 3 7

如果取每个数第三次出现的顺序,就是

4 5 2 6 7 3 1

这分别对应了

前序遍历、中序遍历和后序遍历。(左神nb)

所以前序、中序和后序遍历的区别只有一个:打印行为在代码中的位置。

void bianli(Node* head){
    if(head == NULL){
        return;
    }
    //printf("%d", head.value)  先序遍历  头 左 右
    bianli(head->left);
    //printf("%d", head.value)  中序遍历  左 头 右
    bianli(head->right);
    //printf("%d", head.value)  后序遍历  左 右 头
}

不用递归的遍历

递归的遍历代码很简单(菜鸟如我其实还不能完全理解递归)

所有递归行为都可以转化为非递归的过程(硬学的一句话,我也不懂为什么这么干)

所以折磨自己的事情来了!不用递归的遍历。

先序遍历

先说思路:

1.准备一个栈, 把头结点存进去, 再弹出并打印

2.因为是先序遍历,输出节点是先左再右,而栈是先进后出的次序。所以要实现先压入右节点,再压入左节点。

3.继续周而复始

还是刚才那棵小红树

如 1

2 3

4 5 6 7

第一步,头结点1存进去,print 1

第二步,压3、压2进栈。

第三步,弹出2(因为2后进栈先出来),再压5、压4。

第四步,把4弹出来,再把4的右节点压进去,左节点压进去。但是5和4明显是没有子节点了,没事发生。

第五步,2的子节点都弹完了,开始弹3,压7压6,弹6弹7。

最终,遍历结果出来了 1 2 4 5 3 6 7,先序遍历。

思路掰扯清楚了,开始写代码(

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//真麻烦 想疯
struct TreeNode{   //
    int value;
    struct TreeNode* left; //左子树
    struct TreeNode* right; // 右子树
}
struct StackNode{
    struct TreeNode* node; //相当于栈里存储的值是一个TreeNode类型的指针node
    struct StackNode* next; //指向下一节点的指针
}
struct Stack{         //定义栈结构体
    struct StackNode* top;
}   
//然后写栈的几个经典操作, push pop和 is_empty
void push(struct Stack* stack, struct TreeNode* node){
    struct StackNode* newnode = (struct StackNode*)malloc(sizeof(struct StackNode)
    newnode->node = node;
    newnode->next = stack->top;
    stack->top = newnode;
}//入栈
struct TreeNode* pop(struct Stack* stack){
    if(stack->top ==NULL){
        return NULL;
    }
    struct StackNode* topnode = stack->top; 
    struct TreeNode* node = topnode->node;
    stack->top = top->next;
    free(topnode);
    return node;
} //出栈 返回头节点
bool is_empty(struct Stack* stack){
    return stack->top ==NULL;
} //判断栈是否为空
//基础操作写完了,开始实现上面的思路
void preorder(struct TreeNode* root){
    if(root==NULL){
        return;
    }
    struct Stack* stack = (struct Stack*)malloc(sizeof(struct Stack));
    stack->top = NULL;          //建立一个空栈
    push(stack, root);          //先把根节点push进栈里头 相当于1进去了
    while(!is_empty(stack)){
        struct TreeNode* node = pop(stack);   //回到这一步 弹出2,作为新的node 再去遍历2的子节点
        printf("%d  ", node->value);                      |
        if(node->right != NULL){   //3压进栈里了           |
            push(stack, node->right);                     |
        }                                                 |
        if(node->left != NULL){   //2压进栈里了  ——————————   
            push(stack, node->left);
        }
    }
    free(stack);
} //再梳理一下操作 传进去的参数是树的根节点

后序遍历

为什么先说后续遍历,因为这里有个骚操作

后序遍历是:左 右 根

先序遍历是:根 左 右

先序遍历实现的方式是: 弹出root、压入right、压入left、弹出root'(之前的left)

那想实现 根右左就很容易: 弹出root、先压入left、再压入right、弹出root'(之前的right)

代码就是把上面的复制粘贴一下,在preorder中把left和right位置互换即可。

这样就得到了 根 右 左的遍历方式。

而我们的目的是 左 右 根

那搞一个收集栈。把之前的 弹出并打印行为 改成 弹出并放进收集栈。收集好了再弹出。根据栈后进先出的性质,就能实现一个逆序。

上代码~

void postorder(struct TreeNode* root){
    if(root==NULL){
        return;
    }
    struct Stack* stack1 = (struct Stack*)malloc(sizeof(struct Stack));
    stack1->top = NULL;          //建立一个空栈
    struct Stack* stack2 = (struct Stack*)malloc(sizeof(struct Stack));
    stack2->top = NULL;          //建立一个收集栈

    push(stack1, root);          
    while(!is_empty(stack1)){
        struct TreeNode* node = pop(stack1);
        //这里把主栈的节点push进收集栈  
        push(stack2, node);                     
        if(node->left != NULL){            
            push(stack, node->left);                     
        }                                                
        if(node->right != NULL){     
            push(stack, node->right);
        }
    }
    //整体收集完了,单独打印
    while(!is_empty(stack2)){
         struct TreeNode* node2 = pop(stack2);
        printf("%d  ", node2->value);
    }   
    free(stack1);
    free(stack2);
}   

可以看出,和先序遍历代码几乎一样。除了左右顺序改变和打印方式改变,没有区别。deal~

中序遍历

思路:左 根 右

又是刚才那颗小红树

如 1

2 3

4 5 6 7

优先左 其次头 最后右

  1. 直接到最下面的4 4没有左节点 弹出并打印 回到2

  1. 2没有除了4的左节点了 弹出并打印 去到5

  1. 5没有左节点.......

那遍历顺序应该是

4 2 5 1 6 3 7

那非递归思路应该这样做:

  1. 整条左边界依次入栈 1、 2、 4

  1. 没有左节点了,就弹出并打印。再去看有没有右节点

  1. 右节点弹出并打印....

上代码

void midorder(struct TreeNode* root){
    if(root == NULL){
        return;
}
    struct Stack* stack = (struct Stack*)malloc(sizeof(stack));
    stack->top = NULL;
    //前面都一样 建空栈

    //先遍历到树左边的底部
    while(!is_empty(stack)|| root!= NULL){
        if(root != NULL){
            push(stack, root);        //把根扔进栈底
            root = root->left;        //一直到底部
        }
        else{
            root = pop(stack);        //没有左树了,弹出来
            printf("%d  ", root->value);
            root = root->right;        //来到右树继续重复条件1 
        }
        free(stack);
    } 

deal!

按层遍历

常规操作

如 1

2 3

4 5 6 7

又是这颗小红树

按层便利应该是 1234567

那就是放什么进去就出什么---所以用队列就简单完成~

上代码

struct TreeNode{
    int value;
    struct TreeNode* right;
    struct TreeNode* left;
}//树节点
struct QueueNode{
    struct TreeNode* node;  //用来存储treenode类型的数据
    struct QueueNode* next; //指向下一个节点的指针
}
struct Queue{
    //跟建栈一样
    struct QueueNode* front;  //队首元素
    struct QueueNode* rear; //队尾元素
}

//入队
void enqueue(struct Queue* queue, struct TreeNode* node){
    struct QueueNode* newnode = (struct QueueNode*)malloc(sizeof(struct QueueNode));
    //从队尾搞进去
    newnode->node = node;
    newnode->next = NULL;
    if(queue->rear ==NULL){
        queue->front = queue->rear = newnode;
    }
    else{
        queue->rear->next = newnode;  //队尾指针的next指针指向newnode
        queue->rear = newnode;
}
//出队
TreeNode* dequeue(Queue* queue){
    if(queue->front ==NULL){      //检查是否为NULL 
        return NULL;
    }
    TreeNode* node = queue->front->node;     
    QueueNode* temp = queue->front;
    queue->front = queue->front->next;
    if(queue->front ==NULL){
        queue->rear =NULL;         //队列已经为空了
    }
    free(temp);
    return node;
}
bool is_empty(struct Queue* queue){
    return queue->front ==NULL;
}
void levelorder(TreeNode* root){
    if(root ==NULL){
        return;
    }
    struct Queue* queue =(struct Queue*)malloc(sizeof(struct Queue));
    queue->rear = NULL;
    queue->front =NULL;// 先搞个空队列
    enqueue(queue, root);  //根节点传到队尾去
    //开始遍历
    while(queue->!=NULL){
        TreeNode* node = dequeue(queue);
        printf("%d  ", node->value);
        if(node->left !=NULL){
            enqueue(queue, node->left);
        }
        if(node->right !=NULL){
            enqueue(queue, node->right);
        }
     }
}

求最大宽度

刚才那种遍历,只能实现打印操作。

如果题目问:求树的最大宽度,或是求树当前在哪层,就无法实现。

所以就设计一个机制,用于发现当前层的结束/下一层的开始。

先考虑思路:

寻找每层最右节点(最后一个节点)

然后记录 再看节点数量 让max++

设置变量

  1. curend 现在弹出节点所在的层中最右的节点

  1. nextend 下一层最右节点

  1. cur 用来记录现在到了第几个节点

  1. max 用来记录最大值

又是小红树

如 1

2 3

5 6 7

一开始curend = 1

nextend=NULL

这时候1节点进队列

dequeue 1节点。 此时让2 3分别进队列

这时候nextend 先等于2 3进去后nextend变成3

这时候 相当于第一层结束了。 统计一下 cur=1,所以max=1

把nextend拷贝到curend 现在curend =3

到第二层,先弹出队列front 节点2

然后开始看2的左右子树 没有左子树 有右子树5

就让5进队列 然后5变成新的nextend....

int width(struct TreeNode* root){
    if(root==NULL){
        return 0;
    }    
    struct Queue* queue = (struct Queue*)malloc(sizeof(struct Queue));  
    queue->front = NULL;
    queue->rear = NULL;       //初始化一个队列
    int max_width=1;                  //记录最大宽度
    enqueue(queue, root);
    struct TreeNode* curend = root;
    struct TreeNode* nextend = NULL;
    int cur=0;                  //计数
    while(!is_empty(queue)){
        TreeNode* node = dequeue(queue);       
        cur++;
        // 左右节点开始入队
        if(node->left != NULL){
            enqueue(queue, node->left);
            nextend = node->left;
        }
        if(node->right != NULL){
            enqueue(queue, node->right);
            nextend = node->right;
        }
        if(node == curend && !is_empty(queue)){
            curend = nextend;
            if(cur >= max_width){
                max_width = cur;
            }
            cur = 0;
            nextend = NULL;
         }
    }
    if(cur>=max_width){
        max_width = cur;
    }
    return max_width;
}

序列化和反序列化

为了关机之后再打开还能变成树...听起来大概是这个意思

序列化

两颗新的小红树

1 1

1 1

1 1

无论是什么遍历法,都是1 1 1,这样不可能还原回原来的树。

这时候!!递归序就有用了

把所有的空也记住!

比如左边这棵树 先序遍历法给他序列化

1->1->null->1->null->null-null

右边这棵树

1->null->1->1->null->null->null

void serialize(TreeNode* root, FILE* fp) {
    if (root == NULL) {
        fprintf(fp, "# ");  // #表示空节点
    } else {
        fprintf(fp, "%d ", root->val);
        serialize(root->left, fp);         
        serialize(root->right, fp);
    }
}
//这里面用的文件指针fp 凑合看 还是递归~

反序列化

用什么方式序列化,就用什么方式反序列化

TreeNode* deserialize(FILE* fp) {
    int val;
    if (fscanf(fp, "%d ", &val) == EOF || val == '#') {
        return NULL;
    }
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = deserialize(fp);
    node->right = deserialize(fp);
    return node;
}

不难,每次怼回去一个节点~

按层遍历的序列化

整体上就是宽度优先遍历

同样,还是不忽略空,然后按层写

总结

这边最后其实还是有点问题的,比如建节点乱七八糟的。左神用java写的感觉比c语言简洁好多...

C好麻烦TAT

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值