前言:在上一篇中介紹了二叉樹的基礎知識,這一篇介紹遍歷二叉樹的實現
一、二叉樹的存儲結構:
二叉樹的存儲結構可以採用順序存儲,也可以採用鏈式存儲,其中鏈式存儲更加靈活。
在鏈式存儲結構中,與線性鏈表類似,二叉樹的每個結點採用結構體表示,結構體包含三個域:數據域、左指針、右指針。
二、二叉樹的遍歷:
二叉樹實現需要用到的數據結構代碼如下:
struct BiTNode{ //定義二叉樹
char data; //每個結點的數據
BiTNode *lchild, *rchild; //左右孩子指針
};
struct Stack{ //定義棧
int top; //棧頂指針
BiTNode *stacksize[100]; //棧的容量
};
void InitStack(Stack &S) //初始化一個空棧
{
S.top = -1;
}
int Push(Stack &S, BiTNode *pt) //元素進棧
{
S.stacksize[++S.top] = pt;
return 1;
}
BiTNode * Pop(Stack &S) //元素出棧
{
BiTNode *pt;
pt = S.stacksize[S.top--];
return pt;
}
BiTNode * GetTop(Stack S) //獲取棧頂元素
{
BiTNode *pt;
if (S.top == -1)
return 0;
else
{
pt = S.stacksize[S.top];
return pt;
}
}
int IsEmptyStack(Stack S) //判斷是否空棧,空棧返回1,否則返回0
{
return S.top == -1;
}
二叉樹遍歷通常借用“棧”這種數據結構實現,有兩種方式:遞歸方式及非遞歸方式。
(1)在遞歸方式中,棧是由操作系統維護的,用戶不必關心棧的細節操作,用戶只需關心“訪問順序”即可。因而,採用遞歸方式實現二叉樹的遍歷比較容易理解,算法簡單,容易實現。
(2)借用“棧”採用非遞歸方式,也能實現遍歷。但是,這時的棧操作(push、pop等)是由用戶進行的,因而實現起來會複雜一些,而且也不容易理解,但有助於我們對樹結構的遍歷有一個深刻、清晰的理解。(a)非遞歸先序遍歷:
在遍歷某一個二叉(子)樹時,以一當前指針記錄當前要處理的二叉(左子)樹,以一個棧保存當前樹之後處理的右子樹。首先訪問當前樹的根結點數據,接下來應該依次遍歷其左子樹和右子樹,然而程序的控制流只能處理其一,所以考慮將右子樹的根保存在棧裏面,當前指針則指向需先處理的左子樹,爲下次循環做準備;若當前指針指向的樹爲空,說明當前樹爲空樹,不需要做任何處理,直接彈出棧頂的子樹,爲下次循環做準備,代碼如下
int PreOrderTraverse(BiTNode *T) //二叉樹先序遍歷的非遞歸算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
cout << pt->data;
Push(S, pt->rchild); //右子樹進棧,讓當前指針指向左子樹,爲下一個循環做準備
pt = pt->lchild;
}
else
pt=Pop(S); //左子樹爲空,不做任何處理,直接彈出右子樹,爲下一個循環準備
}
return 1; //遍歷完成
}
相對於非遞歸先序遍歷,非遞歸的中序/後序遍歷稍複雜一點。
(b)非遞歸中序遍歷:
若當前樹不爲空樹,則訪問其根結點之前應先訪問其左子樹,因而先將當前根節點入棧,然後考慮其左子樹,不斷將非空的根節點入棧,直到左子樹爲一空樹;當左子樹爲空時,不需要做任何處理,彈出並訪問棧頂結點,然後指向其右子樹,爲下次循環做準備。代碼如下:
int InOrderTraverse(BiTNode *T) //二叉樹中序非遞歸遍歷
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
Push(S, pt); //訪問根節點之前先訪問左結點,所以現將根節點進棧
pt = pt->lchild;
}
else //根指針退棧,訪問根節點,遍歷右子樹
{
pt=Pop(S);
cout << pt->data;
pt = pt->rchild; //訪問根節點之後,訪問右子樹
}
}
return 1;
}
(c)後序遍歷的非遞歸實現是三種遍歷方式中最難的一種。因爲在後序遍歷中,要保證左孩子和右孩子都已被訪問,並且左孩子在右孩子之前訪問才能訪問根結f點,這就爲流程控制帶來了難題。下面介紹一種思路。
要保證根結點在左孩子和右孩子訪問之後才能訪問,因此對於任一結點p,先將其入棧。若p不存在左孩子和右孩子,則可以直接訪問它,或者p存在左孩子或右孩子,但是其左孩子和右孩子都已經被訪問過了,則同樣可以直接訪問該結點。若非上述兩種情況,則將p的右孩子和左孩子依次入棧,這樣就保證了每次取棧頂元素的時候,左孩子在右孩子之前別訪問,左孩子和右孩子都在根結點前面被訪問。代碼如下:
int PostOrderTraverse(BiTNode *T) //二叉樹的後序非遞歸算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
BiTNode *pre, *cur;
pre = NULL;
if (!pt) //空樹
{
cout << "the tree is null" << endl;
return 0;
}
Push(S, pt);
while (IsEmptyStack(S)!=1)
{
cur =GetTop(S); //獲取棧頂元素
if ((cur->lchild == NULL&&cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
{
//如果當前結點沒有孩子或者左右孩子都已經訪問過,就直接訪問該結點
cout << cur->data;
Pop(S);
pre = cur; //標識它成爲上一個被訪問的元素
}
else
{
if (cur->rchild != NULL) //當前結點既有左右孩子沒有被訪問,那麼就該結點就進棧
Push(S, cur->rchild);
if (cur->lchild != NULL)
Push(S, cur->lchild);
}
}
return 1;
}
三、完整程序代碼:
#include "stdafx.h"
#include <iostream>
using namespace std;
struct BiTNode{ //定義二叉樹
char data; //每個結點的數據
BiTNode *lchild, *rchild; //左右孩子指針
};
struct Stack{ //定義棧
int top; //棧頂指針
BiTNode *stacksize[100]; //棧的容量
};
void InitStack(Stack &S) //初始化一個空棧
{
S.top = -1;
}
int Push(Stack &S, BiTNode *pt) //元素進棧
{
S.stacksize[++S.top] = pt;
return 1;
}
BiTNode * Pop(Stack &S) //元素出棧
{
BiTNode *pt;
pt = S.stacksize[S.top--];
return pt;
}
BiTNode * GetTop(Stack S) //獲取棧頂元素
{
BiTNode *pt;
if (S.top == -1)
return 0;
else
{
pt = S.stacksize[S.top];
return pt;
}
}
int IsEmptyStack(Stack S) //判斷是否空棧,空棧返回1,否則返回0
{
return S.top == -1;
}
int PreOrderTraverse(BiTNode *T) //二叉樹先序遍歷的非遞歸算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
cout << pt->data;
Push(S, pt->rchild); //右子樹進棧,讓當前指針指向左子樹,爲下一個循環做準備
pt = pt->lchild;
}
else
pt=Pop(S); //左子樹爲空,不做任何處理,直接彈出右子樹,爲下一個循環準備
}
return 1; //遍歷完成
}
int InOrderTraverse(BiTNode *T) //二叉樹中序非遞歸遍歷
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
while (pt != NULL || IsEmptyStack(S)!=1)
{
if (pt != NULL)
{
Push(S, pt); //訪問根節點之前先訪問左結點,所以現將根節點進棧
pt = pt->lchild;
}
else //根指針退棧,訪問根節點,遍歷右子樹
{
pt=Pop(S);
cout << pt->data;
pt = pt->rchild; //訪問根節點之後,訪問右子樹
}
}
return 1;
}
int PostOrderTraverse(BiTNode *T) //二叉樹的非遞歸算法
{
Stack S;
InitStack(S);
BiTNode *pt = NULL;
pt = T;
BiTNode *pre, *cur;
pre = NULL;
if (!pt)
{
cout << "the tree is null" << endl;
return 0;
}
Push(S, pt);
while (IsEmptyStack(S)!=1)
{
cur =GetTop(S); //獲取棧頂元素
if ((cur->lchild == NULL&&cur->rchild == NULL) || (pre != NULL && (pre == cur->lchild || pre == cur->rchild)))
{
//如果當前結點沒有孩子或者左右孩子都已經訪問過,就直接訪問該結點
cout << cur->data;
Pop(S);
pre = cur; //標識它成爲上一個被訪問的元素
}
else
{
if (cur->rchild != NULL) //當前結點既有左右孩子沒有被訪問,那麼就該結點就進棧
Push(S, cur->rchild);
if (cur->lchild != NULL)
Push(S, cur->lchild);
}
}
return 1;
}
int CreateBiTree(BiTNode *&T) //採用先序遍歷法創建二叉樹
{
char c;
c = getchar();
if (c == '#') // 空樹
T = NULL;
else
{
T = new BiTNode;
T->data = c;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return 1;
}
int _tmain(int argc, _TCHAR* argv[])
{
BiTNode *T = NULL;
cout << "先序輸入字符創建一個二叉樹:" << endl;
CreateBiTree(T);
cout << "前序遍歷二叉樹序列爲:" << endl;
PreOrderTraverse(T);
cout << endl;
cout << "中序遍歷二叉樹序列爲:" << endl;
InOrderTraverse(T);
cout << endl;
cout << "後序遍歷二叉樹序列爲:" << endl;
PostOrderTraverse(T);
cout << endl;
return 0;
}
參考:嚴蔚敏《數據結構》(c語言版)