二叉树的创建以及遍历C语言
二叉树的C语言创建以及遍历
今天在看二叉树相关的知识,于是乎便想实现一下,接下来给上我的代码
首先是bitree.h
/*
* Description: releated data struct
* author: badapple
* file: bitree.h
*/
#include <stdio.h>
#include <stdlib.h>
typedef struct bi_node //二叉树节点定义以及别名
{
char data;
struct bi_node *lchild;
struct bi_node *rchild;
}bitree, binode;
sttic void bitree_create(bitree ** t) //用前序递归创建一棵二叉树
{
char data;
binode *node;
node = *t;
fflush(stdin);
scanf("%c", &data);
if(data == "#")
node = NULL;
else
{
node = (binode *)malloc(binode);
node->data = data;
bitree_create(&(node->lchild));
bitree_create(&(node->rchild));
}
//以上是网上的版本,这样创建的二叉树在这个子函数里是存在,但是当你主函数*bitree永远是空,不知道其他博主怎么能创建一棵树的
*t = node; //添加的代码
}
为了让代码可读性强点,又创建了bitree_recursion.h,里面放递归算法实现的遍历函数
/*
* Description: use recursion to traverse a binary tree
* author: badapple
* file: bitree_recursion.h
*/
#include "bitree.h"
stattic void preorder_traverse(bitree *t) //DLR
{
if(t)
{
printf("%-2c ", t->data);
preorder_traverse(t->lchild);
preorder_traverse(rchild);
}
}
stattic void inorder_traverse(bitree *t) //LDR
{
if(t)
{
preorder_traverse(t->lchild);
printf("%-2c ", t->data);
preorder_traverse(rchild);
}
}
stattic void postorder_traverse(bitree *t) //LRD
{
if(t)
{
preorder_traverse(t->lchild);
preorder_traverse(rchild);
printf("%-2c ", t->data);
}
}
测试文件test.c
#include "bitree_recursion.h"
int main()
{
bitree *t;
t = NULL;
create_bitree(&t);
preorder_traverse(t);
putchar(10);
inorder_traverse(t);
putchar(10);
postorder_traverse(t);
putchar(10);
return 0;
}
测试的二叉树如图,结果如图所示
如果将bitree.h中注释添加的代码,即
//*t = node
那么结果如下OK上述都是其中的插曲~,主要问题是遍历算法的非递归实现
编辑bitree.h,加入以下代码
typedef struct stack_node //定义栈结构体
{
bitree *data; //栈存储的元素
struct stack_node *next;
}stack;
static stack *stack_init() //栈初始化函数
{
stack *s;
s = (stack *)malloc(sizeof(stack));
s->data = NULL;
s->next = NULL;
return s;
}
static void stack_push(stack *s, binode *node) //入栈操作
{
stack *pos, *tmp;
tmp = (stack *)malloc(sizeof(stack)); //新建栈节点
tmp->data = node; //将二叉树节点存入到栈节点内
tmp->next = NULL;
if(!s->next) //如果栈空
s->next = tmp;
else
{
pos = s->next; //使用pos暂存现在的栈顶元素
s->next = tmp; //将tmp作为新的栈顶元素
tmp->next = pos; //将原先的栈顶元素链接到现在的栈顶元素后面
}
}
static binode *stack_get(stack *s) //获取栈顶元素的值
{
if(!s->next)
return NULL;
return s->next->data;
}
static void stck_pop(stack *s) //出栈操作
{
stack *s;
if(!s->next) //如果栈空
return;
pos = s->next; //暂存栈顶元素
s->next = pos->next; //将栈顶元素指向原栈顶元素的下一位
free(pos); //释放原先的栈顶元素
return;
}
OK,关于栈的操作已写好,接下来就用非递归方式实现遍历
新建bitree_no_recursion.h
#include "bitree_no_recursion.h"
static void preorder_traverse_no_recursion(bitree *t) //非递归先序遍历
{
stack *s; //声明栈
bitree *pos; //定义遍历指针
s = stack_init(); //初始化栈,返回栈顶指针
pos = t; //将二叉树root赋值给遍历指针
while(pos || s->next) //当树非空或者栈非空
{
if(pos) //如果树非空
{
stack_push(s, pos); //将这个树节点压栈
printf("%-2c ", pos->data);
pos = pos->lchild; //遍历指针指向左边的叶子
}
else
{
pos = stack_get(s); //获取栈顶元素
stack_pop(s); //出栈操作
pos = pos->rchild; //指向右边叶子节点
}
}
}
static void inorder_traverse_no)recursion(bitree *t) //非递归中序遍历
{
stack *s; //声明栈
bitree *pos; //定义遍历指针
s = stack_init(); //初始化栈,返回栈顶指针
pos = t; //将二叉树root赋值给遍历指针
while(pos || s->next) //当树非空或者栈非空
{
if(pos)
{
stack_push(s, pos); //将这个树节点压栈
pos = pos->lchild; //遍历指针指向左节点
}
else
{
pos = stack_get(s); //获取栈顶元素
printf("%-2c ", pos->data); //输出
pos = pos->rchild; //遍历指针指向右节点
}
}
}
static void postorder_traverse(bitree *t) //非递归后序遍历
{
stack *s;
bitree *pos, *last_visit;
s = stack_init();
pos = t, last_visit = NULL;
while(pos) //将pos指针指向树的最左边
{
stack_push(s, pos);
pos = pos->lchild;
}
while(s->next) //当栈非空
{
pos = stack_get(s);
stack_pop(s);
if(!(pos->rchild) || pos->rchild == last_visit) //当节点的右节点为空或已被访问
{
printf("%-2c ", pos->data);
last_visit = pos;
}
else
{
stack_push(s, pos);
pos = pos->rchild;
while(pos)
{
stack_push(s, pos);
pos = pos->lchild);
}
}
}
}
修改test.c,测试非递归遍历算法
#include "bitree_no_recursion.h"
int main()
{
bitree *t;
t = NULL;
create_bitree(&t);
preorder_traverse_no_recursion(t);
putchar(10);
inorder_traverse_no_recursion(t);
putchar(10);
postorder_traverse_no_recursion(t);
putchar(10);
return 0;
}
结果如图
总结
这三种遍历算法的非递归实现,其实都是参考的递归的算法。只是递归算法本质上也是栈,只是栈的元素是函数,我们非递归实现就是将栈的元素从函数变成我们的数据结构,从而减少开销
后序遍历相较于其他两个算法稍微难点,举个例子
第一步,我们将遍历指针指向二叉树的最左边,即图中的节点C
第二步,我们想想,后序遍历的顺序是左右根。那么想要遍历某个节点前提便是左右节点都已经遍历过,由于开始的时候我们就已经找到树的最左边即节点C,C的左子树肯定为空。当C的右子树为空或者右子树已经被遍历过,那么我们就可以遍历右子树,即代码
if(!(pos->rchild) || pos->rchild == NULL)
第三步,②情况能理解,可能③情况不理解。当遍历到③这种情况,很像②,但是与②不同的是,E节点的左节点不为空,那么自然不能直接输出E节点。这种情况,我们就将F视作根节点。遍历F的前提是F的左右子树皆遍历过,这不就又回到①情况。
PS
操作系统OS:Ubuntu 18.04 LTS
gcc version:7.3.0
表达能力不是很好,见谅了!