【算法】理解堆排序

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

(二插堆)可以用一种近似的完全二叉树来表示,该二叉树除了叶子结点之外,其余节点均具有两个子女,每一个节点都有一个用于排序的关键字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)
    在这里插入图片描述

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

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

相关文章

HCIP--RIP协议的实验 + RIP笔记

RIP实验&#xff1a; 实验思路&#xff1a; 1.规划IP&#xff0c;配置环回&#xff0c;接口IP 2.在3个路由器上跑通rip; 2.在边界路由器上用rip协议 设置缺省路由&#xff1b; [r3]rip [r3-rip-1]default-route originate 3.在r1、r2的主干接口上设置路由汇总 RIPV2手工汇…

MySQL数据库的约束

MySQL对于数据库存储的数据, 做出一些限制性要求, 就叫做数据库的"约束". 在每一列的 列名, 类型 后面加上"约束". 一. not null (非空) 指定某列不能存储null值. 二. unique (唯一) 保证这一列的每行必须有唯一值. 我们可以看到, 给 table 的 sn 列插…

Ubuntu系统配置DDNS-GO【笔记】

DDNS-GO 是一个基于 Go 语言的动态 DNS (DDNS) 客户端&#xff0c;用于自动更新你的 IP 地址到 DNS 记录上。这对于经常变更 IP 地址的用户&#xff08;如使用动态 IP 的家庭用户或者小型服务器&#xff09;非常有用。 此文档实验环境为&#xff1a;ubuntu20.04.6。 在Ubuntu…

基于Django的博客系统之登录增加忘记密码(八)

需求 描述&#xff1a; 用户忘记密码时&#xff0c;提供一种重置密码的方法&#xff0c;以便重新获得账户访问权限。规划&#xff1a; 创建一个包含邮箱输入字段的表单&#xff0c;用于接收用户的重置密码请求。用户输入注册时使用的邮箱地址&#xff0c;系统发送包含重置密码…

量产导入 | 芯片测试介绍可靠性测试

作者:桃芯科技链接:https://picture.iczhiku.com/weixin/message1583129221975.html半导体芯片的defects、Faults 芯片在制造过程中,会出现很多种不同类型的defects,比如栅氧层针孔、扩散工艺造成的各种桥接、各种预期外的高阻态、寄生电容电阻造成的延迟等等,如下面图(1)…

Spring高手之路19——Spring AOP注解指南

文章目录 1. 背景2. 基于AspectJ注解来实现AOP3. XML实现和注解实现AOP的代码对比4. AOP通知讲解5. AOP时序图 1. 背景 在现代软件开发中&#xff0c;面向切面编程&#xff08;AOP&#xff09;是一种强大的编程范式&#xff0c;允许开发者跨越应用程序的多个部分定义横切关注点…

数据隐私重塑:Web3时代的隐私保护创新

随着数字化时代的不断深入&#xff0c;数据隐私保护已经成为了人们越来越关注的焦点之一。而在这个数字化时代的新篇章中&#xff0c;Web3技术作为下一代互联网的代表&#xff0c;正在为数据隐私保护带来全新的创新和可能性。本文将深入探讨数据隐私的重要性&#xff0c;Web3时…

解锁数据宝藏:高效查找算法揭秘

代码下载链接&#xff1a;https://gitee.com/flying-wolf-loves-learning/data-structure.git 目录 一、查找的原理 1.1 查找概念 1.2 查找方法 1.3平均查找长度 1.4顺序表的查找 1.5 顺序表的查找算法及分析 1.6 折半查找算法及分析 1.7 分块查找算法及分析 1.8 总结…

很多人讲不明白HTTPS,但是我能

很多人讲不明白HTTPS&#xff0c;但是我能 今天我们用问答的形式&#xff0c;来彻底弄明白HTTPS的过程 下面的问题都是 小明和小丽两个人通信为例 可以把小明想象成服务端&#xff0c;小丽想象成客户端 1. https是做什么用的&#xff1f; 答&#xff1a;数据安全传输用的。…

数学建模 —— 聚类分析(3)

目录 一、聚类分析概述 1.1 常用聚类要素的数据处理 1.1.1 总和标准化 1.1.2 标准差标准化 1.1.3 极大值标准化 1.1.4 极差的标准化 1.2 分类 1.2.1 快速聚类法&#xff08;K-均值聚类&#xff09; 1.2.2 系统聚类法&#xff08;分层聚类法&#xff09; 二、分类统计…

Ubuntu18.04安装pwntools报错解决方案

报错1&#xff1a;ModuleNotFoundError: No module named ‘setuptools_rust’ 报错信息显示ModuleNotFoundError: No module named setuptools_rust&#xff0c;如下图所示 解决方案&#xff1a;pip install setuptools_rust 报错2&#xff1a;pip版本低 解决方案&#xff…

【数据结构(邓俊辉)学习笔记】图02——搜索

文章目录 0. 概述1. 广度优先搜索1.1 策略1.2 实现1.3 可能情况1.4 实例1.5 多联通1.6 复杂度1.7 最短路径 2. 深度优先搜索2.1 算法2.2 框架2.3 细节2.4 无向边2.5 有向边2.6 多可达域2.7 嵌套引理 3 遍历算法的应用 0. 概述 此前已经介绍过图的基本概念以及它在计算机中的表…

设计模式(十四)行为型模式---访问者模式(visitor)

文章目录 访问者模式简介分派的分类什么是双分派&#xff1f;结构UML图具体实现UML图代码实现 优缺点 访问者模式简介 访问者模式&#xff08;visitor pattern&#xff09;是封装一些作用于某种数据结构中的元素的操作&#xff0c;它可以在不改变这个数据结构&#xff08;实现…

Visual Studio Installer 点击闪退

Visual Studio Installer 点击闪退问题 1. 问题描述2. 错误类型3. 解决方法4. 结果5. 说明6. 参考 1. 问题描述 重装了系统后&#xff08;系统版本&#xff1a;如下图所示&#xff09;&#xff0c;我从官方网站&#xff08;https://visualstudio.microsoft.com/ ) 下载了安装程…

Three.js-实现加载图片并旋转

1.实现效果 2. 实现步骤 2.1创建场景 const scene new THREE.Scene(); 2.2添加相机 说明&#xff1a; fov&#xff08;视场角&#xff09;&#xff1a;视场角决定了相机的视野范围&#xff0c;即相机可以看到的角度范围。较大的视场角表示更广阔的视野&#xff0c;但可能…

如何在镜像中安装固定版本的node和npm

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用 Dockerfile 创建自定义镜像二、如何安装固定版本的node及npm总结 前言 最近在做前端工程化相关的内容&#xff0c;需要在一个镜像内安装固定版本的 N…

Microservices with Martin Fowler

Summary The article “Microservices” by Martin Fowler discusses an architectural style for software systems that has been gaining popularity due to its flexibility and scalability. Here’s a summary highlighting the key points: Microservice Architectural…

十_信号4-SIGCHLD信号

SIGCHLD信号 在学习进程控制的时候&#xff0c;使用wait和waitpid系统调用何以回收僵尸进程&#xff0c;父进程可以阻塞等待&#xff0c;也可以非阻塞等待&#xff0c;采用轮询的方式不停查询子进程是否退出。 采用阻塞式等待&#xff0c;父进程就被阻塞了&#xff0c;什么都干…

【魅力网页的背后】:CSS基础魔法,从零打造视觉盛宴

文章目录 &#x1f680;一、css基础知识⭐1. 认识css &#x1f308;二、选择器初级❤️id与class命名 &#x1f680;一、css基础知识 ⭐1. 认识css 概念 CSS(英文全称&#xff1a;Cascading Style Sheets)&#xff0c;层叠样式表。它是网页的装饰者&#xff0c;用来修饰各标签…

YOLOv5改进(六)--引入YOLOv8中C2F模块

文章目录 1、前言2、C3模块和C2F模块2.1、C3模块2.2、BottleNeck模块2.3、C2F模块 3、C2F代码实现3.1、common.py3.2、yolo.py3.3、yolov5s_C2F.yaml 4、目标检测系列文章 1、前言 本文主要使用YOLOv8的C2F模块替换YOLOv5中的C3模块&#xff0c;经过实验测试&#xff0c;发现Y…