椋鸟数据结构笔记#7:堆排序、TopK


萌新的学习笔记,写错了恳请斧正。

堆排序

堆排序,就是利用堆的思想进行排序,是一种非常高效的排序方法。

它的基本思想是将待排序的序列构建成一个堆,这样,序列的最大(最小)值就是堆顶的根节点。将其与堆的最后一个元素交换,然后将剩余的 n-1 个元素的序列重新构建成一个堆,这样就会得到 n 个元素的次大(小)值。如此反复执行,便能得到一个有序序列。

基本步骤如下:

  1. 建堆:将序列构建成堆,如果想要从大到小的序列,就构建大根堆,反正构建小根堆。
  2. 交换:将堆顶与最后一个元素交换,然后将堆容量减一(原堆顶不再视为堆中元素)
  3. 向下调整:对顶元素向下调整,使得新的根节点为新堆的最大值。
  4. 重复上述过程直到完全排序

这个过程看起来似乎也不简单,但是计算可发现其时间复杂度仅为 O ( n log ⁡ n ) O(n\log n) O(nlogn),空间复杂度仅为 O ( 1 ) O(1) O(1)

注意:堆排序是一种不稳定的原地排序算法,在进行堆顶和堆尾元素的交换时可能会破坏相同元素的相对顺序

堆排序的实现
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//#define MIN_HEAP
#define MAX_HEAP

#ifdef MIN_HEAP
#define HEAP_COMPARE(a, b) ((a) < (b))
#endif // MIN_HEAP - 排序后由大到小
#ifdef MAX_HEAP
#define HEAP_COMPARE(a, b) ((a) > (b))
#endif // MAX_HEAP - 排序后由小到大

typedef int HPDataType;

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

void AdjustUp(HPDataType* data, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (HEAP_COMPARE(data[child], data[parent]))
		{
			Swap(&data[child], &data[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void AdjustDown(HPDataType* data, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && HEAP_COMPARE(data[child + 1], data[child]))
		{
			++child;
		}
		if (HEAP_COMPARE(data[child], data[parent]))
		{
			Swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(HPDataType* a, int n)
{
	//建堆
	for (int i = (n - 2) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	//排序
	for (int i = 0; i < n; ++i)
	{
		Swap(&a[0], &a[n - 1 - i]);
		AdjustDown(a, n - 1 - i, 0);
	}
}

//测试
int main()
{
	printf("请输入要排序的数字个数:");
	int n;
	scanf("%d", &n);
	HPDataType* a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	//文件输入
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		printf("文件打开失败\n");
		return 0;
	}
	for (int i = 0; i < n; ++i)
	{
		fscanf(fp, "%d", &a[i]);
	}
	fclose(fp);

	HeapSort(a, n);

	//文件输出
	fp = fopen("output.txt", "w");
	if (fp == NULL)
	{
		printf("文件打开失败\n");
		return 0;
	}
	for (int i = 0; i < n; ++i)
	{
		fprintf(fp, "%d\n", a[i]);
	}
	fclose(fp);

	free(a);
	return EXIT_SUCCESS;
}

生成随机数
//int main()
//{
//	srand((unsigned int)time(NULL));
//	printf("请输入要生成的随机数个数:");
//	int n;
//	scanf("%d", &n);
//	
//	FILE* fp = fopen("data.txt", "w");
//	if (fp == NULL)
//	{
//		printf("文件打开失败\n");
//		return 0;
//	}
//	for (int i = 0; i < n; ++i)
//	{
//		fprintf(fp, "%d\n", rand());
//	}
//	fclose(fp);
//
//	return EXIT_SUCCESS;
//}
TopK问题

TopK就是求数据集合中前k个最大或者最小的数据。

比较好想到的办法就是排序然后取前面K个值,但是如果数据量非常大,排序就会变的不太可能无法全部加载到内存中。

而比较好的方式就是用堆来寻找TopK,其思路如下:

  1. 用数据集合中前K个元素来建堆
    • 取前K个最大元素则建小堆
    • 取前K个最小元素则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素比较,如果不满足大小关系则替换堆顶元素并向下调整。
用堆实现TopK的时间复杂度

对于前K个元素构建堆的过程,时间复杂度是 O ( K ) O(K) O(K);对于剩余的N-K个元素,每个元素都需要和堆顶元素进行比较,并可能需要插入堆中,这个过程的时间复杂度是 O ( log ⁡ K ) O(\log K) O(logK)(因为每次插入或删除堆顶元素都需要进行堆调整)。所以,对于所有的N-K个元素,时间复杂度是 O ( ( N − K ) log ⁡ K ) O((N-K) \log K) O((NK)logK)

综合起来,使用堆实现TopK的总时间复杂度是 O ( K + ( N − K ) log ⁡ K ) = O ( N log ⁡ K ) O(K + (N-K) \log K) = O(N \log K) O(K+(NK)logK)=O(NlogK)

TopK问题的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define MIN_HEAP
//#define MAX_HEAP

#ifdef MIN_HEAP
#define HEAP_COMPARE(a, b) ((a) < (b))
#endif // MIN_HEAP - 寻找前k个最大值
#ifdef MAX_HEAP
#define HEAP_COMPARE(a, b) ((a) > (b))
#endif // MAX_HEAP - 寻找前k个最小值

void AdjustDown(int* arr, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && HEAP_COMPARE(arr[child + 1], arr[child]))
		{
			++child;
		}
		if (HEAP_COMPARE(arr[child], arr[parent]))
		{
			int temp = arr[child];
			arr[child] = arr[parent];
			arr[parent] = temp;
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand((unsigned int)time(NULL));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (size_t i = 0; i < n; ++i)
	{
		int x = rand();
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

void PrintTopK(int k)
{
	FILE* fin = fopen("data.txt", "r");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	int val = 0;
	int* arr = (int*)malloc(sizeof(int) * k);
	if (arr == NULL)
	{
		perror("malloc error");
		return;
	}
	for (int i = 0; i < k; ++i)
	{
		fscanf(fin, "%d", &val);
		arr[i] = val;
	}
	//前k个数建堆
	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		AdjustDown(arr, k, i);
	}
	int x = 0;
	while (fscanf(fin, "%d", &x) != EOF)
	{
		if (!HEAP_COMPARE(x, arr[0]))
		{
			arr[0] = x;
			AdjustDown(arr, k, 0);
		}
	}
	for (int i = 0; i < k; ++i)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int k = 0;
	printf("请输入要找的前k个数:");
	scanf("%d", &k);
	CreateNDate();
	PrintTopK(k);
	return 0;
}
  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

椋鸟Starling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值