一:二叉樹的幾種遍歷方法
1:先序遍歷
根→左→右
先訪問根節點,再遍歷左子樹,最後遍歷右子樹;並且在遍歷左右子樹時,仍需先訪問根節點,然後遍歷左子樹,最後遍歷右子樹。
比如上圖,先序遍歷的輸出如下 : - + a * b - c d / e f
根據上面的思想,很容易用遞歸的形式寫出先序遍歷的代碼:
//先序遍歷
Status PreOrderTraverse(BiTree T , Status(*visit)(TElemType)){
if(T){
if(visit( T->data )){//打印T->data
if(PreOrderTraverse( T->lchild , visit )){
if(PreOrderTraverse( T->rchild , visit )){
return OK;
}
}
}
}else return OK;
}
2,中序遍歷
左→根→右
先遍歷左子樹、然後訪問根節點,最後遍歷右子樹;並且在遍歷左右子樹的時候。仍然是先遍歷左子樹,然後訪問根節點,最後遍歷右子樹。
比如上圖,中序遍歷的輸出如下 : a + b * c - d - e / f
3,後序遍歷
左→右→根
先遍歷左子樹,然後遍歷右子樹,最後訪問根節點;同樣,在遍歷左右子樹的時候同樣要先遍歷左子樹,然後遍歷右子樹,最後訪問根節點。
比如上圖,後序遍歷的輸出如下 : a b c d - * + e f / -
例題:
知道後序DABEC,中序DEBAC,求二叉樹圖
由後序最後一個字母知:整個樹的開始結點爲C;
由中序C的位置知:C前面的爲結點C的左子樹;C後面的爲結點C的右子樹;
所以經過第一次推理,C爲根結點,DEBA爲其左子樹;
然後去掉C,考慮下面的左子樹;
後序DABE 中序DEBA
由後序最後一個字母知:整個左子樹的開始結點爲E;
由中序E的位置知:E前面的爲結點E的左子樹;E後面的爲結點E的右子樹;
所以經過第一次推理,E爲開始結點,D爲E的左結點.BA爲E的右結點.
然後去掉DE,考慮下面E的右子樹;
後序AB 中序BA
易知:B爲根結點,A爲其右結點.
所以整個樹爲:C(E(D,B(,A)));
先序:CEDBA
二:使用代碼實現遍歷
1,先創建二叉樹
#include <iostream>
using namespace std;
char array[] = "ABD##E##C#F##";
int i(0);
typedef struct BTree{
char data;
struct BTree *left,*right;
};
//創建子節點(遞歸)
void CerateNode(BTree *&p){//注意*&p在c中表示爲**p,是指針地址
i++;
if(strlen(array)>i&&array[i]!='#'){
BTree* node = (BTree*)malloc(sizeof(BTree));
node->left = 0;
node->right = 0;
node->data = array[i];
p = node;//指針地址指向節點
CerateNode(node->left);//左
CerateNode(node->right);//右
}else{
return;
}
}
//創建根節點
BTree* CreateRoot(){
BTree* node = (BTree*)malloc(sizeof(BTree));
node->left = 0;
node->right = 0;
node->data = array[0];
return node;
}
//主函數
void main(void){
BTree* head = CreateRoot();//創建根節點
CerateNode( head->left);//添加左子樹
CerateNode(head->right);//添加右子樹
cout<<endl<<"先序:";
PreOrderTravse(head,0);
cout<<endl<<"中序:";
PreOrderTravse(head,1);
cout<<endl<<"後序:";
PreOrderTravse(head,2);
cout<<endl;
system("pause");
}
2,遞歸方便遍歷
//遍歷i=0時爲先序,i=1是爲中序,i=2時爲後序
void PreOrderTravse(BTree* &t,int i){
if(t){
if(i==0)cout<<t->data;//先序
if(!t->left==0)PreOrderTravse(t->left,i);
if(i==1)cout<<t->data;//中序
if(!t->right==0)PreOrderTravse(t->right,i);
if(i==2)cout<<t->data;//後序
}
}
3,非遞歸方法遍歷
後序遍歷二叉樹(非遞歸)思想:
1.對於一棵樹t,如果t非空,首先應該進入t的左子樹訪問,此時由於t的右子樹及根結點尚未訪問,
2. 因此必須將t保存起來,放入棧中,以便訪問完左子樹後,從棧中取出t,進行其右子樹及根結點的訪問。
3. 這裏值得注意的是,當一個元素位於棧頂即將處理時,其左子樹的訪問一定已經完成,
4. 如果其右子樹尚未遍歷,接下來應該進入其右子樹的訪問,而此時該棧頂元素是不能出棧的,
5. 因爲其根結點還未被訪問;只有等到其右子樹也訪問完成後,該棧頂元素才能出棧,並輸其根結點的值。
6. 因此,在二叉樹後序遍歷的過程中,必須使用標記數組,其每個元素取值爲0或1,
7. 用於標識棧中每個元素的狀態。當一個元素剛進棧時,其對應的tag值置0;當它第一次位於棧頂即將
8. 被處理時,其tag值爲0,意味着應該訪問其右子樹,於是將其右子樹作爲當前處理的對象,
9. 此時該棧頂元素仍應該保留在棧中,並將其對應的tag值改爲1;當其右子樹訪問完成後,
10. 該元素又一次位於棧頂,而此時其tag值爲1,意味着應該訪問其根結點,並將其出棧。
11.在整個二叉樹後序遍歷的過程中,程序要做的工作始終分成兩個部分:當前正在處理的樹(子樹)
12.和保存在棧中待處理的部分。只有這兩部分的工作均完成後,程序方能結束。
BTree *stack[30] = {0}; //用一個靜態數組模擬棧
int ntag[30] = {0};//標記數組標記每個元素的左右子樹是否都已經被訪問過了
int top = -1;//初始狀態下Top=-1表示棧空
//非遞歸的先序,中序遍歷
void PreOrderTravse2(BTree* t,int i){
int top = -1;
BTree *p = t;
while(p!=0||top!=-1){//當前指針不空或棧不空
while (p){//當前指針不空
if(i==0)cout<<p->data;
stack[++top] = p;//將當前指針入棧
p = p->left;
}
if(top!=-1){//棧不空
p = stack[top--];//獲取棧頂指針且棧頂指針出棧
if(i==1)cout<<p->data;
p = p->right;
}
}
}
//後序遍歷
void hou(BTree* t){
top = -1;
BTree *p = t;
while(p!=0||top!=-1){//當前指針不空或棧不空
while (p){//當前指針不空
stack[++top] = p;//將當前指針入棧
ntag[top] = 0;//表示第一次位於棧頂
p = p->left;
}
//棧不空且棧頂指針指向的結點被標記爲1即表示給結點的左右子樹都已經被訪問過了,第二次位於棧頂
while(top!=-1&&ntag[top]==1){
BTree *c = stack[top--];//獲取棧頂指針且棧頂指針出棧
cout<<c->data;
}
if(top!=-1){//棧不空 此時標記爲0,意味着應該訪問其右子樹
p = stack[top];//取到棧頂元素,注意此時不能出棧
ntag[top] = 1;
p = p->right;
}
}
}