二叉树非递归遍历(前序、中序、后序)

1. 二叉树结点非递归遍历流程分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2hixU4r1-1668255400934)(norecrutionimage.png)]

结点遍历流程分析(因为前序中序后序遍历流程一样,故而很重要)

  • T 指向 【1】
    • if 【1】 非空
      • then 【1】 入栈 and T 指向 【1】 的左儿子(即 【2】) S:1
  • T 指向 【2】
    • if 【2】 非空
      • then 【2】 入栈 and T 指向 【2】 的左儿子(即【4】) S:1、2
  • T 指向 【4】
    • if 【4】 非空
      • then 【4】 入栈 and T 指向 【4】 的左儿子(没有左儿子,因此为空,即 T 指向空) S:1、2、4
  • T 指向空(下一步应该是遍历【4】的右子树)
    • 栈 S 出栈元素 【4】,T 指向出栈元素 【4】 的右儿子(因为 【4】 没有右儿子,因此 T 指向空)S:1、2
  • T 指向空(下一步应该是遍历【2】的右子树)
    • 栈 S 出栈元素 【2】,T 指向出栈元素 【2】 的右儿子(即【5】)S:1
  • T 指向【5】
    • if 【5】 非空
      • then 【5】 入栈 and T 指向 【5】 的左儿子(即 【8】)S:1、5
  • T 指向 【8】
    • if 【8】 非空
      • then 【8】 入栈 and T 指向 【8】 的左儿子(即指向空)S:1、5、8
  • T 指向空(下一步应该是遍历【8】的右子树)
    • 栈 S 出栈元素 【8】,T 指向出栈元素 【8】 的右儿子(即指向空)S:1、5
  • T 指向空(下一步应该是遍历【5】的右子树)
    • 栈 S 出栈元素 【5】,T 指向出栈元素 【5】 的右儿子(即指向空)S:1
  • T 指向空(下一步应该是遍历 【1】 的右子树)
    • 栈 S 出栈元素 【1】,T 指向出栈元素 【1】 的右儿子(即指向【3】) S:NULL
  • T 指向 【3】(剩下的就略写了)
    ==》【3】 入栈,T 指向左儿子【6】 S:3
  • T 指向 【6】
    ==》【6】 入栈,T 指向左儿子空 S:3、6
  • T 指向空
    ==》【6】 出栈,T 指向右儿子空 S:3
  • T 指向空
    ==》【3】 出栈,T 指向右儿子 【7】 S:NULL
  • T 指向 【7】
    ==》【7】 入栈,T 指向左儿子空 S:7
  • T 指向空
    ==》【7】 出栈,T 指向右儿子 【9】 S:NULL
  • T 指向 【9】
    ==》【9】入栈,T 指向左儿子空 S:9
  • T 指向空
    ==》【9】出栈,T 指向右儿子空 S:NULL
  • T 指向空
    ==》没有元素可以出栈,遍历结束

小结 1
T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的左儿子
T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的右儿子
终止条件 =》 T 指向空 且 栈空

小结 2
由于三种遍历方式经过结点的路线相同,因此我们可以通过调整结点访问位置,从而实现三种遍历
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5LdFm3XA-1668255400935)(common3.png)]

上述过程的代码实现

void PreOrderTraversalWithStack(BinTree BT)
{
	BinTree T = BT;
	Stack S = CreateStack(1000);

	while (T || !IsEmpty(S)) // 终止条件 =》 T 指向空 且 栈空
	{
		while (T) //T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的左儿子
		{
			Push(S, T); 
			T = T->Left;
		}
		if (!IsEmpty(S)) // T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的右儿子
		{
			BinTree *v;
			Pop(S, v);
			T = *v;

			T = T->Right; 
		}
	}
}

2. 中序遍历的非递归实现

如上图,我们进行非递归算法实现的思路整理(堆栈实现)

  • 手算易得,中序遍历结果为 4、2、8、5、1、6、3、7、9

由 “1. 二叉树结点非递归遍历流程分析” 小结2,易知,我们只需要对上述代码进行修改,即可实现中序遍历

  • 根据 “1. 二叉树结点非递归遍历流程分析” 的 “结点遍历流程分析” 我们可以知道,每次出栈的值,刚好就是中序遍历的结果,那么我们的改进方法就是“在结点出栈后加上输出操作”,代码见下,
if (!IsEmpty(S)) // T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的右儿子
{
	BinTree *v;
	Pop(S, v);
	T = *v;

	printf("%d\t", T->data); // 加入语句

	T = T->Right; 
}

3. 前序遍历的非递归实现

如上图,我们进行非递归算法实现的思路整理(堆栈实现)

  • 手算易得,前序遍历结果为 1、2、4、5、8、3、6、7、9

由 “1. 二叉树结点非递归遍历流程分析” 小结2,易知,我们只需要对上述代码进行修改,即可实现中序遍历

  • 根据 “1. 二叉树结点非递归遍历流程分析” 的 “结点遍历流程分析” 我们可以知道,每次入栈的值,刚好就是前序遍历的结果,那么我们的改进方法就是“在结点入栈前or后加上输出操作”,代码见下,
while (T) //T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的左儿子
{
	// printf("%d\t", T->data); // 加入语句(位置2)

	Push(S, T); 

	printf("%d\t", T->data); // 加入语句
	
	T = T->Left;
}

4. 后序遍历的非递归实现

如上图,我们进行非递归算法实现的思路整理(堆栈实现)

  • 手算易得,后序遍历结果为 4、8、5、2、6、9、7、3、1

由 “1. 二叉树结点非递归遍历流程分析” 小结2,易知,我们只需要对上述代码进行修改,即可实现中序遍历

  • 根据 “1. 二叉树结点非递归遍历流程分析” 的 “结点遍历流程分析” 我们可以知道,!!!竟然没有对的上的值!!!那该怎么解决呢?
    别急,一个相应的解决方式是
    如果我们规定根节点的右儿子优先,那么根据我们的分析,前序遍历就成了 “根右左的顺序”,我们可以倒过来看,即“左右根”,这不就是我们想要的后序遍历吗!!因此我们基于右儿子优先进行前序遍历的输出结果进行逆序,那就是我们想要的后序遍历了!!!
  • 验证一下,基于右儿子优先的前序遍历结果为 “1、3、7、9、6、2、5、8、4” 发现,其逆序和我们计算的 4、8、5、2、6、9、7、3、1 后序遍历结果相同,因此上述思路没错!

代码改进思路
基于右儿子优先实现前序遍历,在前序遍历中,将输出的结果存储到 另一个栈中,则最后这个栈元素出栈顺序,就是前序遍历元素顺序的逆序!!!

void PostOrderTraversalWithStack(BinTree BT)
{
	BinTree T = BT;
	Stack S = CreateStack(1000);
	Stack post = CreateStack(1000);

	while (T || !IsEmpty(S)) // 终止条件 =》 T 指向空 且 栈空
	{
		while (T) //T 指向非空 =》则指向的结点进栈,T 更新为 指向结点的右儿子
		{
			Push(S, T); 
			Push(post, T);
			T = T->Right;
		}
		if (!IsEmpty(S)) // T 指向空 =》 栈顶结点出栈,T 更新为出栈结点的左儿子
		{
			BinTree *v;
			Pop(S, v);
			T = *v;

			T = T->Left; 
		}
	}

	// 输出 post 元素,就是我们需要的后序遍历的结果(也是二叉树基于右儿子优先实现的前序遍历元素的逆序)
	while(!IsEmpty(post)) // post 不为空
	{
		BinTree *v;
		Pop(post, v);
		printf("%d\t", (*v)->data);
	}
}

5. 小结

  1. 前序、中序遍历 基于 左儿子优先的结点遍历(见第一段代码)进行修改可以得到
  2. 后序遍历 基于 右儿子优先的结点遍历下前序遍历结果的逆序得到

6. 代码补全(二叉树数据类型定义、栈数据类型的定义)

/* 二叉树数据类型的定义 */
/* 一个结点有一个数据域及两个指针域,分别指向左右子树 */
typedef int DataType;

typedef struct TreeNode
{							/* 树结点定义 */
	DataType Data;			/* 结点数据 */
	struct TreeNode *Left;	/* 指向左子树 */
	struct TreeNode *Right; /* 指向右子树 */
} TreeNode;

typedef TreeNode *BinTree; /* 二叉树类型 */
typedef BinTree Position;

/* Stack 数据类型的定义(顺序存储结构) */
// typedef int ElementType; // 堆栈中存储元素的类型
typedef BinTree ElementType; // 堆栈的元素类型是二叉树结点类型的指针

typedef struct
{
	ElementType *data; // 使用数组存储堆栈元素,数组中的元素,是一个指向二叉树结点的指针
	int top;		   // 栈顶元素对应在数组中的位置,空栈则 top = -1
	int MaxSize;	   // 堆栈容量
} SqNode;			   // SqNode 是一种数据类型,即顺序堆栈

typedef SqNode *Stack; // 栈数据类型

/* Stack 操作的声明及定义 */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 建栈
Stack CreateStack(int MaxSize)
{
	Stack S = (Stack)malloc(sizeof(SqNode)); // 给栈指针分配空间
	S->data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
	S->top = -1;
	S->MaxSize = MaxSize;
	return S;
}

// 栈满判断
bool IsFull(Stack S)
{
	return S->top + 1 == S->MaxSize;
}

// 栈空判断
bool IsEmpty(Stack S)
{
	return S->top == -1;
}

// 出栈操作
bool Pop(Stack S, ElementType *Value) // Value 类型是 *BinTree
{
	if (IsEmpty(S))
		return false;
	*Value = S->data[(S->top)--];
	return true;
}

// 入栈操作
bool Push(Stack S, ElementType Value)
{
	if (IsFull(S))
		return false;
	S->data[++(S->top)] = Value;
	return true;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 二叉树中序前序后序遍历算法分为递归和非递归两种方法。 递归遍历算法: 1. 中序遍历:先遍历子树,再访问根节点,最后遍历右子树。 2. 前序遍历:先访问根节点,再遍历子树,最后遍历右子树。 3. 后序遍历:先遍历子树,再遍历右子树,最后访问根节点。 非递归遍历算法: 1. 中序遍历:使用栈来模拟递归过程,先将根节点入栈,然后将子树的所有节点入栈,直到遇到叶子节点,然后出栈并访问该节点,再将右子树的所有节点入栈,重复上述过程。 2. 前序遍历:使用栈来模拟递归过程,先将根节点入栈,然后出栈并访问该节点,再将右子树入栈,最后将子树入栈,重复上述过程。 3. 后序遍历:使用栈来模拟递归过程,先将根节点入栈,然后将子树的所有节点入栈,直到遇到叶子节点,然后将右子树的所有节点入栈,最后出栈并访问该节点,重复上述过程。需要使用一个辅助栈来记录已经访问过的节点。 ### 回答2: 二叉树中序前序后序遍历是树的三种基本遍历方法,他们都有递归和非递归两种算法。 1. 中序遍历 中序遍历是先访问子树,再访问根节点,最后访问右子树。递归遍历可以先递归访问子树,然后输出根节点,继续递归访问右子树。非递归遍历可以用栈进行模拟,从根节点开始,先访问子树,遇到子树都入栈,然后输出根节点,最后遍历右子树。 2. 前序遍历 前序遍历是先访问根节点,再访问子树,最后访问右子树。递归遍历可以先输出根节点,然后递归访问子树和右子树。非递归遍历可以用栈进行模拟,从根节点开始,先输出根节点,然后将右子树入栈,最后遍历子树。 3. 后序遍历 后序遍历是先访问子树,再访问右子树,最后访问根节点。递归遍历可以先递归访问子树和右子树,最后输出根节点。非递归遍历可以用栈进行模拟,在遍历每个节点的时候都要记录下该节点是否已经被访问过了,当右子树都被访问过的时候,再输出根节点。 总之,递归遍历的代码比较简单,但是会使用到函数调用栈,容易导致栈溢出,所以针对比较大的二叉树,我们通常使用非递归遍历非递归遍历可以使用栈来模拟递归过程,但是需要注意栈空的时候需要退出循环。 ### 回答3: 二叉树遍历是指以某种顺序依次访问二叉树中所有结点,是二叉树最常用的操作之一。常见的遍历方式有中序遍历前序遍历后序遍历。这三种遍历方式都可以采用递归和非递归算法实现。 1. 中序遍历 中序遍历的顺序是孩子-根节点-右孩子。递归实现中序遍历: ``` void inorder(TreeNode* root) { if (root == nullptr) return; inorder(root->left); visit(root); inorder(root->right); } ``` 非递归实现中序遍历: ``` vector<int> inorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> s; TreeNode* cur = root; while (cur || !s.empty()) { while (cur) { s.push(cur); cur = cur->left; } cur = s.top(); s.pop(); res.push_back(cur->val); cur = cur->right; } return res; } ``` 2. 前序遍历 前序遍历的顺序是根节点-孩子-右孩子。递归实现前序遍历: ``` void preorder(TreeNode* root) { if (root == nullptr) return; visit(root); preorder(root->left); preorder(root->right); } ``` 非递归实现前序遍历: ``` vector<int> preorderTraversal(TreeNode* root) { vector<int> res; if (root == nullptr) return res; stack<TreeNode*> s; s.push(root); while (!s.empty()) { TreeNode* tmp = s.top(); s.pop(); res.push_back(tmp->val); if (tmp->right) s.push(tmp->right); if (tmp->left) s.push(tmp->left); } return res; } ``` 3. 后序遍历 后序遍历的顺序是孩子-右孩子-根节点。递归实现后序遍历: ``` void postorder(TreeNode* root) { if (root == nullptr) return; postorder(root->left); postorder(root->right); visit(root); } ``` 非递归实现后序遍历: ``` // 双栈法 vector<int> postorderTraversal(TreeNode* root) { vector<int> res; if (root == nullptr) return res; stack<TreeNode*> s1, s2; s1.push(root); while (!s1.empty()) { TreeNode* tmp = s1.top(); s1.pop(); s2.push(tmp); if (tmp->left) s1.push(tmp->left); if (tmp->right) s1.push(tmp->right); } while (!s2.empty()) { res.push_back(s2.top()->val); s2.pop(); } return res; } ``` ``` // 单栈法 vector<int> postorderTraversal(TreeNode* root) { vector<int> res; if (root == nullptr) return res; stack<TreeNode*> s; TreeNode* cur = root; TreeNode* last = nullptr; while (cur || !s.empty()) { while (cur) { s.push(cur); cur = cur->left; } cur = s.top(); if (cur->right == nullptr || cur->right == last) { res.push_back(cur->val); s.pop(); last = cur; cur = nullptr; } else { cur = cur->right; } } return res; } ``` 非递归的实现需要用到栈,代码实现可能有些繁琐,但是值得熟练掌握。在实际编码中,也可使用Morris遍历来实现二叉树遍历并节省空间。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值