1.概覽
二叉樹的遍歷(traversing binary tree)是指從根節(jié)點出發(fā)
,按照某種次序
依次訪問
二叉樹中所有結點,使得每個節(jié)點被訪問依次且僅被訪問一次。
注意:
- 從根節(jié)點出發(fā),并不一定是從根節(jié)點開始遍歷,只是從根節(jié)點開始邏輯
- 次序表示有一定的規(guī)則
- 訪問表示可以得到這個結點的信息
二叉樹的遍歷次序不同于線性結構,線性結構最多也就是分為順序、循環(huán)、雙向等簡單的遍歷方式。
二叉樹的遍歷方式可以很多,如果我們限制了從左到右的習慣方式,那么主要就分為以下四種:
- 前序遍歷
- 中序遍歷
- 后序遍歷
- 層序遍歷
2.前序遍歷
定義:若二叉樹為空,則空操作返回,否則先訪問根節(jié)點,然后前序遍歷左子樹,再前序遍歷右子樹
如圖所示,先訪問根節(jié)點A,然后訪問A的左子樹B,再訪問B的左子樹D,再訪問D的左子樹H,由于H是終端結點,則再訪問D的右子樹I,訪問完了B的左子樹,再訪問B的右子樹,剩下的類推可得到
總結:前序遍歷中,遵循根節(jié)點->左子樹->右子樹
的順序來訪問結點
3. 中序遍歷
定義:若二叉樹為空,則空操作返回,否則從根節(jié)點開始(注意并不是先訪問根節(jié)點),中序遍歷根節(jié)點的左子樹,然后是訪問根節(jié)點,最后中序遍歷右子樹。
總結:中序遍歷遵循左子樹->根節(jié)點->右子樹
的順序來訪問結點
4.后序遍歷
定義:若樹為空,則空操作返回,否則從左到右先葉子后結點的方式遍歷訪問左右子樹,最后訪問根節(jié)點
總結:后序遍歷遵循左子樹->右子樹->根節(jié)點
的順序來訪問結點
5.層序遍歷
定義:若樹為空,則空操作返回,否則從樹的第一層,也就是根節(jié)點開始訪問,從上而下逐層遍歷,在同一層中,按從左到右的順序對結點逐個訪問
總結:層序遍歷遵循層級遞增,同層級中從左至右
的順序訪問結點
6.算法:使用前序遍歷,建立二叉樹并進行遍歷,輸出各個結點的層次
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef char ElemType;
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
//創(chuàng)建一棵二叉樹,約定用戶遵照前序遍歷的方式輸入數(shù)據(jù)
Status createBiTree(BiTree *T){
//接收輸入
ElemType c = 0;
scanf("%c",&c);
if ( c == ' ') {
//輸入空格便是結束這棵子樹
*T = NULL;
return ERROR;
} else {
*T = (BiTree)malloc(sizeof(BiTNode));
(*T) -> data = c;
//遞歸創(chuàng)建
//創(chuàng)建左子樹
createBiTree(&((*T)->lchild));
//創(chuàng)建右子樹
createBiTree(&((*T)->rchild));
return OK;
}
}
//訪問二叉樹結點的具體操作
void visit(ElemType c,int level){
printf("data:%c,level:%d",c,level);
}
//前序遍歷
Status preOrderTraverse(BiTree T,int level){
if ( !T ) {
return ERROR;
}
visit(T->data, level);
//遞歸遍歷
preOrderTraverse(T->lchild, level+1);
preOrderTraverse(T->rchild, level+1);
return OK;
}
int main(){
int level = 1;
BiTree T = NULL;
createBiTree(&T);
preOrderTraverse(T, level);
return 0;
}
7.二叉樹的前序、中序、后序遍歷的遞歸與非遞歸實現(xiàn)
二叉樹遍歷的非遞歸實現(xiàn)需要借助棧來實現(xiàn)(由于棧LIFO的特性),我們先建立一個樹的結點棧,如下所示:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef char ElemType;
typedef struct BiTNode{
ElemType data;
struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef struct BiTreeStack{
BiTNode *base;//棧底
BiTNode *top;//棧頂
int stackSize;
}BiTreeStack;
Status initStack(BiTreeStack *s){
s->base = (BiTNode *)malloc(sizeof(BiTNode) * STACK_INIT_SIZE);
if (!s->base) {
return ERROR;
}
s->top = s->base;
s->stackSize = STACK_INIT_SIZE;
return OK;
}
Status Push(BiTreeStack *s,BiTNode node){
if (s->top - s->base >= s->stackSize) {
s->base = (BiTNode *)realloc(s->base, sizeof(BiTNode)*(s->stackSize + STACKINCREMENT));
if (!s->base) {
return ERROR;
}
s->top = s->base + s->stackSize;
s->stackSize += STACKINCREMENT;
}
*(s->top) = node;
(s->top)++;
return OK;
}
Status Pop(BiTreeStack *s,BiTNode *node){
if (s->top - s->base == 0) {
return ERROR;
}
*node = *(--(s->top));
return OK;
}
int StackLength(BiTreeStack *s){
return (int)(s->top - s->base);
}
void visitNode(BiTree node){
printf("data:%c",node->data);
}
前序遍歷
前序遍歷的遞歸實現(xiàn)比較簡單,遵循根節(jié)點-左子樹-右子樹
的順序即可
//遞歸
Status PreorderTraversalRecursive(BiTree T){
if (!T) {
return ERROR;
}
visitNode(T);
PreorderTraversalRecursive(T->lchild);
PreorderTraversalRecursive(T->rchild);
return OK;
}
非遞歸實現(xiàn)我們需要創(chuàng)建一個棧,先把二叉樹的根節(jié)點壓入棧,訪問后,再依次壓入右結點,再壓入左結點,這樣訪問的時候就遵循了根節(jié)點-左子樹-右子樹
的順序
//非遞歸
Status PreorderTraversalNotRecursive(BiTree T){
if (!T) {
return ERROR;
}
BiTNode node = *T;
BiTreeStack s;
BiTreeStack *stack = NULL;
initStack(&s);
stack = &s;
Push(stack, node);
while (StackLength(stack) != 0) {
Pop(stack, &node);
visitNode(&node);
//先把右子樹根節(jié)點壓入棧
if (node.rchild) {
Push(stack, *(node.rchild));
}
//再把左子樹根節(jié)點壓入棧
if (node.lchild) {
Push(stack, *(node.lchild));
}
}
return OK;
}
中序遍歷
中序遍歷的遞歸實現(xiàn)比較簡單,遵循左子樹-根節(jié)點-右子樹
的順序即可
//遞歸
Status InorderTraversalRecursive(BiTree T){
if (!T) {
return ERROR;
}
InorderTraversalRecursive(T->lchild);
visitNode(T);
InorderTraversalRecursive(T->rchild);
return OK;
}
中序遍歷的非遞歸實現(xiàn)稍微麻煩一點,我們需要先從根節(jié)點出發(fā),逐個將左子樹壓入棧,由于我們知道葉子結點是沒有子樹的,那么我每次每次訪問的就當做是根節(jié)點,我們判斷它是否有右子樹,有右子樹的話,就重復之前步驟,將左子樹依次加入到棧中,這樣也就遵循了中序遍歷左子樹-根節(jié)點-右子樹
的順序
//非遞歸
Status InorderTraversalNotRecursive(BiTree T){
if (!T) {
return ERROR;
}
BiTNode *p = T;
BiTreeStack s;
BiTreeStack *stack = NULL;
initStack(&s);
stack = &s;
//將左子樹都壓入棧
while (p) {
Push(stack, *p);
p = p->lchild;
}
//開始遍歷
while (StackLength(stack)) {
//每一個出棧的結點我們都當做根節(jié)點
Pop(stack, p);
visitNode(p);
//判斷有無右子樹
if (p->rchild) {
p = p->rchild;
Push(stack, *p);
while (p->lchild) {
p = p->lchild;
Push(stack, *p);
}
}
}
return OK;
}
后序遍歷
后序遍歷的遞歸實現(xiàn)也比較簡單,遵循左子樹-右子樹-根節(jié)點
的順序即可
//遞歸
Status PostorderTraversalRecursive(BiTree T){
if (!T) {
return ERROR;
}
PostorderTraversalRecursive(T->lchild);
PostorderTraversalRecursive(T->rchild);
visitNode(T);
return OK;
}
后序遍歷的非遞歸實現(xiàn)看上去是最麻煩的,不過我們仍然可以用按照后序遍歷左子樹-右子樹-根節(jié)點
的思路來進行思考:
- 對于每一個結點,如果它是非葉子結點,且它的左孩子和右孩子都不是上一次訪問的結點的情況下,循環(huán)將它的左子樹壓入棧,這樣就確定了訪問的順序
- 將棧頂結點推出,判斷結點是否有右孩子且右孩子不是之前訪問的結點的情況下,將右孩子壓入棧
- 棧頂結點推出,不符合2的條件下,訪問結點,并存儲新訪問結點的指針,然后將出棧新節(jié)點有用于比較
//非遞歸
Status PostorderTraversalNotRecursive(BiTree T){
if (!T) {
return ERROR;
}
BiTNode *p = T;
//記錄上一次訪問的結點
BiTNode *pre = T;
BiTreeStack s;
BiTreeStack *stack = NULL;
initStack(&s);
stack = &s;
while(p || StackLength(stack)){
if(p != NULL && pre != p->lchild && pre != p->rchild){ //結點不為空且左孩子和右孩子都不是上一次訪問的,入棧左子樹
Push(stack, *p);
p = p->lchild;
}
else{
Pop(stack, p);
if(p->rchild != NULL && pre != p->rchild){ //右子樹不為空且右孩子沒有訪問過,入棧右子樹結點
Push(stack, *p);
p = p->rchild;
Push(stack, *p);
}
else{
//訪問到最后的右子樹的結點后,退棧
visitNode(p);
pre = p;
Pop(stack, p);
}
}
}
return OK;
}