劍指offer——已知二叉樹的先序和中序排列,重構二叉樹

這是劍指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 }
View Code

  下面咱們看看程序結果。

從結果中咱們能夠看見,重構後二叉樹的中序遍歷和咱們輸入的二叉樹的中序遍歷相同。咱們還能夠輸出重構後的先序遍歷,來肯定咱們的重構是正確的。二叉樹的重構暫且先探討這。二叉樹還有其它不少特性須要沒咱們去研究,擇日再說。  
相關文章
相關標籤/搜索