首先,對於給定二叉樹遍歷序列,如果只有前序遍歷、后序遍歷、中序遍歷的任意一個,無法唯一確定一棵二叉樹。舉個反例,如果給定二叉樹前序序列AB,則該二叉樹可以以A為根,B為左子樹,也可以以A為根,B為右子樹。這兩棵樹的前序遍歷序列都為AB,如圖1所示。
同樣的,只給定后序序列也無法唯一確定一棵二叉樹。對於圖1的兩棵樹,它們的后序序列都為BA。而如果只給定中序序列呢?同樣不行,如圖2所示,這兩棵樹的中序遍歷序列都是BA。
那么給定哪些遍歷序列可以唯一確定一棵二叉樹呢?答案是,前序遍歷和中序遍歷或者后序遍歷和中序遍歷。總之要有一個中序遍歷的信息。如果只給定前序遍歷和后序遍歷同樣無法唯一確定一棵二叉樹。還是以圖1為例,這兩棵二叉樹的前序遍歷和后序遍歷序列都一樣。前序為AB,后序為BA。那么如何通過前序遍歷和中序遍歷序列或者后序遍歷和中序遍歷序列構建一棵二叉樹呢?下面以后序遍歷和中序遍歷序列為例,說明一下構建流程。
如果給定中序遍歷序列為CBEDAHGIJF,后序遍歷序列為CEDBHJIGFA。則由后序遍歷的規則可知,最后一個輸出的值為根節點的值,則該二叉樹的樹根值為‘A’。那么再由中序遍歷規則可知,在根節點前輸出的為左子樹序列,在根節點后輸出的為右子樹序列。則‘CBED’為該棵樹的左子樹節點序列,‘HGIJF’為該棵樹的右子樹節點序列。那么,再由后序遍歷‘CEDB’序列可知,左子樹的根節點為‘B’。然后再由中序遍歷可知左子樹的左子樹序列為‘C’,左子樹的右子樹序列為‘ED’。如此遞歸下去,即可還原該棵二叉樹,最后結果如圖3所示。
實際上二叉樹的定義可以由遞歸方式給出,所以它的很多操作都可以由遞歸來實現。下面給出由后序序列和中序序列構建對應二叉樹的代碼。
1 //給出一棵二叉樹的中序遍歷和后序遍歷序列還原該二叉樹 2 //中序序列CBEDAHGIJF 3 //后序序列CEDBHJIGFA 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 typedef int ElementType; 8 typedef struct TreeNode* PtrNode; 9 typedef PtrNode Tree; 10 struct TreeNode { 11 ElementType data; 12 Tree left; 13 Tree right; 14 }; 15 16 PtrNode createTreeNode(char data) { 17 PtrNode node = malloc(sizeof(struct TreeNode)); 18 if (!node) return NULL; 19 node->data = data; 20 node->left = NULL; 21 node->right = NULL; 22 return node; 23 } 24 25 26 Tree recoveryTree(const char* inOrderStr, const char* postOrderStr) { 27 const char* p = inOrderStr; //中序遍歷字符串 28 const char* q = postOrderStr; //后序遍歷字符串 29 Tree root; 30 int i = 0, j = 0; 31 int m, n; 32 if (!(*p) && !(*q)) { //如果中序遍歷字符串和后序遍歷字符串都為空則返回NULL 33 return NULL; 34 } 35 while (q[i]) { //遍歷后序字符串,找到最后一個節點 36 j = i; 37 i++; 38 } 39 char rootData = q[j]; //使用最后節點構建樹根 40 root = createTreeNode(rootData); 41 for (m = 0, n = 0; p[m] && q[n]; m++, n++) { //在中序序列中定位根節點 42 if (p[m] == rootData) 43 break; 44 } 45 char subStr1[20], subStr2[20], subStr3[20]; //在中序序列中拷貝左子樹節點 46 for (m = 0; m < n; m++) { 47 subStr1[m] = p[m]; 48 } 49 subStr1[m] = '\0'; 50 for (m = 0; m < n; m++) { //在后序序列中拷貝左子樹節點 51 subStr2[m] = q[m]; 52 } 53 subStr2[m] = '\0'; 54 55 for (i = 0, m = n; m < j; i++, m++) { //在后序序列中拷貝右子樹節點 56 subStr3[i] = q[m]; 57 } 58 subStr3[i] = '\0'; 59 60 root->left = recoveryTree(subStr1, subStr2); //遞歸構建左子樹和右子樹 61 root->right = recoveryTree(&p[n + 1], subStr3); //中序序列的右子樹可由之前的定位根節點信息獲得 62 return root; //返回構建的樹 63 } 64 65 //后序遍歷 66 void postOrderTraversal(Tree t) { 67 if (t) { 68 postOrderTraversal(t->left); 69 postOrderTraversal(t->right); 70 printf("%c ", t->data); 71 } 72 } 73 74 //中序遍歷 75 void inOrderTraversal(Tree t) { 76 if (t) { 77 inOrderTraversal(t->left); 78 printf("%c ", t->data); 79 inOrderTraversal(t->right); 80 } 81 } 82 83 84 int main(void) { 85 char* inOrderStr = "CBEDAHGIJF"; 86 char* postOrderStr = "CEDBHJIGFA"; 87 Tree tree = recoveryTree(inOrderStr, postOrderStr); 88 postOrderTraversal(tree); 89 printf("\n"); 90 inOrderTraversal(tree); 91 return 0; 92 }
代碼只假定了中序序列和后序序列對應的是同一個棵二叉樹未做特殊處理。由於C的標准字符串是以‘\0‘結尾,所以在構建過程中需要將子字符串拷貝出並添加‘\0’。