二叉树的前中后序非递归遍历实现

二叉树的递归遍历实现非常简单,而根据根节点的访问顺序分为前中后三种遍历顺序,下面是递归遍历实现的代码:

#include <stdio.h>
#include <stdlib.h>

typedef struct treeNode {
	int data;
	struct treeNode *left;
	struct treeNode *right;
} treeNode;

void printTreeRecursiveFirst(treeNode *root)
{
        // 先序遍历
	printf("%d\n", root->data);
	if (root->left) {
		printTreeRecursive(root->left);
	}
	if (root->right) {
		printTreeRecursive(root->right);
	}
}

void printTreeRecursiveMiddle(treeNode *root)
{
	if (root->left) {
		printTreeRecursive(root->left);
	}
        // 中序遍历
	printf("%d\n", root->data);
	if (root->right) {
		printTreeRecursive(root->right);
	}
}

void printTreeRecursiveLast(treeNode *root)
{
	if (root->left) {
		printTreeRecursive(root->left);
	}
	if (root->right) {
		printTreeRecursive(root->right);
	}
        // 后序遍历
	printf("%d\n", root->data);
}

那有没有办法不用递归来实现二叉树的遍历呢,实际上函数调用的实现主要借助是栈这种后进先出的数据结构,我们自然会想到使用栈结构来实现二叉树的非递归遍历。

 

实际上,对于如下的一棵二叉树,利用栈来遍历,思路主要如下:

第一步我们从根节点开始,不停的遍历左节点,并把每次遍历到的左节点压入栈中,直到没有左节点结束,对应上图1、2、4、6四个节点被压入栈中。

第二步,依次弹出第一步中压入的节点,每次弹出一个节点,访问它的右节点,此时对应上图中的节点6被弹出,它的右节点是9。

第三步,每次从栈中弹出一个节点,访问该节点的右节点,并把这个右节点看成一棵新的树,再次应用第一步和第二步的操作。

第四步,重复执行第三步,直到栈中的节点全部弹出,此时二叉树的所有节点都被遍历到了。

 

对于这种利用栈的遍历方式,不同的打印节点值的时机也就决定了我们是用什么顺序在遍历。

先序遍历:先打印节点的值,然后再压入栈中

中序遍历:节点弹出栈的时候打印节点的值

后序遍历:节点弹出栈,此时需要等到把它的右节点也处理完才能打印,所以需要把这个节点再压回去,等到该节点的右节点都处理完第二次弹出栈的时候打印。

 

相应的代码如下:

typedef struct linkNode {
	treeNode *tNode;
	struct linkNode *next;
} linkNode;

typedef struct stack {
	linkNode head;
} stack;

stack *stack_init()
{
	stack *s = malloc(sizeof(stack));
	s->head.next = NULL;
	return s;
}

void stack_push(stack *s, treeNode *tNode)
{
	linkNode *lNode = malloc(sizeof(linkNode));
	lNode->tNode = tNode;
	if (s->head.next) {
		lNode->next = s->head.next;
		s->head.next = lNode;
	} else {
		s->head.next = lNode;
		lNode->next = NULL;
	}
}

treeNode *stack_pop(stack *s) {
	if (s->head.next == NULL) {
		return NULL;
	}
	linkNode *tmp = s->head.next;
	s->head.next = tmp->next;
	treeNode *tNode = tmp->tNode;
	free(tmp);
	return tNode;
}

int stack_is_empty(stack *s)
{
	return s->head.next == NULL;
}



// 先序遍历
void printTreeFirst(treeNode *root)
{
	treeNode *tmp = root;
	stack *s = stack_init();
	
	while(tmp) {
		printf("%d\n", tmp->data);
		stack_push(s, tmp);
		tmp = tmp->left;
	}
	
	while(!stack_is_empty(s)) {
		tmp = stack_pop(s);
		tmp = tmp->right;
		while (tmp) {
			printf("%d\n", tmp->data);
			stack_push(s, tmp);
			tmp = tmp->left;
		}
	}
}

// 中序遍历
void printTreeMiddle(treeNode *root)
{
	treeNode *tmp = root;
	stack *s = stack_init();
	
	while(tmp) {
		stack_push(s, tmp);
		tmp = tmp->left;
	}
	
	while(!stack_is_empty(s)) {
		tmp = stack_pop(s);
		printf("%d\n", tmp->data);
		tmp = tmp->right;
		while (tmp) {
			stack_push(s, tmp);
			tmp = tmp->left;
		}
	}
}




后序遍历有两种实现思路,第一种就如上所说,节点第二次弹出栈的时候打印节点的值,那如何知道节点是否是第二次弹出呢,我们可以给节点加个标志位,标志位的初始值为0,在第二次压栈前把标志位的值置为1,这样每次在弹出节点的时候判断一下这个标志位的值,如果是1就打印节点的值。

typedef struct treeNode {
	int data;
	int isSecond;  // 增加标志位,初始值赋为0
	struct treeNode *left;
	struct treeNode *right;
} treeNode;


// 后序遍历1
void printTreeLast(treeNode *root)
{
	treeNode *tmp = root;
	stack *s = stack_init();
	
	while(tmp) {
		stack_push(s, tmp);
		tmp = tmp->left;
	}
	
	while(!stack_is_empty(s)) {
		tmp = stack_pop(s);
		if(tmp->isSecond) {
			printf("%d\n", tmp->data);
		} else {
			tmp->isSecond = 1;
			stack_push(s, tmp);	
			tmp = tmp->right;
			while (tmp) {
				stack_push(s, tmp);
				tmp = tmp->left;
			}
		}
	}
}

另一种思路:

我们知道先序遍历是:根-->左-->右,而后序遍历是:左-->右-->根,其实我们只要把先序遍历稍微修改变成:根-->右-->左,就发现它正好是后序遍历的倒序,那么我们只要根据 根-->右-->左遍历,把打印节点变成压入一个临时的栈中,结束后再对这个临时的栈依次弹栈打印节点,最终就是我们要的后序遍历。

// 后序遍历2
void printTreeLast(treeNode *root)
{
	treeNode *tmp = root;
	stack *s = stack_init();
	stack *s2 = stack_init();
	
	while(tmp) {
		stack_push(s2, tmp); // 打印改为压栈,临时保存起来
		stack_push(s, tmp);
		tmp = tmp->right;
	}
	
	while(!stack_is_empty(s)) {
		tmp = stack_pop(s);
		tmp = tmp->left;
		while (tmp) {
			stack_push(s2, tmp);
			stack_push(s, tmp);
			tmp = tmp->right;
		}
	}
	
	// 再依次打印栈中的节点
	while(!stack_is_empty(s2)) {
		treeNode *tmp = stack_pop(s2);
		printf("%d\n", tmp->data);
	}
}

 

That’s all!

 

  • 16
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值