【天梯pta】堆中的路徑 (25 分)
求堆中的路徑,其實是可以看作是一道最小堆(小頂堆)的板子題。主要問題是解決建立最小堆,問題就可以迎刃而解了。
因此我們將此問題分解成兩個子問題,第一步是建立最小堆,第二步是輸出路徑。其中建立最小堆更為關鍵,輸出路徑則容易得多。
一、建立最小堆
【關鍵詞】最小堆,線上處理,一維陣列,遞迴,完全二叉樹
【知識點Tips】
1.線上處理:每當輸入一個數字,就實時進行操作。
2.完全二叉樹:一層一層按照順序填入的二叉樹。當不能全部填滿的時候,最後一層靠左填滿。(表達能力有限,類似下圖的既為完全二叉樹)
3.最小堆:父結點的數字比它的左右子結點的數字小即可。至於左右子結點的大小與它們在左在右並無關係。
【最小堆建立的背景】給定數字個數與一列數字,要求輸出按照最小堆方法的陣列。
【例】
5
46 23 26 24 10
【思考步驟】
第一步:獲取46後,因為是空堆,所以我們直接將其插入陣列第一位。a[1]=46。
第二步:獲取23後,我們先將其直接接在陣列a的最後一個元素46後面。a[2]=23,陣列a暫時為46 23。我們要保證,每次新插入一個數字後的陣列是一個標準的最小堆陣列,因此要實時給出操作。所以我們要比較編號為2與其編號為2/2=1的父結點的大小。若比父結點小,則交換兩者位置,從而保證小的結點總在上面。
第三步:獲取26後,我們先將其直接接在陣列a的最後一個元素46後面。a[3]=26,陣列a暫時為23 46 26。按照上面所講述原則,我們比較編號為3與其編號為3/2=1(至於為什麼3/2=1則參照上文Tips3)的父結點的大小。26>23,則我們不改變陣列位置。因為23已經是陣列第一位了,則陣列a為23 46 26。
第四步:獲取24後,直接接在a陣列後面。a[4]=24,陣列a暫時為23 46 26 24。比較編號為4的24與其編號為4/2=2的父結點46的大小。24<46,則交換24與46的位置,陣列a暫時為23 24 26 46。
第五步:獲取10後直接接在a陣列後面,則陣列a暫時為23 24 26 46 10。比較編號為5的10與其編號為5/2=2的父結點24的大小。10<24,則交換10與24的位置,陣列a暫時為23 10 26 46 24。比較編號為2的10與其編號為2/2=1的父結點23的大小。10<23,則交換10與23的位置。因為此時已經比較到陣列的首位,所以陣列a結果為10 23 26 46 24。
【關鍵提要】
可見,我們對於每一個新加入的數字進行的操作都是相同的,即:先接在陣列後面,再把新數字像“冒泡”一樣跟其父結點比較,若比父結點小則交換,直至陣列首位。以及子結點跟父結點有關係,則我們很容易想到用遞迴來解決此問題。
二、輸出路徑
既然我們已經成功建立了最小堆的陣列,以及清楚了子結點與父結點的關係後,輸出路徑已經不是難事了。輸出即:子結點,每次的子結點的編號/2的結點值,直至編號到1.
在這裡我用了一個小技巧。添加了一個bool值來將建立最小堆和輸出路徑在一個函式裡解決。建立的標誌是0,要輸出的標誌是1.要記得每次輸出要放在函式的開頭,要不然在結點編號為1的時候直接return則沒機會輸出了。
三、AC程式碼
#include <bits/stdc++.h>
using namespace std;
int n, m;
int a[100001];
bool flag;
void func(int index,bool f) {
if (flag) {
cout << a[index];
if (index != 1) {
cout << " ";
}
}
if (index == 1) {
return;
}
if (a[index / 2] > a[index]) {
int t = a[index];
a[index] = a[index / 2];
a[index / 2] = t;
}
func(index / 2, flag);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
func(i, flag);
}
int t;
flag = 1;
while (m--) {
cin >> t;
func(t, flag);
cout << endl;
}
return 0;
}