前言:存儲二叉樹的關鍵是如何表示結點之間的邏輯關系,也就是雙親和孩子之間的關系。在具體應用中,可能要求從任一結點能直接訪問到它的孩子。
一、二叉鏈表
二叉樹一般多采用二叉鏈表(binary linked list)存儲,其基本思想是:令二叉樹的每一個結點對應一個鏈表結點鏈表結點除了存放與二叉樹結點有關的數據信息外,還要設置指示左右孩子的指針。二叉鏈表的結點結構如下圖所示:
lchild | data | rchild |
其中,data為數據域,存放該結點的數據信息;
lchild為左指針域,存放指向左孩子的指針,當左孩子不存在時為空指針;
rchild為右指針域,存放指向右孩子的指針,當右孩子不存在時為空指針;
可以用C++語言中的結構體類型描述二叉鏈表的結點,由於二叉鏈表的結點類型不確定,所以采用C++的模板機制。如下:
1 // 二叉鏈表的節點 2 template<class T> 3 struct BiNode 4 { 5 T data; // 數據域 6 BiNode<T>*lchild, *rchild; // 左右指針域 7 };
二、C++實現
將二叉樹的二叉鏈表存儲結構用C++的類實現。為了避免類的調用者訪問BiTree類的私有變量root,在構造函數、析構函數以及遍歷函數中調用了相應的私有函數。
具體代碼實現如下:
1、頭文件“cirqueue.h”
此頭文件為隊列的類實現,層序遍歷要用到隊列,所以自己定義了一個隊列。
1 #pragma once 2 #include <iostream> 3 const int queueSize = 100; 4 template<class T> 5 class queue 6 { 7 public: 8 .... 9 T data[queueSize]; 10 int front, rear; 11 .... 12 };
2、頭文件“bitree.h”
此頭文件為二叉鏈表的類實現。
#pragma once #include <iostream> #include "cirqueue.h" // 二叉鏈表的節點 template<class T> struct BiNode { T data; // 數據域 BiNode<T>*lchild, *rchild; // 左右指針域 }; // 二叉鏈表類實現 template<class T> class BiTree { public: BiTree() { root = Creat(root); } // 構造函數,建立一顆二叉樹 ~BiTree() { Release(root); } // 析構函數,釋放各節點的存儲空間 void PreOrder() { PreOrder(root); } // 遞歸前序遍歷二叉樹 void InOrder() { InOrder(root); } // 遞歸中序遍歷二叉樹 void PostOrder() { PostOrder(root); } // 遞歸后序遍歷二叉樹 void LeverOrder(); // 層序遍歷二叉樹 private: BiNode<T>* root; // 指向根節點的頭節點 BiNode<T>* Creat(BiNode<T>* bt); // 構造函數調用 void Release(BiNode<T>* bt); // 析構函數調用 void PreOrder(BiNode<T>* bt); // 前序遍歷函數調用 void InOrder(BiNode<T>* bt); // 中序遍歷函數調用 void PostOrder(BiNode<T>* bt); // 后序遍歷函數調用 }; template<class T> inline void BiTree<T>::LeverOrder() { queue<BiNode<T>*> Q; // 定義一個隊列 Q.front = Q.rear = -1; // 順序隊列 if (root == NULL) return; Q.data[++Q.rear] = root; // 根指針入隊 while (Q.front != Q.rear) { BiNode<T>* q = Q.data[++Q.front]; // 出隊 cout << q->data; if (q->lchild != NULL) Q.data[++Q.rear] = q->lchild; // 左孩子入隊 if (q->rchild != NULL) Q.data[++Q.rear] = q->rchild; // 右孩子入隊 } } template<class T> inline BiNode<T>* BiTree<T>::Creat(BiNode<T>* bt) { T ch; cin >> ch; // 輸入結點的數據信息,假設為字符 if (ch == '#') // 建立一棵空樹 bt = NULL; else { bt = new BiNode<T>; // 生成一個結點,數據域為ch bt->data = ch; bt->lchild = Creat(bt->lchild); // 遞歸建立左子樹 bt->rchild = Creat(bt->rchild); // 遞歸建立右子樹 } return bt; } template<class T> inline void BiTree<T>::Release(BiNode<T>* bt) { if (bt != NULL) { Release(bt->lchild); // 釋放左子樹 Release(bt->rchild); // 釋放右子樹 delete bt; // 釋放根節點 } } template<class T> inline void BiTree<T>::PreOrder(BiNode<T>* bt) { if (bt == NULL) // 遞歸調用的結束條件 return; cout << bt->data; // 訪問根節點bt的數據域 PreOrder(bt->lchild); // 前序遞歸遍歷bt的左子樹 PreOrder(bt->rchild); // 前序遞歸遍歷bt的右子樹 } template<class T> inline void BiTree<T>::InOrder(BiNode<T>* bt) { if (bt == NULL) return; InOrder(bt->lchild); cout << bt->data; InOrder(bt->rchild); } template<class T> inline void BiTree<T>::PostOrder(BiNode<T>* bt) { if (bt == NULL) return; PostOrder(bt->lchild); PostOrder(bt->rchild); cout << bt->data; }
說明:1、除了層序遍歷,其他遍歷均為遞歸算法。
2、為什么層序遍歷使用隊列:在進行層序遍歷時,對某一層的結點訪問完后,再按照它們的訪問次序對各個結點的左孩子和右孩子順序訪問,這樣一層一層進行,先訪問的結點其左右孩子也要先訪問,這符合隊列的操作特性,因此,在進行層序遍歷時,可設置一個隊列存放已訪問的結點。
3、構造函數對二叉樹的特殊處理:將二叉樹中每個結點的空指針引出一個虛結點,其值為一特定值,如‘#’,以標識其為空。
4、二叉鏈表屬於動態內存分配,需要在析構函數中釋放二叉鏈表的所有結點。在釋放某結點時,該結點的左右都子樹已經釋放,所以應該采用后序遍歷。
3、主函數
1 #include"bitree.h" 2 using namespace std; 3 4 int main() 5 { 6 BiTree<char>* bitree=new BiTree<char>(); // 創建一棵二叉樹 7 bitree->PreOrder(); // 前序遍歷 8 cout << endl; 9 bitree->InOrder(); // 中序遍歷 10 cout << endl; 11 bitree->PostOrder(); // 后序遍歷 12 cout << endl; 13 bitree->LeverOrder(); // 層序遍歷 14 delete bitree; 15 16 system("pause"); 17 return 0; 18 }
三、實例
建立如下二叉樹,並輸出四種遍歷的結果。
運行結果:
結果正確。
參考文獻:
[1]王紅梅, 胡明, 王濤. 數據結構(C++版)[M]. 北京:清華大學出版社。
馬上元旦了,祝大家元旦快樂!!2017-12-29