【天梯pta】堆中的路径 (25 分)


求堆中的路径,其实是可以看作是一道最小堆(小顶堆)的板子题。主要问题是解决建立最小堆,问题就可以迎刃而解了。

因此我们将此问题分解成两个子问题,第一步是建立最小堆,第二步是输出路径。其中建立最小堆更为关键,输出路径则容易得多。

零、题目

将一系列给定数字插入一个初始为空的小顶堆H[]。随后对任意给定的下标i,打印从H[i]到根结点的路径。

输入格式:

每组测试第1行包含2个正整数N和M(1000),分别是插入元素的个数、以及需要打印的路径条数。下一行给出区间[-10000, 10000]内的N个要被插入一个初始为空的小顶堆的整数。最后一行给出M个下标。

输出格式:

对输入中给出的每个下标i,在一行中输出从H[i]到根结点的路径上的数据。数字间以1个空格分隔,行末不得有多余空格。

输入样例:

5 3
46 23 26 24 10
5 4 3
结尾无空行

输出样例:

24 23 10
46 23 10
26 10
结尾无空行
 

一、建立最小堆

【关键词】最小堆,在线处理,一维数组,递归,完全二叉树

【知识点Tips】

  1.在线处理:每当输入一个数字,就实时进行操作。

  2.完全二叉树:一层一层按照顺序填入的二叉树。当不能全部填满的时候,最后一层靠左填满。(表达能力有限,类似下图的既为完全二叉树)

  3.最小堆:最小堆一定是一颗完全二叉树的结构。最小堆中,其父结点的数字比它的左右子结点的数字小即可。至于左右子结点的大小与它们在左在右并无关系。按照层的从上到下从左到右编号,即可获取规律:左子结点的编号/2==父结点的编号,右子结点的编号/2==父结点的编号。例:2/2=1,3/2=1(整数相除则答案取整);6/2=3,7/2=3。

      

  

【最小堆建立的背景】给定数字个数与一列数字,要求输出按照最小堆方法的数组。

【例】

输入样例

  5

  46  23  26  24  10

输出样例

  10  23  26  46  24

【思考步骤】

  第一步:获取46后,因为是空堆,所以我们直接将其插入数组第一位。a[1]=46。

  第二步:获取23后,我们先将其直接接在数组a的最后一个元素46后面。a[2]=23,数组a暂时为46  23。

  我们要保证,每次新插入一个数字后的数组是一个标准的最小堆数组,因此要实时给出操作。所以我们要比较编号为2与其编号为2/2=1的父结点的大小。若比父结点小,则交换两者位置,从而保证小的结点总在上面。于是比较46与23的大小,23<46,则交换23与46的位置。因为23已经是数组第一位了,则数组a为23  46。

  第三步:获取26后,我们先将其直接接在数组a的最后一个元素46后面。注意,在树状图中,接是按照父子关系接,因此在树状图中是连在父结点23上。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。

  请注意,我们并没有比较到数组的开头,因此依旧无法保证此数组为目前的最小堆数组。因此我们再次比较编号为2的24与其父结点2/2=1的23大小,得出不用改变,则数组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 
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; }