【数据结构】堆的应用——堆排序、top-k问题

一、堆排序

堆排序 即 利用大堆、小堆的性质 ,从而排出 升序或者降序。

1.思路

总共分为两个步骤:

1.1建堆

排升序,建大堆

排降序,建小堆

1.2利用堆删除的思想进行排序

以排升序 ,建大堆 为例 

step1.交换首尾

(此时栈顶最大元素被交换到尾部 并且尾部固定不再变动)

step2.新的栈顶元素向下调整 从而 满足大堆的特性

(完成调整,此时的栈顶是次大的元素)

重复上述步骤,过程中尾部一直向头部靠近 直到首尾相同

2.代码实现

#include<stdio.h>
void Swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}

//建大堆 向上调整
void AdjustUp(int*a ,int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
//向下调整
void AdjustDown(int* a, int size, int parent)
{
	int child = 2 * parent + 1;
	while (child < size)
	{
		//假设法 找出较大孩子 默认左孩子更大 如果不是 更新一下
		if (child+1<size &&a[child] < a[child + 1])
		{
			child++;
		}
		//如果 父结点 小于 较大孩子 交换、
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			//更新 父节点 孩子节点下标
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
//对数组进行堆排序
void HeapSort(int* a, int n)
{
	//手动建堆 
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, i);
	}
	//首尾元素交换
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		//新的栈顶元素 不满足大堆特性 向下调整
		AdjustDown(a, end, 0);
		end--;
	}
}
int main()
{
	int a[] = { 4,7,9,10,6,3,5,0,1 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

3.运行测试结果

 

二、top-k问题

问题描述

TOP-K问题:在数据量比较大情况下,求前k个最大值或最小值

基本思路

1. 用数据集合中前K个元素来建堆

前k个最大的元素,则建小堆

前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

关键点理解:

如果找最大元素 为什么需要建的是小堆?

答:如果找最大元素 使用大堆 假设此时最大的元素已经被替换进堆顶 ,那么接下来 次大的元素 无法进入堆 !所以用大堆的话 只能找出最大的一个元素 后面次大的k-1个元素 无法找出!

再来理清 用小堆 找前k个最大元素 的思路:

比堆顶元素大 则替换堆顶元素 然后向下调整堆顶元素 从而满足小堆特性   这样大的数就会沉底 遍历完所有元素  次大的元素、最大的元素 会在k个元素的小堆里沉底,过程中小堆的元素是不断被替换的。

代码实现

注意:这里我们用CreateNData()函数 造10000个 小于1000000的随机数 ,并且存储在data.txt。生成随机数后 为了 测试PrintTopK()函数的功能 ,我们将data.txt 中的 随机五个数 手动改为 大于1000000的数 ,看能否把这五个改动的数找出来,以此完成测试!

#include<stdio.h>
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */
void CreateNData()
{
	int n = 10000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}
void swap(int* pa, int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
//调整为小堆
void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void AdjustDown(int* a, int size, int parent)
{
	int child = 2 * parent + 1;
	while (child < size)
	{
		//假设法 找出较小孩子 默认左孩子更小 如果不是 更新一下
		if (child + 1 < size && a[child] > a[child + 1])
		{
			child++;
		}
		//如果 父结点 大于 较小孩子 交换、
		if (a[child] < a[parent])
		{
			swap(&a[child], &a[parent]);
			//更新 父节点 孩子节点下标
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
void PrintTopK(int k)
{
	//造一个存有10000个数据的文件
	//CreateNData();
	//打开文件
	FILE* fout = fopen("data.txt", "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}
	//开辟k个空间
	int* a = (int*)malloc(sizeof(int) * k);
	//依次从文件中读取数据到k个空间里
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &a[i]);
	}
	//建一个包含k个数的小堆
	for (int i = 0; i < k; i++)
	{
		AdjustUp(a, i);
	}
	//依次读取文件里的数据 直到读取失败返回EOF
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		//如果比根节点 大  则替换
		if (x > a[0])
		{
			a[0] = x;
		}
		//向下调整 根节点元素 使其满足小堆特性
		AdjustDown(a, k, 0);
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
}
int main()
{
	
	PrintTopK(5);
	return 0;
}

测试运行结果

 

评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值