05-树8 堆中的路径 (25分)

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

输入格式:

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

输出格式:

对输入中给出的每个下标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;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值