【数据结构】堆排序
堆是一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。
大堆:每个节点的值都大于或者等于他的左右孩子节点的值
小堆:每个结点的值都小于或等于其左孩子和右孩子结点的值
不管是大堆还是小堆父节点的数组下标和其孩子节点的下标关系都为
parent=(child-1)/2 leftchild=2*parent+1 rightchild=2*parent+2
建堆
建堆有两种方法1.向上调整建堆2.向下调整建堆
向上调整建堆
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustUp(int* a, int child) //向上调整
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]) //建大堆还是小堆在这里调整{ //大堆a[parent]<a[child] 小堆反之Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}void HeapSort(int* a, int n)
{for (int i = 0; i < n; ++i){AdjustUp(a, i);}
}int main()
{int a[] = { 1,10,2,20,3,30,4,40,5,50 };HeapSort(a, sizeof(a) / sizeof(int));for (int i = 0; i < sizeof(a) / sizeof(int); ++i){printf("%d ", a[i]);}return 0;
}
向上调整建堆是从数组的第一个元素开始一次向后遍历数组元素,每一个数组元素都向上调整建堆,这个过程就模拟建立起了堆,这个过程的时间复杂度推导过程:O(N*logN)

2.向下调整建堆(推荐使用)
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustDown(int* a, int n, int parent) //向下调整
{int minChild = parent * 2 + 1;while (minChild < n){if (minChild + 1 < n && a[minChild + 1] > a[minChild]) //这里控制大小堆{minChild++;}if (a[minChild] > a[parent]) //这里控制大小堆{Swap(&a[minChild], &a[parent]);parent = minChild;minChild = parent * 2 + 1;}else{break;}}}
void HeapSort(int* a, int n)
{for (int i = (n - 1-1) / 2; i >= 0; --i){AdjustDown(a, n, i);}
}int main()
{int a[] = { 1,10,2,20,3,30,4,40,5,50 };HeapSort(a, sizeof(a) / sizeof(int));for (int i = 0; i < sizeof(a) / sizeof(int); ++i){printf("%d ", a[i]);}return 0;
}

向下调整的开始的位置是最后一个元素的父节点位置,因为最后一行的元素本来就符合大堆或者小堆的性质所以不用调整,根据数组的最后一个元素的下标计算出该节点的父节点的数组下标,从这个父节点开始向下调整,调整完后再将父节点的数组下标--,再从这个节点开始向下调整,直到调整完根节点后调整结束,堆就建立好了,大小堆是根据向下调整函数里的比较决定的。时间复杂度推导如下O(N):

因为向下调整的时间复杂度低于向上调整的时间复杂度,所以推荐使用的是向下调整建堆。
2.选数
了解完以后,我们来实现堆排序:
void AdjustDown(HPDataType* a, int n, int parent)
{//最小的默认为左孩子int minchild = 2 * parent + 1;while (minchild <n){//找出小的那个孩子if (minchild+1<n&&a[minchild+1] < a[minchild]){minchild++;}//小堆if (a[minchild] < a[parent]){Swap(&a[minchild], &a[parent]);parent = minchild;minchild = 2 * parent + 1;}else{break;}}
}void HeapSort(int* a, int n)
{for (int i = (n-1-1)/2; i>=0; i--){AdjustDown(a, n, i);}//选数int i = 1;while (i < n){Swap(&a[0], &a[n - i]);AdjustDown(a, n - i, 0);i++;}}int main()
{int a[] = { 15,1,19,25,8,34,65,4,27,7 };HeapSort(a, sizeof(a) / sizeof(int));for (int i = 0; i < sizeof(a) / sizeof(int); i++){printf("%d ", a[i]);}printf("\n");return 0;
}
这里的思路就是先对数组进行向下调整建堆,如果要升序就建立大堆,将根节点位置的元素和最后一个元素交换位置使得最大的元素放到了数组的最后边,放到后边的元素不参与向下调整(通过传参控制),然后让被换到根节点的元素向下调整,回到符合堆的性质的位置,此时调整完成后次大的元素被调整到了根节点的位置,再让根节点的元素和倒数第二个元素交换,交换到后边的元素不参与向下调整,再次进行向下调整,直到i=n时结束调整,此时输出数组就是升序的。如果要降序那就建立小堆。
总结一句话:
升序--建大堆,降序--建小堆
3.TOP-K问题
如何从数组中找到前K个大或者前K个小的数?
错误的思维:
选前K个大的数建立的是大堆,那最大的元素就在最上方,我们拿走了最大的元素,那剩下的元素堆的关系全乱了,又得重新排序,再选出最大的元素,这样一来假设要从特别大的一堆数据中进行选数那代价就非常大了,效率变得很低。同理选小数不能建小堆。
正确方法:
用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。
代码实现:
#include<stdlib.h>
#include<stdio.h>void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustDown(int* a, int n, int parent) //向下调整
{int minChild = parent * 2 + 1;while (minChild < n){if (minChild + 1 < n && a[minChild + 1] < a[minChild]){minChild++;}if (a[minChild] < a[parent]){Swap(&a[minChild], &a[parent]);parent = minChild;minChild = parent * 2 + 1;}else{break;}}}
void Topk(int* a, int k,int n)
{int* minheap = (int*)malloc(k * sizeof(int));if (minheap == NULL){perror("malloc fail!");exit(-1);}for (int i = 0; i < k; i++){minheap[i] = a[i];}for (int j = (k - 2) / 2; j >= 0; --j){AdjustDown(minheap, k, j); //向下调整建小堆因为选的是前K大的数 ---如果选小数就建大堆}int k1 = k;//遍历k后的元素交换然后调整while (k <n){if (a[k] > minheap[0]){minheap[0] = a[k];AdjustDown(minheap, k, 0);}++k;}for (int i = 0; i < k1; ++i){printf("%d ", minheap[i]);}}
int main()
{int a[] = { 1,10,2,20,3,30,4,40,5,50 };int k = 5; //选出前5个大的数int n = sizeof(a) / sizeof(int); //数组元素个数Topk(a, k,n);return 0;
}

相关文章:

【数据结构】堆排序
堆是一种叫做完全二叉树的数据结构,可以分为大根堆,小根堆,而堆排序就是基于这种结构而产生的一种程序算法。大堆:每个节点的值都大于或者等于他的左右孩子节点的值小堆:每个结点的值都小于或等于其左孩子和右孩子结点…...

论文阅读笔记《GAMnet: Robust Feature Matching via Graph Adversarial-Matching Network》
核心思想 本文提出一种基于图对抗神经网络的图匹配算法(GAMnet),使用图神经网络作为生成器分别生成源图和目标图的节点的特征,并用一个多层感知机作为辨别器来区分两个特征是否来自同一个图,通过对抗训练的办法提高生成器特征提取…...

数据安全—数据完整性校验
1、数据安全保障三要素即 保密性 完整性、可用性机密性:要求数据不被他人轻易获取,需要进行数据加密。完整性:要求数据不被他人随意修改,需要进行签名技术可用性:要求服务不被他人恶意攻击,需要进行数据校验…...

Java 最小路径和
最小路径和中等给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。示例 1:输入:grid [[1,3,1],[1,5,1],[4,2,1]]输出&…...

Flask+VUE前后端分离的登入注册系统实现
首先Pycharm创建一个Flask项目: Flask连接数据库需要下载的包: pip install -U flask-cors pip install flask-sqlalchemy Flask 连接和操作Mysql数据库 - 王滚滚啊 - 博客园 (cnblogs.com) sqlAlchemy基本使用 - 简书 (jianshu.com) FlaskVue前后端分…...

【Go】用Go在命令行输出好看的表格
用Go在命令行输出好看的表格前言正文生成Table表头设置插入行表格标题自动标号单元格合并列合并行合并样式设置居中设置数字自动高亮标红完整Demo代码结语前言 最近在写一些运维小工具,比如批量进行ping包的工具,实现不困难,反正就是ping&am…...
怎么处理消息重发的问题?
消息队列在消息传递的过程中,如果出现传递失败的情况,发送方会重试,在重试的过程中,可能会产生重复的消息。 消息重复的情况必然存在 关于传递消息时能够提供的服务质量标准,MQTT协议给出了三种不同的标准࿱…...

JVM 运行时数据区(数据区组成表述,程序计数器,java虚拟机栈,本地方法栈)
JVM 运行时数据区JVM 运行时数据区3.1运行时的数据区组成概述3.1.1程度计数器3.1.2java虚拟机栈3.1.3本地方法栈3.1.4java堆3.1.5方法区3.2程序计数器3.3java虚拟机栈3.4本地方法栈JVM 运行时数据区 堆,方法区(元空间) 主要用来存放数据 是线程共享的. 程序计数器,本地方法栈…...
Oracle ASM磁盘组配置、日常运维、故障处理等操作资料汇总
ASM(自动存储管理)在数据库中是非常重要的组成部分,它可以为磁盘提供统一的存储管理、提高磁盘访问的性能和可用性、简化管理复杂度,从而为数据库的运行提供更好的支持。这里就为大家整理了墨天轮数据社区上一些ASM相关基础知识、…...

java对象的创建与内存分配机制
文章目录对象的创建与内存分配机制对象的创建类加载检查分配内存初始化零值设置对象头指向init方法其他:指针压缩对象内存分配对象在栈上分配对象在Eden区中分配大对象直接分配到老年代长期存活的对象进入老年代对象动态年龄判断老年代空间分配担保机制对象的内存回…...

本地存储localStorage、sessionStorage
目录 一、localStorage 二、sessionStorage 三、本地存储处理复杂数据 一、localStorage 介绍 (1)数据存储在用户浏览器中 (2)设置、读取方便、甚至页面刷新不会丢失数据 (3)容量较大,se…...
JavaSE: 网络编程
1.1 概述java程序员面对统一的网络编程环境B/S 架构 和 C/S架构1.2 网络通信的两个要素通信双方的地址:ip 端口号网络通信协议:TCP/IP协议(事实上的国际规则)、OSI模型(理想化)1.3 Inet Address本地回环地…...

计算机图形学09:二维观察之点的裁剪
作者:非妃是公主 专栏:《计算机图形学》 博客地址:https://blog.csdn.net/myf_666 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 文章目录专栏推荐专栏系列文章序一、二维观察基本…...
2023Java 并发编程面试题
Java 并发编程 1、在 java 中守护线程和本地线程区别? java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(boolon…...

CAD如何绘制A0/A1/A2/A3/A4图框?
在CAD制图时,设计师一般会使用企业的定制图框模板或者个人的特色图框模板,让设计方案更加标准化、规范化。对于新人设计师而言,完成CAD制图已经非常头疼了,图框的绘制更是手忙脚乱。那么是否有更加高效的方式来完成A0、A1、A2、A3…...
R 安装 “umap-learn“ python 包
首先需要在R中下载并读取reticulate包,该包提供了一系列R-Python的交互式命令由于之前在电脑中通过三个方式安装了Python:直接安装 Python 3.10安装Anaconda,携带3.9安装 Miniconda,又是另外一个版本的Python版本各不相同…...
测试同学如何快速开发测试平台?
转眼已经好几个月没有发表什么文章了,因为疫情原因,大家工作都不怎么顺利,没有什么心情。再者,最近一直在搞移动端精准测试的项目,有太多技术难点需要攻克。从各个网站上都找不到解决方案,只能不断地尝试&a…...
【程序员接口百宝箱】免费常用API接口
一、短信发送 短信的应用可以说是非常的广泛了,短信API也是当下非常热门的API~ 短信验证码:可用于登录、注册、找回密码、支付认证等等应用场景。支持三大运营商,3秒可达,99.99%到达率,支持大容量高并发。…...

使数组和能被P整除[同余定理+同余定理变形]
同余定理同余定理变形前言一、使数组和能被P整除二、同余定理变形总结参考资料前言 同余定理非常经典,采用前缀和 map,当两个余数前缀和为一个值时,则中间一段子数组刚好对P整除。但是能否找到前面是否有一段子数组和可以对P整除呐…...
25k的Java开发常问的Synchronized问题有哪些?
前言:面试高频的Synchronized问题大多集中在应用场景、底层实现原理、锁的升级过程。 文章目录 Synchronized定义应用场景对象加锁实现原理JDK6以前JDK6版本及以后对象从无锁到偏向锁转化的过程(大概讲五分钟)轻量级锁升级的过程(大概讲五分钟)自旋锁策略(大概讲五分钟)…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...