二叉树的递归和非递归遍历。递归遍历很简单,非递归遍历乍看起来很难,理解之后也是挺简单的。
1、二叉树的节点结构:
struct Node
{
DataType data;
Node *left;
Node *right;
};
2、用递归实现二叉树的先序、中序、后序遍历:
首先看下二叉树的递归序,代码如下:
void f(Node *node)
{
if (nullptr == node)
{
return;
}
//1
f(node->left);
//2
f(node->right);
//3
}
如果在//1、//2、//3处都做一次打印本节点的操作,则流程如下:
这样打印出来的序列就是递归序。
在递归序的基础上可以加工出三种形式的遍历:先序,中序,后序。
先序(头左右):即在递归序的基础上,第一次遇到节点的时候打印,后两次遇到不打印,比如上面递归序的先序遍历过程为:
1打印,2打印,4打印,4不打印,4不打印,2不打印,5打印,5不打印,5不打印,2不打印,1不打印,3打印,6打印,6不打印,6不打印,3不打印,7打印,7不打印,7不打印,3不打印,1不打印。
这样先序遍历之后的结果就是1、2、4、5、3、6、7。
中序(左头右):即在递归序的基础上,第二次遇到节点的时候打印。中序遍历之后的结果为:
4、2、5、1、6、3、7。
后序(左右头):即在递归序的基础上,第三次遇到节点的时候打印。后序遍历之后的结果为:
4、5、2、6、7、3、1。
代码如下:
//先序遍历递归算法
#include <iostream>
void preOrder(Node *node)
{
if (nullptr == node)
{
return;
}
std::cout << node->data << " ";
preOrder(node->left);
preOrder(node->right);
}
//中序遍历递归算法
#include <iostream>
void inOrder(Node *node)
{
if (nullptr == node)
{
return;
}
inOrder(node->left);
std::cout << node->data << " ";
inOrder(node->right);
}
//后序遍历递归算法
#include <iostream>
void postOrder(Node *node)
{
if (nullptr == node)
{
return;
}
postOrder(node->left);
postOrder(node->right);
std::cout << node->data << " ";
}
3、用非递归实现二叉树的先序、中序、后序遍历:
任何递归都可以改成非递归。
(1)先序遍历的非递归:
分析:递归算法是系统压栈,所以非递归序要我们自己准备一个栈,实现手动压栈。栈的特点是后 进先出。而先序遍历是先头节点后左再右。那我们就利用的栈的特点,首先试一下将头节点1压栈,然后出栈打印1。接下来我们想打印2,假如让2先进栈,3后进栈,这时出栈的话就会打印3,不符合我们的需要。所以我们需要1的右孩子3先进栈,左孩子2后进栈,然后出栈打印2。再让2的右孩子5进栈,左孩子4进栈,出栈打印4。4无左右孩子,然后再出栈打印5,5无左右孩子,然后出栈打印3,让3的右孩子7进栈,再3的左孩子6进栈。出栈打印6,6无左右孩子,出栈打印7,7无左右孩子,栈空结束。
算法步骤:
1、根节点压栈。
2、出栈一个节点cur。
3、打印(处理)cur。
4、先将节点cur右孩子(若有)压栈,再将节点cur左孩子(若有)压栈。
5、重复2-4步骤,直至栈空结束。
代码实现如下:
//先序遍历非递归算法
#include <iostream>
#include <stack>
void preOrderNoRecur(Node *node)
{
if (nullptr == node)
{
return;
}
std::stack<Node*> nodeStack;
nodeStack.push(node);
while (!nodeStack.empty())
{
Node *cur = nodeStack.top();
nodeStack.pop();
std::cout << cur->data << " ";
if (nullptr != cur->right)
{
nodeStack.push(cur->right);
}
if (nullptr != cur->left)
{
nodeStack.push(cur->left);
}
}
}
(2)后序遍历的非递归:
分析:先序遍历顺序是头左右,我们已知其非递归算法压栈时是头右左。假如我们想要一种遍历顺序是头右左,那么其非递归算法压栈时只需改成头左右就可以。而头右左的遍历顺序是后序遍历顺序左右头的相反顺序,我们自然就想到如果将头右左的遍历顺序压栈,再将栈依次出栈打印,则得到左右头的顺序,这就是后序遍历。
算法步骤:
1、根节点进栈A
2、栈A出栈节点cur进辅助栈B
3、节点cur的左孩子(若有)进栈A,节点cur的右孩子(若有)进栈A
4、重复2-3步骤,直至栈Α空
5、依次出栈Β打印结果
代码实现如下:
//后序遍历非递归算法
#include <iostream>
#include <stack>
void postOrderNoRecur(Node *node)
{
if (nullptr == node)
{
return;
}
std::stack<Node*> nodeStackA;
std::stack<Node*> nodeStackB;//辅助栈
nodeStackA.push(node);//根节点进栈
while (!nodeStackA.empty())
{
Node *cur = nodeStackA.top();
nodeStackA.pop();
nodeStackB.push(cur);//cur节点进辅助栈
if (nullptr != cur->left)
{
nodeStackA.push(cur->left);
}
if (nullptr != cur->right)
{
nodeStackA.push(cur->right);
}
}
//辅助栈B依次出栈打印
while (!nodeStackB.empty())
{
Node *cur = nodeStackB.top();
nodeStackB.pop();
std::cout << cur->data << " ";
}
}
(2)中序遍历的非递归:
分析:中序遍历顺序是左头右。试想一下,任何一棵二叉树皆可被左子树分解,如果将每一个左子树依次入栈,即头入栈、左子树入栈,那么出栈顺序就是左->头。出栈时将当前节点的右孩子的左子树也依次入栈,则最后出栈时就是左->头->(右)左->头 …… 这样连起来就是左头右的顺序。
算法步骤:
1、每棵子树,整棵树左边界依次进栈,直至没有左边界
2、依次出栈打印
3、对于出栈的每个节点右孩子,重复1-2步骤,直至栈空结束。
代码实现如下:
//中序遍历非递归算法
#include <iostream>
#include <stack>
void inOrderNoRecur(Node *node)
{
if (nullptr == node)
{
return;
}
Node *p = node;//临时指针变量指向根节点,以防指针移动时改变根节点指针位置。
std::stack<Node*> nodeStack;
while (!nodeStack.empty() || nullptr != p)
{
if (nullptr != p)//左边界依次入栈
{
nodeStack.push(p);
p = p->left;
}
else //左边界到底时出栈
{
p = nodeStack.top();
nodeStack.pop();
std::cout << p->data << " ";//出栈打印
p = p->right;//p指向当前节点的右孩子
}
}
}