二叉查找树的C语言实现(二)
接着上次的话题。这次我们要讨论,二叉查找树的中序遍历和后序遍历(递归和非递归),另外还有先序遍历(非递归)
1.中序遍历(递归)
static void __in_order(struct bnode_info *bnode,
void (*todo)(struct bnode_info *bnode))
{
if (bnode != NULL)
{
__in_order(bnode->lchild, todo);
todo(bnode);
__in_order(bnode->rchild, todo);
}
} static void btree_in_order(struct btree_info *info,
void (*todo)(struct bnode_info *bnode))
{
assert(todo != NULL && info != NULL); __in_order(info->root, todo);
}
其实很简单了,就是把语句的顺序换一换就可以了。
--in_order--
5 16 24 26 50 80
这是运行的结果。有没有发现,数字是按从小到大排序的!如果一棵二叉查找树的节点值是数值,那么中序遍历的结果为升序排列的数组。
2.中序遍历-非递归
static void btree_in_order_norecur(struct btree_info *info,
void (*todo)(struct bnode_info *bnode))
{
assert(info != NULL && todo != NULL); if (btree_is_empty(info))
return ; //栈空间的创建
struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info)); assert(stack != NULL);
//栈初始化
stack_init(stack); struct bnode_info *cur = info->root; while (!stack->is_empty(stack) || cur != NULL)
{
if (cur != NULL)
{
stack->push(stack, &cur, sizeof(cur));
cur = cur->lchild;
}
else
{
stack->pop(stack, &cur, sizeof(cur));
todo(cur);
cur = cur->rchild;
}
} stack_destroy(stack);
free(stack);
}
思路是创建一个栈,顺着树根,一直往左边的节点走,一路压栈,走到最左边的那个节点,也压栈。这时候当前节点为NULL,开始出栈,弹出的这个元素就是子树的树根,todo(cur), 然后遍历右子树。对于右子树,也是同样的方法,一路向左,依次压栈......
3.先序遍历-非递归
static void btree_pre_order_norecur(struct btree_info *info,
void (*todo)(struct bnode_info *bnode))
{
assert(info != NULL && todo != NULL); if (btree_is_empty(info)) {
return ;
} //栈空间的创建
struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info)); assert(stack != NULL);
//栈初始化
stack_init(stack); struct bnode_info *cur = info->root; while (!stack->is_empty(stack) || cur != NULL) {
if (cur != NULL) {
todo(cur);
stack->push(stack, &cur, sizeof(cur));
cur = cur->lchild;
}else{
stack->pop(stack, &cur, sizeof(cur));
cur = cur->rchild;
}
} stack_destroy(stack);
free(stack);
}
其实和上面的代码类似,也是用了栈。首先cur指向树根,既然是先序,直接todo(cur); 之后要把当前节点压栈,因为后面还要利用这个节点找到它的右子树,再然后 cur = cur->lchild, 也就是遍历左子树,一直向左,直到左子树为空,这时候就遍历右子树,于是出栈一个节点,得到他的右孩子,进入下一轮循环。
4.后序遍历--非递归
后序遍历的非递归思路应该是所有顺序中最难的一个了。
错误的思路介绍:
1.首先一路向左,把从树根开始,到树根的左孩子,再到左孩子的左孩子,全部压栈;
2.接着,取栈顶的元素,看看他有没有右孩子,转到3或者4;
3.如果没有,就弹出这个元素,并且打印(也可以是别的操作)这个元素,然后回到2;
4.如果有,就把右孩子作为新的树根,回到1;
以上的思路是错误的,为什么呢?我们结合代码来说明,下面请看错误的代码。
static void btree_post_order_norecur_wrong(struct btree_info *info,
void (*todo)(struct bnode_info *bnode))
{
assert(info != NULL && todo != NULL); if (btree_is_empty(info)) {
return ;
} //栈空间的创建
struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info)); assert(stack != NULL);
//栈初始化
stack_init(stack); struct bnode_info *cur = info->root;
struct bnode_info *pre = NULL; while (!stack->is_empty(stack) || cur != NULL)
{
if (cur != NULL)
{
stack->push(stack, &cur, sizeof(cur));
todo(cur);
printf("in stack\n");
cur = cur->lchild;
}
else
{
stack->top(stack, &cur, sizeof(cur));
todo(cur);
printf("get top stack\n"); if (cur->rchild != NULL)
{
todo(cur);
printf("his rchild not null\n");
cur = cur->rchild;
}
else
{
stack->pop(stack, &cur, sizeof(cur));
todo(cur);//这个是真正的打印,其他地方只是为了打印日志。
printf(" -----ok \n");
cur = NULL;//for out stack continue
}
}
} stack_destroy(stack);
free(stack); }
为了说明清楚,我加了很多打印的地方。运行结果如下(括号里面的是我的解释)
----wrong:post_order_norecur---
50 in stack
24 in stack
16 in stack
5 in stack
5 get top stack
5 ok (这个是对的)
16 get top stack
16 ok (这个是对的)
24 get top stack (24第一次 get top)
24 his rchild not null
26 in stack
26 get top stack
26 ok (这个是对的)
24 get top stack (24第二次 get top, 其实他的右子树已经遍历了,可是后面发现又开始遍历右子树)
24 his rchild not null
26 in stack
26 get top stack
26 ok
24 get top stack(24第三次 get top, 后面再次遍历右子树)
24 his rchild not null
……
……
后面的日志太长了,因为陷入了死循环。程序就是反复地 取栈顶元素24,遍历他的右子树
分析到这里,我们看出了症结,第一次取栈顶元素24没有错,是为了遍历他的右子树。第二次取的时候,右子树已经遍历过了,这时候就应该让24出栈,并且打印。
可是怎么知道栈顶元素的右子树是否已经遍历过了?方法有很多,这里我们采用这样一种方法:根据后序遍历的特性,如果24是第一次取(就是get top 的操作)且右孩子不为空,那么最近遍历的元素,一定不是24的右孩子(因为还没有遍历);如果24是第二次取,也就是说他的右子树遍历过了,那么最近遍历的那个元素,一定是24的右孩子。
所以,我们把上面的错误思路改一下:
1.从树根开始,到树根的左孩子,再到左孩子的左孩子,全部压栈;
2.接着,取栈顶的元素,看看他有没有右孩子,没有就转到3,有就转到4;
3.弹出这个元素,并且打印(也可以是别的操作)这个元素,还要记录这个元素作为最近打印的元素,然后回到2;
4.再看看他的右孩子是否等于最近打印的元素,等于转到5 , 不等于转到6;
5.说明他的右子树已经遍历了,转到3;
6. 把右孩子作为新的树根,回到1;
看看正确的代码吧:
static void btree_post_order_norecur(struct btree_info *info,
void (*todo)(struct bnode_info *bnode))
{
assert(info != NULL && todo != NULL); if (btree_is_empty(info))
return ; //栈空间的创建
struct stack_info *stack = (struct stack_info *)malloc(sizeof(struct stack_info));
assert(stack != NULL);
//栈初始化
stack_init(stack); struct bnode_info *cur = info->root;
struct bnode_info *pre = NULL; // 为了记录最近打印的节点 while (!stack->is_empty(stack) || cur != NULL)
{
if (cur != NULL)
{
stack->push(stack, &cur, sizeof(cur));
cur = cur->lchild; //一路向左压栈
}
else
{
stack->top(stack, &cur, sizeof(cur));
if ((cur->rchild != NULL) && (cur->rchild != pre)) //存在右子树,且右子树没有遍历
{
cur = cur->rchild; //遍历右子树
}
else // 没有右孩子或者右子树已经遍历的情况
{
stack->pop(stack, &cur, sizeof(cur));
todo(cur);
pre = cur;//更新最近打印的节点
cur = NULL;
}
}
} stack_destroy(stack);
free(stack);
}
运行后得到正确的结果:
5 16 26 24 80 50
===============================================================================================================================
5.后序遍历--递归
这个很简单,其实递归遍历思路都一样,先序中序后序的区别就是在于 todo(bnode) 语句的位置不一样。直接上代码:
static void __post_order(struct bnode_info *bnode,void (*todo)(struct bnode_info *bnode))
{
if (bnode != NULL)
{
__post_order(bnode->lchild, todo);
__post_order(bnode->rchild, todo);
todo(bnode);
}
} static void btree_post_order(struct btree_info *info,
void (*todo)(struct bnode_info *bnode))
{
__post_order(info->root, todo);
}
今天就说到这里,还有一些内容,下次再见!
二叉查找树的C语言实现(二)的更多相关文章
- 使用C语言实现二维,三维绘图算法(1)-透视投影
使用C语言实现二维,三维绘图算法(1)-透视投影 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其实想想 ...
- 使用C语言实现二维,三维绘图算法(3)-简单的二维分形
使用C语言实现二维,三维绘图算法(3)-简单的二维分形 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其 ...
- 使用C语言实现二维,三维绘图算法(2)-解析曲面的显示
使用C语言实现二维,三维绘图算法(2)-解析曲面的显示 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其 ...
- Swift语言指南(二)--语言基础之注释和分号
原文:Swift语言指南(二)--语言基础之注释和分号 注释 通过注释向自己的代码中注入不可执行的文本,作为你自己的笔记或提示.Swift编译器运行时会忽略注释. Swift的注释与C语言极其相似,单 ...
- #r语言(二)笔记
#r语言(二)笔记 #早复习 #概述:R是用于统计分析.绘图的语言和操作环境 #对象: #数据类型--统称为对象 #向量(vector):用于存储数值型.字符型或逻辑型数据的一维数组. #定义向量: ...
- iOS开发之SQLite-C语言接口规范(二) —— Prepared Your SQL Statements
在<SQLite的C语言接口规范(一)>中介绍了如何去连接打开数据库,本篇博客就介绍如何操作数据库,本篇主要给出了如何执行数据库查询语句(Select), 然后遍历结果集.本篇博客就直接使 ...
- 遗传算法的C语言实现(二)-----以求解TSP问题为例
上一次我们使用遗传算法求解了一个较为复杂的多元非线性函数的极值问题,也基本了解了遗传算法的实现基本步骤.这一次,我再以经典的TSP问题为例,更加深入地说明遗传算法中选择.交叉.变异等核心步骤的实现.而 ...
- C语言 预处理二(宏定义--#define)
//#define 宏定义(宏定义一般大写) //知识点一-->#define的作用域:从#define开始,从上往下,如果遇到#undef就到#undef处结束,如果没有就是作用于当前整个文件 ...
- C语言之二维数组
二维数组 还是一个数组,只不过数组中得每一个元素又是一个数组 1). 声明语法 类型 数组名[行][列]; 例: int nums[2][3];//2行3列的二维数组,保存的数据类型是int类型 c ...
随机推荐
- 小 M 的算式(dfs)
[问题描述]小 M 在做数学作业的时候遇到了一个有趣的问题:有一个长度为 n 的数字串 S,小 M 需要在数字之间填入若干个“+”和恰好一个“=”,使其成为一个合法的等式.如对于 S=“2349”,可 ...
- JavaAppArguments示例
本实验要求编写一个程序,此程序从命令行接收多个数字,求和之后输出结果.一大难点是命令行参数都是字符串,必须先将其转化为数字,才能相加. 中心想法就是将求和数字转换为整型并依次相加. 程序流程图: pu ...
- NSNumber数字
前言 将基本数据类型包装成 OC 对象 1.NSNumber 与 基本数据类型 的相互转换 // 基本数据类型 转 NSNumber // 对象方法,将整形数据转换为 OC 对象 NSNumber * ...
- java基础之变量和常量、类型转换
一. 变量 变量是可改变的量,每赋个值便会开辟一个新内存地址. 1.首先,变量需要一个声明,例如:int a,这个a也可以当作是一个标签,它指向了一个内存地址,这个地址是属于int类型的套餐, ...
- 想要转行/入行做产品经理,你得先get这些正确姿势
转自:https://mp.weixin.qq.com/s/Bh9QEihdV1JLWwB5I4VJ7Q 参考: 张小龙首次公开演讲(官方无删减版) 前腾讯高级产品经理:如何用“女性思维”做更好的产品 ...
- 【转】asp使用母版页时内容页如何使用css和javascript
源地址:https://www.cnblogs.com/accumulater/p/6767138.html
- linux下redis的安装与django-redis使用方法
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set( ...
- P2900 [USACO08MAR]土地征用Land Acquisition
\(\color{#0066ff}{ 题目描述 }\) 约翰准备扩大他的农场,眼前他正在考虑购买N块长方形的土地.如果约翰单买一块土 地,价格就是土地的面积.但他可以选择并购一组土地,并购的价格为这些 ...
- Xcode的编辑利器Xvim,如何去掉烦人工具栏和文件路径
最近网上看到了一篇关于Xcode的编辑利器,因为以前做FPGA工作时候在ISE SDK下用过vim作为编辑器,所以深知vim的强大,所以安装Xvim: 在安装之后遇到一些配置问题,因为本来就完美控制, ...
- arcgis打印服务
<script> function print1() { require([ "esri/map", ...