這是劍指offer中關於二叉樹重構的一道題。題目原型爲:node
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。ios
1、二叉樹的數據結構算法
作題以前,咱們先熟悉下二叉樹的數據結構。其一,定義:二叉樹是一個連通的無環圖,而且每個頂點的度不大於3。有根二叉樹還要知足根結點的度不大於2。有了根結點以後,每一個頂點定義了惟一的父結點,和最多2個子結點。然而,沒有足夠的信息來區分左結點和右結點。若是不考慮連通性,容許圖中有多個連通份量,這樣的結構叫作森林。(定義來自百度百科)數據結構
由定義可知,二叉樹含有許多節點,而不一樣節點之間經過子父關係來鏈接。通常來講,二叉樹的節點由結構體來定義,以下所示: ide
1 struct TreeNode { 2 int val; 3 TreeNode *left; 4 TreeNode *right; 5 TreeNode(int x) : val(x), left(NULL), right(NULL) {} 6 ~TreeNode() { 7 cout << "TreeNode with value " << val << " has been destroyed." <<endl; 8 } 9 };
該結構體定義了節點的int型變量(固然也能夠是其它類型),以及指向左右孩子的指針。在TreeNode中,咱們還定義了構造函數和析構函數,其實無關緊要,對本題來講沒有多大意義。函數
2、二叉樹的創建優化
二叉樹的創建過程,就是不斷擴展節點的過程。創建根節點,由根節點創建左右子節點,再遞歸的創建子節點的子節點。咱們下面看看代碼的實現過程:spa
1 void BiTree::createBiTree(struct TreeNode* &root) { 2 int val; 3 //cout << "Please input the tree node:" << endl; 4 cin >> val; 5 if (999 == val) { 6 root = NULL; 7 } 8 else { 9 //root = (struct TreeNode*) malloc(sizeof(TreeNode)); 10 root = new TreeNode(1); 11 if (root == NULL) 12 return; 13 root->val = val; 14 15 cout << "Please input the left child of " << val << ": "; 16 createBiTree(root->left); 17 18 cout << "Please input the right child of" << val << ": "; 19 createBiTree(root->right); 20 } 21 }
這裏,我是把建立二叉樹的方法createBiTree()做爲了BiTree的成員函數(簡單說明一下)。debug
一、首先,咱們看參數 struct TreeNode* &root, 這是結構體指針的引用。爲何要傳遞引用呢,就是但願函數體外的指針變量能指向函數體內所新建的根節點。若只是指針傳遞,那麼函數體內新建的節點和函數體外的指針變量是沒有指向關係的。指針
二、而後就是新建節點,
//root = (struct TreeNode*) malloc(sizeof(TreeNode));
root = new TreeNode(1);
這兩條語句的效果相同。若是建立成功,那麼遞歸調用本方法,建立後續節點。
3、二叉樹的遍歷
二叉樹有三種遍歷方法:先序,中序,後序。即父前(父-左-右),父中(左-父-右),父後(左-右-父)。
一、先序遍歷
1 // 這裏參數傳遞指針,或指針的引用均可以 2 void BiTree::preOrder(struct TreeNode* &root) { 3 if (root == NULL) 4 return; 5 cout << root->val << ' '; 6 preTrav.push_back(root->val); 7 preOrder(root->left); 8 preOrder(root->right); 9 }
其中,preTraa.push_back(root->val);我只是把先序遍歷的結果存在了vector 中,方便重構二叉樹時做爲輸入。下同。
2,、中序遍歷
1 void BiTree::inOrder(struct TreeNode* root) { 2 if (root == NULL) 3 return; 4 inOrder(root->left); 5 cout << root->val << ' '; 6 inTrav.push_back(root->val); 7 inOrder(root->right); 8 }
三、後序遍歷,就不貼代碼了。
另外,值得說明一下的是。這裏的遍歷方法都是經過遞歸來實現的,還有遍歷的非遞歸算法(往後再詳細去探討,這裏先着重看這個題目,也不知往後是否會想起這個任務。。。囧)
3、已知先序遍歷,中序遍歷,重構二叉樹
咱們觀察先序遍歷和中序遍歷的結果。先序遍歷的第一個節點確定是根節點,而後在中序遍歷中找到此根節點,咱們能夠看到,中序遍歷中根節點以左是二叉樹的左子樹,根節點以右是二叉樹的右子樹。而後,左右子女樹又能夠單獨的當作一個獨立的二叉樹,而後再對其劃分,如此遞歸,即可重構二叉樹的結構。下面上代碼:
1 struct TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> in){ 2 //TreeNode* head = new TreeNode(pre[0]); 3 /* 注意上面這一條語句不能放在函數體的第一句。 4 * 由於此時還不能肯定pre這個vector是否爲空,若是不爲空,那麼訪問pre[0]就是非法內存訪問。此時若debug,會有 5 * Program received signal SIGSEGV, Segmentation fault.的錯誤信息。其中,SIG是signal的縮寫,SEGV是segmentation violation(段違例)的縮寫。詳見維基百科 6 * 因此,此條語句放在return NULL;語句以後爲宜,由於這個時候已經肯定vector in不爲空。可是好像沒判斷pre是否爲空呢,這就能夠了?? 7 */ 8 vector<int> left_pre, left_in, right_pre, right_in; 9 10 int pos = 0; 11 int length = in.size(); 12 13 if (length == 0) 14 return NULL; 15 16 TreeNode* head = new TreeNode(pre[0]); 17 18 for (int i = 0; i < length; ++i) { 19 if (pre[0] == in[i]) { 20 pos = i; 21 break; 22 } 23 } 24 25 for (int i = 1; i < pos + 1; ++i) { 26 left_pre.push_back(pre[i]); 27 left_in.push_back(in[i-1]); 28 } 29 30 for (int i = pos + 1; i < length; ++i) { 31 right_pre.push_back(pre[i]); 32 right_in.push_back(in[i]); 33 } 34 35 head->left = reConstructBinaryTree(left_pre, left_in); 36 head->right = reConstructBinaryTree(right_pre, right_in); 37 38 return head; 39 40 }
其實,關於這個重構函數,筆者還有一個問題,就如函數中註釋中所訴。還請廣大博友解答下(新手博客,都沒人看,好憂傷~_~!)
4、重構驗證
首先,貼上驗證程序。驗證程序綜合了上述創建二叉樹,遍歷二叉樹以及重構二叉樹的相關實現。
1 #include <iostream> 2 #include <vector> 3 using namespace std; 4 5 // Definition for binary tree 6 struct TreeNode { 7 int val; 8 TreeNode *left; 9 TreeNode *right; 10 TreeNode(int x) : val(x), left(NULL), right(NULL) {} 11 ~TreeNode() { 12 cout << "TreeNode with value " << val << " has been destroyed." <<endl; 13 } 14 }; 15 16 class BiTree { 17 public: 18 int flag; 19 vector<int> preTrav, inTrav; 20 21 BiTree(int _flag) : flag(_flag){ 22 cout << "Instance of Bitree with flag " << flag << " has been constructed." << endl; 23 } 24 ~BiTree() { 25 cout << "Instance of Bitree with flag " << flag << " has been destroyed." << endl; 26 } 27 28 /* createBiTree()注意這裏傳遞的是指針的引用,由於函數體內部在不斷的new,須要讓傳進來的根節點指向內部新開闢的空間, 29 * 不過這樣的話,在main函數內,定義的mytree結構體的空間就浪費了,怎麼去優化呢? 30 * struct TreeNode* tree = nullptr;此條語句解決上訴問題 31 */ 32 void createBiTree(struct TreeNode*&); 33 void preOrder(struct TreeNode*&); //這裏傳遞指針或指針的引用均可以 34 void inOrder(struct TreeNode*); 35 }; 36 // Create the binary tree 37 void BiTree::createBiTree(struct TreeNode* &root) { 38 int val; 39 //cout << "Please input the tree node:" << endl; 40 cin >> val; 41 if (999 == val) { 42 root = NULL; 43 } 44 else { 45 //root = (struct TreeNode*) malloc(sizeof(TreeNode)); 46 root = new TreeNode(1); 47 if (root == NULL) 48 return; 49 root->val = val; 50 51 cout << "Please input the left child of " << val << ": "; 52 createBiTree(root->left); 53 54 cout << "Please input the right child of" << val << ": "; 55 createBiTree(root->right); 56 } 57 } 58 59 // pre order of binary tree 60 void BiTree::preOrder(struct TreeNode* &root) { 61 if (root == NULL) 62 return; 63 cout << root->val << ' '; 64 preTrav.push_back(root->val); 65 preOrder(root->left); 66 preOrder(root->right); 67 } 68 69 // in order of binary tree 70 void BiTree::inOrder(struct TreeNode* root) { 71 if (root == NULL) 72 return; 73 inOrder(root->left); 74 cout << root->val << ' '; 75 inTrav.push_back(root->val); 76 inOrder(root->right); 77 } 78 // 在類外部定義一箇中序遍歷,做調試用 79 void inOrder(struct TreeNode* &root) { 80 if (root == NULL) 81 return; 82 inOrder(root->left); 83 cout << root->val << ' '; 84 inOrder(root->right); 85 } 86 87 // Solution of reconstruct binary tree 88 class Soultion { 89 public: 90 int flag; 91 Soultion(int _flag) : flag(_flag) { 92 cout << "Instance of Solution with flag " << flag << " has been constructed." << endl; 93 } 94 ~Soultion() { 95 cout << "Instance of Solution with flag " << flag << " has been destroyed." << endl; 96 } 97 98 struct TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> in){ 99 //TreeNode* head = new TreeNode(pre[0]); 100 /* 注意上面這一條語句不能放在函數體的第一句。 101 * 由於此時還不能肯定pre這個vector是否爲空,若是不爲空,那麼訪問pre[0]就是非法內存訪問。此時若debug,會有 102 * Program received signal SIGSEGV, Segmentation fault.的錯誤信息。其中,SIG是signal的縮寫,SEGV是segmentation violation(段違例)的縮寫。詳見維基百科 103 * 因此,此條語句放在return NULL;語句以後爲宜,由於這個時候已經肯定vector in不爲空。可是好像沒判斷pre是否爲空呢,這就能夠了?? 104 */ 105 vector<int> left_pre, left_in, right_pre, right_in; 106 107 int pos = 0; 108 int length = in.size(); 109 110 if (length == 0) 111 return NULL; 112 113 TreeNode* head = new TreeNode(pre[0]); 114 115 for (int i = 0; i < length; ++i) { 116 if (pre[0] == in[i]) { 117 pos = i; 118 break; 119 } 120 } 121 122 for (int i = 1; i < pos + 1; ++i) { 123 left_pre.push_back(pre[i]); 124 left_in.push_back(in[i-1]); 125 } 126 127 for (int i = pos + 1; i < length; ++i) { 128 right_pre.push_back(pre[i]); 129 right_in.push_back(in[i]); 130 } 131 132 head->left = reConstructBinaryTree(left_pre, left_in); 133 head->right = reConstructBinaryTree(right_pre, right_in); 134 135 return head; 136 137 } 138 // 在solution類中也定義箇中序遍歷,做調試用 139 void inOrder(struct TreeNode* &root) { 140 if (root == NULL) 141 return; 142 inOrder(root->left); 143 cout << root->val << ' '; 144 inOrder(root->right); 145 } 146 147 }; 148 149 int main() { 150 //struct TreeNode myTree(1); 151 //struct TreeNode* tree = &myTree; 152 struct TreeNode* tree = nullptr; 153 154 BiTree myBiTree(1); 155 cout << "Please input the head tree node: "; 156 myBiTree.createBiTree(tree); 157 158 cout << "Preorder of binary tree." << endl; 159 myBiTree.preOrder(tree); 160 161 cout << endl << "Inorder of binary tree." << endl; 162 myBiTree.inOrder(tree); 163 cout << endl; 164 165 Soultion mySolution(2); 166 cout << endl 167 << "refactor the binary tree based on preorder and inorder\n" 168 << "and print it with preorder" 169 << endl; 170 myBiTree.inOrder(mySolution.reConstructBinaryTree(myBiTree.preTrav, myBiTree.inTrav)); 171 cout << endl; 172 173 delete tree; 174 //struct TreeNode* refactor = mySolution.reConstructBinaryTree(myBiTree.preTrav, myBiTree.inTrav); 175 //myBiTree.inOrder(refactor); 176 //mySolution.inOrder(refactor); 177 178 //inOrder(refactor); 179 return 0; 180 }
下面咱們看看程序結果。
從結果中咱們能夠看見,重構後二叉樹的中序遍歷和咱們輸入的二叉樹的中序遍歷相同。咱們還能夠輸出重構後的先序遍歷,來肯定咱們的重構是正確的。二叉樹的重構暫且先探討這。二叉樹還有其它不少特性須要沒咱們去研究,擇日再說。