二叉查找树的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 ...
随机推荐
- pthread中如何追踪stack over flow
通常在程序挂掉的时候我们会catch 他们挂掉的signal,然后在signal中打印出当时的一个stack,来方便问题调查, 但是在stack overflow的情况发生时,会没有拿到stack.s ...
- 单击GridView控件,高亮单击所在的记录行
看过下面博文的网友,也许都会觉得有点遗憾,就是很难知道自己点击的是哪一记录行.http://www.cnblogs.com/insus/p/3211017.html 针对这个问题Insus.NET再对 ...
- L - Large Division (大数, 同余)
Given two integers, a and b, you should check whether a is divisible by b or not. We know that an in ...
- [SinGuLaRiTy] 图论题目复习
[SInGuLaRiTy-1027] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. [Vijos 1423] 最佳路线 题目描述 年久失修的 ...
- Oracle PL/SQL编程语法
--plsql块结构,计算a,b的和 declare a ; b ; c int; begin c:=a+b; dbms_output.put_line(c); end; --%type数据类型,输出 ...
- (multi)set的某些操作
(multi)set的某些操作 我们可以把multiset当作平衡树用~ 注意,必须定义小于运算符. s.begin() 返回指向第一个元素的迭代器. s.end() 返回指向最后元素的后面那个虚拟元 ...
- 洛谷P5206 [WC2019] 数树(生成函数+容斥+矩阵树)
题面 传送门 前置芝士 矩阵树,基本容斥原理,生成函数,多项式\(\exp\) 题解 我也想哭了--orz rqy,orz shadowice 我们设\(T1,T2\)为两棵树,并定义一个权值函数\( ...
- Python列表删除的三种方法
1.使用del语句删除元素 >>> i1 = ["a",'b','c','d'] >>> del i1[0] >>> prin ...
- ubuntu14.04 搭建samba
1.安装软件 sudo apt-get remove libwbclient0 sudo apt-get remove samba-common sudo apt ...
- Qt 学习之路 2(1):序
https://www.devbean.net/category/qt-study-road-2/page/10 原来开过QT学习之路1, 很棒, 再翻阅时已经没有了. 所以这次把看过的记录下来 Ho ...