1. 程式人生 > 實用技巧 >樹論——已經兩個序列求解二叉樹遍歷的其他序列

樹論——已經兩個序列求解二叉樹遍歷的其他序列

問題模型 - 已經兩個序列求解二叉樹遍歷的其他序列

問題描述: 此類問題通常會告訴我們兩種序列,其中一種一定包含中序序列

我們需要根據這兩種序列求出另外的序列。

此類問題根據需求又可以分以下兩種情況

  • 根據兩種序列求層序序列 / 要求你進行建立完整的二叉樹結構

  • 根據兩種序列求前序 / 後序序列

對於以上類問題來說,第一種問題要求我們必須進行建立一棵完整的二叉樹結構,而第二種則不需要建立完整的樹形結構,只需要進行遍歷即可。


現假設我們有一個樹其結構如圖所示。

根據樹的結構我們可以知道

先序遍歷序列為 : 1 2 4 8 5 9 3 6 10 7

中序遍歷序列為 : 8 4 2 9 5 1 6 10 3 7

後序遍歷序列為 : 8 4 9 5 2 10 6 7 3 1

在這個給定的樹形結構的前提下我們來嘗試討論如何解決以上提出的問題。

型別一 : 已知先序序列和中序序列

首先我們已知了先序序列和中序序列,接下來我們只需要遵循一個原則來劃分這個樹的左右子樹即可。

我們知道先序遍歷的第一個元素一定是整棵樹的根節點 , 其次我們可以到中序遍歷中找到這個根節點所在位置,這個位置的左邊的所有元素是以當前點為根的左子樹,這個位置的右邊的所有元素是以當前結點為根的右子樹

如圖所示:

既然我們已經確定了第一個根節點為先序序列的第一個結點,那麼我們首先可以得到整棵樹的根節點和該節點的左右子樹都有哪些元素但是此時並不知道左右子樹的結構

然後根據此規則我們遞迴的去還原左右子樹的結構:

首先是左子樹:

此時既然我們既然是子樹,我們就不能再用之前的完整的序列進行劃分子樹,我們需要對之前的序列進行拆分,拆分出來左子樹的部分

如何進行拆分呢?

1.記錄下來此時根節點左邊的元素個數,此時根節點 1 左邊有 5 個元素 那麼在先序序列中從 1 開始選取長度為 5 的一段序列作為左子樹的先序序列 : 2 4 8 5 9

2.由於中序序列的兩邊就是樹形結構的左子樹和右子樹,所以我們只需要將根節點 1 左邊的部分照搬下來即可。此時的中序序列為: 8 4 2 9 5 1

然後根據這兩組序列我們可以在根據之前所定義好的規則進行新的左右子樹的劃分。

其次來拆分右子樹:

1.記錄下來此時根節點左邊的元素個數,此時根節點 1 左邊有 5 個元素 那麼在先序序列中從 1 開始選取長度為 n - root + 5 的一段序列作為左子樹的先序序列 : 3 6 7 10

2.由於中序序列的兩邊就是樹形結構的左子樹和右子樹,所以我們只需要將根節點 1 右邊的部分照搬下來即可。此時的中序序列為: 6 10 3 7

然後根據這兩組序列我們可以在根據之前所定義好的規則進行新的左右子樹的劃分。

根據以上規則我們最終可以恢復整顆二叉樹結構。

下面我們考慮如何用程式碼實現以上的思想

我們定義一組變數x , y 來圈定當前子樹的先序序列的範圍 , 另一組變數 p , q 來圈定當前子樹的中序序列的範圍

那麼每一次 我們的 x 變數所記錄的位置一定是這個子樹的根,利用變數 k 來記錄當前根結點 x 在中序序列中的位置

那麼此時

左子樹的劃分一定是: [x + 1 , x + k - p] , [p , k - 1]

右子樹的劃分一定是: [x + k - p + 1, y] , [k + 1 ,q]

程式碼實現

#include <bits/stdc++.h>

using namespace std;

const int N = 105;

int in[N], pre[N], n;

int find(int x)
{
	for(int i = 0; i < n; i ++)
		if(in[i] == x)return i;
	return -1;
}
void getpost(int x, int y, int p, int q)
{
	if(x > y || p > q)return;
	int k = find(pre[x]);
	getpost(x + 1, x + k - p, p, k - 1);
	getpost(x + k - p + 1, y, k + 1, q);
	cout << pre[x] << " ";
}

int main()
{
	cin >> n;
	for(int i = 0; i < n; i ++)cin >> pre[i];
	for(int i = 0; i < n; i ++)cin >> in[i];
	getpost(0, n - 1, 0, n - 1);
	return 0;
}
型別二 : 已知後序序列和中序序列

原理和以上方法類似,但是需要明確的是後序遍歷的最後一個結點為根

後序遍歷的左右子樹劃分(具體i , q 請結合程式碼注意分析與上一種略有不同)

左子樹的劃分一定是: [x , y - k - 1] , [p , i - 1]

右子樹的劃分一定是: [y - k, y - 1] , [i + 1 ,q]

實現程式碼

#include <bits/stdc++.h>

using namespace std;

const int N = 105;

int in[N], pre[N], post[N], n;

int find(int x)
{
	for(int i = 0; i < n; i ++)
		if(in[i] == x) return i;
	return -1;
}
void getpre(int x, int y, int p, int q)
{
	if(x > y || p > q) return;
	int i = find(post[y]), k = q - i;
	cout << post[y] << " ";
	getpre(x, y - k - 1, p, i - 1);
	getpre(y - k, y - 1, i + 1, q);
}
int main()
{
	cin >> n;
	for(int i = 0; i < n; i ++)cin >> post[i];
	for(int i = 0; i < n; i ++)cin >> in[i];
	getpre(0, n - 1, 0, n - 1);
	return 0;
}

建樹 -- 待更新 先佔坑