解密堆排序与TopK问题

📙作者简介: 清水加冰,目前大二在读,正在学习C/C++、Python、操作系统、数据库等。

📘相关专栏:C语言初阶、C语言进阶、C语言刷题训练营、数据结构刷题训练营、有感兴趣的可以看一看。

欢迎点赞 👍 收藏 ⭐留言 📝 如有错误还望各路大佬指正!

✨每一次努力都是一种收获,每一次坚持都是一种成长✨       

在这里插入图片描述

目录

 前言

1. 堆排序

1.1 时间复杂度

 1.1.1 向上调整(建堆)

 1.1.2 向下调整 (建堆)

 1. 2 排序实现

2. Topk问题

 2.1 什么是Topk问题

 2.2 Topk问题的解决

 2.2.1 造数据

 2.2.2 Topk的实现

总结


 前言

        在二叉树的存储结构中提到堆可以进行排序,也就是今天的主题堆排序,堆的排序还可以解决Topk问题,今天我就向大家解密什么是堆排序和Topk问题,它们的原理又是什么。


1. 堆排序

        堆是一种特殊的完全二叉树,堆排序是一种基于二叉堆数据结构的排序算法。堆排序是利用堆的特性来对堆中的数据进行排序,那么问题来了:

        如果我们要排升序,需要建大堆还是小堆?绝大多数人的第一反应是建小堆,但建小堆真的可以将数据进行升序排列吗?

         我们看下边这棵树,如果是小堆,那如何去找第二小的数?我们需要根据堆的特性,对数据进行调整位置,来达到排序的问题。

 显然小堆是无法做到的,要想排升序就必须要建大堆。大堆排升序思路如下:

         大堆的根是整棵树中最大的值,那我们就可以让根和最后一个节点进行交换(70和10进行交换),然后将剩下的节点进行调整(不包含70),调整后的根就是第二大的数。

 然后继续上述操作,将56和15进行交换,然后再进行调整,以此类推,最终数据就会被排为升序。

总结来说,堆排序主要有两大步骤:

  • 建堆
  • 调整数据

 建堆,我们可以选择向上调整或者向下调整。

调整数据部分,必须为向下调整。

1.1 时间复杂度

        说到了排序,那就必须要谈一谈它的性能如何?也就是它的时间复杂度。

 1.1.1 向上调整(建堆)

         向上调整的思路是从孩子(叶子节点)开始调整,最坏调整到根节点,调整情况如下:

         每层的数据个数 * 每层调整次数=一层最多执行的次数。那要求的是总共需要执行的次数就是每层的执行次数之和。我们设总的执行次数为T(h)。

所以:T(h)=2^1*1 + 2^2*2 + 2^3*3 … +2^(h-2)*(h-2) + 2^(h-1)*(h-1)  ;

计算部分为高中数学的知识,私下可以验算一下,最终的结果为:T(h)=(h-2)* 2^h+2,

将h代换成N(节点个数),N=2^h-1,h=log(N+1),

T(N)=( log (N+1) - 2 )  *  (N+1) + 2(此处的log以2为底),最终就约等于N*log N

所以向上调整建堆的时间复杂度就是O(N*log N)

 1.1.2 向下调整 (建堆)

         向下调整是从根节点开始,最坏调整到叶子节点,调整情况如下:

         这里我们可以看出,向下调整的时间复杂度和向上调整不同,我们设总的执行次数为T(h)。

T(h)=2^0*(h-1) + 2^1*(h-2) + 2^2*(h-3) … +2^(h-3)*2 + 2^(h-2)*1 ; 

最终的结果为:T(h)=2^h-h-1 ;

将h代换成N(节点个数),N=2^h-1,h=log(N+1);

T(N)=N-log(N+1);最终就约等于N;

所以向下调整建堆的时间复杂度就是O(N); 

 注意这里要区分清,是建堆操作还是调整操作,单一的执行一次调整,向上调整和向下调整的时间复杂度都是log N。

 1. 2 排序实现

void HeapSort(int* arr, int n)
{//向上调整建堆,时间复杂度O(N*log N)for (int i = 1; i < n; i++){AdjustUp(arr, i);}//向下调整建堆,时间复杂度O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, n, i);}int end = n - 1;while (end > 0){swap(&arr[0], &arr[end]);AdjustDown(arr, end, 0);end--;}}

注意:

        向上调整时我们需要保证上方的数据结构是堆,所以这里我们从头开始读入数据,读一个数据就调整一次。这样就可以确保每次调整时,其他的数据都是堆结构。

        向下调整的前提是左右子树都为堆结构,所以我们需要保证左右子树都为堆,这里我们传参时不能从头开始,要从倒数第一个的非叶子节点开始(最后一个父节点),从后向前进行调整建堆。

 向上调整

这里我们建的是大堆 。

void AdjustUp(int* arr,int child)
{int parent = (child - 1) / 2;while (child > 0){if (arr[parent] < arr[child])//建大堆排升序{swap(&arr[parent], &arr[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}

向下调整

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

2. Topk问题

 2.1 什么是Topk问题

        Top-K问题是指从一组元素中找出前K个最大(或最小)的元素。这个问题在数据处理和算法设计中经常遇到,常见的应用场景包括搜索引擎中的搜索结果排序、推荐系统中的物品推荐、数据分析中的数据筛选等。

Topk问题,我们就可以使用堆来解决,对于Top-K问题,一般有两种情况:

  • 找出前K个最大的元素:可以使用最小堆来解决,维护一个大小为K的最小堆,遍历所有元素,将每个元素与堆顶元素进行比较,如果大于堆顶元素,则将堆顶元素替换为当前元素,并进行堆调整。最终堆中的元素即为前K个最大的元素。
  • 找出前K个最小的元素:可以使用最大堆来解决,维护一个大小为K的最大堆,遍历所有元素,将每个元素与堆顶元素进行比较,如果小于堆顶元素,则将堆顶元素替换为当前元素,并进行堆调整。最终堆中的元素即为前K个最小的元素。

        通过解决Top-K问题,可以快速获取数据中的重要信息,提高算法的效率和性能。

 2.2 Topk问题的解决

 2.2.1 造数据

         为了模拟实现Topk问题在现实生活中的应用,所以在测试时我们需要采用大量的数据进行测试,我们自己手动输入是远远不够的,所以我们这里采用strand函数进行造数据。

void CreateNDate()
{// 造数据int n = 1000000;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() % 10000000;fprintf(fin, "%d\n", x);}fclose(fin);
}

        这里我们造了100万个数据。将数据默认放到了一个data.txt的文件中。我们先运行一下这个函数接口,然后打开在程序的当前路径下找到data.txt文件,将任意的k个数据手动修改(可以前边加4个9,增加辨识度)确保判断找出的数据为前k的最值。这里我们选择找出前k个最大的数据。

注意造完数据后要将造数据接口注释掉,修改数据后记得将文件保存

 2.2.2 Topk的实现

         根据上述的思路,要想找到前k个最大的数据,就需要建小堆,这样堆顶就是堆中最小的数据,比堆顶大就置为根入堆,最后将所有数据遍历一遍之后,留在堆里的数据就是前k个最大的数据。

这里我们依然需要前边的调整代码。具体代码如下:

void PrintTopK(const char* filename,int k) {//打开文件,读取数据FILE* fout = fopen(filename, "r");if (fout == NULL){perror("fopen fail");return;}//建堆int* setHeap = (int*)malloc(sizeof(int) * k);if (setHeap == NULL){perror("malloc fail");exit(-1);}//读取前k个数据建堆for (int i = 0; i < k; i++){fscanf(fout, "%d", &setHeap[i]);}for (int i = 0; i < k; i++){AdjustUp(setHeap, k);}//遍历入堆int x = 0;while (fscanf(fout, "%d", &x) != EOF){if (x > setHeap[0])    //比堆顶大就入堆{setHeap[0] = x;AdjustDown(setHeap, k, 0);    //入堆之后向下调整}}for (int i = 0; i < k; i++){printf("%d ", setHeap[i]);}printf("\n");fclose(fout);
}

建小堆调整代码

void swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustUp(int* arr, int child)
{int parent = (child - 1) / 2;while (child > 0){if (arr[parent] > arr[child])//建大堆排升序{swap(&arr[parent], &arr[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}
void AdjustDown(int* arr, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && arr[child + 1] < arr[child]){child++;}if (arr[parent] > arr[child]){swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

main接口:

int main()
{//CreateNDate();PrintTopK("data.txt", 7);return 0;
}

总结

        本篇博客主要介绍了堆排序算法及其在解决TopK问题中的应用。通过对堆排序的原理和实现步骤的详细讲解,我们可以更好地理解和掌握这一经典的排序算法。同时,通过解决TopK问题的实例,我们也可以看到堆排序在实际应用中的价值和优势。希望本篇博客能够为你提供一些有用的思考和启发。最后,感谢阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/116008.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【力扣-每日一题】2560. 打家劫舍 IV

class Solution { public:bool check(vector<int> &nums,int max_num,int k){//只需要计算可以偷的房间。在满足最大值为max_num下时&#xff0c;能偷的最多的房间&#xff0c;与k值比较//如果大于K&#xff0c;说明max_num还可以缩小//如果小于看&#xff0c;说明ma…

Spring Security :二【原理解析、会话管理、RBAC中集成认证和授权、JWT】

文章目录 三、原理解析3.1 结构分析3.1 登录认证流程分析3.1.1 **UserDetailsService**3.1.2 自定义UserDetailsService3.1.3 **PasswordEncoder** 3.2 授权流程分析3.2.1 配置方式的原理解析3.2.2 注解方式原理解析 四、会话管理4.1 获取用户身份4.2 会话控制 五、 RBAC中集成…

20-SpringCloudAlibaba-1

一 Spring Cloud Alibaba简介 什么是Spring Cloud Alibaba Spring Cloud Alibaba致力于提供微服务开发的一站式解决方案。 此项目包含开发分布式应用微服务的必需组件&#xff0c;方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 为什么要推出Sp…

python随手小练1

题目&#xff1a; 使用python做一个简单的英雄联盟商城登录界面 具体操作&#xff1a; print("英雄联盟商城登录界面") print("~ * "*15 "~") #找其规律 a "1、用户登录" b "2、新用户注册" c "3、退出系统&quo…

基于微信小程序的食堂订餐系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明具体实现截图用户小程序端的主要功能有&#xff1a;商家的主要功能有&#xff1a;管理员的主要功能有&#xff1a;详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言…

FPGA——基础知识合集

文章目录 前言1、简述触发器与锁存器的区别2、简述 if-else 语句和 case 语句的区别3、相对 ARM、DSP 等处理器&#xff0c;谈谈 FPGA 具有哪些优势4、简述 Verilog 语句中阻塞赋值与非阻塞赋值的含义与区别&#xff0c;以及各自的适用的场景5、什么是同步电路&#xff0c;什么…

【Redis】专栏合集,从入门到高级业务场景实战

作者简介 目录 1.概述 2.下载安装 3.基础操作 4.集群 5.实战场景 1.概述 诸如数mysql、Oracle之类的关系型数据库或者NTFS、HDFS之类的文件存储系统&#xff0c;其本质上数据都是存在磁盘上的。这是现代计算机体系架构的架构所决定的&#xff0c;要持久化存储的数据都会落…

RocketMQ快速实战以及集群架构详解

⼀、 MQ 简介 MQ &#xff1a; MessageQueue &#xff0c;消息队列。是在互联⽹中使⽤⾮常⼴泛的⼀系列服务中间件。 这个词可以分两个部分来看&#xff0c;⼀是Message &#xff1a;消息。消息是在不同进程之间传递的数据。这些进程可以部署在同⼀台机器上&#xff0c;也可以…

C/C++程序员技术发展方向(强烈推荐!!)

大家好&#xff0c;我是阿Q。 今天这篇就是专门给现在还迷茫不知道自己到底要做什么方向C开发的同学们。 几年后回过头看的时候&#xff0c;你一定会感谢当初那个努力的自己&#xff01; C作为当下也非常流行的一个面向对象语言&#xff0c;有着非常多的应用&#xff0c;一定…

jenkins 展示测试报告不友好?教你3招,甩出同事3条街!

jenkins 中在展示测试报告的时候很不友好&#xff0c;测试报告样式会丢失&#xff0c;如何展示漂亮的测试报告呢&#xff1f; 今天我们就来解决这个问题&#xff0c;手把手教你3 种方式。 一、jenkins 中展示测试报告效果 二、实现方式一&#xff1a;临时解决(jenkins 重启后失…

PowerDesigner 连接 MYSQL

我使用的是powerDesigner16的版本&#xff0c;使用前先保证安装了 mysql odbc 驱动包 选择&#xff1a;文件 -> 反向工程 -> database… 一大波图片正在来袭。。。 点击确认 至此连接成功

笔记1.2 计算机网络结构

网络边缘 主机、网络应用 接入网络&#xff0c;物理介质 有线或无线通信链路 网络核心&#xff08;核心网络&#xff09;&#xff1a; 互联的路由器&#xff08;或分组转发设备&#xff09; 网络之网络 一、网络边缘 主机&#xff08;端系统&#xff09;&#xff1a; 位…