今天说好的不碰代码的,后来还是没忍住,学了学数据结构和算法,就先讲讲先序中序和后序遍历吧,我还写了代码,一套递归方式实现遍历二叉树,还有两套非递归方式遍历二叉树,
从简单的开始,因为二叉树的所有操作都是要求在能够遍历的基础上啊。
学过数据结构的肯定都明白遍历顺序,
先序遍历就是先自己,然后左子树,然后右子树,
中序遍历就是先左子树,然后自己,然后右子树
后序遍历就是先左子树,然后右子树,然后自己
比如上图这个很简单的二叉树:
先序遍历: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进栈;
}
}
你看,我的模板大概也如此,递归的参数还有逻辑很重要,
递归转栈,如何用栈模拟递归,还有参数很重要,弄明白这些就好了。
这个问题,我过几天还会继续研究,有成果和大家一起分享,
明天还要考试啊,今天赶紧去刷牙了,然后睡觉,讲的不够仔细还望大家见谅