将一系列给定数字插入一个初始为空的小顶堆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
思路:
思路一:下滤法,从最后一个有叶节点的节点入手,时间复杂度为O(n)。
1.一次性把所有的输入数据存入数组,此时根据数组下标所构建的完全二叉树并不是小顶堆。
2.从最后一个有叶节点的节点开始执行下滤操作,即以该节点为根节点;
3.在左、右孩子节点中选出最小的那个,作为子节点;
4.比较根节点和子节点的值:
(1)若根节点的值不大于子节点的值,说明根节点并没有破坏堆的性质,不用调整;
(2)若根节点的值大于子节点,则根节点与子节点互换;
5.互换之后又可能破坏了子节点的堆性质,于是要对子树进行上述调整。让子节点成为新的根节点,从步骤3开始重复。
这样的一次调整我们称之为一次下滤,可以写成一个void PercDown( MinHeap H, int p )函数,将以该节点为根的子堆调整为最小堆。
我们用这个PercDown函数,循环的对一个完全二叉树进行调整。首先从最后一个有叶节点的节点开始,接着是倒数第二个,倒数第三个,一直到根节点。即下渗的节点,即数组下标每次循环减一。
#include <stdlib.h>
#include <stdio.h>
typedef struct{
int * Data; //存储元素的数组
int Size; //堆中个数
}HNode,*Heap;
typedef Heap MinHeap;
#define MINDATA -10001
MinHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最小堆 */
MinHeap H=(MinHeap)malloc(sizeof(HNode));
H->Data=(int*)malloc(sizeof(int)*(MaxSize+1));
H->Size=MaxSize;
H->Data[0]=MINDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/
return H;
}
/*----------- 建造最小堆 -----------*/
void PercDown( MinHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最小堆 */
int Parent,Child;
int temp;
for(Parent=p;Parent*2<H->Size;Parent=Child) //当节点没有孩子时停止
{
Child=Parent*2;
if((H->Data[Child]>H->Data[Child+1]&&(Child!=H->Size)))//找出左右孩子中最小的那个
{
Child+=1;
}
if(H->Data[Child]<H->Data[Parent])//当子节点小于父节点时,交换
{
temp=H->Data[Parent];
H->Data[Parent]=H->Data[Child];
H->Data[Child]=temp;
}
}
}
void BuildHeap( MinHeap H )
{ /* 调整H->Data[]中的元素,使满足最小堆的有序性 */
/* 这里假设所有H->Size个元素已经存在H->Data[]中 */
int p=H->Size/2;
/* 从最后一个结点的父节点开始,到根结点1 */
for(; p>0; p-- )
{
PercDown( H, p );
}
}
int main()
{
int N,M;
scanf("%d",&N);
scanf("%d",&M);
int a[M];
MinHeap H=CreateHeap(N);
for(int i=1;i<=N;i++)
{
scanf("%d",&H->Data[i]);
}
for(int i=0;i<M;i++)
{
scanf("%d",&a[i]);
}
BuildHeap(H);
for(int i=0;i<M;i++)
{
while(a[i]>0)
{
printf("%d",H->Data[a[i]]);
a[i]/=2;
if(a[i]>0)
{
printf(" ");
}
}
printf("\n");
}
}
但是这个方法不能全部,尽管测试结果符合小顶堆的定义——一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于其左孩子和右孩子节点的值。
这是因为小顶堆只对父子节点的关系做了定义,兄弟节点的关系没有要求的原因。
因此这个下渗法得到的结果,和标准答案的所采用的插入法所得到的结果有一定的出入,就是因为可能兄弟节点上的数据可能是相反的导致的。
方法二:
插入法,读入一个数据就对比一次,执行一次函数。时间复杂度为O(nlogn) 。
1.读入一个数据,存入数组,此时根据数组下标所构建的完全二叉树并不是小顶堆。
2.从最后一个有叶节点的节点(即新插入的节点的父节点)开始执行调整,即以该节点为根节点;
3.在左、右孩子节点中选出最小的那个,作为子节点;
4.比较根节点和子节点的值:
(1)若根节点的值不大于子节点的值,说明根节点并没有破坏堆的性质,不用调整;
(2)若根节点的值大于子节点,则根节点与子节点互换;
5.互换之后又可能破坏了子节点的堆性质,于是要对子树进行上述调整。让子节点成为新的根节点,从步骤3开始重复。
插入法的核心和下渗法是一样的,都是从最后一个有叶节点的节点开始,判断节点和孩子节点的大小关系,大了就交换,小了就不不换。然后接着是倒数第二个,倒数第三个,一直到根节点。
插入法和下渗法的区别在于,插入法是吃火锅,一盘黄喉下锅里,熟了捞上来吃掉再下第二盘;下渗法是出去吃炒菜,而且是一次把所有菜上齐。下渗法的复杂度O(n) 明显小于插入法的复杂度O(nlogn) 。
本题标准答案是用插入法解答的,因此一遍AC。
#include <stdlib.h>
#include <stdio.h>
typedef struct{
int * Data; //存储元素的数组
int Size; //堆中个数
}HNode,*Heap;
typedef Heap MinHeap;
#define MINDATA -10001
MinHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最小堆 */
MinHeap H=(MinHeap)malloc(sizeof(HNode));
H->Data=(int*)malloc(sizeof(int)*(MaxSize+1));
H->Size=0;
H->Data[0]=MINDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/
return H;
}
void Insert(MinHeap H,int temp)
{
int Parent,Child;
H->Data[++H->Size]=temp;
for(Parent=H->Size/2;Parent>0;Parent/=2)
{
Child=Parent*2;
if((H->Data[Child]>H->Data[Child+1]&&(Child!=H->Size)))//找出左右孩子中最小的那个
{
Child+=1;
}
if(H->Data[Child]<H->Data[Parent])//当子节点小于父节点时,交换
{
temp=H->Data[Parent];
H->Data[Parent]=H->Data[Child];
H->Data[Child]=temp;
}
}
}
int main()
{
int N,M;
scanf("%d",&N);
scanf("%d",&M);
int a[M];
MinHeap H=CreateHeap(N);
int temp;
for(int i=1;i<=N;i++)
{
scanf("%d",&temp);
Insert(H,temp);
}
for(int i=0;i<M;i++)
{
scanf("%d",&a[i]);
}
for(int i=0;i<M;i++)
{
while(a[i]>0)
{
printf("%d",H->Data[a[i]]);
a[i]/=2;
if(a[i]>0)
{
printf(" ");
}
}
printf("\n");
}
return 0;
}