二叉树的非递归的遍历方式
上篇博客记录了二叉树的递归遍历方式以及根据二叉树的遍历结果还原二叉树的内容。
本篇博客记录二叉树的非递归的遍历方式。
二叉树的非递归遍历需要借助栈来实现,而且三种遍历的方式的算法难易程度并不相同。其中,先序遍历较为简单,中序遍历稍难,后序遍历最伤脑筋。下面分别说一说三种非递归遍历算法的实现方式。
1. 非递归先序遍历
如上所述,非递归先序遍历方式较为简单。其主要思想是:
- 将二叉树的根节点压入栈中。
- 获取栈顶元素
P
并打印其值,做出栈操作。 - 若
p
的右节点存在,将其压入栈中。 - 如
p
的左节点存在,将其压入栈中。 - 如果栈不为空的话,重复2-4操作。
这种方法说的直白一点就是先读取栈顶节点,然后在将该节点的子节点按照先右后左的方式入栈。这样就保证了在每次循环过程中都能先访问根节点,再以先序的方式遍历左子树和右子树。
void PreTraverseNoRecursion(BiTree T)
{
//BiTree 与TreeNode* 是相同类型
stack<TreeNode*> temp_stack;
TreeNode* temp_node;
temp_stack.push(T);
do
{
temp_node = temp_stack.top();
cout << temp_node->data << " ";
temp_stack.pop();
if (temp_node->Rchild)
temp_stack.push(temp_node->Rchild);
if (temp_node->Lchild)
temp_stack.push(temp_node->Lchild);
} while (!temp_stack.empty());
}
2.非递归中序遍历
非递归的中序遍历要比先序遍历难一些,主要是不容易思考全面。
非递归中序遍历的主要思想是:
- 定义一个结点指针
p
,使其指向根节点。 - 若
p
不为空或栈不为空,将根节点和二叉树最左边的各个节点逐个压入栈中。中序遍历必须先访问左子树,因此要把根节点和二叉树最左边的各个节点逐个压入栈中。这个过程能够保证栈顶的节点是整个二叉树最左边最底层的节点,该节点不存在左子树。 - 访问栈顶节点
q
并出栈。 - 令
p
等于q
的右节点。节点q
不存在左子树,但不一定不存在右子树。若其存在右子树,令p
等于q
的右节点,相当于让p
作为一颗新二叉树的根节点,重复2-3操作。若q
没有右子树,p
将访问栈中新的顶节点。 - 重复2-4操作。
void MidTraverseNoRecursion(BiTree T)
{
stack<TreeNode*> temp_stack;
TreeNode* temp_node = T;
while (temp_node || !temp_stack.empty())
{
while (temp_node)
{
temp_stack.push(temp_node);
temp_node = temp_node->Lchild;
}
temp_node = temp_stack.top();
cout << temp_node->data << " ";
temp_stack.pop();
temp_node = temp_node->Rchild;
}
}
3.非递归后序遍历
非递归后序遍历是三种非递归遍历算法中最难的一种,而它难就难在需要另外一个指针来记录上一次访问的节点。
其主要思想是:
- 定义两个节点指针cur_node 和last_node,并使他们指向根节点。
- 将根节点压入栈中。
- 若栈不为空,则令cur_node指向栈顶节点。
- 若 (cur_node指向节点没有子节点) 或 (cur_node指向节点的右节点为空 且last_node指向的节点为当前节点的左节点) 或 (last_node指向的节点为当前节点的右节点),则访问该节点并出栈。否则将该节点的子节点按照先右后左的方式入栈。
- 重复3-4操作。
该方法不易想到,但是想到后理解起来也很容易。其实它要做的就是先将节点从根节点开始按照先右后左的顺序存入栈中,直到找到最左边最底层的叶节点L
。找到该叶节点后对其进行访问,然后令last_node指向它,用已标记,并进行出栈操作,表示完成该节点的访问。若L
的父节点存在右节点(即L
存在兄弟节点),则此时栈顶元素应该是这个L
的兄弟节点(称其为b)。若b无子节点,则对其访问,并用last_node进行标记,然后出栈。否则将其子节点入栈。如果完成了对L
的访问,且b
不存在,则访问其父节点,然后出栈。该过程由cur_node ->Rchild == NULL && last_node == cur_node ->Lchild 条件控制。如果完成了对L
和b
的访问,则访问其父节点,然后出栈。该过程由last_node == cur_node ->Rchild条件控制。
理解清楚之后也不是很难。
void PostTTraverseNoRecursion(BiTree T)
{
stack<TreeNode*> temp_stack;
TreeNode* cur_node = T;
TreeNode* last_node = T;
temp_stack.push(T);
while (!temp_stack.empty())
{
cur_node = temp_stack.top();
if ((cur_node ->Lchild == NULL && cur_node ->Rchild == NULL) || (cur_node ->Rchild == NULL && last_node == cur_node ->Lchild) || (last_node == cur_node ->Rchild))
{
cout << cur_node ->data << " ";
last_node = cur_node ;
temp_stack.pop();
}
else
{
if (cur_node ->Rchild)
temp_stack.push(cur_node ->Rchild);
if (temp_node->Lchild)
temp_stack.push(cur_node ->Lchild);
}
}
}
若有人看到本篇博客,那我就送他一句话:世界上就怕认真二字,格物致知方能有所收获。