您的位置:首页 > 编程语言

2015-03-15---二叉树递归(非递归)实现先序、中序、后序遍历(附代码)

2015-03-16 00:00 323 查看
今天说好的不碰代码的,后来还是没忍住,学了学数据结构和算法,就先讲讲先序中序和后序遍历吧,我还写了代码,一套递归方式实现遍历二叉树,还有两套非递归方式遍历二叉树,

从简单的开始,因为二叉树的所有操作都是要求在能够遍历的基础上啊。

学过数据结构的肯定都明白遍历顺序,

先序遍历就是先自己,然后左子树,然后右子树,

中序遍历就是先左子树,然后自己,然后右子树

后序遍历就是先左子树,然后右子树,然后自己



比如上图这个很简单的二叉树:

先序遍历:1 2 4 5 3 6 7

中序遍历:4 2 5 1 6 3 7

后序遍历:4 5 2 6 7 3 1

具体的遍历方法就是我们上面说的啊。

不说了,我们用代码实现:

先说简单的递归:

class Node //树的节点,没有用c++的模板,我们只说算法,模板慢慢来
{
public:
int data;
Node *pLeft = NULL;
Node *pRight = NULL;
};
Node *pRoot;//简单粗暴的方法初始化一棵树,重点不在这,大家自己进行脑补
Node s1;
Node s2;
Node s3;
Node s4;
Node s5;
Node s6;
Node s7;

s1.data = 1;
s2.data = 2;
s3.data = 3;
s4.data = 4;
s5.data = 5;
s6.data = 6;
s7.data = 7;

pRoot = &s1;
s1.pLeft = &s2;
s1.pRight = &s3;
s2.pLeft = &s4;
s2.pRight = &s5;
s3.pLeft = &s6;
s3.pRight = &s7;

接下来才是重点:
void qiandigui(Node *pRoot) //递归方法,先序遍历二叉树
{
if (pRoot == nullptr)
{
return;
}
cout << pRoot->data << " ";
if (pRoot->pLeft != nullptr)
{
qiandigui(pRoot->pLeft);
}
if (pRoot->pRight != nullptr)
{
qiandigui(pRoot->pRight);
}
}
void zhongdigui(Node *pRoot)<span style="white-space:pre">	</span><span style="font-family: Arial, Helvetica, sans-serif;">//递归方法,中序遍历二叉树</span>
{
if (pRoot == nullptr)
{
return;
}
if (pRoot->pLeft != nullptr)
{
zhongdigui(pRoot->pLeft);
}
cout << pRoot->data << " ";
if (pRoot->pRight != nullptr)
{
zhongdigui(pRoot->pRight);
}
}

void houdigui(Node *pRoot)<span style="white-space:pre">	</span>//递归方法,后序遍历二叉树
{
if (pRoot == nullptr)
{
return;
}
if (pRoot->pLeft != nullptr)
{
houdigui(pRoot->pLeft);
}
if (pRoot->pRight != nullptr)
{
houdigui(pRoot->pRight);
}
cout << pRoot->data << " ";
}


下面开始非递归算法:
注意:和别人的博客不太一样的是,非递归算法,我这里有两套,一套是我自己开始写的,感觉还好,大家可以看看,也可以给我提提意见,还有一套是网上很流行的。

我们先从网上流行的开始:

void stackxianpopulance(Node *pRoot)<span style="white-space:pre"> </span>//非递归方法,先序遍历二叉树

{
stack<Node *> mystack;
Node *pnow = pRoot;
while (pnow != nullptr || false == mystack.empty())
{

while (pnow)
{
cout << pnow->data << " ";
mystack.push(pnow);
pnow = pnow->pLeft;
}
pnow = mystack.top();
mystack.pop();
pnow = pnow->pRight;
}
}
void stackzhongpopulance(Node *pRoot)<span style="white-space:pre">	</span>//非递归方法,中序遍历二叉树
{
stack<Node *> mystack;
Node *pnow = pRoot;
while (pnow != nullptr || false == mystack.empty())
{
while (pnow)
{
mystack.push(pnow);
pnow = pnow->pLeft;
}
pnow = mystack.top();
mystack.pop();
cout << pnow->data << " ";
pnow = pnow->pRight;
}
}


由于这个后序二叉树,在遍历完左子树后要找到右子树,所以网上很流行的版本今天很晚了,就偷了懒,没有实现,因为博主明天还要考试啊,实现完了估计又到夜里了,
所以就网上找一份给大家看看,为了临时的顶上来:

前序、中序、后序的非递归遍历中,要数后序最为麻烦,如果只在栈中保留指向结点的指针,那是不够的,必须有一些额外的信息存放在栈中。
方法有很多,这里只举一种,先定义栈结点的数据结构
typedef struct{Node * p; int rvisited;}SNode //Node 是二叉树的结点结构,rvisited==1代表p所指向的结点的右结点已被访问过。

lastOrderTraverse(BiTree bt){
  //首先,从根节点开始,往左下方走,一直走到头,将路径上的每一个结点入栈。
  p = bt;
  while(bt){
    push(bt, 0); //push到栈中两个信息,一是结点指针,一是其右结点是否被访问过
    bt = bt.lchild;
  }

  //然后进入循环体
  while(!Stack.empty()){ //只要栈非空
    sn = Stack.getTop(); // sn是栈顶结点

    //注意,任意一个结点N,只要他有左孩子,则在N入栈之后,N的左孩子必然也跟着入栈了(这个体现在算法的后半部分),所以当我们拿到栈顶元素的时候,可以确信这个元素要么没有左孩子,要么其左孩子已经被访问过,所以此时我们就不关心它的左孩子了,我们只关心其右孩子。

    //若其右孩子已经被访问过,或是该元素没有右孩子,则由后序遍历的定义,此时可以visit这个结点了。
    if(!sn.p.rchild || sn.rvisited){
      p = pop();
      visit(p);
    }
    else //若它的右孩子存在且rvisited为0,说明以前还没有动过它的右孩子,于是就去处理一下其右孩子。
    {
      //此时我们要从其右孩子结点开始一直往左下方走,直至走到尽头,将这条路径上的所有结点都入栈。

      //当然,入栈之前要先将该结点的rvisited设成1,因为其右孩子的入栈意味着它的右孩子必将先于它被访问(这很好理解,因为我们总是从栈顶取出元素来进行visit)。由此可知,下一次该元素再处于栈顶时,其右孩子必然已被visit过了,所以此处可以将rvisited设置为1。
      sn.rvisited = 1;

      //往左下方走到尽头,将路径上所有元素入栈
      p = sn.p.rchild;
      while(p != 0){
        push(p, 0);
        p = p.lchild;
      }
    }//这一轮循环已结束,刚刚入栈的那些结点我们不必管它了,下一轮循环会将这些结点照顾的很好。
  }
}上面这位大哥的代码我们就这么ctrl+c,ctrl+v粘贴过来了,还请那位,如果看见了就算了啊,没准你也是这么来的呢,哈哈。。。

接下来呢,这一套是我自己写的,不知道合不合大家胃口,大家看一看吧,感觉这个改动起来要比上面的populance版本改动起来要好的多啊,比如前序改中序,后序改前序,等等。

废话少说,贴代码:

class StackNode<span style="white-space:pre"> </span>//这个是栈里面装的类型,
{
public:
bool flag = false;//flag表示的意思也就是可不可以打印吧,这么理解就好了
Node *p = nullptr;
StackNode(bool flag, Node *p) :flag(flag), p(p)
{

}
};

void xianstackmy(Node *pRoot)<span style="white-space:pre">	</span>//非递归实现,先序遍历二叉树
{
stack<StackNode> mystack;
if (pRoot == nullptr)
{
return;
}
mystack.push(StackNode(false, pRoot));
while (false == mystack.empty())
{
StackNode now = mystack.top();
mystack.pop();
if (now.p != nullptr)
{
if (now.flag)//如果可以打印,就打印,flag的意思
{
cout << now.p->data << " ";
}
else
{
mystack.push(StackNode(false, now.p->pRight));//先把右子树压打栈底
mystack.push(StackNode(false, now.p->pLeft));//再把左子树压栈
now.flag = true;//当前节点设置为可以打印
mystack.push(now);//将当前可以打印的节点压栈
}
}
}

cout << endl;
}

void zhongstackmy(Node *pRoot)<span style="white-space:pre">	</span>//非递归实现,中序遍历二叉树
{
stack<StackNode> mystack;
if (pRoot == nullptr)
{
return;
}
mystack.push(StackNode(false, pRoot));
while (false == mystack.empty())
{
StackNode now = mystack.top();
mystack.pop();
if (now.p == nullptr)
{
continue;
}
if (now.flag)//我们之前设置过可以打印的啊,这里可以打印的就直接打印了,不符合的继续执行else
{
cout << now.p->data << " ";
}
else
{
mystack.push(StackNode(false, now.p->pRight));//先将右子树压到栈底
now.flag = true;//当前节点设置为可以打印
mystack.push(now);
mystack.push(StackNode(false, now.p->pLeft));//将左子树压到栈底
}
}

cout << endl;
}


void houstackmy(Node *pRoot)<span style="white-space:pre">	</span>//非递归方式实现,后序遍历二叉树
{
stack<StackNode> mystack;
if (pRoot == nullptr)
{
return;
}
mystack.push(StackNode(false, pRoot));
while (false == mystack.empty())
{
StackNode now = mystack.top();
mystack.pop();
if (now.p == nullptr)
{
continue;
}
if (now.flag)
{
cout << now.p->data << " ";
}
else
{
now.flag = true;//先设置当前节点为可以打印
mystack.push(now);
mystack.push(StackNode(false, now.p->pRight));//压栈右子树
mystack.push(StackNode(false, now.p->pLeft));//压栈左子树
}
}

cout << endl;
}


以上的就是我写的二叉树的遍历方式,有什么问题大家请多指教,

我写的应该算是,递归转栈的间接转换法吧,

模板如下:

间接转换法 :

    该方法使用栈保存中间结果,一般需根据递归函数在执行过程中栈的变化得到。其一般过程如下: 
       <1>将初始状态s0进栈 

    <2>while (栈不为空) 

         { 

             退栈,将栈顶元素赋给s; 

             if (s是要找的结果) {

                       返回;

                 } 

             else { 

                   寻找到s的相关状态s1; 

                   将s1进栈; 

                    } 

         } 

你看,我的模板大概也如此,递归的参数还有逻辑很重要,

递归转栈,如何用栈模拟递归,还有参数很重要,弄明白这些就好了。

这个问题,我过几天还会继续研究,有成果和大家一起分享,

明天还要考试啊,今天赶紧去刷牙了,然后睡觉,讲的不够仔细还望大家见谅
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐