二叉树非递归遍历实现

二叉树遍历的非递归实现

基础知识

二叉树的遍历分为深度优先遍历(DFS)和广度优先遍历(BFS)。

深度优先遍历:尽可能地向左(或右)进行,在遇到第一个转折点,向左(或右)一步,然后再尽可能地向左(或右)发展。这一过程重复直到访问了所以节点。树的深度优先遍历一般有三种:

VLR——前序遍历(根左右)

LVR——中序遍历(左根右)

LRV——后序遍历(左右根)

核心思路

遍历时一定要搞清楚一点就是,输出都是根节点(核心思想),因此不同的遍历方式仅仅是根节点的输出时机不同。

中序遍历:访问顺序为左根右。遇到节点不能直接输出(需要确定该点为根节点),向左发展,直到左节点为空(遇到转折点),向右一步,重复上述过程。在遇到转折点时即可确定该节点为根节点,此时可以输出。

后序遍历也是同样的思想:左右子树都已经遍历才可以输出。因此需要设置标志位,某一个点需要在压入时已经访问了左子树,当访问右子树时我们将标志设为true,下一次访问到即可输出。

二叉树存放节点的结构体

struct TreeNode {
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode() : val(0), left(nullptr), right(nullptr) {}
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
	TreeNode(int x, TreeNode* left, TreeNode* right) : val(x), left(left), right(right) {}
	
};
1、先序遍历

分析:

先序遍历逻辑:根据深度优先的定义,一直往左发展,遇到转折点向右一步,重复上述过程直到访问完成。

非递归实现逻辑:遇到结点就输出并且压入左结点,直到(while)访问到最左端,从栈中取出栈顶元素往右一步,重复上述过程。

代码实现:

class solutions {
private:
	vector<int> res; //存放结果
public:
	//先序遍历递归和非递归版本
	void PreOrderTree(TreeNode* root) {
		if (root != nullptr) {
			res.push_back(root->val);
			PreOrderTree(root->left);
			PreOrderTree(root->right);
		}
	}

	void PreOrderTree2(TreeNode* root) {
		stack<TreeNode*> stk;//使用栈模拟递归的调用
		auto p = root;//记录头结点

		while (p != nullptr || !stk.empty()) {
			//遍历直到叶子结点
			while (p != nullptr) {
				res.push_back(p->val);
				stk.push(p);
				p = p->left;
			}
			//栈不为空,弹出元素,检测右子树
			if (!stk.empty()) {
				p = stk.top();
				stk.pop();
				p = p->right;
			}
		}
	}
}
2、中序遍历

分析:

一直往左发展,遇到转折点(根据左根右的特点,此时可以输出)向右一步,重复上述过程直到访问完成。

非递归实现逻辑:压入左结点,直到(while)访问到最左端,从栈中取出栈顶元素并输出,然后往右一步,重复上述过程。

代码实现:

class solutions {
private:
	vector<int> res; //存放结果
public:

	//中序遍历(左根右)递归和非递归实现
	void InOrderTree(TreeNode* root) {
		if (root != nullptr) {
			InOrderTree(root->left);//到最左边才能输出
			res.push_back(root->val);
			InOrderTree(root->right);//存在右子树,重复上述过程
		}
	}

	void InOrderTree2(TreeNode* root) {
		stack<TreeNode*> stk; //栈模拟递归流程
		auto p = root;
		//中序遍历逻辑:先遍历到最左,弹出过程进行输出,然后转到右子树
		while (p != nullptr || !stk.empty()) {
			while (p != nullptr) {
				stk.push(p);
				p = p->left;
			}
			if (!stk.empty()) {
				p = stk.top();
				stk.pop();
				res.push_back(p->val);
				p = p->right;
			}
		}
	}
}
2、后序遍历

分析:

一直往左发展,遇到转折点向右一步(根据左右根的特点,该点左右已经访问完成,此时可以输出),重复上述过程直到访问完成。

非递归实现逻辑:压入左结点,直到(while)访问到最左端,从栈中取出栈顶元素判断是否输出(当该元素只访问了左子树就不能输出,如何解决?设置标志位,当该节点进行了右子树访问,表示可以输出),然后往右一步(设置标志位),重复上述过程。

代码实现:

class solutions {
private:
	vector<int> res; //存放结果
public:

	//后序(左右根)遍历递归和非递归实现
	void PostOrderTree(TreeNode* root) {
		if (root != nullptr) {
			PostOrderTree(root->left);//遍历到最左边
			PostOrderTree(root->right);

			//如果存在右边,该左就变成了根,转向右子树进行遍历,直到该点左右都为空,才能输出
			//而且在每一个 root->left 的return过程中都会继续向下执行检测 root->right
			res.push_back(root->val);
		}
	}

	void PostOrderTree2(TreeNode* root) {
		stack<TreeNode*> stk;

		//设置标志位和节点同时入栈,因为后序遍历为左右跟,每个输出实际都是根节点
		//只有满足左右子树都遍历过才可以输出。节点第一次入栈时置为false,当该节点往右遍历时置为true,下次即可输出。
		stack<bool> flag;
		auto p = root;
		while (p != nullptr || !stk.empty()) {

			//遍历直到最左端
			while (p != nullptr) {
				stk.push(p);
				flag.push(false);
				p = p->left;
			}

			//栈不为空,转向,进行判断
			if (!stk.empty()) {
				p = stk.top();
				//当标志位为1时打印
				if (flag.top()) {
					res.push_back(p->val);
					stk.pop();
					flag.pop();
					p = nullptr;//flag=1,说明右子树已经遍历,不用执行while
				}
				else {
					flag.top() = true;
					p = p->right;
				}

			}
		}
	}
};
总结

后序遍历遍历思路:左子树右子树根节点。当某一个节点需要输出,其实该节点是根节点。当明白这一点之后遍历的逻辑就很好理解。 既然某一节点(根)需要输出,那么左右子树都已经遍历完成。因此设置标志位在压入左节点时一同压入标志(0),在转向遍历右子树时(此时该节点左右子树都遍历完成—是不是应该将标志位设为1)。之后在进行弹出操作时候判断flag是否满足输出条件即可。

按照输出都为根节点,以及先序遍历(根左右),中序遍历(左根右)的特点,同样可以写出对于的非递归版本。
先序非递归:在往左遍历的时候即可输出;
中序遍历:在左子树遍历完成后,该点(根节点)需要往右子树遍历,根据中序的特点,在转往右子树之前即可输出。

非递归的实现需要先理解递归的实现,迭代只是显示地使用了栈操作。

使用以下二叉树结果进行结果验证:

在这里插入图片描述

先序遍历结果:1,2,3,4,5,6,7,8;
中序遍历结果:4,3,5,2,1,7,6,8;
后序遍历结果:4,5,3,2,7,8,6,1;
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值