结构
一个节点,有两个向左子树指和右子树指的指针。
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
优先左 其次头 最后右
直接到最下面的4 4没有左节点 弹出并打印 回到2
2没有除了4的左节点了 弹出并打印 去到5
5没有左节点.......
那遍历顺序应该是
4 2 5 1 6 3 7
那非递归思路应该这样做:
整条左边界依次入栈 1、 2、 4
没有左节点了,就弹出并打印。再去看有没有右节点
右节点弹出并打印....
上代码
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++
设置变量
curend 现在弹出节点所在的层中最右的节点
nextend 下一层最右节点
cur 用来记录现在到了第几个节点
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