二叉樹的遍歷
“遍歷”是任何類型均有的操作,對線性結構而言,只有一條搜索路徑(因爲每個結點均只有一個後繼),故不需要另加討論。而二叉樹是非線性結構,每個結點都可能有兩個後繼,則存在如何遍歷即按什麼樣的搜索路徑遍歷的問題。
二叉樹的遍歷(traversing binary tree)是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。
對“二叉樹”而言,遍歷方式有很多,如果按照思維從左到右的習慣:
- 先序遍歷DLR:
–若二叉樹爲空,則空操作返回,否則先訪問根結點,然後前序遍歷左子樹,再前序遍歷右子樹。 遍歷的順序爲:ABDHIEJCFKG
-
中序遍歷LDR:若二叉樹爲空,則空操作;否則從根結點開始(注意並不是先訪問根結點),中序遍歷左子樹;訪問根結點;中序遍歷右子樹。遍歷的順序爲:HDIBEJAFKCG
-
後續遍歷LRD:若樹爲空,則空操作返回,否則從左到右先葉子後結點的方式遍歷訪問左右子樹,最後訪問根結點。 遍歷的順序爲:HIDJEBKFGCA
-
層序遍歷:若樹爲空,則空操作返回,否則從樹的第一層,也就是根結點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問。 遍歷的順序爲:ABCDEFGHIJK
實例:題目要求:建議二叉樹並輸出每個字符所在的層數。如右圖要求輸出
A在第一層
B、C在第二層
D、E在第三層
#include<stdio.h>
#include<stdlib.h>
typedef char TElemType;
typedef struct BiTNode
{
TElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
// 創建一棵二叉樹,約定遵照先序遍歷的方式輸入數據
void CreateBiTree(BiTree *T)
{
char c;
scanf("%c", &c);
//用#表示空結點
if('#' == c )
{
*T = NULL;
}
else
{
*T = (BiTNode *)malloc(sizeof(BiTNode));
(*T)->data = c;
CreateBiTree(&(*T)->lchild);
CreateBiTree(&(*T)->rchild);
}
}
void visit(char c,int level)
{
printf("%c 位於第 %d 層\n", c, level);
}
//先序遍歷
void PreOrderTraverse(BiTree T,int level)
{
if( T )
{
visit(T->data, level);
PreOrderTraverse(T->lchild, level+1);
PreOrderTraverse(T->rchild, level+1);
}
}
int main()
{
int level = 1;
BiTree T = NULL;
CreateBiTree(&T);
PreOrderTraverse(T, level);
return 0;
}
void
代碼中已經寫出先序遍歷的遞歸算法,中序遍歷及後序遍歷只需要修改部分代碼位置即可。
- 遍歷算法的應用:統計二叉樹中葉子結點的個數、求二叉樹的深度、先序建立二叉鏈表、表達式二叉樹、根據先序和中序序列構造二叉樹等
//統計二叉樹中葉子結點的個數
void CountLeaf (BiTree T, int *count){
if ( T ) {
if ((!T->lchild)&& (!T->rchild))
count++; // 對葉子結點計數
CountLeaf( T->lchild, count);
CountLeaf( T->rchild, count);
}
} // CountLeaf
先序遍歷二叉樹,在遍歷過程中查找葉子結點,並計數。由此,需在遍歷算法中增添一個“計數”的參數,並將算法中“訪問結點”的操作改爲:若是葉子,則計數器增1。
//求二叉樹深度
int depth( BiTree T ) {
// 本算法返回根指針爲T 的二叉樹的深度
if ( !T ) return 0;
left = depth(T->lchild ); // 求左子樹深度
right = depth(T->rchild ); // 求右子樹深度
return left>right ? left+1 : right+1;
}//depth
首先分析二叉樹的深度和它的左、右子樹深度之間的關係。從二叉樹深度的定義可知,二叉樹的深度應爲其左、右子樹深度的最大值加1。由此,需先分別求得左、右子樹的深度,算法中“訪問結點”的操作爲:求得左、右子樹深度的最大值,然後加 1 。
線索二叉樹
在二叉鏈表中,若結點數爲n,則有2n個指針域,其中,非空指針域 n-1 個,空指針域 n+1 個。
由於 在二叉鏈表中大約有一半指針域爲空; 在二叉鏈表中進行遍歷操作不很方便。
所以爲了• 爲了使二叉樹的遍歷操作更加方便、更加有效,利用二叉鏈表的空指針域存放結點的前驅和後繼的信息。
二叉樹結點中指向某種遍歷次序下的前驅結點或後繼結點的指針稱之爲線索。
加進了線索的二叉鏈表稱爲線索鏈表,其邏輯結構稱爲線索二叉樹。
在線索二叉樹中,結點之間存在兩種關係:1) 雙親—孩子關係(樹形結構中的關係)2) 前驅—後繼關係(線性結構中的關係)
線索的標識: 在線索鏈表中,指針域的值雖然都是地址,但意義不同,線索和孩子結點的地址必須加以區分。爲此,需改變結點結構,增加兩個標誌域
#include<stdio.h>
#include<stdlib.h>
typedef char ElemType;
//枚舉類型 (0和1)
//線索存儲標誌位
//Link(0):表示指向左右孩子的指針
//Thread(1):表示指向前驅後繼的線索
typedef enum{Link, Thread} PointerTag;
typedef struct BiThrNode
{
char data;
struct BiThrNode *lchild, *rchild;
PointerTag LTag,RTag;
}BiThrNode, *BiThrTree;
//設置一個全局變量,始終指向剛剛訪問過的結點
BiThrTree pre;
//創建一棵二叉樹,約定用戶遵照先序遍歷的方式輸入數據
void CreateBiThrTree(BiThrTree *T)
{
char c;
//用空格表示空結點
scanf("%c",&c);
if(' '== c)
{
*T = NULL;
}
else
{
*T =(BiThrNode *)malloc(sizeof(BiThrNode));
(*T)->data = c;
//先默認有左右孩子,之後再將二叉樹線索化
(*T)->LTag = Link;
(*T)->RTag = Link;
CreateBiThrTree(&(*T)->lchild);
CreateBiThrTree(&(*T)->rchild);
}
}
//將中序遍歷線索化
void InThreading(BiThrTree T)
{
if( T )
{
InThreading(T->lchild); //遞歸左孩子線索化
//節點處理
if( !T->lchild) //若該結點沒有左孩子,則設置LTag爲Thread,並把lchild指向剛剛訪問過的結點
{
T->LTag = Thread; //前驅
T->lchild = pre; //指向前一個訪問的結點
}
if(!pre->rchild) //若前一個訪問結點沒有右孩子
{
pre->RTag = Thread;
pre->rchild = T;
}//則設置前一個訪問結點的後繼線索
pre = T;
InThreading(T->rchild);
//遞歸右孩子線索
}
}
//附設的指針pre
//始終保持指針pre指向當前訪問的指針T所指結點的前驅
void InOrderThreading(BiThrTree *p, BiThrTree T)
{
*p = (BiThrTree)malloc(sizeof(BiThrNode));
(*p)->LTag = Link;
(*p)->RTag = Thread;
(*p)->rchild = *p; // 添加頭結點
if(!T) //空疏
{
(*p)->lchild = *p;
}
else
{
(*p)->lchild = T; //指向根
pre = *p;
InThreading(T);
pre->rchild = *p; // 處理最後一個結點
pre->RTag = Thread;
(*p)->rchild = pre;
}
}
void visit(char c)
{
printf("%c",c);
}
void InOrderTraverse(BiThrTree T)
{
BiThrTree p;
p = T->lchild; //指向根節點
while(p != T) //空樹或遍歷結束
{
while(p->LTag == Link)
{
p = p->lchild; //找最左下結點
}
visit(p->data);
while(p->RTag == Thread && p->rchild !=T)//遍歷未結束
{
p = p->rchild;
visit(p->data);
}
p = p->rchild;
}
}
int main()
{
BiThrTree P, T = NULL;
CreateBiThrTree( &T );
InOrderThreading( &P, T );
printf("中序遍歷輸出結果爲: ");
InOrderTraverse( P );
printf("\n");
return 0;
}