堆排序/top-k问题/建堆时间复杂度(C语言)

目录

一、堆排序

1.思路

2.代码实现

二、top-k问题

1.朴素思路

2.思路

3.代码实现

三、建堆时间复杂度

1.向下调整建堆

2.向上调整建堆


一、堆排序

1.思路

在之前我们了解到了堆的特性,大堆可以将一组数据中最大的数调整到堆顶,小堆可以将一组数据中最小的数调整到堆顶,于是我们可以利用这个性质来对数组排序。

在进行堆排序之前我们要先建堆,建堆有两种方法,向上调整和向下调整,二者的时间复杂度我们会在第三部分分析,分别为O(NlogN)  O(N)

在之前出堆的实现中,我们不可以直接将推定元素去除,后节点整体前移。我们还是将堆顶元素与堆末节点交换位置,找一个变量n,来记录要操作的节点数,每次交换后n--,这样就可以将排序好的数放在末尾。然后将堆顶元素向下调整,重复此过程。

所以升序用大堆,降序用小堆。

2.代码实现

void HeapSort(int* a, int n){
	for (int i = (n - 1 - 1) / 2; i >= 0; --i){
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end > 0){
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

二、top-k问题

top-k是一个经典问题,在一堆数据中,筛选出最大的十个(k个)数据,应该怎么做?

1.朴素思路

首先想到的是将所有数据建大堆,然后 HeapPop k次,就可以获得最大的k个数据。

但是这种方法不仅时间复杂度高,而且空间复杂度奇高无比。

2.思路

我们可以建含有k个节点的小堆,每次将一个数据与堆顶数据比较,若小于堆顶数据,则不变,若大于堆顶数据,则替换堆顶数据,然后向下调整,大的数据就沉到堆底。

就像如下的液体分层一样,密度大的才可以沉下去。

3.代码实现

为了模拟topk问题,我们创建一个txt文件包含用时间戳创建的随机数据。

void CreateNDate(){
	// 造数据
	int n = 100000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL){
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i){
		int x = (rand() + i) % 10000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}

top-k的主体

void PrintTopK(const char* file, int k){
	FILE* fout = fopen(file, "r");
	if (fout == NULL){
		perror("fopen error");
		return;
	}
	// 建一个k个数小堆
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL){
		perror("malloc error");
		return;
	}
	// 读取前k个,建小堆
	for (int i = 0; i < k; i++){
		fscanf(fout, "%d", &minheap[i]);
		AdjustUp(minheap, i);
	}
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF){
		if (x > minheap[0]){
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}                            //核心code
	}
	for (int i = 0; i < k; i++){
		printf("%d ", minheap[i]);
	}
	printf("\n");
	free(minheap);
	fclose(fout);
}

三、建堆时间复杂度

谈起建堆,有两种建堆方式,向上调整建堆和向下调整建堆

具体的代码实现在我之前的博客里有,我现在只专注于求取两种方法的时间复杂度。

通过了解建堆的原理,我们可以得到这样的基本公式

层节点数*单节点最大移动层数

1.向下调整建堆

我们从堆顶节点开始,有 2^0 个节点,其要移动 h-1 层

依次的,第二层有 2^1 个节点,要移动 h-2 层

……

倒数第二层有 2^{h-2} 个节点,要移动 1 层

于是有最坏估计下总时间    T(h)=1\times 2^{h-2}+2\times 2^{h-3}+...+(h-1) \times 2^0

用高中的数列求和知识,错位相减法可以求出    T(h)=2^h-1-h

而    N=2^h-1                                   得到   h=log_{2}(N-1)

有    T(N)=N-log_{2}(N+1) 

由时间复杂度的计算,我们知道后面的对数可以忽略

于是有向下调整建堆的时间复杂度O(N)

2.向上调整建堆

我们从堆底节点开始,有 2^{h-1} 个节点,其要移动 h-1 层

依次的,倒数第二层有 2^{h-2} 个节点,要移动 h-2 层

……

倒数第二层有 2^1 个节点,要移动 1 层

于是有最坏估计下总时间    T(h)=1\times 2^1+2\times 2^2+...+(h-1)\times 2^{h-1}

仍然错位相减可以有   T(h)=(h-2)\times 2^h+2

于是   T(N)=(N+1)\times [log_{2}(N+1)-2]+2

可以得到向上调整建堆的时间复杂度O(N\times logN)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值