【c++】構建一棵簡單的二叉樹


  轉載自:

      http://blog.csdn.net/LLZK_/article/details/52829525

  

本文主要講了如何使用c++來構建一個二叉樹類,以及一些功能算法的實現。文中大部分函數的思想都是遞歸,其中賦值運算符重載有傳統寫法和現代寫法兩個版本,層序遍歷是非遞歸,前、中、后序遍歷有遞歸和非遞歸兩個版本。

1、構造函數(遞歸)

2、拷貝構造函數(遞歸)

3、析構函數(遞歸)

4、賦值運算符重載(傳統/現代)

5、前中后序遍歷(遞歸/非遞歸)

6、層序遍歷(非遞歸)

7、查找第k層結點個數(遞歸)

8、精確查找值為x的結點,並返回當前結點的指針(遞歸)

9、查找葉子結點個數(遞歸)

10、查找結點總個數(遞歸)

11、計算樹的深度(遞歸)

 

樹的結點類型,每個結點都需要有一個指向右孩子的指針,一個指向左孩子的指針,以及一個數據。(當然,如果你想構造三叉樹的話,也可以增加一個指向父節點的指針。)

這里我寫出了結點的構造函數,方便我們在創建樹的時候使用。

注意:這棵樹我使用了模板

[cpp]  view plain  copy
 
  1. template<typename T>  
  2. struct BinaryTreeNode  
  3. {  
  4.     BinaryTreeNode<T>* _left;//左孩子  
  5.     BinaryTreeNode<T>* _right;//右孩子  
  6.     T _data;//數據  
  7.     BinaryTreeNode(T data = T())//結點自己的構造函數,T()為一個匿名對象。  
  8.         :_left(NULL)//初始化為空  
  9.         , _right(NULL)  
  10.         , _data(data)  
  11.     {}  
  12. };  
 
下面代碼中會經常用到BinaryTreeNode<T>   所以將其重命名為Node
[cpp]  view plain  copy
 
  1. typedef BinaryTreeNode<T> Node;  



當我們向寫二叉樹類的時候,直接給類設定一個根結點,以這個根結點為基礎,構建二叉樹。
[cpp]  view plain  copy
 
  1. class BinaryTree  
  2. {  
  3.  public:  
  4.  private:  
  5.     BinaryTreeNode<T>* _root;//根節點  
  6. };  


1、構造函數
     BinaryTree(const T* a, size_t size,int index, const T& invalid)
構造函數有4個參數,T類型的指針a,傳參時傳一個數組,負責傳入數據。size保存數組a 的大小,index記錄下標,invalid表示非法值。
因為我們需要用到遞歸,所以在這個函數內部我們需要再封裝一個遞歸函數_MakeTree(),並讓它的返回值為一個Node*類型的指針。
[cpp]  view plain  copy
 
  1. BinaryTree(const T* a, size_t size,int index, const T& invalid)  
  2. {  
  3.     _root = _MakeTree(a,size,index,invalid);  
  4. }  
 
我們先來觀察一個樹:
你會看到上面有許多NULL,這些NULL我們就可以理解為非法值invalid。這棵樹的前序遍歷為:
 
 1  2  3  NULL  NULL  4   NULL   NULL  5   6 
 
最后一個結點不需要非法值,到時候直接創建即可。
 
與上對應,我們傳數組時,應該傳的值即為 int a[10] = {1,2,3,'#','#',4,'#','#',5,6}。非法值的值可以隨意設,這里我設為‘#’,注意,你向以什么的樣的順序建樹,就以什么樣的順序傳參,事先要約定好。(這里我用的是前序)
 
遞歸:當我們從數組讀取到一個數據時,我們先要判斷這個值是不是合法,如果合法則new出一個結點並初始化作為當前結點,此時,進入左孩子遞歸函數讀取下一個數據(++index),並把這個函數的返回值鏈到當前結點root的left,同理,將右孩子遞歸函數的返回值鏈到當前結點的right。如果不合法則return,返回上一層函數。最后我們會得到一個根節點,例圖中的1。
 
 
_MakeTree函數實現:
[cpp]  view plain  copy
 
  1. Node* _MakeTree(const T* a, size_t size, int& index, const T& invalid)  
  2. {  
  3.     Node *root = NULL;  
  4.     if (index < size && a[index] != invalid)  
  5.     {  
  6.         root = new Node(invalid);  
  7.         root->_data = a[index];  
  8.         root->_left = _MakeTree(a, size, ++index, invalid);  
  9.         root->_right = _MakeTree(a, size, ++index, invalid);  
  10.     }  
  11.     return root;  
  12. }  

2、拷貝構造函數
同上,同樣實現一個遞歸函數,返回值仍為Node*
[cpp]  view plain  copy
 
  1. BinaryTree(const BinaryTree<T>& t)  
  2. {  
  3.     _root = CopyTree(t._root);  
  4. }  
 
遞歸:同樣為前序,從根節點開始,先訪問當前結點,判斷,若當前結點為空,則返回一個空。若當前結點不為空,則拷貝當期結點。然后遞歸進入當前結點的左子樹,同樣進行之前的步驟。左子樹處理完之后遞歸處理右子樹。然后返回當前結點(每一層的根節點)。
 
注意:上文提到的當前結點,在每一層的遞歸中值都是不一樣的。每遞歸一層,當前結點就會變成傳入的參數root。包括下文
 
CopyTree實現代碼:
[cpp]  view plain  copy
 
  1. Node* CopyTree(const BinaryTreeNode<T>* _root)3、  
  2. {  
  3.     if (_root == NULL)  
  4.     {  
  5.         return NULL;  
  6.     }  
  7.     Node* root = new Node(_root->_data);  
  8.     root->_left = CopyTree(_root->_left);  
  9.     root->_right = CopyTree(_root->_right);  
  10.     return root;  
  11. }  

3、析構函數
同上,但是析構函數不需要返回值。
[cpp]  view plain  copy
 
  1. ~BinaryTree()  
  2. {  
  3.     Destroy(_root);  
  4. }  
 
遞歸的道理都與上面兩個相同,這里直接給出代碼:
[cpp]  view plain  copy
 
  1. void Destroy( Node* _root)  
  2. {  
  3.     Node* tmp = _root;  
  4.     if (tmp == NULL)//如果根結點為空,則不需要delete,直接return。  
  5.          return;  
  6.     Destroy(tmp->_left);  
  7.     Destroy(tmp->_right);  
  8.     delete tmp;  
  9.     tmp = NULL;  
  10. }  
 
4、賦值運算符重載(=)
 
當我們寫任何賦值運算符重載時,都會有兩種寫法
 
①傳統寫法,一個元素一個元素的拷貝,傳參時傳的是const的引用。
這樣賦值有個麻煩的地方,我們知道,當給一個結點賦值的時候,這個結點原本的內容,空間就要被銷毀掉。就是說我們既要new又要delete。當這個樹有1個億結點時,我們豈不是要重復1億次?有沒有一種更簡單的方法呢?
 
②現代寫法(建議使用,很巧妙),調用swap函數,交換兩個樹的root。傳參時用的值傳遞。
[cpp]  view plain  copy
 
  1. BinaryTree<T>& operator=(BinaryTree<T> t)  
  2. {  
  3.     if (this != &t)//自賦值的優化  
  4.     {  
  5.         std::swap(_root, t._root);  
  6.     }  
  7.     return *this;  
  8. }  
我們都知道,值傳遞傳的是一份臨時拷貝(t為一份臨時拷貝),臨時拷貝的特性就是,它的存活周期只限於這個函數,當函數調用完畢時,它會自動銷毀,而我們也恰恰利用了這個特性。當我們運行std::swap(_root,t._root)這個語句的時候,臨時拷貝t的_root 和 本樹的(this)_root發生了交換。_root原本的值被賦給了臨時拷貝t._root,t._root的值被賦給了_root。當我們執行完這個程序的時候t自動銷毀,幫我們完成了銷毀_root原本內容的工作。我們即完成了賦值,又省去了一大部分工作,一舉兩得。
 
 
5、前中后序遍歷(遞歸)

①前序
這個我就不再多說了,構造,拷貝構造,析構,都是利用前序遍歷的道理來做的。當前結點-->左子樹-->右子樹。
 
代碼:
[cpp]  view plain  copy
 
  1. void PrevOrder()  
  2. {  
  3.     _PrevOrder(_root);  
  4.     cout << endl;  
  5. }  

_PrevOrder()
[cpp]  view plain  copy
 
  1. void _PrevOrder(Node* _root)  
  2. {  
  3.     Node* tmp = _root;  
  4.     if (tmp == NULL)  
  5.     {  
  6.         return;  
  7.     }  
  8.     cout << tmp->_data << " ";  
  9.     _PrevOrder(tmp->_left);  
  10.     _PrevOrder(tmp->_right);  
  11. }  
 
②中序
 

判斷當前結點是否為空,為空的話,不處理,直接返回。先遞歸訪問當前結點左子樹,當左子樹處理完畢,再依次返回處理當前結點,再遞歸訪問當前結點右子樹。

[cpp]  view plain  copy
 
  1. void InOrder()  
  2. {  
  3.     _InOrder(_root);  
  4.     cout << endl;  
  5. }  
 
_InOrder()
[cpp]  view plain  copy
 
  1. void _InOrder(Node* _root)  
  2. {  
  3.     Node* tmp = _root;  
  4.     if (tmp == NULL)  
  5.     {  
  6.         return;  
  7.     }  
  8.     _InOrder(tmp->_left);  
  9.     cout << tmp->_data << " ";  
  10.     _InOrder(tmp->_right);  
  11. }  
 

③后序
判斷當前結點是否為空,為空的話,不處理,直接返回。不為空的話,先遞歸訪問當前結點節點的左子樹,再遞歸訪問當前結點根節點的右子樹,最后訪問當前結點。
[cpp]  view plain  copy
 
  1. void PostOrder()  
  2. {  
  3.     _PostOrder(_root);  
  4.     cout << endl;  
  5. }  
 
_PostOrder()
[cpp]  view plain  copy
 
  1. void _PostOrder(Node* _root)  
  2. {  
  3.     Node* tmp = _root;  
  4.     if (tmp == NULL)  
  5.     {  
  6.         return;  
  7.     }  
  8.     _PostOrder(tmp->_left);  
  9.     _PostOrder(tmp->_right);  
  10.     cout << tmp->_data << " ";  
  11. }  
 
6、前中后序非遞歸。
這里我告訴大家一個真理,任何的遞歸都可以用棧來替換實現。這里我就用一個輔助棧來實現前中后序的非遞歸。
 
①前序
 
 
 
        
 
代碼:
 
[cpp]  view plain  copy
 
  1. void PrevOrder_NonR()  
  2. {  
  3.     Node* cur = _root;  
  4.     stack<Node*> s;  
  5.     if (cur == NULL)  
  6.     {  
  7.         return;  
  8.     }  
  9.     while (cur || !s.empty())  
  10.     {  
  11.         while (cur)  
  12.         {  
  13.             s.push(cur);  
  14.             cout << cur->_data << " ";  
  15.             cur = cur->_left;  
  16.         }  
  17.         Node* top = s.top();  
  18.         s.pop();  
  19.         cur = top->_right;  
  20.     }  
  21.     cout << endl;  
  22. }  


中序和后序的道理與上相同,只是當前節點的輸出做了小小的改動。下面直接貼出代碼
 
②中序(非遞歸)
[cpp]  view plain  copy
 
  1. void InOrder_NonR()  
  2. {  
  3.     Node* cur = _root;  
  4.     stack<Node*> s;  
  5.     if (cur == NULL)  
  6.     {  
  7.         return;  
  8.     }  
  9.     while (cur || !s.empty())  
  10.     {  
  11.         while (cur)  
  12.         {  
  13.             s.push(cur);  
  14.                 cur = cur->_left;  
  15.             }  
  16.         Node* top = s.top();  
  17.         cout << top->_data << " ";  
  18.         s.pop();  
  19.         cur = top->_right;  
  20.     }  
  21.     cout << endl;  
  22. }  
 
③后序(非遞歸)
后序的非遞歸較上面兩個多了一個變量,prev,它記錄了上一次訪問的節點。因為后序是最后訪問當前節點的,當我們訪問一個節點,我們不知道這個節點的右樹是否被訪問過。所以我們需要記錄一下上一個訪問的節點。以便訪問右樹時做判斷。
[cpp]  view plain  copy
 
  1. void PostOrder_NonR()  
  2. {  
  3.     Node* cur = _root;  
  4.     Node* prev = NULL;  
  5.     stack<Node*> s;  
  6.     while (cur || s.empty())  
  7.     {  
  8.         while (cur)  
  9.         {  
  10.             s.push(cur);  
  11.             cur = cur->_left;  
  12.         }  
  13.         Node* top = s.top();  
  14.   
  15.         //如果右樹為空或者右樹已經訪問過,則訪問當前結點,並出棧  
  16.         //如果右樹不為空並且沒有訪問過,則訪問右樹  
  17.         if (top->_right == NULL || prev == top->_right)  
  18.         {  
  19.             cout << top->_data << " ";  
  20.             prev = top;  
  21.             s.pop();//返回父節點  
  22.         }  
  23.         else  
  24.         {  
  25.             cur = top->_right;  
  26.         }  
  27.     }  
  28.     cout << endl;  
  29. }  


7、層序遍歷
 
顧名思義。層序遍歷就是按層來訪問一顆樹,一次訪問一層。這里我們用到了一個隊列。                      
 
代碼:
[cpp]  view plain  copy
 
  1. void _LevelOrder(Node* _root)  
  2. {  
  3.     Node *tmp = _root;  
  4.         queue<Node*> q;  
  5.     q.push(tmp);  
  6.     while (!q.empty())  
  7.     {  
  8.         Node* top = q.front();  
  9.         q.pop();  
  10.         cout << top->_data << " ";  
  11.         if (top->_left)  
  12.         {  
  13.             q.push(top->_left);  
  14.         }  
  15.         if (top->_right)  
  16.         {  
  17.             q.push(top->_right);  
  18.         }  
  19.     }  
  20. }  


8、查找第k層節點個數
 這個問題,我們只需要查找第k-1層有多少個孩子就行了。
 
同樣為遞歸,前序。
 
[cpp]  view plain  copy
 
  1. size_t FindKlevel(size_t k)  
  2. {  
  3.     return _FindKlevel(_root,k);  
  4. }  

_FindKlevel()
[cpp]  view plain  copy
 
  1. size_t _FindKlevel(Node* _root,size_t k)  
  2. {  
  3.     Node *cur = _root;  
  4.     if (cur == NULL || k < 0)  
  5.     {  
  6.         return 0;  
  7.     }  
  8.     if (k == 1)  
  9.     {  
  10.         return 1;  
  11.     }  
  12.     size_t left = _FindKlevel(cur->_left, k-1);  
  13.     size_t right = _FindKlevel(cur->_right, k-1);  
  14.   
  15.     return  left + right;  
  16. }  
 
9、精確查找值為x的結點
 
前序遞歸查找,如果根節點為空,返回NULL,如果當前節點等於x,返回當前節點的指針。如果當前節點不等於x,則遞歸進入左子樹查找,若左子樹沒有,則遞歸進入右子樹查找,若這棵樹中沒有x,返回NULL。
[cpp]  view plain  copy
 
  1. Node* Find(const T& x)  
  2. {  
  3.     return _Find(_root,x);  
  4. }  
 
_Find()
[cpp]  view plain  copy
 
  1. Node* _Find(Node* _root,const T& x)  
  2. {  
  3.     Node* ret = NULL;  
  4.     Node* cur = _root;  
  5.     if (cur == NULL)  
  6.     {  
  7.         return NULL;  
  8.     }  
  9.     if (cur->_data == x)  
  10.     {  
  11.         ret = cur;  
  12.     }  
  13.     else  
  14.     {  
  15.         ret = _Find(cur->_left,x);  
  16.         if (ret == NULL)  
  17.         {  
  18.         ret = _Find(cur->_right,x);  
  19.         }  
  20.     }  
  21.     return ret;  
  22. }  

10、查找結點總個數。
 
結點總個數 = 當前節點個數+左子樹節點個數+右子樹節點個數。
前序遞歸查找,若當前節點為空,返回,不為空則加1,--->遞歸左子樹----->遞歸右子樹。
[cpp]  view plain  copy
 
  1. size_t Size()  
  2. {  
  3.     return _Size(_root);  
  4. }  
_Size()
[cpp]  view plain  copy
 
  1. size_t _Size( BinaryTreeNode<T>* _root )  
  2. {  
  3.     size_t ret = 0;  
  4.     if (_root == NULL)  
  5.     {  
  6.         return ret;  
  7.     }  
  8.     ret++;  
  9.     ret += _Size(_root->_left);  
  10.     ret += _Size(_root->_right);  
  11. }  
 
11、計算樹的深度
樹的深度取左子樹深度+1和右子樹深度+1的最大值。(+1為根節點的深度)
 
[cpp]  view plain  copy
 
  1. size_t Depth()  
  2. {  
  3.     return _Depth(_root);  
  4. }  
 
_Depth()
[cpp]  view plain  copy
 
  1. size_t _Depth(Node* _root)  
  2. {  
  3.     if (_root == NULL)  
  4.     {  
  5.         return 0;  
  6.     }  
  7.     int left = _Depth(_root->_left) + 1;  
  8.     int right = _Depth(_root->_right) + 1;  
  9.     return left > right ? left : right;  
  10. }  



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM