資料結構之二叉樹遞迴操作

Diy_os發表於2015-11-20
二叉樹(binary tree)是n(n>=0)個結點的有限結合,該集合或者為空集(空二叉樹),或由一個根結點和兩棵互不相交的,分別稱為根結點的左子樹(left subtree)和右子樹(right subtree)的二叉樹組成。
二叉樹的特點:
1.每個結點最多有兩棵字樹,所以二叉樹中不存在度大於2的結點
2.二叉樹是有序的,其次序不能任意顛倒,即使樹中的某個結點只有一棵
字樹,也要區分它是左子樹還是右子樹。
下面介紹幾種特殊的二叉樹:
1.斜樹
所有結點都只有左子樹的二叉樹稱為左斜樹;所有結點都只有右子樹的二叉樹稱為右斜樹。是兩棵不同的二叉樹。如下示意圖。
2.滿二叉樹
在一棵二叉樹中,如果所有分支結點都存在左子樹和右字樹,並且所有葉子結點都在同一層上,這樣的二叉樹稱為滿二叉樹。如下示意圖。
                                                                     
3.完全二叉樹
對一棵具有n個結點的二叉樹按層序編號,如果編號為i(1<=i<=n)的結點與同樣深度的滿二叉樹中編號為i的結點在二叉樹中的位置完全相同,則這棵二叉樹稱為完全二叉樹。顯然一棵滿二叉樹必定是一棵完全二叉樹,完全二叉樹的特點是:
1.如果有度為1的結點,只可能有一個,且該結點只有左孩子。
2.葉子結點只能出現在最下面兩層,且最下層的葉子結點都集中在二叉樹左側連續的位置。
                                                                 
以上圖畫的不是太優雅,大家不要介意哈。
關於二叉樹的更多的性質,請讀者檢視維基百科()或者其他資料。

中能用多種方法來構造二叉樹。常用的是順序儲存結構和二叉連結串列儲存結構。
順序儲存結構:
叉樹可以用或線性表來儲存,而且如果這是,這種方法不會浪費空間。用這種緊湊排列,如果一個結點的索引為i,它的子結點能在索引2i+1和2i+2找到,並且它的父節點(如果有)能在索引floor((i-1)/2)找到(假設根節點的索引為0)。這種方法更有利於緊湊儲存和更好的,特別是在前序遍歷中。然而,它需要連續的,這樣在儲存高度為hn個結點組成的一般普通樹時將會浪費很多空間。一種最極壞的情況下如果深度為h的二叉樹每個節點只有右孩子需要佔用2的h次冪減1,而實際卻只有h個結點,空間的浪費太大,這是順序儲存結構的一大缺點。
                                                                          
對於順序儲存結構,本文不作介紹,這種儲存結構的缺點導致物理空間的浪費,是我們難以接受的。下面重點介紹二叉連結串列儲存結構。

二叉連結串列儲存結構:

在使用的程式語言中,二叉樹通常用樹結點結構來儲存。有時也包含指向唯一的父節點的指標。如果一個結點的子結點個數小於2,一些子結點指標可能為空值,或者為特殊的結點。 使用連結串列能避免順序儲存浪費空間的問題,演算法和結構相對簡單,但使用二叉連結串列,由於缺乏父鏈的指引,在找回父節點時需要重新掃描樹得知父節點的節點地址。
                                                                          
以上的儲存結構示意圖我想看的很清楚,這種結構減少了一定空間的浪費,但是這一定是最好的儲存結構麼,後續的文章會談及線索二叉連結串列。
對於二叉連結串列最重要的操作莫過於遍歷了,概括來說有遞迴和非遞迴兩種,本文介紹的是遞迴遍歷二叉樹。

點選(此處)摺疊或開啟

  1. #include<iostream>
  2. using namespace std;
  3. typedef struct node
  4. {
  5.     struct node *leftChild;
  6.     struct node *rightChild;
  7.     char data;
  8. }BiTreeNode, *BiTree;

  9. void createBiTree(BiTree &);
  10. void PreOrderBiTree(BiTree *);
  11. void InOrderBiTree(BiTree *);
  12. void PostOrderBiTree(BiTree *);
  13. int BiTreeDeep(BiTree *);
  14. int LeafTreecount(BiTree *);

  15. void createBiTree(BiTree &T)
  16. {
  17.     char c;
  18.     cin >> c;
  19.     if ('#' == c)
  20.         T = NULL;
  21.     else
  22.     {
  23.         T = new BiTreeNode;
  24.         T->data = c;
  25.         createBiTree(T->leftChild);
  26.         createBiTree(T->rightChild);
  27.     }
  28. }

  29. void PreOrderBiTree(BiTree &T)
  30. {
  31.     if (T == NULL)
  32.         return;
  33.     cout << T->data << " ";
  34.     PreOrderBiTree(T->leftChild);
  35.     PreOrderBiTree(T->rightChild);

  36. }
  37. void InOrderBiTree(BiTree &T) {

  38.     if (T == NULL)
  39.         return;
  40.         InOrderBiTree(T->leftChild);
  41.         cout << T->data << " ";
  42.         InOrderBiTree(T->rightChild);
  43.     
  44. }
  45. void PostOrderBiTree(BiTree &T) {
  46.     if (T == NULL)
  47.         return;
  48.     PostOrderBiTree(T->leftChild);
  49.     PostOrderBiTree(T->rightChild);
  50.     cout << T->data<<" ";
  51. }

  52. int BiTreeDeep(BiTree T)
  53. {
  54.     int deep = 0;
  55.     if (T)
  56.     {
  57.         int leftdeep = BiTreeDeep(T->leftChild);
  58.         int rightdeep = BiTreeDeep(T->rightChild);
  59.         deep = leftdeep >= rightdeep ? leftdeep + 1 : rightdeep + 1;
  60.     }
  61.     return deep;
  62. }

  63. //求二叉樹葉子結點個數

  64. int LeafTreecount(BiTree T,int &num)
  65. {
  66.     if (T)
  67.     {
  68.         if (T->leftChild == NULL &&T->rightChild == NULL)
  69.             num++;
  70.         LeafTreecount(T->leftChild,num);
  71.         LeafTreecount(T->rightChild,num);

  72.     }
  73.     return num;
  74. }
  75. void DeleteBiTree(BiTree T) {
  76.     if (T == NULL)
  77.         return;
  78.     DeleteBiTree(T->leftChild);
  79.     DeleteBiTree(T->rightChild);
  80.     delete T;
  81.     
  82. }
  83. int main()
  84. {
  85. BiTree T;
  86. cout << "輸入的字元是:" << endl;
  87. createBiTree(T);
  88. cout << "前序遍歷:" << endl;
  89. PreOrderBiTree(T); \
  90. cout << endl;
  91. cout <<"中序遍歷:"<< endl;
  92. InOrderBiTree(T);
  93. cout << endl;
  94. cout << "後序遍歷:" << endl; \
  95. PostOrderBiTree(T);
  96. cout << endl;
  97. cout << "二叉樹的高度為:" << BiTreeDeep(T) << endl;
  98. int num = 0;
  99. cout << "二叉樹的葉子結點個數為:" << LeafTreecount(T, num)<< endl;
  100. DeleteBiTree(T);
  101. cout << "樹已銷燬!!!" << endl;
  102. return 0;
  103. }

執行結果如下:


一定要注意建立的二叉樹時的方法,上面也是透過遞迴的方法建立二叉樹,根據前中後序遍歷,很容易畫出這棵二叉樹:(如下字母即指結點又指結點資料域)
下面詳細的介紹是如何進行遞迴遍歷的,以上面構建的二叉樹為例:
前序遍歷:
呼叫PostOrderBiTree(BiTree &T),T根結點不為空,列印字元A
再呼叫PreOrderBiTree(T->leftChild),訪問根結點的左孩子,不為NULL,列印字元B
再次呼叫PreOrderBiTree(T->leftChild),訪問B結點的左孩子,不為空,列印C
再次呼叫PreOrderBiTree(T->leftChild),訪問結點C的左孩子,因為C沒有左孩子,T=NULL,返回函式。呼叫PreOrderBiTree(T->rightChild),C的右孩子也為空,此時返回到 上一級遞迴的函式,也就是列印B結點的函式;此時呼叫PreOrderBiTree(T->rightChild),B沒有右孩子,為空,此時返回到上一級遞迴的函式,就是列印A結點的函式;此時呼叫PreOrderBiTree(T->rightChild),列印了D;呼叫PreOrderBiTree(T->leftChild),因為D沒有左孩子,T=NULL,返回函式,呼叫了PreOrderBiTree(T->rightChild),D也沒有右孩子,T=NULL,返回函式,到此前序遍歷了整個二叉樹。
中序遍歷:
呼叫PostOrderBiTree(BiTree &T),T根結點不為空
呼叫PreOrderBiTree(T->leftChild),訪問根結點的左孩子B,不為NULL
呼叫PreOrderBiTree(T->leftChild),訪問B結點的左孩子C,不為NULL
呼叫PreOrderBiTree(T->leftChild),訪問C結點的左孩子,由於C結點沒有左孩子,所以T=NULL,返回函式;列印字元C,又呼叫PreOrderBiTree(T->rightChild),因為C沒有右PreOrderBiTree(T->rightChild)孩子,所以返回到上級遞迴函式,列印字元B;呼叫PreOrderBiTree(T->rightChild),由於B沒有右孩子,返回到上級遞迴函式;列印字元A,呼叫了PreOrderBiTree(T->rightChild),訪問了根結點的右孩子D,不為空;然後呼叫了PreOrderBiTree(T->leftChild),因為D沒有左孩子,返回函式;列印字元D,又呼叫PreOrderBiTree(T->rightChild),因為D沒有右孩子,返回函式,到此中序遍歷了整個二叉樹。
後序遍歷操作讀者感興趣可以自己完成,這裡不作贅述,從上面的分析,整個過程不是太複雜,關鍵要理解遞迴的思想。
程式碼中也簡單實現了列印二叉樹的葉子結點個數,二叉樹的高度,以及銷燬一棵二叉樹。如果明白了二叉樹的建立和遍歷,那麼樹的其他操作對於我們也不是難事。







來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29876893/viewspace-1841619/,如需轉載,請註明出處,否則將追究法律責任。

相關文章