《算法導論》讀書筆記之第10章 基本數據結構之二叉樹


摘要

  書中第10章10.4小節介紹了有根樹,簡單介紹了二叉樹和分支數目無限制的有根樹的存儲結構,而沒有關於二叉樹的遍歷過程。為此對二叉樹做個簡單的總結,介紹一下二叉樹基本概念、性質、二叉樹的存儲結構和遍歷過程,主要包括先根遍歷、中根遍歷、后根遍歷和層次遍歷。

1、二叉樹的定義

  二叉樹(Binary Tree)是一種特殊的樹型結構,每個節點至多有兩棵子樹,且二叉樹的子樹有左右之分,次序不能顛倒。

  由定義可知,二叉樹中不存在度(結點擁有的子樹數目)大於2的節點。二叉樹形狀如下下圖所示:

2、二叉樹的性質

(1)在二叉樹中的第i層上至多有2^(i-1)個結點(i>=1)。備注:^表示此方

(2)深度為k的二叉樹至多有2^k-1個節點(k>=1)。

(3)對任何一棵二叉樹T,如果其終端結點數目為n0,度為2的節點數目為n2,則n0=n2+1。

滿二叉樹:深度為k且具有2^k-1個結點的二叉樹。即滿二叉樹中的每一層上的結點數都是最大的結點數。

完全二叉樹:深度為k具有n個結點的二叉樹,當且僅當每一個結點與深度為k的滿二叉樹中的編號從1至n的結點一一對應。

可以得到一般結論:滿二叉樹和完全二叉樹是兩種特殊形態的二叉樹,滿二叉樹肯定是完全二叉樹,但完全二叉樹不不一定是滿二叉樹。

舉例如下圖是所示:

(4)具有n個節點的完全二叉樹的深度為log2n + 1。

3、二叉樹的存儲結構

  可以采用順序存儲數組和鏈式存儲二叉鏈表兩種方法來存儲二叉樹。經常使用的二叉鏈表方法,因為其非常靈活,方便二叉樹的操作。二叉樹的二叉鏈表存儲結構如下所示:

1 typedef struct binary_tree_node 2 { 3     int elem; 4     struct binary_tree_node *left; 5     struct binary_tree_node *right; 6 }binary_tree_node,*binary_tree;

舉例說明二叉鏈表存儲過程,如下圖所示:

從圖中可以看出:在還有n個結點的二叉鏈表中有n+1個空鏈域。

4、遍歷二叉樹

  遍歷二叉樹是按照指定的路徑方式訪問書中每個結點一次,且僅訪問一次。由二叉樹的定義,我們知道二叉數是由根結點、左子樹和右子樹三部分構成的。通常遍歷二叉樹是從左向右進行,因此可以得到如下最基本的三種遍歷方法:

(1)先根遍歷(先序遍歷):如果二叉樹為空,進行空操作;否則,先訪問根節點,然后先根遍歷左子樹,最后先根遍歷右子樹。采用遞歸形式實現代碼如下:

1 void preorder_traverse_recursive(binary_tree root) 2 { 3     if(NULL != root) 4  { 5         printf("%d\t",root->elem); 6         preorder_traverse_recursive(root->left); 7         preorder_traverse_recursive(root->right); 8  } 9 }

具體過程如下圖所示:

(2)中根遍歷(中序遍歷):如果二叉樹為空,進行空操作;否則,先中根遍歷左子樹,然后訪問根結點,最后中根遍歷右子樹。遞歸過程實現代碼如下:

1 void inorder_traverse_recursive(binary_tree root) 2 { 3     if(NULL != root) 4  { 5         inorder_traverse_recursive(root->left); 6         printf("%d\t",root->elem); 7         inorder_traverse_recursive(root->right); 8  } 9 }

具體過程如下圖所示:

(3)后根遍歷(后序遍歷):如果二叉樹為空,進行空操作;否則,先后根遍歷左子樹,然后后根遍歷右子樹,最后訪問根結點。遞歸實現代碼如下:

1 void postorder_traverse_recursive(binary_tree root) 2 { 3     if(NULL != root) 4  { 5         postorder_traverse_recursive(root->left); 6         postorder_traverse_recursive(root->right); 7         printf("%d\t",root->elem); 8  } 9 }

具體過程如下圖所示:

  寫一個完整的程序練習二叉樹的三種遍歷,采用遞歸形式創建二叉樹,然后以遞歸的形式遍歷二叉樹,后面會接着討論如何使用非遞歸形式實現這三種遍歷,程序采用C語言實現,完整程序如下:

View Code
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 //the structure of binary tree
 5 typedef struct binary_tree_node
 6 {
 7     int elem;
 8     struct binary_tree_node *left;
 9     struct binary_tree_node *right;
10 }binary_tree_node,*binary_tree;
11 
12 void init_binary_tree(binary_tree *root);
13 void create_binary_tree(binary_tree *root);
14 
15 //previous root
16 void preorder_traverse_recursive(binary_tree root);
17 //inorder root
18 void inorder_traverse_recursive(binary_tree root);
19 //post order root
20 void postorder_traverse_recursive(binary_tree root);
21 
22 int main()
23 {
24     binary_tree root;
25    init_binary_tree(&root);
26     create_binary_tree(&root);
27     preorder_traverse_recursive(root);
28     inorder_traverse_recursive(root);
29     postorder_traverse_recursive(root);
30     exit(0);
31 }
32 
33 void init_binary_tree(binary_tree *root)
34 {
35     *root = NULL;
36 }
37 
38 void create_binary_tree(binary_tree* root)
39 {
40     int elem;
41     printf("Enter the node value(0 is end): ");
42     scanf("%d",&elem);
43     if(elem == 0)
44         *root = NULL;
45     else
46     {
47         *root = (binary_tree)malloc(sizeof(binary_tree_node));
48         if(NULL == root)
49         {
50              printf("malloc error.\n");
51              exit(-1);
52         }
53         (*root)->elem = elem;
54         printf("Creating the left child node.\n");
55         create_binary_tree(&((*root)->left));
56         printf("Createing the right child node.\n");
57         create_binary_tree(&((*root)->right));
58     }
59 }
60 
61 void preorder_traverse_recursive(binary_tree root)
62 {
63     if(NULL != root)
64     {
65         printf("%d\t",root->elem);
66         preorder_traverse_recursive(root->left);
67         preorder_traverse_recursive(root->right);
68     }
69 }
70 
71 void inorder_traverse_recursive(binary_tree root)
72 {
73     if(NULL != root)
74     {
75         inorder_traverse_recursive(root->left);
76         printf("%d\t",root->elem);
77         inorder_traverse_recursive(root->right);
78     }
79 }
80 
81 void postorder_traverse_recursive(binary_tree root)
82 {
83     if(NULL != root)
84     {
85         postorder_traverse_recursive(root->left);
86         postorder_traverse_recursive(root->right);
87         printf("%d\t",root->elem);
88     }
89 }

程序測試結果如下:

  現在來討論一下如何采用非遞歸實現這以上三種遍歷。將遞歸形式轉換為非遞歸形式,引入了額外的輔助結構棧。另外在討論一次二叉樹的層次遍歷,可以借助隊列進行實現。具體討論如下:

(1)先根遍歷非遞歸實現

  先根遍歷要求順序是根左右,可以借助棧s來實現。先將根root入棧,然后循環判斷s是否為空,非空則將結點出棧,記為節點p,然后依次先將結點p的右子樹結點入棧,然后將結點p的左子樹結點入棧,循環操作直到棧中所有元素都出棧為止,出棧順序即是先根遍歷的結果。采用C++中模板庫stack實現先根遍歷如下:

 1 void preorder_traverse(binary_tree root)  2 {  3     if(NULL != root)  4  {  5         stack<binary_tree_node*> s;  6         binary_tree_node *ptmpnode;  7  s.push(root);  8         while(!s.empty())  9  { 10             ptmpnode = s.top(); 11             cout<<ptmpnode->elem<<" "; 12  s.pop(); 13             if(NULL != ptmpnode->right) 14                 s.push(ptmpnode->right); 15             if(NULL != ptmpnode->left) 16                 s.push(ptmpnode->left); 17 
18  } 19  } 20 }

(2)中根遍歷非遞歸實現

  中根遍歷要求順序是左根右,借助棧s實現。先將根root入棧,接着從根root開始查找最左的子孩子結點直到為空為止,然后將空節點出棧,再將左子樹節點出棧遍歷,然后判斷該左子樹的右子樹節點入棧。循環此過程,直到棧為空為止。此時需要注意的是入棧過程中空結點也入棧了,用以判斷左孩子是否結束和左孩子是否有右孩子結點。采用C++中模板庫stack實現先根遍歷如下:

 1 void inorder_traverse(binary_tree root)  2 {  3     if(NULL != root)  4  {  5         stack<binary_tree_node*> s;  6         binary_tree_node *ptmpnode;  7  s.push(root);  8         while(!s.empty())  9  { 10             ptmpnode = s.top(); 11             while(NULL != ptmpnode) 12  { 13                 s.push(ptmpnode->left); 14                 ptmpnode = s.top(); 15  } 16             s.pop();//空結點出棧
17             if(!s.empty()) 18  { 19                 ptmpnode = s.top(); 20                 cout<<ptmpnode->elem<<" "; 21  s.pop(); 22                 //右子樹結點如棧
23                 s.push(ptmpnode->right); 24  } 25  } 26  } 27 }

另外一種簡潔的實現方法如下:

 1 void inorder_traverse_two(binary_tree root)  2 {  3     if(NULL != root)  4  {  5         stack<binary_tree_node*> s;  6         binary_tree_node *ptmpnode;  7         ptmpnode = root;  8         while(NULL != ptmpnode || !s.empty())  9  { 10             //將左子樹結點入棧
11             if(NULL != ptmpnode) 12  { 13  s.push(ptmpnode); 14                 ptmpnode = ptmpnode->left; 15  } 16             else
17  { 18                 //出棧遍歷
19                 ptmpnode = s.top(); 20  s.pop(); 21                 cout<<ptmpnode->elem<<" "; 22                 //右子樹結點
23                 ptmpnode = ptmpnode->right; 24  } 25  } 26  } 27 }

(3)后根遍歷遞歸實現

  后根遍歷要求訪問順序是左右根,采用輔助棧實現時,需要一個標記,判斷結點是否訪問了,因為右子樹是通過跟結點的信息得到的。實現過程是先將根結點及其左子樹入棧,並初始標記為0,表示沒有訪問,然后通過判斷棧是否為空和標記的值是否為1來判斷是否訪問元素。

參考:http://www.cnblogs.com/hicjiajia/archive/2010/08/27/1810055.html

采用C++模板庫stack具體實現程序如下:

 1 void postorder_traverse(binary_tree root)
 2 {
 3     if(NULL != root)
 4     {
 5         stack<binary_tree_node*> s;
 6         binary_tree_node *ptmpnode;
 7         int flags[100];
 8         ptmpnode = root;
 9         while(NULL != ptmpnode || !s.empty())
10         {
11             //將結點左子樹結點入棧
12             while(NULL != ptmpnode)
13             {
14                 s.push(ptmpnode);
15                 flags[s.size()] = 0;   //標記未訪問
16                 ptmpnode=ptmpnode->left;
17             }
18             //輸出訪問的結點
19             while(!s.empty() && flags[s.size()] == 1)
20             {
21                 ptmpnode = s.top();
22                 s.pop();
23                 cout<<ptmpnode->elem<<" ";
24             }
25             //從右子樹開始遍歷
26             if(!s.empty())
27             {
28                 ptmpnode = s.top();
29                 flags[s.size()] = 1;  //登記訪問了
30                 ptmpnode = ptmpnode->right;
31             }
32             else
33                 break;
34         }
35     }
36 }

(4)層次遍歷實現

  層次遍歷要求從根向下、從左向右進行訪問,可以采用隊列實現。先將根入隊,然后隊列進程出隊操作訪問結點p,再將結點p的左子樹和右子樹結點入隊,循環執行此過程直到隊列為空。出隊順序即是層次遍歷結果。采用C++的模板庫queue實現如下:

 1 void levelorder_traverse(binary_tree root)  2 {  3     if(NULL != root)  4  {  5         queue<binary_tree_node*> q;  6         binary_tree_node *ptmpnode;  7  q.push(root);  8         while(!q.empty())  9  { 10             ptmpnode = q.front(); 11  q.pop(); 12             cout<<ptmpnode->elem<<" "; 13             if(NULL != ptmpnode->left) 14                 q.push(ptmpnode->left); 15             if(NULL != ptmpnode->right) 16                 q.push(ptmpnode->right); 17  } 18  } 19 }

 綜合上面的分析過程寫個完整的程序測試二叉樹遍歷的非遞歸實現,采用C++語言,借助stack和queue實現,完整程序如下所示:

View Code
  1 #include <iostream>
  2 #include <stack>
  3 #include <queue>
  4 #include <cstdlib>
  5 using namespace std;
  6 
  7 typedef struct binary_tree_node
  8 {
  9     int elem;
 10     struct binary_tree_node *left;
 11     struct binary_tree_node *right;
 12 }binary_tree_node,*binary_tree;
 13 
 14 void init_binary_tree(binary_tree *root);
 15 void create_binary_tree(binary_tree *root);
 16 void preorder_traverse(binary_tree root);
 17 void inorder_traverse(binary_tree root);
 18 void inorder_traverse_two(binary_tree root);
 19 void postorder_traverse(binary_tree root);
 20 void levelorder_traverse(binary_tree root);
 21 
 22 int main()
 23 {
 24     binary_tree root;
 25     create_binary_tree(&root);
 26     cout<<"preodrer traverse: ";
 27     preorder_traverse(root);
 28     cout<<"\ninodrer traverse: ";
 29     inorder_traverse_two(root);
 30     cout<<"\npostodrer traverse: ";
 31     postorder_traverse(root);
 32     cout<<"\nleverorder traverse: ";
 33     levelorder_traverse(root);
 34     exit(0);
 35 }
 36 
 37 void init_binary_tree(binary_tree *root)
 38 {
 39     *root = NULL;
 40 }
 41 
 42 void create_binary_tree(binary_tree* root)
 43 {
 44     int elem;
 45     cout<<"Enter the node value(0 is end): ";
 46     cin>>elem;
 47     if(elem == 0)
 48         *root = NULL;
 49     else
 50     {
 51         *root = (binary_tree)malloc(sizeof(binary_tree_node));
 52         if(NULL == root)
 53         {
 54              cout<<"malloc error.\n";
 55              exit(-1);
 56         }
 57         (*root)->elem = elem;
 58         cout<<"Creating the left child node.\n";
 59         create_binary_tree(&((*root)->left));
 60         cout<<"Createing the right child node.\n";
 61         create_binary_tree(&((*root)->right));
 62     }
 63 }
 64 
 65 void preorder_traverse(binary_tree root)
 66 {
 67     if(NULL != root)
 68     {
 69         stack<binary_tree_node*> s;
 70         binary_tree_node *ptmpnode;
 71         s.push(root);
 72         while(!s.empty())
 73         {
 74             ptmpnode = s.top();
 75             cout<<ptmpnode->elem<<" ";
 76             s.pop();
 77             if(NULL != ptmpnode->right)
 78                 s.push(ptmpnode->right);
 79             if(NULL != ptmpnode->left)
 80                 s.push(ptmpnode->left);
 81 
 82         }
 83     }
 84 }
 85 void inorder_traverse(binary_tree root)
 86 {
 87     if(NULL != root)
 88     {
 89         stack<binary_tree_node*> s;
 90         binary_tree_node *ptmpnode;
 91         s.push(root);
 92         while(!s.empty())
 93         {
 94             ptmpnode = s.top();
 95             while(NULL != ptmpnode)
 96             {
 97                 s.push(ptmpnode->left);
 98                 ptmpnode = s.top();
 99             }
100             s.pop();
101             if(!s.empty())
102             {
103                 ptmpnode = s.top();
104                 cout<<ptmpnode->elem<<" ";
105                 s.pop();
106                 s.push(ptmpnode->right);
107             }
108         }
109     }
110 }
111 
112 void inorder_traverse_two(binary_tree root)
113 {
114     if(NULL != root)
115     {
116         stack<binary_tree_node*> s;
117         binary_tree_node *ptmpnode;
118         ptmpnode = root;
119         while(NULL != ptmpnode || !s.empty())
120         {
121             //將左子樹結點入棧
122             if(NULL != ptmpnode)
123             {
124                 s.push(ptmpnode);
125                 ptmpnode = ptmpnode->left;
126             }
127             else
128             {
129                 //出棧遍歷
130                 ptmpnode = s.top();
131                 s.pop();
132                 cout<<ptmpnode->elem<<" ";
133                 //右子樹結點
134                 ptmpnode = ptmpnode->right;
135             }
136         }
137     }
138 }
139 
140 void postorder_traverse(binary_tree root)
141 {
142     if(NULL != root)
143     {
144         stack<binary_tree_node*> s;
145         binary_tree_node *ptmpnode;
146         int flags[100];
147         ptmpnode = root;
148         while(NULL != ptmpnode || !s.empty())
149         {
150             //將結點左子樹結點入棧
151             while(NULL != ptmpnode)
152             {
153                 s.push(ptmpnode);
154                 flags[s.size()] = 0;   //標記未訪問
155                 ptmpnode=ptmpnode->left;
156             }
157             //輸出訪問的結點
158             while(!s.empty() && flags[s.size()] == 1)
159             {
160                 ptmpnode = s.top();
161                 s.pop();
162                 cout<<ptmpnode->elem<<" ";
163             }
164             //從右子樹開始遍歷
165             if(!s.empty())
166             {
167                 ptmpnode = s.top();
168                 flags[s.size()] = 1;  //登記訪問了
169                 ptmpnode = ptmpnode->right;
170             }
171             else
172                 break;
173         }
174     }
175 }
176 void levelorder_traverse(binary_tree root)
177 {
178     if(NULL != root)
179     {
180         queue<binary_tree_node*> q;
181         binary_tree_node *ptmpnode;
182         q.push(root);
183         while(!q.empty())
184         {
185             ptmpnode = q.front();
186             q.pop();
187             cout<<ptmpnode->elem<<" ";
188             if(NULL != ptmpnode->left)
189                 q.push(ptmpnode->left);
190             if(NULL != ptmpnode->right)
191                 q.push(ptmpnode->right);
192         }
193     }
194 }

程序測試結果如下:


免責聲明!

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



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