二叉树的非递归遍历的实现

1.前言:当然了,这也是复习。因为数据结构要考试了,把之前的算法拿出来敲一遍。

2.二叉树的遍历是一个基于二叉树很重要的操作,应用也很多。我们知道递归是树的特性,所以树上的所有算法几乎都是基于递归做的。但是在一些嵌入式设备中,系统栈不允许很深的递归。这时候就要把递归的算法转换为非递归算法,嗯,没错,应用场景看上去有点实际,但是真正促使我的还是考试,哈哈。

先给出一个遍历的递归算法把。

typedef struct BinaryTreeNode
{
	int data;
	struct BinaryTreeNode *Left;
	struct BinaryTreeNode *Right;
}Node,*node;
//先序遍历
void preOrderTraverse(Node* root)
{
	if (root)
	{
		cout << root->data << ' ';
		preOrderTraverse(root->Left);
		preOrderTraverse(root->Right);
	}
}

//中序遍历
void inOrderTraverse(Node* root)
{
	if (root)
	{
		inOrderTraverse(root->Left);
		cout << root->data << ' ';
		inOrderTraverse(root->Right);
	}
}

//后序遍历
void lastOrderTraverse(Node* root)
{
	if (root)
	{
		lastOrderTraverse(root->Left);
		lastOrderTraverse(root->Right);
		cout << root->data << ' ';
	}
}

其实用递归写很简单了,就是看访问的语句在什么地方喽,前边就是前序,中间就是中序,后边就是后序。代码十分的简介而且易懂。但是,上边我们也说了因为老师考试,哦不对,是有些设备不允许这样的程序出现在它内部。所以我们就要把递归转成非递归算法。那么我们就来看一下怎么转换呗。

3.前序的非递归实现。我们知道前序遍历的顺序是根-左-右。根据这个特点我们知道,在访问完根节点之后,我们就访问他的左子树,然后是右子树。一种BFS的思想,其实也不难,就简单写一下OK。

void preOrder(Node*root)
{
	stack<node>st;
	st.push(root);
	Node*tem = NULL;
	while (!st.empty())
	{
		tem = st.top();
		st.pop();
		if (tem != NULL)
		{
			cout << tem->data << " ";
			st.push(tem->Left);
			st.push(tem->Right);
		}
	}
}

4.中序遍历的非递归实现。中序的顺序是左-根-右。我们的想法是首先要递归左子树,把左子树的节点全部都放在栈里,想象一下这个过程,其实这个栈的内部存的就是二叉树最左边的那条路径。访问的时候,我们首先弹出栈顶,访问,检查这个节点有没有右子树,如果有,切换为右子树做同样的操作。那么这个时候有人可能会有疑问:根节点哪去了?其实仔细想想,根节点相对于它的上一个元素不就是左子树上的点吗?所以这样就会按照这个顺序访问完所有的节点。并且,需要注意的是,这个左右子树是在交替的,看上去栈里只有左子树的根节点,其实整棵树都在。或者我觉得,左右子树是一开始我们就定义好的,其实他们是一样的。下边给出代码实现:

void inOrder(Node*root)
{
	stack<node>st;
	node p = root;
	do
	{
		while (p != NULL)
		{
			st.push(p);
			p = p->Left;
		}
		p = st.top();
		st.pop();
		cout << p->data << " ";
		if (p->Right != NULL)
		{
			p = p->Right;
		}
		else
		{
			p = NULL;
		}
	} while (p!=NULL||!st.empty());
}

5.最后是后续遍历的实现:这个是有些困难的,我觉得是这三个中最不好写的。我们知道后序的顺序是左-右-根。这样其实会出现一个问题,就是当你访问根节点的时候不知带左右子树是否已经被访问过了。解决办法有很多,有的是给节点加一个属性,但是我在这里使用额外O(1)的空间来完成这件事。我们还是要左子树入栈,但是需要有一个保存最新被栈弹出的节点。我们跟着程序跑一遍来解释这个问题。首先我们把左节点全部入栈,当前弹出来的节点初始化为root,当我们完成这些之后,开始访问节点。这个时候已经被到了二叉树最左边的点,第一个if不成立了。检查第二个依旧不成立。进入else弹出这个节点,更新弹出的节点,访问它。好了,这个时候我们已经完成了最左边的叶子节点的访问(虽然不一定是叶子节点,但是不影响)。第二轮继续进入循环,p被更新到父节点,虽然p-left不再是NULL,但是p-left=last,所以第一个条件不成立,这个时候我们发现第二个条件成立了,所以把p的右节点压进去访问。last被更新为右节点,继续进入循环,这个时候我们发现两个条件都是不成立的,所以只能访问根节点。这样就完成了访问。是不是很神奇,代码之道,让人惊叹!

//非递归后续遍历二叉树
void lastOrder(node root)
{
	assert(root);
	stack<node>st;
	st.push(root);
	node last = root;
	while (!st.empty())
	{
		node p = st.top();
		if (p->Left != NULL && p->Left != last && p->Right != last)
		{
			st.push(p->Left);
		}
		else if (p->Right != NULL && p->Right != last && (p->Left == NULL || p->Left == last))
		{
			st.push(p->Right);
		}
		else
		{
			st.pop();
			last = p;
			cout << p->data << " ";
		}
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_41863129/article/details/85171932