数据结构与算法:堆排序和TOP-K问题

朋友们大家好,本节内容来到堆的应用:堆排序和topk问题

堆排序

  • 1.堆排序的实现
    • 1.1排序
  • 2.TOP-K问题
  • 3.向上调整建堆与向下调整建堆
    • 3.1对比两种方法的时间复杂度

我们在c语言中已经见到过几种排序,冒泡排序,快速排序(qsort)

冒泡排序的时间复杂度为O(N2),空间复杂度为O(1);qsort排序的时间复杂度为
O(nlogn),空间复杂度为O(logn),而今天所讲到的堆排序在时间与空间复杂度上相比于前两种均有优势

堆排序可以在原数组上进行,其空间复杂度为O(1);
堆排序提供了稳定的 (O(nlogn)) 时间复杂度

接下来我们进行讲解

首先我们来看这组代码:

int main()
{
	int a[] = { 6,3,5,7,11,4,9,13,1,8,15 };
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);
	}
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	printf("\n");

	return 0;

}

上节课我们知道,hp这个堆里面,a[i]并不一定是有序的

在这里插入图片描述
这里我们每次打印首元素,即最小元素,再删除掉,下一次获得到的堆顶元素仍为最小的,所以打印出来结果为有序的。但这个并不是堆排序,他只是每次获取堆顶最小元素

堆排序是直接在数组上实现的

1.堆排序的实现

堆排序的实现可以分为两部分:构建最大堆(或最小堆)和执行排序过程

首先我们来看建堆过程

在上述代码中,我们是通过HeapPush(&hp, a[i]);来实现堆的插入,推其本质,是每次插入元素后进行向上调整,我们构建一个堆排序函数,其参数为传入的数组,和数组的元素个数:

void HeapSort(HPDataType* a, int n);

首先建堆,这里我们用向上调整建堆,在文章末尾会给大家引入向下调整建堆

for (int i = 1; i < n; i++)
{
	Ajustup(a, i);
}

从第二个元素开始,每次向上调整,完成堆的构建
在这里插入图片描述

建好之后我们则需要排序

1.1排序

思考一下,如果我们想要进行升序排序,需要建立大堆还是小堆呢?

在上述示例中,如果我们想进行升序,该怎么操作???

这里,如果我们想要升序排序,则需要建立大堆

小堆如果我们想要升序,堆顶元素在对应位置,剩余元素重新建立小堆,则时间复杂度大大增加

上述示例中,我们建了一个小堆,可以将Ajustup父节点与子节点大小关系改变来建立为大堆:

在这里插入图片描述
那思考一下,建立了大堆,我们如何实现升序呢?

这里我们就需要与删除堆顶元素相同的思路

  1. 排序过程
    在大堆构建完成后,数组的根节点(即数组的第一个元素)是当前堆中的最大元素。通过将它与堆的最后一个元素交换,然后减少堆的大小(实际上是忽略数组的末尾元素),可以确保最大元素位于数组的正确位置上。

  2. 调整堆
    交换根节点和最后一个节点之后,新的根节点可能破坏了大堆的性质,因此需要进行调整。调整的方法是将新的根节点“下沉”,直到恢复大堆的性质。

  3. 重复过程
    重复对堆顶元素进行移除并调整堆的过程,直到堆的大小减少到1。在每一次重复过程中,都会将当前的最大元素放置到它在数组中的最终位置上。

所以我们代码实现就两步:

  • 交换首尾元素
  • 向下调整
void HeapSort(HPDataType* a, int n)
{
	//建堆
	for (int i = 1; i < n; i++)
	{
		Ajustup(a, i);
	}
	while (n>1)
	{
		Swap(&a[0], &a[n - 1]);
		n--;
		Ajustdown(a, n, 0);
	}
}

我们进行代码测试
在这里插入图片描述

所以,堆这里可以促进我们快速选数,它的本质是选择排序

2.TOP-K问题

TOP-K问题指的是从一个大规模的数据集中找出“最重要”或“最优”的K个元素的问题,对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决

思路如下:

  1. 用数据集合中前K个元素来建堆
    • 前k个最大的元素,则建小堆
    • 前k个最小的元素,则建大堆
  2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

基于已经提供的堆操作函数,我们可以实现一个特定的PrintTopK函数,其目的是从数组a中找到最大的k个元素。

实现这一目标的思路是:

  • 首先,使用数组a中的前k个元素建立一个最小堆。
  • 然后,遍历剩余的n-k个元素。对于每个元素,如果它大于堆顶元素,则用它替换堆顶元素,然后对堆顶元素进行向下调整以维护最小堆的性质。
  • 遍历完成后,堆中的k个元素即为整个数组中最大的k个元素。
void PrintTopK(int* a, int n, int k)
{
    Heap php;
    HeapInit(&php);
    
    for (int i = 0; i < k; ++i) {
        HeapPush(&php, a[i]);
    }
    for (int i = k; i < n; ++i) {
        if (a[i] > HeapTop(&php)) { // 如果当前元素比堆顶大
            HeapPop(&php); // 移除堆顶
            HeapPush(&php, a[i]); // 将当前元素加入堆中
        }
    }
    
    // 打印堆中的元素,即TOP K元素
    for (int i = 0; i < k; ++i) {
        printf("%d ", php.a[i]);
    }
    printf("\n");
    HeapDestroy(&php);
}

  1. 用a中前k个元素建立堆
  2. 将剩余n-k个元素与堆顶比较,替换并调整

测试代码:
在这里插入图片描述

3.向上调整建堆与向下调整建堆

对于数组a,进行向上调整建堆:

for (int i = 1; i < n; i++)
{
	Ajustup(a, i);
}

要通过向下调整的方式建立堆,我们通常是从最后一个非叶子节点开始,逐层向上进行调整,这能保证每个子树都满足堆的性质

for (int i = n/2 - 1; i >= 0; i--) {
        AdjustDown(a, n, i);
    }

3.1对比两种方法的时间复杂度

向下调整建堆

这个方法从最后一个非叶子节点开始,逆序对数组中的元素执行向下调整的操作。每个节点需要执行的向下调整操作取决于其高度,而数组中大约一半的节点是叶子节点,它们不需要被向下调整。对于剩下的节点,只有很少的节点需要移动到树的较低层次。具体地说,树的每一层上的节点数量减半,而向下移动的最大深度从0开始线性增加。

for (int i = n/2 - 1; i >= 0; i--) {
    AdjustDown(a, n, i);
}

在这里插入图片描述
设向下调整的累计次数为T(h).

  • 倒数第二层调整次数:2h-2*1
  • 倒数第三层调整次数:2h-3*2
  • ……
  • 第一层调整次数:20*(h-1);

对其进行累加和:

为等差×等比求和,通过错位相减则可求出结果:

T(h)=2^h-1-h;
h=log (n+1);
T(n)=n-log(n+1)

导致最大影响的项为n
所以向下调整的时间复杂度为O(N)

向上调整建堆

从第二层开始向上调整:

  • 第二层调整次数:21*1
  • 第三层调整次数:22*2;
  • 倒数第二层:2h-2*(h-2);
  • 倒数第一层:2h-1*(h-1);

向上调整建堆

对于一个节点来说,向上调整可能需要比较和移动直到它的根节点,这在最坏的情况下是树的高度,对于一个完全二叉树来说,树的高度是 O ( log ⁡ n ) O(\log n) O(logn)。对于代码段:

for (int i = 1; i < n; i++) {
    AdjustUp(a, i);
}

这个方法从第二个元素开始,逐一对数组中的元素执行向上调整的操作。对于数组中的第i个元素,最坏情况下向上调整操作需要沿着一条从叶节点到根节点的路径移动,路径的长度大约等于树的高度 h h h,即 O ( log ⁡ i ) O(\log i) O(logi)。因此,对于所有元素的总时间复杂度为:

T ( n ) = ∑ i = 1 n O ( log ⁡ i ) = O ( log ⁡ n ! ) = O ( n log ⁡ n ) T(n) = \sum_{i=1}^{n} O(\log i) = O(\log n!) = O(n \log n) T(n)=i=1nO(logi)=O(logn!)=O(nlogn)

使用斯特灵公式( n ! ≈ 2 π n ( n e ) n n! \approx \sqrt{2\pi n}(\frac{n}{e})^n n!2πn (en)n),可以推导出 O ( log ⁡ n ! ) O(\log n!) O(logn!) 的大致等于 O ( n log ⁡ n ) O(n \log n) O(nlogn),所以向上调整建堆的时间复杂度大约为 O ( n log ⁡ n ) O(n \log n) O(nlogn)

向上调整建堆的时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn),而向下调整建堆的时间复杂度是 O ( n ) O(n) O(n)。因此,对于从零开始构建堆的场景,通常更倾向于使用向下调整的方法,因为它更加高效。

本节内容到此结束!感谢大家支持!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024.3.5

作业1、使用select实现tcp服务器端&#xff0c;poll实现tcp客户端 服务器端&#xff1a; #include <myhead.h> #define SER_IP "192.168.199.131" //服务端IP #define SER_PORT 8888 //服务端端口号int main(int argc, const char *argv[])…

星瑞格数据库管理系统

一. 产品介绍 随着信息化的到来&#xff0c;数据安全成为保障信息化建设的一个关键问题&#xff1b;数据库作为信息化系统的基础软件其自身安全以及对数据的保障是至关重要。现阶段国内重要部门的信息系统存放着大量敏感数据&#xff0c;为了保障其数据的安全性&#xff0c;使用…

Mathcad tips_table相关

1. 可以插入表格&#xff0c;或者2. 从excel 文件导入 选择列 选择其中一行的数值

Batch Normalization和Layer Normalization和Group normalization

文章目录 前言一、Group normalization二、批量规范化(Batch Normalization)三、层规范化&#xff08;Layer Normalization&#xff09; 前言 批量规范化和层规范化在神经网络中的每个批次或每个层上进行规范化&#xff0c;而GroupNorm将特征分成多个组&#xff0c;并在每个组内…

HarmonyOS创建项目和应用—设置数据处理位置

项目和应用介绍 关于项目 项目是资源、应用的组织实体。资源包括服务器、数据库、存储&#xff0c;以及您的应用、终端用户的数据等。在您使用部分服务时&#xff0c;您是数据的控制者&#xff0c;数据将按照您设置的数据处理位置来存储在指定区域。 通常&#xff0c;您不需…

Mybatis实现分页查询数据(代码实操讲解)

在MyBatis中实现分页查询的常见方式有两种&#xff1a;使用MyBatis内置的分页插件如PageHelper&#xff0c;或者手动编写分页的SQL语句。下面我将为你提供两种方式的示例代码。 使用PageHelper分页插件 首先&#xff0c;确保你的项目中已经添加了PageHelper的依赖。在Maven项…

ZYNQ--关于一些SDK调试问题记录

Debug configuaration中没有debug applicaton 问题如下图&#xff1a; 解决方法&#xff1a; 在Target Setup中的Debug Type中选择如下即可 注意选完之后application中必须勾选运行内核&#xff0c;否则不运行main文件。

Java项目:36 springboot图书个性化推荐系统的设计与实现003

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 springboot003图书个性化推荐系统的设计与实现 管理员&#xff1a;首页、个人中心、学生管理、图书分类管理、图书信息管理、图书预约管理、退…

2195. 深海机器人问题(网络流,费用流,上下界可行流,网格图模型)

活动 - AcWing 深海资源考察探险队的潜艇将到达深海的海底进行科学考察。 潜艇内有多个深海机器人。 潜艇到达深海海底后&#xff0c;深海机器人将离开潜艇向预定目标移动。 深海机器人在移动中还必须沿途采集海底生物标本。 沿途生物标本由最先遇到它的深海机器人完成采…

vue3基础教程(2)——创建vue3+vite项目

博主个人微信小程序已经上线&#xff1a;【中二少年工具箱】。欢迎搜索试用 正文开始 专栏简介1. 前言2.node版本检测3.创建vue项目 专栏简介 本系列文章由浅入深&#xff0c;从基础知识到实战开发&#xff0c;非常适合入门同学。 零基础读者也能成功由本系列文章入门&#x…

循环队列:一道使数据结构萌新知道什么是“愁滋味“的题目

这破题目肝了我一天半才搞明白,也正是因为这道题目,我才豁然明白了李煜所说的"剪不断,理还乱...别是一般滋味在心头"到底是什么"滋味".在完全搞明白之前,真的是放有放不下,理也理不清... 但是理解之后你会发现,嘛い---,也就那么个回事嘛O(∩_∩)O 目录 1…

【DreamTalk】源码部署

安装 # 下载源码 git clone https://github.com/ali-vilab/dreamtalk cd dreamtalkconda create -n dreamtalk python3.10 conda activate dreamtalkconda install -c conda-forge yacs0.1.8 conda install -c conda-forge numpy1.21.5 conda install -c conda-forge av10.0.0…

如何使用宝塔面板部署MySQL数据库,并结合内网穿透实现固定公网地址远程连接

文章目录 前言1.Mysql服务安装2.创建数据库3.安装cpolar3.1 开放局域网端口3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 宝塔面板的简易操作性,使得运维难度降低,简化了Linux命令行进行繁琐的配置,下面简单几…

为什么Spring Cloud 应用程序中,应用程序的加载配置必须写在bootstrap.yaml这个配置文件中,是在哪里规定的?

在 Spring Cloud 应用程序中&#xff0c;bootstrap.yaml&#xff08;或bootstrap.properties&#xff09;的使用并非强制性的&#xff0c;但它扮演着一个特定的角色&#xff0c;主要是因为 Spring Cloud 的设计和工作流程。 背景和设计 Spring Cloud 构建在 Spring Boot 之上…

STM32FreeRTOS-事件组1(STM32Cube高效开发教程)

文章目录 一、事件组的原理和功能1、事件组与队列信号量特点2、事件组存储结构3、事件组运行原理 二、事件组部分函数1、xEventGroupCreate()创建事件组函数2、xEventGroupSetBits&#xff08;&#xff09;事件组置位函数3、xEventGroupSetBitsFromISR&#xff08;&#xff09;…

Geeker Admin添加若以分离版本的后台作为后台

添加验证码 下载若依赖前后端分离版本&#xff0c;配置好自己数据库&#xff0c;redis连接地址 登录添加验证码 配置自己的若依后端连接地址 添加验证码请求方法 登录页面登录输入框添加验证码&#xff0c;uuid,调用的验证码刷新方法 注意&#xff1a;这里要用响应式定义验证…

外汇天眼:蓝莓市场终止所有MT4/MT5专业公司业务

总部位于澳大利亚的零售外汇和差价合约经纪商蓝莓市场宣布&#xff0c;已终止其数据和平台服务产品&#xff0c;该产品旨在通过利用其基础设施为专业公司行业提供服务。 蓝莓市场表示&#xff0c;已经对其数据和平台服务产品“落下帷幕”&#xff0c;与所有专业交易公司包括MyF…

分类问题经典算法 | 二分类问题 | Logistic回归:梯度下降

目录 一. 损失函数1. 交叉熵损失函数2. 梯度下降 一. 损失函数 Logistic回归算法公式推导篇中&#xff0c;我们通过对似然函数求对数&#xff0c;得到 l ( θ ) l(\theta ) l(θ)&#xff1a; l ( θ ) l n [ L ( θ ) ] ∑ i 1 M { y ( i ) l n [ h θ ( x ( i ) ) ] ( …

Jekins 自启动Java应用的Shell笔记

背景 最近在研究jdk 的jvisualvm 对JVM服务远程监控时&#xff0c;意外的与jekins接轨了。公司使用jekins自动从Git上获得源码&#xff0c;打包后传到测试服务器并启动jar包&#xff0c;实现自动部署&#xff0c;而我需要做的是在测试服务器启动jar包时添加几个我设置的命令&am…

【YOLO v5 v7 v8 v9小目标改进】DWRSeg:优化的多尺度处理,传统的深度学习模型可能在不同尺度的特征提取上存在冗余

DWRSeg&#xff1a;优化的多尺度处理&#xff0c;传统的深度学习模型可能在不同尺度的特征提取上存在冗余 提出背景问题&#xff1a;实时语义分割需要快速且准确地处理图像数据&#xff0c;提取出有意义的特征来识别不同的对象。 小目标涨点YOLO v5 魔改YOLO v7 魔改YOLO v8 魔…