【数据结构初阶】堆排序和TOP-K问题

文章介绍了堆排序的两种实现方式,包括建大堆的向上调整和向下调整建堆,并详细解析了每一步的思路和代码实现。此外,还探讨了Top-K问题的解决方案,通过建立小堆来找到文件中最大的k个数。文章强调了数据结构和算法在解决问题中的重要性。
摘要由CSDN通过智能技术生成

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:数据结构初阶
⭐代码仓库:Data Structure
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!


前言

堆排序作为八大经典排序之一,是有很高的效率,比我们之前学过的冒泡排序和选择排序有很高的提升,所以我们学习完堆排序以后会有对代码很深的理解,而我们今天要实现的TopK问题是很经典的一个问题,因为我们在日常生活中经常能看到筛选这种事情,这种TopK的经典问题可以筛选出前多少多少人。


一、复杂的堆排序

我们之前写过一个实现堆的顺序结构的函数,那个照样可以把数据按照顺序进行输出,那大家肯定就说,我们写的堆的顺序结构就是堆排序,似乎好像是正确的,没错,是正确的,但是,这么长一个函数确实可以实现排序,但是未免也太长了吧?我们写的冒泡排序和选择排序都只需要几行代码就解决了,但是堆排序却需要这么多代码,我要自己写一个堆,然后再进行插入数据……太麻烦了,所以我们就有了新的建堆,我们不需要这么复杂的建堆,我们只需要利用一个算法就可以建堆了!
在这里插入图片描述


建堆 – 向上或者向下调整算法,当我们一个一个数据进入堆中的时候,我们只需要分子树来进行向下或者向上调整算法即可,具体在下面有所讲解。

二、堆排序1

第一种堆排序我们需要先建堆,这样才能进行排序,我们拿升序进行举例。

1、为什么建大堆?

升序是需要建大堆的,很多同学有疑惑了,升序不应该是上面的结点数据小吗?这大堆不是上面大吗?很奇怪,那我们其实想一想,如果我们建小堆,一个一个数据插入,先建堆,最小值是找到了,但是最大值不一定找得到,向下调整的话最大的值根本找不到,就乱套了,而当我们建大堆,大的数据在根部,这只需要我们进行将刚插入的数据与根元素交换,然后将最大的数据放在堆外,除去那个最大的数据的其他数据向下调整变成一个新的堆,是不是很方便。

2、思路

先建个大堆,将数据一个一个插入,也就是向上调整算法,然后将新插入的元素与根元素交换,除去最大元素的其他元素进行向下调整,将数据进行排好序即可。
在这里插入图片描述

3、代码实现

//堆排序1
void HeapSort(int* a, int n)
{
	//插入建堆
	//建堆 -- 向上调整
	//时间复杂度为O(N*logN)
	//升序 -- 建大堆
	int i = 1;
	for (i = 1; 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[10] = { 12,31,21,20,56,76,38,37,30,3 };
	HeapSort(a, 10);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
//向上调整(除了child,其余全是堆)
//时间复杂度:O(N*logN)
void AdjustUp(HPDataType* a, HPDataType 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;
		}
	}
}

//向下调整
//时间复杂度:O(N)
void AdjustDown(HPDataType* a, HPDataType n, HPDataType parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

4、时间复杂度计算

在这里插入图片描述


三、堆排序2

1、思路

思路和第一种堆排序类似,只不过在建堆的时候更加清奇,我们利用向下调整建堆来解决,向下调整建堆是先找第一个非叶子结点,以这个非叶子子树的头头为根进行向下调整建堆,再往前找下一个非叶子结点,依次为根往下调整建堆,然后一直到整个二叉树的根节点将它向下调整建堆,这样一个大堆就建成了。

在这里插入图片描述

2、代码实现

//堆排序2
void HeapSort1(int* a, int n)
{
	//建堆 -- 向下调整建堆 - 时间复杂度为O(N)
	int parent = n - 1;//下标
	int i = 0;
	for (i = (parent - 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;
	}
}
int main()
{
	int a[10] = { 12,31,21,20,56,76,38,37,30,3 };
	HeapSort1(a, 10);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
//向下调整
//时间复杂度:O(N)
void AdjustDown(HPDataType* a, HPDataType n, HPDataType parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

3、时间复杂度计算

在这里插入图片描述


四、Top-K问题

1、概述

在这里插入图片描述

2、实现

我们这里来解决一个选取前k个最大的元素,那就需要建立一个以k为容量的小堆,然后利用文件,进行随机数的选取,然后存到文件中,再将前k个元素设立一个小堆,然后再将文件中的剩余所有数据一一与根元素进行比较,然后大于就替换,继续向下调整,形成一个新的堆,存到数组里面,最后再打印到控制台中。我们这里文件里生成10000个随机数字,而我们想要输出更加明显,那就改k个数据增加几位,然后再输出。

3、代码实现

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<string.h>
#include<time.h>

//交换
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType temp = *p1;
	*p1 = *p2;
	*p2 = temp;

}

//向下调整 - 小堆
void AdjustDown1(HPDataType* a, HPDataType n, HPDataType parent)
{
	int child = parent * 2 + 1;//父子节点关系
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child])
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//打印前k个值
void PrintfTopK(const char* file, int k)
{
	//创立数组空间进行存储
	int* TopK = (int*)malloc(sizeof(int) * k);
	if (TopK == NULL)
	{
		perror("malloc TopK");
		return;
	}

	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen file");
		return;
	}

	//读出前k个元素存入数组
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &TopK[i]);
	}
	//进行建堆 - 小堆
	for (int j = (k - 2) / 2; j >= 0; j--)
	{
		AdjustDown1(TopK, k, j);
	}

	//剩余n-k个元素依次与堆顶元素交换,不满则替换
	int val = 0;
	int ret = fscanf(fout, "%d", &val);
	while (ret != EOF)
	{
		if (val > TopK[0])
		{
			TopK[0] = val;
			AdjustDown1(TopK, k, 0);
		}
		ret = fscanf(fout, "%d", &val);
	}

	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", TopK[i]);
	}
	fclose(fout);
	fout = NULL;

}
//创建数据
void CreatNData()
{
	//TopK问题 - 随机数
	int n = 10000;
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen file");
		return;
	}
	//写进文件
	for (int i = 0; i < n; i++)
	{
		int x = rand() % 10000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
	fin = NULL;
}


int main()
{
	//CreatNData();
	PrintfTopK("data.txt", 10);

	return 0;
}

文件信息:
在这里插入图片描述输出信息:
在这里插入图片描述大家看,是不是一模一样!要想来个排序,那就很简单了,利用上面的排序2函数即可。


总结

本节的堆排序和Top-K问题对于学习数据结构是很有帮助的,对于堆的概念,我们可以理解成一个二叉树,其排序就是二叉树的排序,这不容易想象,就需要画图去解决这个问题,让数据展示在画板上,更加地形象。


家人们不要忘记点赞收藏+关注哦!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jjrenhai

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

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

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

打赏作者

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

抵扣说明:

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

余额充值