【树】二叉树的非递归遍历(栈&Morris)

本文介绍了二叉树的非递归遍历方法,包括栈和Morris遍历。栈用于实现前序和中序遍历,Morris遍历则利用线索二叉树概念,在不使用额外空间的情况下进行遍历。文章详细讲解了前序、中序和后序遍历的Morris算法,并提供了代码示例,分析了空间和时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录:

1.【树】二叉树的非递归遍历(栈&Morris)

2.【树】二叉树转为双向链表



一、二叉树的非递归遍历【前中的差别只有一句位置不同!  后序最特殊】

两种方法:1.用栈 2.用Morris

栈:

因为所谓的前中后序都是深度优先遍历,所以用栈数据结构。广度(按层/之字)遍历时才用队列数据结构。


例:树从上往下 从左往右是abcdefghi。
前序:a b d h i e c f g
中序:h d i b e a f c g
后序:h i d e b f g c a


三种写法特点:

1.下划线处是内层while循环的依据。

2.外层的while循环体内都是6句:

压入栈

压入vector(后序是插入)

root更新为左子树

root更新为右子树

root = 栈顶

pop栈顶


*********
preorder:
*********
vector<int> PreorderTraversal(TreeNode* root) {
	vector<int> result;
	stack<TreeNode*> nodeStack;
	while (root || !nodeStack.empty()) {
		while (root) {
			nodeStack.push(root); 
			result.push_back(root->val); 
			root = root->left; 
		}
		root = nodeStack.top(); 
		nodeStack.pop();
		
		root = root->right;
	}
	return result;
}
*********
inorder:
*********
vector<int> InorderTraversal(TreeNode* root) {
	vector<int> result;
	stack<TreeNode*> nodeStack;
	while (root || !nodeStack.empty()) {
		while (root) {
			nodeStack.push(root);
			root = root->left;
		}
		root = nodeStack.top();
		nodeStack.pop();
		
		result.push_back(root->val); //与前序的唯一区别:把result.push_back挪到了这里
		root = root->right;
	}
	return result;
}
*********
postorder:
*********
vector<int> PostorderTraversal(TreeNode* root) {
	vector<int> result;
	stack<TreeNode*> nodeStack;
	while (root || !nodeStack.empty()) {
		while (root) {
			nodeStack.push(root);
			result.insert(result.begin(),root->val);//与前序的区别:1.把result从前面开始插入  2.left right换了顺序
			root = root->right;
		}
		root = nodeStack.top();
		nodeStack.pop();
		
		root = root->left;
	}
	return result;
}


Morris分析:

要使用O(1)空间进行遍历,最大的难点在于,遍历到子节点的时候怎样重新返回到父节点(假设节点中没有指向父节点的p指针),由于不能用栈作为辅助空间。为了解决这个问题,Morris方法用到了线索二叉树(threaded binary tree)的概念。在Morris方法中不需要为每个节点额外分配指针指向其前驱(predecessor)和后继节点(successor),只需要利用叶子节点中的左右空指针指向某种顺序遍历下的前驱节点或后继节点就可以了


设当前节点为curr,前驱节点为prev。

Morris中序遍历思想前后都是基于中序的改动

1. 如果curr的左孩子为空,则 1.printf curr。 2.curr更新为curr的右孩子(意为左边没法遍历了,操作下一个点)。

2. 否则(如果curr的左孩子不为空),while循环:在curr的左子树中找到curr在中序遍历下的前驱节点prev

   a) 如果prev的右孩子为空,则 1.将prev的右孩子指向curr(连起来~)。 2.curr更新为curr的左孩子(意为操作下一个点)。

   b) 如果prev的右孩子为curr(即已经执行过“连起来”了),则 1.printf curr 2将prev的右孩子重新置为NULL(恢复树的形状)。 3.curr更新为curr的右孩子(意为左边都遍历完了,操作下一个点)

3. 重复以上1、2步,直至curr为空。


图示:

下图为每一步迭代的结果(从左至右,从上到下),cur代表当前节点,蓝色节点表示该节点已输出。


代码:

void InorderMorrisTraversal(TreeNode *root) {
	TreeNode *cur = root, *prev = NULL;
	while (cur != NULL)
	{
		if (cur->left == NULL)
		{
			printf("%d ", cur->val);
			cur = cur->right;
		}
		else
		{
			prev = cur->left;
			while (prev->right != NULL && prev->right != cur)
				prev = prev->right;

			if (prev->right == NULL)
			{
				//前序在这里输出
				prev->right = cur;
				cur = cur->left;
			}
			else
			{
				printf("%d ", cur->val);  // 与前序的唯一区别
				prev->right = NULL;
				cur = cur->right;
			}
		}
	}
}

Morris前序遍历思想:前序中序唯一的区别就是红字处,即printf curr是在2.a(前)还是2.b(中)

1. 如果curr的左孩子为空,则 1.printf curr。 2.curr更新为curr的右孩子(意为左边没法遍历了,操作下一个点)。

2. 否则(如果curr的左孩子不为空),while循环:在curr的左子树中找到curr在前序遍历下的前驱节点prev

   a) 如果prev的右孩子为空,则  1.printf curr2.将prev的右孩子指向curr(连起来~)。 3.curr更新为curr的左孩子(意为操作下一个点)。

   b) 如果prev的右孩子为curr(即已经执行过“连起来”了),则 1.将prev的右孩子重新置为NULL(恢复树的形状)。 2.curr更新为curr的右孩子(意为左边都遍历完了,操作下一个点)

3. 重复以上1、2步,直至curr为空。


图示:



复杂度分析:

空间复杂度:O(1),因为只用了两个辅助指针。

时间复杂度:O(n)。证明时间复杂度为O(n),最大的疑惑在于寻找中序遍历下二叉树中所有节点的前驱节点的时间复杂度是多少,即以下两行代码:

while (prev->right != NULL && prev->right != cur)
	prev = prev->right;
直觉上,认为它的复杂度是O(nlgn),因为找单个节点的前驱节点与树的高度有关。但事实上,对于单个节点来讲,平均情况下查找复杂度是O(lgn),最好时是O(1)。
考虑所有节点总的查找复杂度的话,是O(n)。n个节点的二叉树中一共有n-1条边,整个过程中每条边最多只走3次,一次是为了定位到某个节点,另一次是为了寻找上面某个节点的前驱节点,如下图所示,其中红色是为了定位到某个节点,黑色线是为了找到前驱节点。所以复杂度为O(n)。


Morris后序遍历思想:

过阵再整理

http://www.cnblogs.com/AnnieKim/archive/2013/06/15/MorrisTraversal.html

http://blog.youkuaiyun.com/wdq347/article/details/8853371

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值