堆排序(TOP K)

堆的定义,它是一个完全二叉树,存储结构为数组。给出一个数组之后,可以认为这个初始堆是按照结点序号从小到大存放的。

满足对的定义要求,所有父结点的键值都要不小于(或者不大于)子结点的键值,称为大顶堆(小顶堆)。

对堆的操作有插入、删除,这些操作都要对堆进行一定的调整以保证堆的定义。

堆的插入直接放在堆最后一个位置,同时从此位置开始向上调整,向上调整的代码:

void MinHeapFixup(int a[],int i)
{
	int j=(i-1)/2;
	while(j>=0)//结束条件是还存在父结点,或子结点大于父结点
	{
		if(a[j]>a[i])//子结点跟父结点比较
		{
			swap(a[j],a[i]);//子结点较小则交换
			i=j;
			j=(i-1)/2;
		}
		else break;
	}
}

i为结点序号(从0开始的)。

堆的删除都是从堆顶删除的,再对剩下的数进行调整,使依然为一个小顶堆。实际操作的时候将堆顶元素与最后一个元素交换,然后对新换的堆顶进行向下调整。

在小顶堆中对第i个位置的数进行向下调整的代码:

void MinHeapFixdown(int a[],int j,int n)
{
	//堆中删除第i位置处的元素,需要在以这个点为根节点树中向下调整
	int temp=a[j];//记录a[j]的值
//	int j=i;
	while(2*j+1<=n)//有子节点
	{
		if(2*j+2>n)//仅有左孩子没有右孩子
		{
			if(a[2*j+1]<a[j])
			{
				swap(a[2*j+1],a[j]);
				j=2*j+1;//指向左孩子
			}
			else break;
		}
		else 
			if(a[2*j+1]<=a[2*j+2])//如果有两个孩子且左孩子小于右孩子
		{
			if(a[2*j+1]<a[j])
			{
				swap(a[2*j+1],a[j]);
				j=2*j+1;
			}
			else break;
		}
			else//有两个孩子,右孩子小于左孩子
			{
				if(a[2*j+2]<a[j])
				{
					swap(a[2*j+2],a[j]);
					j=2*j+2;
				}
				else break;
			}
	}
	a[j]=temp;
}

这个里面只用了参数里给出的一个变量j和一个temp用于存a[j]的值,后面的判断语句比较多,代码较长。

下面这个多用了一个变量,使判断句少了很多:

void MinHeapFixdown(int a[],int i,int n)
{
	int temp=a[i];
	int j=2*i+1;//比上一个多用了一个变量,后面的比较语句减少
	while(j<n)
	{
		if(j+1<n&&a[j+1]<a[j])//well down!
			j=j+1;

		if(a[j]>a[i])//if和else的顺序,good
			break;
		swap(a[j],a[i]);
		i=j;
		j=2*i+1;
	}
	a[i]=temp;
}


怎么通过堆的基本操作构建出一个小顶堆呢?因为小顶堆只要求父结点的键值小于子结点的键值,所以需要对所有的父结点进行一次向下调整。而又因为堆可以看做是一个完全二叉树,由完全二叉树的定义可知,其中度为2和1的结点为父结点,且度为0的点(叶子结点)个数是度为2的结点数+1,即n0=n2+1,而n=n0+n1+n2;所以可以得出最后一个度不为0的结点序号为n/2,因此调整只需从n/2处开始。

构建小顶堆的代码:

void MinHeapDelete(int a[],int n)//删除a[0]处的值,放在最后的位置
{
	swap(a[0],a[n]);
	MinHeapFixdown(a,0,n);
}

void MakeMinHeap(int a[],int n)//构造小顶堆
{
	for(int i=(n-1)/2;i>=0;i--)
		MinHeapFixdown(a,i,n);
}

堆排序就是根据大小顶堆得特性来按序输出的,首先根据输出构造出一个这里是小顶堆,然后对堆顶元素进行删除,放在最后,删完之后数组里的数就已经是按序排列的了,由代码可以看出,小顶堆对应的输出是按降序排列的:

int main()
{
	int a[7];
	for(int i=0;i<7;i++)
		scanf("%d",&a[i]);
	MinHeapFixup(a,6);
	/*MakeMinHeap(a,7);//构建小顶堆
	for(i=6;i>0;i--)
	MinHeapDelete(a,i);//依次删除堆顶元素放在最后,注意数组长度i是递减的*/
	for(i=0;i<7;i++)
		printf("%d",a[i]);
	return 0;
}

大根堆排序结束后,按序读数组应该是一个升序序列,小根堆对应降序序列。

在解决TOP K(max)问题时,可以通过大根堆实现,每次调整大根堆得到一个当前最大元素,进行K次得到TOP K,空间复杂度为0(N),时间复杂度为0(K*logN)。但这样在N较大,或者数据为流式数据时,总是要先将数据缓存完才能开始计算,而如果使用小根堆,可以只维护一个K大小的堆,作为当前的TOP K,然后依次读后面的数据,与当前的最小值(堆顶)进行比较,如果大于堆顶,则替代堆顶,并调整小根堆,得到新的最小值,再与后面的数据相比较,直到结束。空间复杂度0(K),时间复杂度0((N-K)*logK)。

小根堆实现TOP K(max):

int heap_TOPK()
{
	//小顶堆找 TOP K大
	int a[max+1],i=0;
	int K=3;
	for(i=1;i<=max;i++)
		scanf("%d",&a[i]);

	MinHeapCreate(a,K);//堆大小为K
	for(i=4;i<=max;i++)
	{
		if(a[i]>a[1])
			a[1]=a[i];
		MinHeapFixDown(a,1,K);//依次比较和调整堆
	}

	for(i=1;i<=K;i++)
		printf("%d ",a[i]);

	system("pause");
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值