接着上次的话题。这次我们要讨论,二叉查找树的中序遍历和后序遍历(递归和非递归),另外还有先序遍历(非递归)

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语言实现(二)的更多相关文章

  1. 使用C语言实现二维,三维绘图算法(1)-透视投影

    使用C语言实现二维,三维绘图算法(1)-透视投影 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其实想想 ...

  2. 使用C语言实现二维,三维绘图算法(3)-简单的二维分形

    使用C语言实现二维,三维绘图算法(3)-简单的二维分形 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其 ...

  3. 使用C语言实现二维,三维绘图算法(2)-解析曲面的显示

    使用C语言实现二维,三维绘图算法(2)-解析曲面的显示 ---- 引言---- 每次使用OpenGL或DirectX写三维程序的时候, 都有一种隔靴搔痒的感觉, 对于内部的三维算法的实现不甚了解. 其 ...

  4. Swift语言指南(二)--语言基础之注释和分号

    原文:Swift语言指南(二)--语言基础之注释和分号 注释 通过注释向自己的代码中注入不可执行的文本,作为你自己的笔记或提示.Swift编译器运行时会忽略注释. Swift的注释与C语言极其相似,单 ...

  5. #r语言(二)笔记

    #r语言(二)笔记 #早复习 #概述:R是用于统计分析.绘图的语言和操作环境 #对象: #数据类型--统称为对象 #向量(vector):用于存储数值型.字符型或逻辑型数据的一维数组. #定义向量: ...

  6. iOS开发之SQLite-C语言接口规范(二) —— Prepared Your SQL Statements

    在<SQLite的C语言接口规范(一)>中介绍了如何去连接打开数据库,本篇博客就介绍如何操作数据库,本篇主要给出了如何执行数据库查询语句(Select), 然后遍历结果集.本篇博客就直接使 ...

  7. 遗传算法的C语言实现(二)-----以求解TSP问题为例

    上一次我们使用遗传算法求解了一个较为复杂的多元非线性函数的极值问题,也基本了解了遗传算法的实现基本步骤.这一次,我再以经典的TSP问题为例,更加深入地说明遗传算法中选择.交叉.变异等核心步骤的实现.而 ...

  8. C语言 预处理二(宏定义--#define)

    //#define 宏定义(宏定义一般大写) //知识点一-->#define的作用域:从#define开始,从上往下,如果遇到#undef就到#undef处结束,如果没有就是作用于当前整个文件 ...

  9. C语言之二维数组

    二维数组 还是一个数组,只不过数组中得每一个元素又是一个数组 1). 声明语法 类型 数组名[行][列]; 例:  int nums[2][3];//2行3列的二维数组,保存的数据类型是int类型 c ...

随机推荐

  1. Vue v-if ToolList

    可根据v-if="IsOk",动态判断标签是否展示 <template> <div id="app"> <input type=& ...

  2. 对XML文档进行修改

    怎样对XML文档时行修改.Insus.NET在此举个简单的例子.XML文档,就以这篇博文:http://www.cnblogs.com/insus/p/3274220.html 如果我们想对其中一个节 ...

  3. charles 抓取app端 https 请求

    测试需要抓取app的https请求链接,百度了一下教程,能设置的都设置成功了,但就是抓取不成功,显示如下图 无奈之下还是用谷歌搜索了下(网速极慢),但是庆幸的找到了问题的答案,原因还是手机设置的问 打 ...

  4. Java与C++比较

    本文仅从片面的角度比较Java与C++的一些特性,如有错误的地方,请指正. 语言特性上的一些差异: 1.Java没有无符号整数,C++/C#都有. 2.Java中不存在指针.Java的引用是功能弱化的 ...

  5. 【转】C# 中的委托和事件(详解)

    源地址:http://www.cnblogs.com/SkySoot/archive/2012/04/05/2433639.html

  6. iOS 11 ScrollView偏移问题解决

    if (@available(iOS 11.0, *)){//避免滚动视图顶部出现20的空白以及push或者pop的时候页面有一个上移或者下移的异常动画的问题 [[UIScrollView appea ...

  7. 老男孩Day6作业:计算器

    作业需求: 1.实现加减乘除及拓号优先级解析 2.用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) ...

  8. 解读人:林山云,Proteomic Identification of Protein Glutathionylation in Cardiomyocytes(心肌细胞蛋白质谷胱甘肽修饰的蛋白质组鉴定)

    发表时间:(2019年4月) IF:3.950 单位:韦恩州立大学化学系 物种:小鼠心肌细胞 技术:谷胱甘肽修饰蛋白组学 一. 概述: 本研究采用化学选择性蛋白组学方法,鉴定出过氧化物诱导HL-1小鼠 ...

  9. ubuntu中出现:程序 'java' 已包含在下列软件包中的解决方法

    已经安装sun java 在终端中输入java,出现以下提示: 程序 'java' 已包含在下列软件包中: * default-jre * gcj-4.8-jre-headless * gcj-4.9 ...

  10. hdu6223 Infinite Fraction Path 2017沈阳区域赛G题 bfs加剪枝(好题)

    题目传送门 题目大意:给出n座城市,每个城市都有一个0到9的val,城市的编号是从0到n-1,从i位置出发,只能走到(i*i+1)%n这个位置,从任意起点开始,每走一步都会得到一个数字,走n-1步,会 ...