【算法】理解堆排序

堆排序,无疑与堆这种数据结构有关。在了解堆排序之前,我们需要先了解堆的建立与维护方法。

(二插堆)可以用一种近似的完全二叉树来表示,该二叉树除了叶子结点之外,其余节点均具有两个子女,每一个节点都有一个用于排序的关键字key。根据堆顶元素性质,堆可以分为大根堆和小根堆。对于大根堆而言,其堆顶是整棵树最大的节点,并且以其为祖先的每一个节点均是一个大根堆。小根堆反之亦然。堆排序采用大根堆完成,所以我们下面用大根堆来介绍堆的建立。

用一个长为 n n n 的数组表示一棵近似完全二叉树,其下标从0n-1。那么对于其中的每一个节点,其父节点、左右子女节点可如下表示:

p a r e n t ( i ) = ( i − 1 ) / 2 parent(i) = (i - 1)/2 parent(i)=(i1)/2
l e f t ( i ) = 2 i + 1 left(i) = 2i + 1 left(i)=2i+1
r i g h t ( i ) = 2 i + 2 right(i) = 2i + 2 right(i)=2i+2
显然,随便拿到的一个数组通常不具备最大堆的性质。以其中一个节点 i 为例,该节点有可能不是以该节点为根的子树中的最大节点。对此,我们的策略是,只要让每一个节点i,均比自己的左右子女大,那么就可以建立起来一个大根堆。

因此,我们考虑对每一个节点施加一种操作,使该索引上的节点满足大于左右子女的性质。这个操作被称之为堆化操作max-healpify

堆化

堆化操作的步骤是,首先确定节点i左右子女中的最大值,然后将其与左右子女相比较,如果比左右子女小,则该节点不符合要求,需要与较大的那个子女相交换。原目标节点被交换到其子女位置后,可能仍旧比其当前子女小,这样相当于破坏了节点i子女的最大堆性质。因此需要继续跟新子女比较,并根据比较结果向下交换,直到其比子女大为止。

这样的max-heapify操作,既保证了i节点符合最大堆性质,又不会破坏其子女的最大堆的性质。示例代码如下:

void max_heapify(int *arr, int n, int i){int l = i * 2 + 1, r = i * 2 + 2;if (r >= n) return;int max_child = arr[l] >= arr[r] ? l : r;if (arr[i] < arr[max_child]) {swap(arr[i], arr[max_child]);max_heapify(arr, n, max_child);}
}

堆的建立

max-heapify操作的效果是,使目标节点i大于左右子女,并且不破坏以其为根节点的子树的所有子节点的最大堆性质。也就是说,如果以其为根的子树的其余节点,如果不符合最大堆性质,那么堆化操作实际上是失败的。

因此如果我们希望将任意一个数组转化为最大堆,应当自底向上对每一个节点执行堆化操作。由于,。在近似完全二叉树中,最后一个非叶子结点的索引是n/2。示例代码如下:

void make_heap(int *arr, int n){for (int i = n - 1; i >= 0; --i) {max_heapify(arr, n, i);}
}

建堆的时间复杂度是 O ( n ) O(n) O(n)

堆性质的维护

最大堆提供抽取堆顶元素的操作。当最大堆的堆顶被删除时,堆大小减1。此时应当将堆的最后一个元素移至堆顶,然后对堆顶节点执行max-heapify操作,从而维护最大堆性质。

int pop(int *arr, int n) {int top = arr[0];arr[0] = arr[n - 1];max_heapify(arr, n - 1);return top;
}

使用C++标准库去处理堆

C++在<algorithm>头文件中提供了对数据结构的支持。主要包括以下几个API。C++的泛型算法库基于范围range概念对数据进行操作。一个范围可以由一对迭代器beginend表示,这个范围的具体区间是左闭右开的[begin, end)

1. make_heap 建堆

void make_heap( RandomIt first, RandomIt last, Compare comp );

该函数可以将一个范围,按照对应的比较器构建成堆。比较器默认为operator<(),且根据该小于运算符构建一个最大堆。如果想构建一个最小堆,可以提供一个大于运算对象std::greater<>()作为比较器。

2. push_heap 向堆中插入元素

void push_heap( RandomIt first, RandomIt last, Compare comp );

该函数将一个元素插入堆中,并使[first, last + 1)构建成堆。堆的大小此时相比之前+1。

3. pop_heap 删除堆顶

void pop_heap( RandomIt first, RandomIt last, Compare comp );

该函数将堆顶元素与last - 1表示的元素进行交换,并使范围[first, last - 1)维持堆的性质。注意,堆的大小减小,但是原堆顶元素仍然停留在容器中,没有被删除。举个例子,最大堆[9 5 4 1 1 3]经过pop_heap后,将变为[5 3 4 1 1 9],可以看到9仍旧停留在容器中,堆的范围减1。

4. 堆性质检验

bool is_heap( RandomIt first, RandomIt last, Compare comp );

如果范围是关于对应比较器的堆就返回 true,否则返回 false。

堆排序

堆排序是借助数据结构进行的一种基于交换的排序算法,其操作步骤是,对于一个长为n的数组,首先建立最大堆。然后在第i次循环中,将堆顶与堆底交换,堆的大小减1,直至堆的大小为1。这样可以逐步将一个升序数组中的较大元素按倒序填充到数组尾部。示例代码如下:

void heap_sort(int *arr, int n){make_heap(arr, n);for (int i = n - 1; i >= 1; --i) {swap(arr[0], arr[i]);max_heapify(arr, i, 0);}
}

堆排序的时间复杂度为 O ( n l g n ) O(nlgn) O(nlgn)

技巧应用

面试题 17.14. 最小K个数 - 力扣(LeetCode)

设计一个算法,找出数组中最小的k个数。以任意顺序返回这k个数均可。

示例:
输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]
提示:
0 <= len(arr) <= 100000
0 <= k <= min(100000, len(arr))

还是这道题。在之前的一节中,我们利用快速排序的划分思想,使数组中索引为kk-1的元素,其前面的元素一定比它小,后面的元素一定比它大,进而获取前k小数字。这一节,我们利用堆的思想,去换一种方法解决这个问题。

首先,直观一点来处理,我们可以将这个数组转换为最小堆,依次从这个最小堆中弹出k个数,即为前k小的数。示例代码如下:

class Solution {
public:// 小根堆解法,堆排序vector<int> smallestK(vector<int>& arr, int k) {if (arr.empty() || arr.size() < k) return {};make_heap(arr.begin(), arr.end(), std::greater<int>());vector<int> rtn;while(k--) {pop_heap(arr.begin(), arr.end(), std::greater<int>());rtn.push_back(arr.back());arr.pop_back();}return rtn;}
};
  • 时间复杂度: O ( n l g n ) O(nlgn) O(nlgn)
  • 空间复杂度: O ( k ) O(k) O(k)

一种更好的思路是,使用最大堆来处理。在一开始,我们将数组的前k个元素放入结果数组中,并建立最大堆。此后,对于区间[k, n - 1]的数组元素,依次与堆顶进行比较。若比堆顶大,则其势必不在前k小数组中。若其比堆顶小,则其有可能为前k小数字,我们将该数设置为新堆顶,并对堆顶执行max-heapify操作。如此到最后,由于不要求按序返回结果,我们直接返回堆数组即可。示例代码如下:
`

class Solution {
public:// 最大堆解法vector<int> smallestK(vector<int>& arr, int k) {if(arr.empty() || arr.size() < k || k == 0) return {};vector<int> rtn(arr.begin(), arr.begin() + k);make_heap(rtn.begin(), rtn.end());for (int i = k; i < arr.size(); ++i) {if (arr[i] < rtn[0]) {pop_heap(rtn.begin(), rtn.end());rtn[k - 1] = arr[i];push_heap(rtn.begin(), rtn.end());}}return rtn;}
};
  • 时间复杂度: O ( n l g k ) O(nlgk) O(nlgk)
  • 空间复杂度: O ( k ) O(k) O(k)
    在这里插入图片描述

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

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

相关文章

模板-初阶

引言&#xff1a; 在C&#xff0c;我们已经学过了函数重载&#xff0c;这使得同名函数具有多个功能。但是还有一种更省力的方法&#xff1a;采用模板。 本文主要介绍以下内容 1. 泛型编程 2. 函数模板 3. 类模板 1.泛型编程 在将这一部分之前&#xff0c;通过一个故事引…

nginx的配置粗记

小白nginx的配置随笔&#xff08;随便记记&#xff09; 前言 我们都知道nginx有很多用途&#xff0c;比如&#xff1a;负载均衡&#xff0c;反向代理&#xff0c;网关路由&#xff0c;解决跨域等问题。我这次开发项目&#xff0c;用到的一些功能也涉及到了对nginx的配置&#…

Vue.js 动态组件与异步组件

title: Vue.js 动态组件与异步组件 date: 2024/6/2 下午9:08:50 updated: 2024/6/2 下午9:08:50 categories: 前端开发 tags:Vue概览动态组件异步加载性能提升路由管理状态控制工具生态 第1章 Vue.js 简介 1.1 Vue.js 概述 Vue.js 是一个渐进式的JavaScript框架&#xff0c;…

MedSAM 学习笔记(续):训练自定义数据集

1、下载官方权重 官方的预训练权重:https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth 下载后保存在:work_dir/SAM/sam_vit_b_01ec64.pth 目录 2、摆放数据集 因为MedSAM 分割模型需要对3D数据集进行切片处理,也就是对nii.gz 数据处理成 npy 格式 …

Linux--构建进程池

目录 1.进程池 1.1.我们先完成第一步&#xff0c;创建子进程和信道 1.2. 通过channel控制&#xff0c;发送任务 1.3回收管道和子进程 1.4进行测试 1.5完整代码 1.进程池 进程池其产生原因主要是为了优化大量任务需要多进程完成时频繁创建和删除进程所带来的资源消耗&#…

Mysql(一)查询Sql是如何执行的

Hello&#xff0c;大家好我是极客涛&#x1f60e;&#xff0c;我最近在整理Mysql相关的知识点&#xff0c;所以准备开启一个Mysql的主线任务&#xff0c;大概耗时3周左右&#xff0c;整个节奏还是由浅入深&#xff0c;主要包括Mysql的架构、事务实现、索引组织形式、SQL优化、日…

图解大模型分布式并行各种通信原语

背景 在分布式集群上执行大模型任务时候&#xff0c;往往使用到数据并行&#xff0c;流水线并行&#xff0c;张量并行等技术&#xff0c;这些技术本质上也就是对数据进行各种方案的切分&#xff0c;然后放到不同的节点上运算。不同节点在计算的过程中需要对数据分发或者同步等…

python的一种集成开发工具:PyCharm开发工具

一. 简介 本文简单了解两种 python语言所使用的 集成开发环境&#xff1a; PyCharm、vscode。 python语言学习中&#xff0c;可以任意选中这两个集成开发环境的一种就可以。本文先来简单学习 PyCharm开发工具安装与使用。 二. python的一种集成开发工具&#xff1a;PyChar…

实现Redis和数据库数据同步问题(JAVA代码实现)

这里我用到了Redis当中的发布订阅模式实现(JAVA代码实现) 先看图示 下面为代码实现 首先将RedisMessageListenerContainer交给Spring管理. Configuration public class redisConfig {AutowiredRedisConnectionFactory redisConnectionFactory;AutowiredQualifier("car…

Linux线程:线程分离

目录 一、什么是线程分离 1.1pthread_detach 1.2pthread线程库存在的意义 1.3__thread线程的局部存储 1.4系统调用clone 一、什么是线程分离 1.1pthread_detach 默认情况下&#xff0c;新创建的线程是joinable的&#xff0c;线程退出后&#xff0c;需要对其进行pthread_joi…

数据标准的制定落地

目录 什么是数据标准 基本定义 目的 数据标准体系分类 从内容层面分类 从管理视角分类 从面向的对象分类 从数据结构的角度分类 数据标准价值 业务价值 技术价值 管理价值 数据标准和数据治理的关系 数据标准在数据治理各项任务中的作用 数据标准与主数据 数据…

车联网安全入门——ICSim模拟器使用

文章目录 车联网安全入门——ISCim模拟器使用介绍主要特点&#xff1a;使用场景&#xff1a; 安装使用捕获can流量candumpcansnifferwiresharkSavvyCAN主要特点&#xff1a;使用场景&#xff1a; 重放can报文cansendSavvyCAN 总结 车联网安全入门——ISCim模拟器使用 &#x1…

LabVIEW步进电机的串口控制方法与实现

本文介绍了在LabVIEW环境中通过串口控制步进电机的方法&#xff0c;涵盖了基本的串口通信原理、硬件连接步骤、LabVIEW编程实现以及注意事项。通过这些方法&#xff0c;用户可以实现对步进电机的精确控制&#xff0c;适用于各种自动化和运动控制应用场景。 步进电机与串口通信…

【刷题(15】普通数组

一 普通数组基础 首先&#xff0c;我们根据下图先了解一下什么是前缀和。 既然我们明白了前缀和是怎么回事&#xff0c;那我们就来看一下我们该怎么输入 先给出答案&#xff0c;然后再给出分析。 答案&#xff1a; for (int i 1; i < n; i ){cin >> a[i];s[i] s…

Pytest框架中用例用例执行常用参数介绍

pytest 支持通过命令行参数来定制测试运行的方式。以下是一些常用的 pytest 执行参数介绍。 学习目录 -q 或 --quiet: 安静模式&#xff0c;只显示进度和摘要 -s : 选项允许在测试的输出中捕获 stdout 和 stderr。 -v : 选项会使 pytest 的输出更加详细。 -k &#xff1a;…

DIYP对接骆驼后台IPTV管理,退出菜单中显示用户名已经网络信息,MAC,剩余天数,套餐名称等

演示&#xff1a;https://url03.ctfile.com/f/1779803-1042599473-4dc000?p8976 (访问密码: 8976) 后台加上EPG&#xff0c;增加一些播放源的动态端口替换。 前台app上&#xff0c;退出菜单中显示用户名已经网络信息&#xff0c;MAC&#xff0c;剩余天数&#xff0c;套餐名称…

QT之常用控件

一个图形化界面当然需要有各种各样的控件&#xff0c;QT也不例外&#xff0c;在QT designer中就有提供各种各样的控件&#xff0c;用以开发图形化界面。 而想使用好一个QT控件&#xff0c;就需要了解这些控件。 QWidget 在QT中&#xff0c;所有控件都继承自 QWidget 类&…

中间件模版引擎

文章目录 中间件1.自定义中间件1&#xff09;全局2&#xff09;局部中间件 2.内置中间件(静态资源目录&#xff09; Art-template1.模板语法1&#xff09;输出2&#xff09;原文输出3&#xff09;条件判断4&#xff09;循环5&#xff09;子模版6&#xff09;模版继承7&#xff…

git远程仓库限额的解决方法——大文件瘦身

Git作为世界上最优秀的分布式版本控制工具&#xff0c;也是优秀的文件管理工具&#xff0c;它赋予了项目成员对项目进行远程协同开发能力&#xff0c;因此受到越来越多的行业从业人员的喜爱。很多优秀的项目管理平台&#xff0c;比如国内的Gitee&#xff0c;国外的Github&#…

Django表单革命:打造安全、高效、用户友好的Web应用

Django表单处理&#xff0c;听起来是不是有点枯燥&#xff1f;别急&#xff0c;阿佑将带你领略Django表单的艺术之美。我们将以轻松幽默的语言&#xff0c;一步步引导你从表单的创建到管理&#xff0c;再到验证和自定义&#xff0c;让你在不知不觉中掌握Django表单的精髓。文章…