二叉樹的遍歷,就是指按某條搜索路徑訪問樹中的每個結點,使得每個結點均被訪問一次,而且僅被訪問一次。
遍歷一棵二叉樹便要決定對根結點N,左子樹L和右子樹R的訪問順序。按照先遍歷左子樹再遍歷右子樹的原則,常見的遍歷次序有先序(NLR)、中序(LNR)、後序(LRN)三種遍歷算法,其中的序是指根結點在何時被訪問。
一、先序遍歷
先序遍歷(PreOrder)的操作過程如下:
若二叉樹爲空,則什麼也不做;否則:
1)訪問根結點;
2)先序遍歷左子樹;
3)先序遍歷右子樹。
對應的遞歸算法如下:
void PreOrder(BiTree T)
{
if(T!=NULL)
{
visit(T); //訪問根結點
PreOrder(T->lchild); //遞歸遍歷左子樹
PreOrder(T->rchild); //遞歸遍歷右子樹
}
}
上圖先序遍歷得到的結果爲1,2,4,6,3,5。
注意:這裏提供的是僞代碼的形式,主要是明白遍歷思想,具體要結合實際問題實際分析,下同。
二、中序遍歷
中序遍歷(InOrder)的操作過程如下:
若二叉樹爲空,則什麼也不做;否則,
1)中序遍歷左子樹;
2)訪問根結點;
3)中序遍歷右子樹。
對應的遞歸算法如下:
void InOrder(BiTree T)
{
if(T!=NULL)
{
InOrder(T->lchild); //遞歸遍歷左子樹
visit(T); //訪問根結點
InOrder(T->rchild); //遞歸遍歷右子樹
}
}
又是這個圖,其中序遍歷得到的結果爲2,6,4,1,3,5。
這裏可能不太好理解,分步列一下:
(1)進入結點1左子樹;
(2)進入結點2左子樹,爲空,結點2左子樹訪問完畢,返回結點2;
(3)訪問結點2,訪問順序爲2;
(4)進入結點2右子樹;
(5)進入結點4左子樹;
(6)進入結點6左子樹,爲空,返回結點6;
(7)訪問結點6,訪問順序爲2,6;
(8)進入結點6右子樹,爲空,返回結點6,結點4左子樹訪問完畢,返回結點4;
(9)訪問結點4,訪問順序爲2,6,4;
(10)進入結點4右子樹,爲空,返回結點4,結點2右子樹訪問完畢,返回結點1;
(11)訪問根結點1,訪問順序爲2,6,4,1;
右子樹的訪問思路同上。
三、後序遍歷
後序遍歷(PostOrder)的操作過程如下:
若二叉樹爲空,則什麼也不做;否則,
1)後序遍歷左子樹;
2)後序遍歷右子樹;
3)訪問根結點。
對應的遞歸算法如下:
void PostOrder(BiTree T)
{
if(T!=NULL)
{
PostOrder(T->lchild); //遞歸遍歷左子樹
PostOrder(T->rchild); //遞歸遍歷右子樹
visit(T); //訪問根結點
}
}
還是這個圖,後序遍歷得到的結果爲6,4,2,5,3,1
這個應該還是比較好理解的。
三種遍歷算法中,遞歸遍歷左、右子樹的順序都是固定的,只是訪問根結點的順序不同。不管採用哪種遍歷算法,每個結點都訪問一次且僅訪問一次,故時間複雜度都是O(n)。
四、遞歸算法和非遞歸算法的轉換
藉助棧,我們可以將二叉樹的遞歸遍歷算法轉換爲非遞歸算法。以中序遍歷爲例,先掃描根結點的所有左結點並將它們一一進棧,然後出棧一個結點*p,訪問它,然後掃描該結點的右孩子結點,將其進棧,再掃描該右孩子結點的所有左結點並一一進棧,如此繼續,直到棧空爲止。
中序遍歷的非遞歸算法如下:
void InOrder2(BiTree T)
{ //二叉樹中序遍歷的非遞歸算法需要藉助一個棧
InitStack(S);
BiTree p=T; //初始化棧;p是遍歷指針
while(p||!IsEmpty(S)) //棧不空或p不空時循環
{
if(P) //根指針進棧,遍歷左子樹
{
Push(S,p); //每遇到非空二叉樹先向左走
p=p->lchild;
}
else //根指針退棧,訪問根結點,遍歷右子樹
{
Pop(S,p); //退棧,訪問根結點
visit(p);
p=p->rchild; //再向右子樹走
}
}
}
非遞歸算法的效率肯定比遞歸算法的效率高滴!
五、層次遍歷
層次遍歷顧名思義就是對每一層都進行遍歷,如下圖所示:
要進行層次遍歷需要一個隊列。先將二叉樹根結點入隊,然後出隊,訪問該結點,若它有左子樹,則將左子樹根結點入隊;若它有右子樹,則將右子樹根結點入隊。然後出隊,對出隊結點訪問,如此反覆,直到隊列空。
二叉樹層次遍歷算法如下:
void LevelOrder(BiTree T)
{
InitQueue(Q); //初始化輔助隊列
BiTree p;
EnQueue(Q,T); //將根結點入隊
while(!IsEmpty(Q)) //隊列不空循環
{
DeQueue(Q,p); //隊頭元素出隊
visit(p); //訪問當前p所指向結點
if(p->lchild!=NULL)
EnQueue(Q,p->lchild); //左子樹不空,則左子樹入隊列
if(p->rchild!=NULL)
EnQueue(Q,p->rchild); //右子樹不空,則右子樹入隊列
}
}
六、由遍歷序列構造二叉樹
由二叉樹的先序序列和中序序列可以唯一地確定一棵二叉樹。
由二叉樹的後序序列和中序序列也可以唯一地確定一棵二叉樹。
由二叉樹的層次序列和中序序列也可以唯一地確定一棵二叉樹。
這告訴我們一個道理:想要利用遍歷序列構造二叉樹,中序序列必不可少!!