二叉树的前中后序可以采用递归方式实现,但是当树的层数较大时,递归效率就会降低,因此可以试着采用非递归方式进行树的前中后序遍历。
但树的遍历涉及到左右孩子,它不像链表那样只有一个前驱后继,因此我们需要借助一些简单的数据结构来帮助我们实现遍历:
以下只给出功能实现部分的的代码,其中树的创建及递归遍历配套代码可以查看我的上一篇博文,c语言实现二叉树的创建及遍历
你可以将本次代码和上篇的代码合并验证。
树的层序遍历
层序遍历是对树逐层遍历,从上到下,自左向右遍历
使用队列作为辅助空间时;具体思路是:先打印根节点,然后左孩子入队,右孩子入队(根据队列的先入先出特点,左孩子在左部分,我们要从左向右打印,因此先将左孩子入队)
其中记录每一层的未遍历节点数,每出队一个节点打印,该层未遍历节点数减 1 ,当减为 0 时说明该层打印结束,此时队列中保存的节点数就是下一层的节点个数。这样就可以不断层序遍历
当使用栈作为辅助空间的话,和上面实现不同之处在于栈是先入先出的特点,那么就要先右孩子入栈,再左孩子入栈。其余均相同。
代码实现:
void BinaryTreeLevelOrder(BTNode* root)
{
assert(root!=NULL);
queue<BTNode*> qu;
qu.push(root);
int nLeveNum=1;//第一行节点个数为1
while(!qu.empty())
{
BTNode*tmp=qu.front();
qu.pop();
printf("%d ",tmp->_data);
//printf("%c ",tmp->_data);
nLeveNum--;
if(tmp->_left)
{
qu.push(tmp->_left);
}
if(tmp->_right)
{
qu.push(tmp->_right);
}
if(nLeveNum==0)
{
nLeveNum=qu.size();
putchar('\n');
}
}
}
树的前序遍历
前序遍历就是先遍历根节点,再访问左右子树。
因此我们需要将右节点保存起来,当访问完根节点和左子树时,再去访问右子树;
具体思路:
- 不断遍历根节点,并在操作后将右孩子入栈,然后再进入根节点的左孩子。直到左孩子为空,
- 然后取栈顶节点(上个根节点的右孩子)作为根节点,再次执行步骤 1
- 当栈为空时,说明右孩子遍历结束了,那么前序遍历也结束
代码实现:
void BinaryTreePrevOrderNonR(BTNode* root)
{
assert(root!=NULL);
stack<BTNode*> st;
st.push(root);
BTNode*cur=root;
while(!st.empty())
{
printf("%d ",cur->_data);
//printf("%c ",cur->_data);
if(cur->_right)
{
st.push(cur->_right);
}
if(cur->_left)
{
cur=cur->_left;
}
else
{
cur=st.top();
st.pop();
}
}//栈为空且右孩子为空时(树中最右下角的节点处)遍历结束
}
树的中序遍历
中序遍历是先遍历左子树后遍历根节点,再去遍历右子树
仍然借助栈结构帮我们保存节点,我们需要先遍历左子树,但是同时还要保存根节点,当在遍历根节点时将右孩子保存。
具体实现:
- 先将根节点入栈(保存根节点),再进入左孩子,
- 重复步骤 1,直到左孩子为空,打印根节点,出栈,取栈顶(上一个根节点)
- 打印上一个根节点,出栈,进入该根节点的右孩子,该右孩子作为根节点回到步骤 1
代码实现:
void BinaryTreeInOrderNonR(BTNode* root)
{
assert(root!=NULL);
stack<BTNode*> st;
BTNode*cur=root;
while(cur||!st.empty())
{
for(;cur;cur=cur->_left)
{
st.push(cur);
}
cur=st.top();
st.pop();
printf("%d ",cur->_data);
//printf("%c ",cur->_data);
cur=cur->_right;
}
}
树的后序遍历
后续遍历是在遍历完左右子树之后再遍历根节点
同样是借助栈实现,可以这里情况比较复杂,中序遍历中我们可以保存根节点,然后再遍历根节点之后遍历右子树,但是我们在后序,如果我们遍历完右子树,我们还能回到根节点吗?由于我们实现的是二叉链的链式存储结构,所以我们不能从右孩子直接回到根节点,那么就需要其他办法了!
既然不能从右子树回到根节点,那么我们就不让根节点出栈,先让它待在栈中,当左右子树都遍历结束之后再通知它出栈遍历。那么如何通知它呢?什么时候通知呢?
我们可以在增加一个辅助空间来标记每个根节点的右子树是否被遍历,当标记未遍历时,我们就让他乖乖待在栈中,当我们遍历它的右子树时改变它的标记,当右子树遍历结束后,它发现自己的标记改变了(说明右孩子遍历结束了)然后就可以出狱了!
具体实现:
- 根节点入栈,并标记为false,进入左孩子;重复步骤一,直到左孩子为空,
- 取栈顶(上一个根节点),同时判断该节点的标志位。 标志位为false,说明右孩子未遍历,去步骤三;如果标志位为true,打印该根节点,出栈。重复步骤二
- 将栈顶节点的标记改变为true(说明该根节点的右孩子被遍历),进栈顶节点的右孩子,右孩子作为根节点回到步骤一
- 当栈为空时(整颗树的根节点被打印出栈)结束!
void BinaryTreePostOrderNonR(BTNode* root)
{
assert(root);
stack<BTNode*> st;
stack<bool> flag;//设标志,标志该节点是否已经被访问过
BTNode*cur=root;
while(cur||!st.empty())
{
for(;cur;cur=cur->_left)
{
st.push(cur);
flag.push(false);
}
while(!st.empty()&&(flag.top()==true))
{
cur=st.top();
flag.pop();
st.pop();
printf("%d ",cur->_data);
//printf("%c ",cur->_data);
cur=NULL;
}
if(!st.empty())
{
cur=st.top();
flag.top()=true;
cur=cur->_right;
}
}
}
执行结果: