数据结构——堆,堆排序

前提

我们都知道内存分布中的堆区(Heap section)new出来的空间都在堆区上。和堆区有一个名字很相近的数据结构——堆(Heap),虽然名称相近,但两者是完全不同的东西。

因为十大排序算法中有一个堆排序,所以从头开始了解下这个数据结构, 终学习下堆排序算法

堆——数据结构

堆是什么

堆的本质是完全二叉树,只不过要在完全二叉树上加一些限制条件。根据加的限制条件的不同,堆又被分为大顶堆小顶椎

大顶堆

大顶堆:任意节点的值>=其子节点的值。
如下所示:
在这里插入图片描述

小顶堆

小顶堆:任意节点的值<=其子节点的值。
如下所示:
在这里插入图片描述

堆的实现

接下来我们实现一个大顶堆。

堆的存储与表示

堆的本质是完全二叉树,树形结构可以使用数组链表进行存储。但是对于完全二叉树来说,使用数组存储更为合适。为什么呢?接下来做个简单分析。
在这里插入图片描述
如果使用数组进行存储,头节点放在数组中下标为0的位置,剩于的所有节点顺序在数组中存放。
这样当我们访问下标为i的结点时:

  • 可以使用2i+i访问它的左子节点
  • 使用2i+2访问它的右子节点
  • 使用 (i-1)/2 访问它的节点
  • 同时,如果计算出的下标大于数组的容量,代表没有此节点

访问堆顶元素

我们知道对于堆来说,堆顶元素存储在数组的0号下标,所以直接返回即可。

元素入堆

要给堆添加元素时,添加的元素可能会破坏堆的成立条件 (对于大顶堆,任意节点的值>=其子节点的值) 。因此添加元素到堆底后,我们要调整堆中节点的位置。

那么一个简单的思路是:比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。

这里最坏的清况是:插入的节点是新的根节点。所以如果整个堆有 n n n 个节点,二叉树高 O ( l o g n ) O(logn) O(logn),此时的时间复杂度为 O ( l o g n ) O(logn) O(logn)。最好的清况是:插入的节点不需要移动。此时的时间复杂度为 O ( 1 ) O(1) O(1)

堆顶元素出堆

堆顶元素出堆后,新的堆顶元素是谁?而且由于堆中少了一个元素,所以堆中剩于元素在数组中的索引会发生变化,所以如何确定新的元素索引呢?

这里的算法为:

  1. 交换堆顶元素与堆底元素(交换根节点与最右叶节点)。
  2. 交换完成后,将堆底从列表中删除(注意,由于已经交换,因此实际上删除的是原来的堆顶元素)。
  3. 从根节点开始,从顶至底执行堆化。

实现代码

#include <iostream>
#include <vector>template <typename T> class Heap {
public:Heap() = default;T peek() { /* Get the top element of the heap */if (data.empty())throw std::runtime_error("Heap is empty");return data[0];}void push(T val) { /* Insert an element into the heap */data.push_back(val);siftUp(data.size() - 1);}void pop() { /* Remove the top element of the heap */if (data.empty()) {throw std::runtime_error("Heap is empty");}std::swap(data[0], data[data.size() - 1]);data.pop_back();siftDown(0);}int size() { return data.size(); }private:std::vector<T> data;inline int left(int i) { return 2 * i + 1; }     // Get the left child of iinline int right(int i) { return 2 * i + 2; }    // Get the right child of iinline int parent(int i) { return (i - 1) / 2; } // Get the parent of ivoid siftUp(int i) {                           // Move the element up the heapwhile (i > 0 && data[parent(i)] < data[i]) { /* Compare with the parent */std::swap(data[parent(i)], data[i]);i = parent(i);}}void siftDown(int i) { // Move the element down the heapwhile (true) {int l = left(i), r = right(i), ma = i;if (l < data.size() && data[l] > data[ma]) {ma = l;}if (r < data.size() && data[r] > data[ma]) {ma = r;}std::swap(data[i], data[ma]);if (ma == i) {break;}i = ma;}}
};int main() {Heap<int> heap;heap.push(1);heap.push(2);heap.push(3);heap.push(4);heap.push(5);std::cout << "Heap size: " << heap.size() << std::endl;std::cout << "Heap top: " << heap.peek() << std::endl;heap.pop();std::cout << "Heap size: " << heap.size() << std::endl;std::cout << "Heap top: " << heap.peek() << std::endl;
}

优先队列

堆通常可以用来实现优先队列,优先队列(Priority Queue)是一种抽象数据类型,类似于普通的队列或堆栈数据结构,但每个元素都有一个优先级优先级高的元素会优先出队(被处理),而不是按照元素入队的顺序来处理。

堆排序

如果你明白上面的内容后,那么堆排序就比较简单了。
流程如下:

  1. 将给定的无序数组建成一个堆(假定是大顶堆)
  2. 将堆顶元素弹出
  3. 重新对堆中剩于的元素堆化
  4. 重复2和3,直到堆中无元素

这里存在一个问题:如何将给定的无序数组建成一个堆?
一个很笨的方法是:我们将无序数组中的元素一个一个取出来,然后push到堆中。伪代码如下:

  std::vector<int> vec{1, 3, 5, 2, 0, 4};Heap<int> heap;for (int i = 0; i < vec.size(); i++) {heap.push(vec[i]);}

如果有 n n n个节点的话,这里的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)


参考链接2,有一个时间复杂度为 O ( n ) O(n) O(n)的建堆方法。

堆排序代码

简单思路版本:

/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */
void siftDown(std::vector<int> &nums, int n, int i) {while (true) {// 判断节点 i, l, r 中值最大的节点,记为 maint l = 2 * i + 1;int r = 2 * i + 2;int ma = i;if (l < n && nums[l] > nums[ma])ma = l;if (r < n && nums[r] > nums[ma])ma = r;// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出if (ma == i) {break;}// 交换两节点std::swap(nums[i], nums[ma]);// 循环向下堆化i = ma;}
}/* 堆排序 */
std::vector<int> heapSort(std::vector<int> &nums) {// 建堆操作:堆化除叶节点以外的其他所有节点for (int i = nums.size() / 2 - 1; i >= 0; --i) {siftDown(nums, nums.size(), i);}std::vector<int> res; /* 用来收集堆顶元素 */// 从堆中提取最大元素for (int i = nums.size() - 1; i >= 0; --i) {// 交换根节点与最右叶节点(交换首元素与尾元素)std::swap(nums[0], nums[nums.size() - 1]);res.push_back(nums.back());nums.pop_back(); // 将堆顶元素弹出siftDown(nums, nums.size(), 0);  // 重新对堆中剩于的元素堆化}return res;
}int main() {std::vector<int> vec{1, 3, 5, 2, 0, 4};std::vector<int> res = heapSort(vec);for (int i = 0; i < res.size(); i++) {std::cout << res[i] << " ";}
}

上面的这种写法要多定义一个空间复杂度为 O ( n ) O(n) O(n)的结果数组,用来保存结果,这会造成额外的空间浪费。

那么有没有方法可以建堆后,直接对进行排序。

答案是有的,每次将堆的顶点和堆底交换后,顶点元素本是要弹出的(目的是对剩于的堆中的元素重新进行排序)。我们可以弹出操作变为动态减少堆的大小。代码如下:

/* 堆的长度为 n ,从节点 i 开始,从顶至底堆化 */
void siftDown(std::vector<int> &nums, int n, int i) {while (true) {// 判断节点 i, l, r 中值最大的节点,记为 maint l = 2 * i + 1;int r = 2 * i + 2;int ma = i;if (l < n && nums[l] > nums[ma])ma = l;if (r < n && nums[r] > nums[ma])ma = r;// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出if (ma == i) {break;}// 交换两节点std::swap(nums[i], nums[ma]);// 循环向下堆化i = ma;}
}/* 堆排序 */
void heapSort(std::vector<int> &nums) {// 建堆操作:堆化除叶节点以外的其他所有节点for (int i = nums.size() / 2 - 1; i >= 0; --i) {siftDown(nums, nums.size(), i);}// 从堆中提取最大元素,循环 n-1 轮, because the last element is the smallestfor (int i = nums.size() - 1; i > 0; --i) {// 交换根节点与最右叶节点(交换首元素与尾元素)std::swap(nums[0], nums[i]);siftDown(nums, i, 0); // every loop, the heap size is reduced by 1}
}int main() {std::vector<int> vec{1, 3, 5, 2, 0, 4};heapSort(vec);for (int i = 0; i < vec.size(); i++) {std::cout << vec[i] << " ";}
}

所以堆排序的时间复杂度应为 O ( n l o g n ) O(nlogn) O(nlogn),因为for循环n-1次,每一次堆化的复杂度为 O ( l o g n ) O(logn) O(logn)

参考链接

  1. https://www.hello-algo.com/chapter_heap/heap/#3
  2. https://www.hello-algo.com/chapter_heap/build_heap/#821

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

JAVASE-医疗管理系统项目总结

文章目录 项目功能架构运行截图数据库设计设计模式应用单列设计模式JDBC模板模板设计模式策略模式工厂设计模式事务控制代理模式注解开发优化工厂模式 页面跳转ThreadLocal分页查询实现统计模块聊天 项目功能架构 传统的MVC架构&#xff0c;JavaFX桌面端项目&#xff0c;前端用…

Linux如何正确安装MySQL数据库

对于Linux安装mysql,如果大家有不会的可以来参考小编的详细安装步骤哦&#xff0c;小编带你一步步走向成功~ 首先对于Linux系统&#xff0c;我们通过小编的上一篇文章中知道安装软件的命令为wget&#xff0c;所以首先需要写出命令获取mysql&#xff1a; wget https://cdn.mys…

高频面试题-CSS

BFC 介绍下BFC (块级格式化上下文) 1>什么是BFC BFC即块级格式化上下文&#xff0c;是CSS可视化渲染的一部分, 它是一块独立的渲染区域&#xff0c;只有属于同一个BFC的元素才会互相影响&#xff0c;且不会影响其它外部元素。 2>如何创建BFC 根元素&#xff0c;即HTM…

【Git远程操作】向远程仓库推送 | 拉取远程仓库

目录 1.向远程仓库推送 ​1.1本地仓库的配置 1.2remote-gitcode本地仓库 1.3推送至远程仓库 2.拉取远程仓库 现阶段以下操作仅在master主分支上。 1.向远程仓库推送 工作区☞add☞暂存区☞commit☞本地仓库☞推送push☞远程仓库注意&#xff1a;本地仓库的某个分支 ☞推…

趣谈linux操作系统 9 网络系统-读书笔记

文章目录 网络协议栈基础知识回顾网络分层网络分层的目的各层作用简介延伸-ip地址,有类,无类,cidr socket实现分析tcp/udp回顾socket编程回顾TCP编程回顾UDP编程回顾差异 socket相关接口实现浅析sokcet实现解析创建socket的三个参数socket函数定义及其参数创建socket结构体关联…

小白新手搭建个人网盘

小白新手搭建个人网盘 序云服务器ECS重置密码远程连接ECS实例 安装OwnCloud安装Apache服务PHP运行环境NAS挂载挂载验证操作体验 序 阿里云文件存储NAS&#xff08;Apsara File Storage NAS&#xff09;是一个可大规模共享访问&#xff0c;弹性扩展的分布式文件系统。本文主要是…

(三)原生js案例之滚动到底部解锁按钮状态

业务主要是注册页面&#xff0c;有很长的条款需要用户去读&#xff0c;必须确认用户是不是看到全部的条款&#xff0c;这个场景下可以使用 效果 代码实现 必要的css <style>*{padding: 0;margin: 0;}ul{list-style: none;width: 330px;height: 100%;/* height: 200px;…

Adobe国际认证详解-影视后期

在当今的数字媒体时代&#xff0c;影视后期制作作为创意产业的核心环节&#xff0c;对于专业技能的要求日益提高。Adobe国际认证&#xff0c;作为全球创意设计领域的重要标杆&#xff0c;为影视后期制作人员提供了一个展示自我、提升技能的国际舞台。 何为影视后期&#xff1f;…

在学习使用LabVIEW的过程中,需要注意哪些问题?

在学习使用LabVIEW的过程中&#xff0c;需要注意以下问题&#xff1a; 1. 基础知识 图形化编程思维&#xff1a; LabVIEW采用图形化编程方式&#xff0c;与传统的文本编程语言有很大不同&#xff0c;需要适应这种新的编程思维方式。数据流概念&#xff1a; 理解LabVIEW的核心数…

【Git标签管理】理解标签 | 创建标签 | 查看标签 | 删除标签 | 推送标签

目录 1.理解标签 2.创建标签 3.查看标签 4.删除本地仓库的标签 5.推送标签 6.删除远程仓库的标签 1.理解标签 Git提供一个打标签的功能tag&#xff0c;对某一次事务/提交的表示&#xff08;作用/意义&#xff09;。标签 tag &#xff0c;可以简单的理解为是对某次 comm…

免费的数字孪生平台助力产业创新,让新质生产力概念有据可依

关于新质生产力的概念&#xff0c;在如今传统企业现代化发展中被反复提及。 那到底什么是新质生产力&#xff1f;它与哪些行业存在联系&#xff0c;我们又该使用什么工具来加快新质生产力的发展呢&#xff1f;今天我将介绍一款为发展新质生产力而量身定做的数字孪生工具。 新…

Ubuntu 24.04 LTS 桌面安装MT4或MT5 (MetaTrader)教程

运行脚本即可在 Ubuntu 24.04 LTS Noble Linux 上轻松安装 MetaTrader 5 或 4 应用程序&#xff0c;使用 WineHQ 进行外汇交易。 MetaTrader 4 (MT4) 或 MetaTrader 5 是用于交易外汇对和商品的流行平台。它支持各种外汇经纪商、内置价格分析工具以及通过专家顾问 (EA) 进行自…

酷炫末世意境背景404单页HTML源码

源码介绍 酷炫末世意境背景404单页HTML源码&#xff0c;背景充满着破坏一切的意境&#xff0c;彷佛末世的到来&#xff0c;可以做网站错误页或者丢失页面&#xff0c;将下面的代码放到空白的HTML里面&#xff0c;然后上传到服务器里面&#xff0c;设置好重定向即可 效果预览 …

PyTorch 深度学习实践-循环神经网络(高级篇)

视频指路 参考博客笔记 参考笔记二 文章目录 上课笔记总代码练习 上课笔记 个人能力有限&#xff0c;重看几遍吧&#xff0c;第一遍基本看不懂 名字的每个字母都是一个特征x1,x2,x3…&#xff0c;一个名字是一个序列 rnn用GRU 用ASCII表作为词典&#xff0c;长度为128&#x…

OceanBase:引领下一代分布式数据库技术的前沿

OceanBase的基本概念 定义和特点 OceanBase是一款由蚂蚁金服开发的分布式关系数据库系统&#xff0c;旨在提供高性能、高可用性和强一致性的数据库服务。它结合了关系数据库和分布式系统的优势&#xff0c;适用于大规模数据处理和高并发业务场景。其核心特点包括&#xff1a; …

源码分析SpringCloud Gateway如何加载断言(predicates)与过滤器(filters)

我们今天的主角是Gateway网关&#xff0c;一听名字就知道它基本的任务就是去分发路由。根据不同的指定名称去请求各个服务&#xff0c;下面是Gateway官方的解释&#xff1a; Spring Cloud Gateway&#xff0c;其他的博主就不多说了&#xff0c;大家多去官网看看&#xff0c;只…

<数据集>蛋壳裂缝检测数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;2520张 标注数量(xml文件个数)&#xff1a;2520 标注数量(txt文件个数)&#xff1a;2520 标注类别数&#xff1a;2 标注类别名称&#xff1a;[crack, egg] 序号类别名称图片数框数1crack245128352egg25142514 使…

最新 Docker 下载镜像超时解决方案:Docker proxy

现在Docker换源也下载失败太常见了&#xff0c;至于原因&#xff0c;大家懂得都懂。本文提供一种简洁的方案&#xff0c; 利用 Docker 的http-proxy&#xff0c;代理至本机的 proxy。 文章目录 前言Docker proxy 前言 这里默认你会安装 clash&#xff0c;然后有配置和数据库。…

LLM大模型实战项目--基于Stable Diffusion的电商平台虚拟试衣

本文详细讲解LLM大模型实战项目&#xff0c;基于Stable Diffusion的电商平台虚拟试衣 一、项目介绍 二、阿里PAI平台介绍 三、阿里云注册及开通PAI 四、PAI_DSW环境搭建 五、SDLORA模型微调 一、项目介绍 AI虚拟试衣是一种创新的技术&#xff0c;利用人工智能和计算机视觉技…

科技核心~书法用纸结合

书法用纸******对墨迹扩散的影响 传统书法用纸制作****与现代改进 书法用纸的*****表面结构关系研究