二叉樹的基本操作請看:http://blog.csdn.net/sysu_arui/article/details/7865876
前面簡答介紹了二叉樹的基本操作,包括二叉樹的建立,銷燬,遞歸遍歷,以及其他一些常見的遞歸算法,現在集中討論一下二叉樹的層次遍歷和非遞歸遍歷。
二叉樹的層次遍歷要用到隊列,前序、中序和後序非遞歸遍歷要用到棧,這裏我就不自己寫隊列和棧了,直接使用C++標準庫中的容器適配器queue和stack。
1、 二叉樹的層次遍歷
二叉樹的層次遍歷類似圖的廣度優先遍歷,都用到了隊列。
基本思路:
(1)根結點非空,則入隊列
(2)隊列非空,隊首元素出隊列,輸出結點值,若結點有左孩子,左孩子入隊列;若結點有右孩子,右孩子也入隊列。
(3)重複步驟(2)直到隊列爲空
顯然通過以上步驟,得到的是二叉樹從上至下,從左到右的層次遍歷序列。實現代碼如下:
//二叉樹的層次遍歷,用到了隊列
void levelOrderTraverse(const BiTree& T)
{
queue<BiTree> q;
BiTree p = NULL;
if(T)//若根結點非空,則入隊列
{
q.push(T);
}
while(!q.empty())//隊列非空
{
p = q.front();
q.pop();
cout<<p->data<<" ";
if(p->lchild)//左孩子不空,入隊列
{
q.push(p->lchild);
}
if(p->rchild)//右孩子不空,入隊列
{
q.push(p->rchild);
}
}
}
2、二叉樹的先序非遞歸遍歷
二叉樹的先序非遞歸遍歷比較簡單,基本思路是先輸出結點值,再入棧,然後遍歷左子樹。退棧時,遍歷棧頂結點的右子樹,看代碼:
//二叉樹的先序非遞歸遍歷
void iterativePreOrderTraverse(const BiTree& T)
{
stack<BiTree> s;
BiTree p = T;
while(p || !s.empty())//p非空,或棧非空
{
if(p)
{
//輸出根結點,根結點入棧,遍歷左子樹
cout<<p->data<<" ";
s.push(p);
p = p->lchild;
}
else
{
//根結點退棧,遍歷右子樹
p = s.top();
s.pop();
p = p->rchild;//轉右孩子結點
}
}
}
3、二叉樹的中序非遞歸遍歷
二叉樹的中序非遞歸遍歷也很簡單,和先序不同的是,中序是先進棧,再遍歷左子樹;出棧時,才輸出結點值,然後遍歷右子樹,代碼如下:
//二叉樹的中序非遞歸遍歷
void iterativeInOrderTraverse(const BiTree& T)
{
stack<BiTree> s;
BiTree p = T;
while(p || !s.empty())//p非空,或棧非空
{
if(p)
{
//根指針進棧 ,遍歷左子樹
s.push(p);
p = p->lchild;
}
else
{
//根指針退棧,訪問根結點,遍歷右子樹
p = s.top();
s.pop();
cout<<p->data<<" ";
p = p->rchild;
}
}
}
4.二叉樹的後序非遞歸遍歷
二叉樹的後序非遞歸遍歷,比先序和中序非遞歸遍歷,稍微複雜點。因爲後序遍歷的順序是左子樹->右子樹->根結點,所以我們在遍歷過程中得標記左右子樹的狀態。這裏我們用一個bool變量標識結點p的右子樹遍歷狀態,false表示右子樹未遍歷,true則表示右子樹已遍歷。代碼如下:
(1)版本1
//二叉樹的後序非遞歸遍歷
void iterativePostOrderTraverse(const BiTree& T)
{
stack<pair<BiTree,bool> > s;
BiTree p = T;
while(p || !s.empty())
{
if(p)
{
s.push(make_pair(p,false));//false表示根結點p的右子樹未訪問
p = p->lchild;
}
else
{
if(s.top().second == false)//根結點的右子樹未訪問
{
s.top().second = true;//標誌右子樹爲已訪問
p = s.top().first->rchild;//遍歷右子樹
}
else//右子樹已訪問
{
cout<<s.top().first->data<<" "; //輸出根結點值
s.pop();//根結點出棧
}
}
}
}
(2)版本2//後序非遞歸遍歷的另一個版本
void anotherIterativePostOrderTraverse(const BiTree& T)
{
stack<pair<BiTree,bool> > s;
BiTree p = T;
do
{
while(p)
{
s.push(make_pair(p,false));
p = p->lchild;
}
while(!s.empty() && s.top().second == true)
{
cout<<s.top().first->data<<" ";
s.pop();
}
if(!s.empty())
{
s.top().second = true;
p = s.top().first->rchild;
}
}while(!s.empty());
}
注:我們來分析一下,後序非遞歸遍歷的執行過程。舉一個簡單的例子吧,使用根結點爲A,其左右子樹分別爲B、C的樹來講解版本(1)的執行過程。
(1)初始時,p =T,接着執行循環,若根結點非空,則<p,false>入棧,表示根結點p的右子樹還未遍歷,同時置p爲其左指針,遍歷左子樹。若根結點爲空(此時棧也爲空),循環條件不滿足,直接結束。
(2)在接下來的循環中,若p爲空則看棧頂結點狀態。若其右子樹未遍歷,則遍歷其右子樹(p值發生改變),否則直接輸出結點值,並彈出棧頂結點(p值未改變)。
(3)p=A,<A,false>入棧,p=A->lchild=B;
p=B,<B,false>入棧,p=NULL;
p=NULL,棧非空,且棧頂結點B的右子樹未訪問,置true,然後訪問B的右子樹,p=B->rchild=NULL;
p=NULL,棧非空,且棧頂結點B的右子樹已訪問,輸出結點B,彈出棧頂結點B,注意此時p==NULL,因爲這個過程p的值未改變。
p=NULL,棧非空,且此時棧頂結點A的右子樹未訪問,置true,然後訪問A的右子樹,p=A->rchild=C;
p=C,非空,<C,false>入棧,p = C->lchild=NULL;
p=NULL,棧非空,棧頂元素C的右子樹爲訪問,置true,然後訪問C的右子樹,p=C->rchild=NULL;
p=NULL,棧非空,棧頂元素C的右子樹已訪問,輸出結點C,彈出棧頂元素C。
p=NULL,棧非空,棧頂元素A的右子樹已訪問,輸出結點A,彈出棧頂元素A。
p=NULL,此時棧已爲空。
(4)注意到在上述的分析過程中,我們發現當p的值爲NULL,出棧,p的值未改變,此時我們不用判斷p的值,可以直接判斷棧頂元素的右子樹的訪問狀態,於是調整循環結構就得到了版本(2)。但是它們原理都是一樣的。
5、完整測試代碼
#include <cstdlib>
#include <iostream>
#include <stack>
#include <queue>
using namespace std;
//二叉樹定義
typedef char ElementType;
typedef struct BiTreeNode
{
ElementType data;
struct BiTreeNode* lchild;
struct BiTreeNode* rchild;
}BiTreeNode, *BiTree;
//遞歸的建立一棵二叉樹
//輸入爲二叉樹的先序序列
void createBiTree(BiTree &T)
{
char data;
data = getchar();
if(data == '#')
{
T = NULL;
}
else
{
T = new BiTreeNode;
T->data = data;
createBiTree(T->lchild);
createBiTree(T->rchild);
}
}
//遞歸銷燬一棵二叉樹
void destroyBiTree(BiTree &T)
{
if(T)
{
destroyBiTree(T->lchild);
destroyBiTree(T->rchild);
delete T;
T = NULL;
}
}
//二叉樹的層次遍歷,用到了隊列
void levelOrderTraverse(const BiTree& T)
{
queue<BiTree> q;
BiTree p = NULL;
if(T)//若根結點非空,則入隊列
{
q.push(T);
}
while(!q.empty())//隊列非空
{
p = q.front();
q.pop();
cout<<p->data<<" ";
if(p->lchild)//左孩子不空,入隊列
{
q.push(p->lchild);
}
if(p->rchild)//右孩子不空,入隊列
{
q.push(p->rchild);
}
}
}
//二叉樹的先序非遞歸遍歷
void iterativePreOrderTraverse(const BiTree& T)
{
stack<BiTree> s;
BiTree p = T;
while(p || !s.empty())//p非空,或棧非空
{
if(p)
{
//輸出根結點,根結點入棧,遍歷左子樹
cout<<p->data<<" ";
s.push(p);
p = p->lchild;
}
else
{
//根結點退棧,遍歷右子樹
p = s.top();
s.pop();
p = p->rchild;//轉右孩子結點
}
}
}
//二叉樹的中序非遞歸遍歷
void iterativeInOrderTraverse(const BiTree& T)
{
stack<BiTree> s;
BiTree p = T;
while(p || !s.empty())//p非空,或棧非空
{
if(p)
{
//根指針進棧 ,遍歷左子樹
s.push(p);
p = p->lchild;
}
else
{
//根指針退棧,訪問根結點,遍歷右子樹
p = s.top();
s.pop();
cout<<p->data<<" ";
p = p->rchild;
}
}
}
//二叉樹的後序非遞歸遍歷
void iterativePostOrderTraverse(const BiTree& T)
{
stack<pair<BiTree,bool> > s;
BiTree p = T;
while(p || !s.empty())
{
if(p)
{
s.push(make_pair(p,false));//false表示根結點p的右子樹未訪問
p = p->lchild;
}
else
{
if(s.top().second == false)//根結點的右子樹未訪問
{
s.top().second = true;//標誌右子樹爲已訪問
p = s.top().first->rchild;//遍歷右子樹
}
else//右子樹已訪問
{
cout<<s.top().first->data<<" "; //輸出根結點值
s.pop();//根結點出棧
}
}
}
}
//後序非遞歸遍歷的另一個版本
void anotherIterativePostOrderTraverse(const BiTree& T)
{
stack<pair<BiTree,bool> > s;
BiTree p = T;
do
{
while(p)
{
s.push(make_pair(p,false));
p = p->lchild;
}
while(!s.empty() && s.top().second == true)
{
cout<<s.top().first->data<<" ";
s.pop();
}
if(!s.empty())
{
s.top().second = true;
p = s.top().first->rchild;
}
}while(!s.empty());
}
int main(int argc, char *argv[])
{
BiTree T = NULL;
createBiTree(T);//建立二叉樹 如輸入AB#D##CE###
// createBiTreeWithGenList(T);//如輸入A(B(,D),C(E))#
cout<<"levelOrder: ";
levelOrderTraverse(T);
cout<<endl;
cout<<"preOrder: "; //先序遍歷
// preOrderTraverse(T);
iterativePreOrderTraverse(T);
cout<<endl;
cout<<"inOrder: ";//中序遍歷
// inOrderTraverse(T);
iterativeInOrderTraverse(T);
cout<<endl;
cout<<"postOrder: ";//後序遍歷
// postOrderTraverse(T);
// iterativePostOrderTraverse(T);
anotherIterativePostOrderTraverse(T);
cout<<endl;
destroyBiTree(T);//銷燬二叉樹,釋放空間
system("PAUSE");
return EXIT_SUCCESS;
}
注:測試代碼中的一些其他函數請看二叉樹的基本操作:http://blog.csdn.net/sysu_arui/article/details/7865876