最近筆面老碰上這類題,老忘,因此寫個備忘。
二叉樹的三種遍歷常用於恢復:先序,中序,後序。對於先+中,後+中這兩種組合,對任意二叉樹的恢復都有唯一解,但對先+後的情況則不是,這種情況下要滿足要求:對所有非葉節點,其兩個子節點都存在,也即,一個節點要麼是葉節點,要麼有兩個節點。
典型的恢復方法是遞歸建構節點的左,右子樹。一個一個看:
假設二叉樹原型如下,爲了方便,節點的值剛好等於層次遍歷索引
先序:
1,2,4,5,10,11,3,6,7,
中序:
4,2,10,5,11,1,6,3,7,
後序:
4,10,11,5,2,6,7,3,1,
先+中恢復:
對先序,注意第一個節點是根節點,其遍歷順序是中左右,因此,若把第一個節點作爲基準,則其左右子樹連續存儲在該節點之後,不過,目前我們還不知道到底左右子樹的分界點在哪,因此需要結合中序來確定其分界點。先序的第一個節點在中序的第5個位置(從0開始算),而我們知道中序的存儲順序是:先中後,因此,對中序列,該節點的左邊是其左子樹,右邊是右子樹。因此,根據節點在中序中的位置可以確定其左子樹的元素個數,對應到先序即可得到該節點的左,右子樹分別在先,中序的位置。根據上述信息就可遞歸的恢復根節點的左,右子樹,進而得到整個樹。
後+中:
與上述類似,只不對後序,根節點在末尾,其它的可依此類推。
先+後:
這種情況下恢復的二叉樹不一定有唯一解,考慮如下的樹:
1
/
2
先序:1,2
後序:2,1
與
1
\
2
先序: 1,2
後序:2,1
可看到不同的樹,先,後序遍歷是一樣的。
其唯一解的條件文章開頭已經說過了。不過沒有經過嚴格考究!
這裏只針對有唯一解的情況做討論,還考慮上圖的例子,結合實例描述如下:
先序:
1,2,4,5,10,11,3,6,7,
後序:
4,10,11,5,2,6,7,3,1,
對先序,第一個節點與後序最後節點對應,然後再看先序的第二個節點(值爲2),我們知道,如果先序存在子樹,則必同時存在左右子樹,因此可斷定,第二個節點正是根節點的左子樹節點,可先恢復成:
1
/
2
而它又把後序分成兩個部分,一左一右(右邊不包括最末的根節點):左(4,10,11,5,2),右(6,7,3),說到這裏,再結合上圖,一切都明白了:“左”正是根的左子樹,“右”正是根的右子樹。於是,我們又得到了根節點的左右子樹,遞歸,搞定。
上代碼:
- typedef struct tagTREE
- {
- int val; //值
- tagTREE* left; //左節點
- tagTREE* right; //右節點
- tagTREE* parent;//父節點(有時候爲了方便,不一定都定義它)
- bool isVisited; //標記該結點是否已訪問,給某些特殊操作準備
- }TREE;
- template<class T>
- void show(T* vec,int N)
- {
- for (int i=0;i<N;++i)
- {
- cout<<vec[i]<<",";
- }
- cout<<endl;
- }
- //按數組裏指定的層次數值,生成任意二叉樹結構,數組裏缺失的數值表示對應該層次的該節點沒有
- void CreateTree(TREE** node, int a[], int N )
- {
- //預處理,記錄節點在全部節點中的索引,而不是其真正位置號
- cout<<endl;
- int* arr = new int[a[N-1]];
- for (int i=0;i<a[N-1];++i)
- {
- arr[ i ] = 0;
- }
- int k=0;
- for (int i=0;i<N;++i)
- {
- arr[ a[i]-1 ] = i;
- }
- TREE* arrTree = new TREE[N];
- //root
- arrTree[0].parent = NULL;
- for (int i=1;i<=N;++i)
- {
- arrTree[i-1].val = a[i-1];
- arrTree[i-1].isVisited = false;
- int parentIdx = int(a[i-1] / 2);
- if( parentIdx == 0 )
- arrTree[i-1].parent = NULL;
- else
- arrTree[i-1].parent = &arrTree[ arr[ parentIdx-1 ] ];
- int leftIdx = int(a[i-1] * 2 );
- int rightIdx = leftIdx + 1;
- if ( leftIdx > a[N-1] || arr[leftIdx-1] == 0 )
- {
- arrTree[i-1].left = NULL;
- }
- else
- {
- arrTree[i-1].left = &arrTree[ arr[ leftIdx-1 ] ];
- }
- if ( rightIdx > a[N-1] || arr[rightIdx-1] == 0 )
- {
- arrTree[i-1].right = NULL;
- }
- else
- {
- arrTree[i-1].right = &arrTree[ arr[ rightIdx-1 ] ];
- }
- }
- *node = arrTree;
- //test
- for (int i=1;i<=N;++i)
- {
- cout<<"val="<<arrTree[i-1].val;
- cout<<" left=";
- if (arrTree[i-1].left)
- {
- cout<<arrTree[i-1].left->val;
- }
- cout<<" right=";
- if (arrTree[i-1].right)
- {
- cout<<arrTree[i-1].right->val;
- }
- cout<<" parent=";
- if (arrTree[i-1].parent)
- {
- cout<<arrTree[i-1].parent->val;
- }
- cout<<endl;
- }
- }
- //先序遍歷,第一個參數是二叉樹的頭結點,第二個參數是用於記錄遍歷序列的數組,下同
- void PreOrder(TREE* pTree,int** out)
- {
- if( !pTree )
- return;
- *(*out)++ = pTree->val;
- if ( pTree->left )
- PreOrder(pTree->left,out);
- if ( pTree->right )
- PreOrder(pTree->right,out);
- }
- void InOrder(TREE* pTree,int** out)
- {
- if( !pTree )
- return;
- if ( pTree->left )
- InOrder(pTree->left,out);
- *(*out)++ = pTree->val;
- if ( pTree->right )
- InOrder(pTree->right,out);
- }
- void PostOrder(TREE* pTree,int** out)
- {
- if( !pTree )
- return;
- if ( pTree->left )
- PostOrder(pTree->left,out);
- if ( pTree->right )
- PostOrder(pTree->right,out);
- *(*out)++ = pTree->val;
- }
- //先+中恢復二叉樹
- TREE* pre_in(const int* pre,const int* in, int n)
- {
- if( n == 0 )
- return NULL;
- TREE* head = new TREE();
- //先序的第一個節點是根節點
- head->val = pre[0];
- head->parent = NULL;
- //由根節點在中序的位置,基左邊是根的左子樹,右邊是右子樹
- int i=0;
- for (;i<n;++i)
- {
- if( pre[0] == in[i] )
- break;
- }
- //遞歸的構建節點的左右子樹,這裏需要確定左/右子樹對應的先序/中序段
- TREE* left = pre_in( pre+1,in,i );
- TREE* right = pre_in( pre+i+1,in+i+1,n-i-1 );
- head->left = left;
- head->right = right;
- //返回頭節點
- return head;
- }
- //後+中恢復二叉樹
- TREE* post_in(const int* post,const int* in, int n)
- {
- if (n==0)
- return NULL;
- TREE* head = new TREE();
- //後序與先序類似,最後一節點是根節點
- head->val = post[n-1];
- head->parent = NULL;
- //確定根在中序中的位置
- int i=0;
- for (;i<n;++i)
- {
- if( post[n-1] == in[i] )
- break;
- }
- //遞歸的構建左右子樹,這裏需要確定左/右子樹對應的後序/中序段
- TREE* left = post_in(post,in,i);
- TREE* right = post_in(post+i,in+i+1,n-i-1);
- head->left = left;
- head->right = right;
- return head;
- }
- //先+後恢復二叉樹
- TREE* pre_post(const int* pre,const int* post, int n)
- {
- if (n==0)
- return NULL;
- TREE* head = new TREE();
- head->val = pre[0];
- head->parent = NULL;
- //對有唯一解的二叉樹,若將先序的第一個節點當做父節點,則第二個節點pre[1]必是左子樹節點
- //pre[1]在後序中的位置確定了pre[0](post[n-1])的左右子樹範圍
- if( n < 2 )
- return head;
- int i = 0;
- for (;i<n-1;++i)
- {
- if( pre[1] == post[i] )
- break;
- }
- TREE* left = pre_post(pre+1,post,i+1);
- TREE* right = pre_post(pre+i+2,post+i+1,n-i-2);
- head->left = left;
- head->right = right;
- return head;
- }
- int _tmain(int argc,char* argv[])
- {
- TREE* pTree = new TREE();
- //任意二叉樹,不能用於測試pre_post函數
- //const int N = 7;
- //int a[N] = {1,2,3,4,5,6,11};
- //先+後有唯一解的二叉樹,用於測試pre_post函數
- const int N = 9;
- int a[N] = {1,2,3,4,5,6,7,10,11};
- CreateTree(&pTree,a,N);
- int pre[N];
- int in[N];
- int post[N];
- int* pre_ptr = (int*)pre;
- int* in_ptr = (int*)in;
- int* post_ptr = (int*)post;
- PreOrder(pTree,&pre_ptr);
- InOrder(pTree,&in_ptr);
- PostOrder(pTree,&post_ptr);
- cout<<"pre="<<endl;
- show(pre,N);
- cout<<"in="<<endl;
- show(in,N);
- cout<<"post="<<endl;
- show(post,N);
- //------------- pre_in_post
- TREE* head = pre_in(pre,in,N);
- int pre_in_post[N];
- int* pre_in_post_ptr = (int*)pre_in_post;
- PostOrder(head,&pre_in_post_ptr);
- cout<<endl<<"pre_in_post:"<<endl;
- show(pre_in_post,N);
- //------------- post_in_pre
- head = post_in(post,in,N);
- int post_in_pre[N];
- int* post_in_pre_ptr = (int*)post_in_pre;
- PreOrder(head,&post_in_pre_ptr);
- cout<<endl<<"post_in_pre:"<<endl;
- show(post_in_pre,N);
- //------------- pre_post_in
- //注意:這種情況況不是任意二叉樹都有唯一解,只對這種二叉樹纔有唯一解:每個非葉節點都有個孩子
- head = pre_post(pre,post,N);
- int pre_post_in[N];
- int* pre_post_in_ptr = (int*)pre_post_in;
- InOrder(head,&pre_post_in_ptr);
- cout<<endl<<"pre_post_in:"<<endl;
- show(pre_post_in,N);
- return 0;
- }