⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:数据结构初阶
⭐代码仓库:Data Structure
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!
堆排序和TOP-K问题
前言
堆排序作为八大经典排序之一,是有很高的效率,比我们之前学过的冒泡排序和选择排序有很高的提升,所以我们学习完堆排序以后会有对代码很深的理解,而我们今天要实现的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问题对于学习数据结构是很有帮助的,对于堆的概念,我们可以理解成一个二叉树,其排序就是二叉树的排序,这不容易想象,就需要画图去解决这个问题,让数据展示在画板上,更加地形象。
家人们不要忘记点赞收藏+关注哦!!!