C++遞迴建立、非遞迴遍歷二叉樹的基本操作
最近學了二叉樹,這個資料結構和它的名字一樣,真是二叉。如果單純的讓我想這樣的演算法,以筆者的腦子想一輩子都想不出來。二叉樹剛學完,老師又講了圖。
俗話說,不能在一棵樹上吊死,那我選擇在二叉樹上吊死。關鍵是圖還在下面扯著你,二叉樹這個資料結構不會真的是憋死人。
下面,我將我各種查閱書籍與上網翻找得出的結果告訴大家,讓大家能夠清楚地瞭解二叉樹的基本操作。
#include <iostream> using namespace std; #define MAXSIZE 100 class BTNode{ public: char data; BTNode *leftChild; BTNode *rightChild; BTNode():leftChild(NULL),rightChild(NULL){} }; class binaryTree{ private: BTNode *root; public: binaryTree(); ~binaryTree(); void deleteTree (BTNode *rt); BTNode *create_Tree(); BTNode *getRoot(); void PreOrder(BTNode *root); void InOrder(BTNode *root); void PostOrder(BTNode *root); void LevelOrder(BTNode *root); }; binaryTree::binaryTree() { root = create_Tree(); } binaryTree::~binaryTree() { deleteTree(root); } BTNode *binaryTree::create_Tree() { BTNode *root; char ch; cin>>ch; if(ch == '#') root = NULL; else { root = new BTNode; root->data = ch; root->leftChild = create_Tree(); root->rightChild = create_Tree(); } return root; } void binaryTree::deleteTree (BTNode *rt) { if (rt!=NULL) { deleteTree(rt->leftChild); deleteTree(rt->rightChild); delete rt; } } BTNode *binaryTree::getRoot() { return root; } void binaryTree::PreOrder(BTNode *root) //先序周遊非遞迴演算法(用棧實現) { BTNode *st[MAXSIZE],*p = NULL; int top = -1; if(root != NULL) { top++; st[top] = root; //根節點入棧 while(top>-1) //判斷當棧非空時迴圈 { p = st[top]; //棧頂內容賦給p(即將輸入的內容存放在p中) top--; cout<<p->data<<" "; //輸出棧頂內容 if(p->rightChild != NULL) //右孩子入棧 { top++; st[top] = p->rightChild; } if(p->leftChild != NULL) //左孩子入棧 { top++; st[top] = p->leftChild; } } cout<<endl; } } void binaryTree::InOrder(BTNode *root) //中序周遊非遞迴實現(用棧實現) { BTNode *st[MAXSIZE],*p =NULL; int top = -1; if(root !=NULL) { p = root; //將root賦給p while(top>-1 || p !=NULL) //當棧不為空且root存在的時候,開始遍歷左孩子 { while(p !=NULL) //將root進棧,並將指標向左孩子方向移動,直到移動至葉子 { top++; st[top] = p; p = p->leftChild; } //左孩子部分已遍歷完 if(top>-1) //開始函式的輸出部分 { p = st[top]; //將左孩子放入棧中並準備輸出 top--; cout<<p->data<<" "; p = p->rightChild; //將指標移動到具有相同父節點的右孩子上 } } cout<<endl; } } void binaryTree::PostOrder(BTNode *root) //後序周遊非遞迴實現(借用棧與下標) { BTNode *st[MAXSIZE],*p =NULL; int flag,top = -1; //flag為所設下標 if(root !=NULL) { do { while(root !=NULL) //將左節點入棧 { top++; st[top] = root; //逐步實現將左孩子放進棧中 root = root->leftChild; //指標指向左孩子,直到葉子 } p = NULL; //將p置空,防止野指標出現 flag = 1; //設定下標 while(top != -1 && flag) { root = st[top]; if(root->rightChild == p) //此時的葉節點的右孩子不存在的時候 { cout<<root->data<<" ";//直接輸出當前節點的資料 top--; p = root; //p指向剛剛被訪問的節點 } else { root = root->rightChild;// flag = 0; } } }while(top != -1); cout<<endl; } } void binaryTree::LevelOrder(BTNode *root) //層序遍歷(廣度優先周遊)二叉樹演算法實現(藉助佇列實現) { int front =0,rear =0; //設定下標 BTNode *st[MAXSIZE]; //佇列 BTNode *q; if(root ==NULL)return; //當二叉樹判空時,直接返回 else{ st[rear++] = root; //根節點存入佇列 while(front != rear) { q = st[front++]; //由於是佇列儲存,控制下標,,準備輸出 cout<<q->data<<" "; if(q->leftChild !=NULL) st[rear++] = q->leftChild;//將左孩子儲存至佇列尾 if(q->rightChild !=NULL) st[rear++] = q->rightChild;//將右孩子儲存至佇列尾 } } cout<<endl; } int main() { binaryTree t1; BTNode *root = t1.getRoot(); t1.create_Tree(); cout<<"先序周遊結果為:"; t1.PreOrder(root); cout<<"中序周遊結果為:"; t1.InOrder(root); cout<<"後序周遊結果為:"; t1.PostOrder(root); cout<<"層序周遊結果為:"; t1.LevelOrder(root); return0; }
以上是整體程式碼,下面進行分步講解:
BTNode *binaryTree::create_Tree()
{
BTNode *root;
char ch;
cin>>ch;
if(ch == '#')
root = NULL;
else
{
root = new BTNode;
root->data = ch;
root->leftChild = create_Tree();
root->rightChild = create_Tree();
}
return root;
}
二叉樹的建立過程,利用到了遞迴思想。建立順序為:左子樹-->左孩子-->右孩子-->右子樹-->左孩子-->右孩子······的迴圈格式,先逐層建立左孩子,若左孩子為空則輸入“#”表明。左孩子輸入完再輸入右孩子。左子樹建立完成,開始建立右子樹,建立順序如左子樹。
void binaryTree::PreOrder(BTNode *root) //先序周遊非遞迴演算法(用棧實現) { BTNode *st[MAXSIZE],*p = NULL; int top = -1; if(root != NULL) { top++; st[top] = root; //根節點入棧 while(top>-1) //判斷當棧非空時迴圈 { p = st[top]; //棧頂內容賦給p(即將輸入的內容存放在p中) top--; cout<<p->data<<" "; //輸出棧頂內容 if(p->rightChild != NULL) //右孩子入棧 { top++; st[top] = p->rightChild; } if(p->leftChild != NULL) //左孩子入棧 { top++; st[top] = p->leftChild; } } cout<<endl; } } void binaryTree::InOrder(BTNode *root) //中序周遊非遞迴實現(用棧實現) { BTNode *st[MAXSIZE],*p = NULL; int top = -1; if(root != NULL) { p = root; //將root賦給p while(top>-1 || p != NULL) //當棧不為空且root存在的時候,開始遍歷左孩子 { while(p != NULL) //將root進棧,並將指標向左孩子方向移動,直到移動至葉子 { top++; st[top] = p; p = p->leftChild; } //左孩子部分已遍歷完 if(top>-1) //開始函式的輸出部分 { p = st[top]; //將左孩子放入棧中並準備輸出 top--; cout<<p->data<<" "; p = p->rightChild; //將指標移動到具有相同父節點的右孩子上 } } cout<<endl; } }
先序周遊與中序周遊所用到的思想一樣,藉助棧來存放元素,通過棧的特性列印元素。一個元素出棧並輸出,兩個元素入棧,再次讓一個元素出棧,再兩個元素入棧,反覆,直到輸出所有結果。不同之處在根的處理。
void binaryTree::PostOrder(BTNode *root) //後序周遊非遞迴實現(借用棧與下標)
{
BTNode *st[MAXSIZE],*p = NULL;
int flag,top = -1; //flag為所設下標
if(root != NULL)
{
do
{
while(root != NULL) //將左節點入棧
{
top++;
st[top] = root; //逐步實現將左孩子放進棧中
root = root->leftChild; //指標指向左孩子,直到葉子
}
p = NULL; //將p置空,防止野指標出現
flag = 1; //設定下標
while(top != -1 && flag)
{
root = st[top];
if(root->rightChild == p) //此時的葉節點的右孩子不存在的時候
{
cout<<root->data<<" "; //直接輸出當前節點的資料
top--;
p = root; //p指向剛剛被訪問的節點
}
else
{
root = root->rightChild;//
flag = 0;
}
}
}while(top != -1);
cout<<endl;
}
}
後序周遊難度最大,藉助棧與下標實現。先將左子樹的做節點入棧,指標p置空,下標置為一。如果次節點的右孩子為空,直接輸出資料;如果不為空,則將指標指向右孩子,將下標置0,從根節點開始再次重複上述過程。
void binaryTree::LevelOrder(BTNode *root) //層序遍歷(廣度優先周遊)二叉樹演算法實現(藉助佇列實現)
{
int front = 0,rear = 0; //設定下標
BTNode *st[MAXSIZE]; //佇列
BTNode *q;
if(root == NULL) return; //當二叉樹判空時,直接返回
else{
st[rear++] = root; //根節點存入佇列
while(front != rear)
{
q = st[front++]; //由於是佇列儲存,控制下標,,準備輸出
cout<<q->data<<" ";
if(q->leftChild != NULL)
st[rear++] = q->leftChild; //將左孩子儲存至佇列尾
if(q->rightChild != NULL)
st[rear++] = q->rightChild;//將右孩子儲存至佇列尾
}
}
cout<<endl;
}
層序遍歷的基本思想是:左孩子入隊--->右孩子入隊-->左孩子出隊-->左孩子入隊-->右孩子入隊-->右孩子出隊······迴圈深度優先周遊:
時間複雜度:每個節點訪問一次,因此時間複雜度為o(n)
空間複雜度:空間主要輸藉助了棧,因此最差情況棧的容量等於樹高,為
o(n)
廣度優先周遊:
時間複雜度:o(n)
空間複雜度:空間利用了佇列,佇列最大長度為(n+1)/2
二叉樹演算法是現階段學習的重要演算法,也是難點。其儲存結構為鏈式儲存結構,建立過程利用了遞迴思想。周遊過程若用遞迴思想,演算法的實現便會簡化很多。
在今後的學習工作中,我們應該以實際情況選取遞迴或者非遞迴的方式去實現二叉樹的周遊。遞迴在系統內部呼叫棧的資料結構,容易溢位,而迭代則是不容易溢位,但是佔用了更大的儲存空間。迭代的效率要大於遞迴。