数据结构之二叉树的遍历
今年暑假准备复习一下数据结构与算法
今天看到二叉树这一节,发现自己对二叉树的遍历的非递归方法还没有掌握,因此写个博客记录一下
typedef struct node
{
int value;
struct node * left;
struct node * right;
} *Binarytree;
首先先摆递归版本
前序遍历
void r_pre_traverse(Binarytree b)
{
if (b!=NULL)
{
printf("%d\n",b->value);
pre_traverse(b->left);
pre_traverse(b->right);
}
}
其他递归遍历方法只是换了个位置而已,就不放在这里占位置了
非递归循环版本
中序遍历(中序遍历比较简单先写这个)
首先我们来分析一下主要操作
- 寻找左子树,将当前节点压入栈,直到左子树为空
- 从栈中弹出来一个节点,访问他的值(出栈即代表访问),然后切换到他的右子树,将右子树压入栈
循环结束的情况分析
- 节点为空,栈为空
- 结束循环
- 节点不为空,栈为空
- 刚刚开始执行,函数还未进入循环体时
- 在循环过程中,栈为空即代表当前节点没有父节点,所以此时执行到根节点,正准备切换到根节点的右子树
- 也有可能在循环体的访问最后一个右节点时出现
- 节点为空,栈不为空
- 栈不为空代表有父节点,节点为空,说明该节点为
st.top()
的右子树
- 栈不为空代表有父节点,节点为空,说明该节点为
- 节点不为空,栈不为空
- 这个没什么好说的
总体的流程大概是
while(b!=nullptr||!st.empty()){//右子树为空且栈空时结束
while(b!=nullptr){//直到左子树为空
执行1;
}
if(!st.empty()){
执行2;
}
}
丢代码
void inoder_traverse(Binarytree b)
{
if (b==nullptr)
return;
stack<Binarytree > st;
while (b!=nullptr||!st.empty())
{
while (b!=nullptr)//节点不为空
{
st.push(b);//压入当前节点
b=b->left;//切换到左子树
}
//左子树为空,且栈不为空的情况下切换到父节点
if(!st.empty()){
b=st.top();
st.pop();
//访问节点
cout<<b->value<<endl;
//如果有右子树,则切换到右子树,没有的话为空,再次循环时也会正常,所以不需要判断
b=b->right;
}
}
}
前序遍历
首先我们来分析一下主要操作
- 边访问节点边输出当前节点的值,并把节点存入栈中,然后访问左子树,直到访问节点为空
- 如果栈不空,从栈中弹出节点,访问他的右子树
循环结束的情况分析
- 感觉情况和中序遍历差不多的样子,就不列举了
总体的流程大概是
while(b!=nullptr||!st.empty()){
while(b!=nullptr){
执行1;
}
if(!st.empty()){
执行2;
}
}
丢代码
void pre_traverse(Binarytree b)
{
if (b==nullptr)
return;
stack<Binarytree > st;
while (b!=nullptr||!st.empty())
{
while (b!=nullptr)//节点不为空
{
//访问节点
cout<<b->value<<endl;
st.push(b);//压入当前节点
b=b->left;//切换到左子树
}
//左子树为空,且栈不为空的情况下切换到父节点
if(!st.empty()){
b=st.top();
st.pop();
//如果有右子树,则切换到右子树,没有的话为空,再次循环时也会正常,所以不需要判断
b=b->right;
}
}
}
后序遍历,这个好像有点复杂来着(有两种思路,一种是节点增加一个参数用以确认是否第一次访问,具体方法呢和上面两种差不多,第二种是使用双指针的方法,这里着重讨论第二种)
具体思路
利用两个指针 cur和pre进行操作,其中cur用来保存当前节点的指针,pre前一次访问的节点(pre和cur没有确定的父子关系).要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。
- 如果P不存在左孩子和右孩子,则可以直接访问它;
- 或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。
- 若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。
首先我们来分析一下主要操作
- 访问节点 直接将节点压入栈中
- 如果节点不存在子树,直接访问
- 如果节点存在节点,待按照顺序访问子节点后再从栈中弹出父节点进行访问
循环结束的情况分析(循环开始前将根节点压入)
- 节点为空,栈为空
- 空树,直接过滤
- 节点不为空,栈为空
- 根节点输出完毕,结束循环
- 节点为空,栈不为空
- 右端子树访问完毕,
- 节点不为空,栈不为空
- 这个没什么好说的
总体的流程大概是
while(!st.empty()){
if((cur->left==nullptr&&cur->right==nullptr)||
((pre!=nullptr)&&(pre==cur->left||pre==cur->right))){//节点不存在子树,或者子树已经都被访问过了
执行1;
}
else{
执行2;
}
}
丢代码
void postorder_traversal(Binarytree b)
{
if (b==nullptr)
return;
Binarytree cur;//是指针哦 保存当前访问的节点
Binarytree pre=nullptr;
stack<Binarytree > st;
st.push(b);//先把根节点压入堆栈
while (!st.empty())//堆栈不空,堆栈空的时候表示根节点已经被访问了
{
cur=st.top();//当前访问的节点为栈顶节点 不弹出
//节点没有子树
if ((cur->left==nullptr&&cur->right==nullptr)||
(pre!=nullptr&&(pre==cur->left||pre==cur->right)))//或者节点的子树都被访问过了,即上一个访问的节点是当前节点的子节点
{
//访问当前节点
cout<<cur->value<<endl;
st.pop();//弹出堆栈 弹出就代表访问了该节点
pre=cur;//为下一次访问做准备
}
else//存在还未访问的子树,进行子树的访问
{
if (cur->left!=nullptr)
st.push(cur->left);
if (cur->right!=nullptr)
st.push(cur->right);
}
}
}
这个算法的实现我参考了这个大佬的博客:二叉树的非递归遍历