数据结构 -- 堆

一.堆的概念

1.1 堆是什么

        堆也叫做优先队列,一些按照重要性或优先级来组织的对象称为优先队列。

1.2 为什么需要堆

        在现实生活中,存在许多需要从一群人、一些任务或一些对象中找出“下一位最重要”目标的情况。例如:在平时处理事情的时候我们总会先把“下一个最重要的”的事情取出来先处理。在处理的过程中,可能还会有其他的事情加进来。因此在事情处理完的事情,我们又要重新找出“下一个最重要”的事情进行处理。

  堆就是处理这种情况的,我们通常把堆中的数据按照一定的重要性进行排序,从而按照顺序一个个取出堆中的元素。

1.3 如何创建一个堆

        通常来说,我们容易想到以下的方法:

        对所有元素进行一次排序然后取出最大值。但这种方法不是很好。它多做了很多无用功。因为其实我可能只要取一个最大值就OK了,但是却画蛇添足地帮我把所有元素都排好序了。很浪费时间。排序的时间复杂度至少为O(nlogn),插入和删除操作的时间复杂度为O(n)。

   而理论上我们所要实现的优先队列的时间复杂度是可以比这个更优的。

二.堆的属性

2.1 定义

  • 堆是一棵(近乎)满二叉树,我们可以用数组来实现这棵二叉树(因为如果是满二叉树的话,其极其符合数组的形式,并且节点也可以用下标来表示,下面会证明)。
  • 堆中的数据是局部有序的。我们使节点储存的值和它的子节点之间存在某种关系,从而利用数组模拟出堆这种形式。

2.2 堆的类型 

  1. 最大值堆:任意一个节点的值都大于它的子节点的值:这样根节点存储的就是这组数据的最大值。
  2. 最小值堆:任意一个节点的值都小于它的子节点的值这样子根节点存储的就是这组数据的最小值。

 

 2.3 堆的特殊规律

  既然堆的逻辑结构是完全二叉树,那么它就同样具有完全二叉树的性质 。

    对于完全二叉树,若从上至下、从左至右编号,以根节点为0开始,则编号为i的节点,其左孩子节点编号为2i+1,右孩子节点编号为2i+2,父亲节点为 (i-1) / 2。 这个规律非常重要!!!

验证:

    一棵节点个数为N的完全二叉树,其高度为 h = log 2 (N+1),(2为底数)。 

三.堆的实现 

3.1 堆的基本结构

  我们在上面讲解了,对于一个满二叉树形式的结构,我们可以用一个数组来模拟,但有时候我们也可以用其它类型来进行模拟,在库里面,其实底层是一个模板参数,堆可以根据传入的类型来更改底层类型,不过默认为vector.

如下:

namespace My {template<class T, class Container = vector<T>>class Priority_queue {public:private:Container _con;};}

3.2 堆的向下调整算法 

  向下调整:是让调整的结点与其孩子节点进行比较,若想将其调整为小堆,那么根结点的左右子树必须都为小堆,若想将其调整为大堆,那么根结点的左右子树必须都为大堆,一般用于创建堆。

(小根堆)示例:

  

向下调整算法的基本思想(最小堆示例):

  1. 从根结点处开始,选出左右孩子中  值较小的孩子。
  2. 让较小的孩子与其父亲进行比较。
  3. 若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
  4. 若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

注意:这里我们的大根堆和小根堆的大小对比明显是完全不一样的,那么我们就需要写两个向下调整代码吗? 不,不需要,我们可以在创建一个对象时传入一个仿函数,从而实现让用户自己来控制需要的堆。

  模板修改 + 仿函数实现:

	//构建大根堆函数struct Less{bool operator()(int k1,int k2){return k1 < k2;}};//构建小根堆函数struct Greater{bool operator()(int k1, int k2) {return k1 > k2;}};//默认构建大根堆template<class T,class Kvalue=Less>

向下调整代码的实现:

//在类外,这个函数是完全不需要的,因此设置为private
private:void adjust_down(int parent) {//构建仿函数Compare _com;int child = parent * 2 + 1;while (child < _con.size()) {//判断右节点有没有越界,顺便求出两个节点较大值if (child + 1 < _con.size() && _com(_con[child], _con[child + 1])) {child++;}//根据对应仿函数判断是否需要交换if (_com(_con[parent], _con[child])) {swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}//不符合仿函数判断条件直接返回else {break;}}}

3.3 堆的创建

 我们都知道,堆的底层结构是一个数组类型, 那么我们除了让一个空堆一个个插入,还能直接用一个数组来构建堆吗,答案是完全可以。

 那么我们该如何做呢? 首先,堆的底层结构就是一个数组类型,那么我们可以直接用堆内的数组 copy 一下 传入的数组,然后按照向下调整算法,构建一个堆。

  从上面的向下调整算法我们得知,我们使用向下调整算法时,需要保证左右两个子树都为堆,因此,单纯的从头节点开始向下调整是不可以的。

  这里,我们可以把最底层的节点看为一个个堆,从最底层开始调整,但是如果从最底层开始调节,又有点太浪费资源,这里建议:

   从最后一个父节点(size/2)-1 的位置逐个往前调整所有父节点(直到根节点),确保每一个父节点都是一个最大堆,最后整体上形成一个最大堆 。

  ps:因为最后一个父节点,最多只有两个子节点,因此,它的左右子树也必定是一个堆。

	//在C++ 中 我们现在基本都用迭代器构造 因此,创建一个迭代器模板类型template <class InputIterator>Priority_queue(InputIterator first, InputIterator last):_con(first, last)//直接调用底层数据结构的迭代器构造{   //这里减2 的原因是 数组下标的问题 因为从0开始,所以真正的大小应该减一,在带入先前的算法for (int i = (_con.size() - 2) / 2; i >= 0; i--) {adjust_down(i);}}

3.4 堆的向上调整代码 

   向上调整:是让调整的结点与其父亲结点进行比较,当我们已经有一个堆,显然我们在头部插入数据,会破坏原有的堆结构,因此我们需要在堆的末尾插入数据,再对其进行调整,使其任然保持堆的结构,这里我们就需要用到堆的向上调整算法。

  

向上调整算法的基本思想:

  1. 将目标结点与其父结点比较
  2. 若目标结点的值比其父结点的值大,则交换目标结点与其父结点的位置
  3. 将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是大堆了

代码示例:

void adjust_up(int child) {Compare _com;int parent = (child - 1) / 2;while (child > 0) {if (_com(_con[parent], _con[child])) {swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else {break;}}
}

3.5 堆的插入

   我们直接对数组进行尾插,然后在插入位置向上调整,因为插入时,只改变插入位置到根这一条路径,因此,只向上调整一次即可。

  代码示例:

	void push(const T& x) {_con.push_back(x);adjust_up(_con.size() - 1);}

3.6 堆的删除

  堆的删除这里我们有特殊的规则,像我们直接删掉尾部的数据,其实是不改变堆的结构的,但那样有意义吗?

  堆本来就起到一个排序的作用,你怎么知道最后一位数据,到底是多少呢?堆只能取到头部的顺序,因此,在我们删除时,是删除头部的数据

  但是直接删除的话,会把我们的堆结构变得极其混乱,对此,我们提出了另一种解法:

  我们把头元素和尾部元素进行一次交换,然后把新尾部删除,让新头部元素向下调整(这样交换后,头元素的左右依旧是堆结构,只需要一次调整就能得到一个新堆。

  代码如下:

	void pop() {swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}

3.7 其它接口

	bool empty() const {return _con.empty();}size_t size() const {return _con.size();}


四.代码测试
  

oid test1() {My::Priority_queue < int> pq1;pq1.push(5);pq1.push(2);pq1.push(3);pq1.push(9);pq1.push(4);while (!pq1.empty()) {cout << pq1.top()<< endl;pq1.pop();}}void test2(){vector<int> v1 = { 1,3,4,5,8,10,12 };Priority_queue<int> q1(v1.begin(), v1.end());Priority_queue<int, vector<int>, Greater<int>> q2(v1.begin(), v1.end());cout << "size:" << q1.size() << endl;while (!q1.empty()){cout << q1.top() << ' ';q1.pop();}cout << endl;cout << "size:" << q2.size() << endl;while (!q2.empty()){cout << q2.top() << ' ';q2.pop();}cout << endl;}

答案:

 

五.完整代码 

My_Priority_Queue.h

#pragma once
#include<iostream>
#include<vector>using namespace std;namespace My {template<class T>struct Less {bool operator()(const T& t1, const T& t2) {return t1 < t2;}};template<class T>struct Greater {bool operator()(const T& t1, const T& t2) {return t1 > t2;}};template<class T, class Container = vector<T>, class Compare = Less<T >>class Priority_queue {public:Priority_queue() {}template <class InputIterator>Priority_queue(InputIterator first, InputIterator last):_con(first, last){for (int i = (_con.size() - 2) / 2; i >= 0; i--) {adjust_down(i);}}bool empty() const {return _con.empty();}size_t size() const {return _con.size();}void push(const T& x) {_con.push_back(x);adjust_up(_con.size() - 1);}void pop() {swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);}const T& top() {return _con[0];}private:void adjust_down(int parent) {Compare _com;int child = parent * 2 + 1;while (child < _con.size()) {if (child + 1 < _con.size() && _com(_con[child], _con[child + 1])) {child++;}if (_com(_con[parent], _con[child])) {swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}else {break;}}}void adjust_up(int child) {Compare _com;int parent = (child - 1) / 2;while (child > 0) {if (_com(_con[parent], _con[child])) {swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}else {break;}}}private:Container _con;};void test1() {My::Priority_queue < int> pq1;pq1.push(5);pq1.push(2);pq1.push(3);pq1.push(9);pq1.push(4);while (!pq1.empty()) {cout << pq1.top()<< endl;pq1.pop();}}void test2(){vector<int> v1 = { 1,3,4,5,8,10,12 };Priority_queue<int> q1(v1.begin(), v1.end());Priority_queue<int, vector<int>, Greater<int>> q2(v1.begin(), v1.end());cout << "size:" << q1.size() << endl;while (!q1.empty()){cout << q1.top() << ' ';q1.pop();}cout << endl;cout << "size:" << q2.size() << endl;while (!q2.empty()){cout << q2.top() << ' ';q2.pop();}cout << endl;}
}

 test.cc

#include"My_Priority_Queue.h"int main() {try{My::test2();}catch (...){cout << "未知异常" << endl;}return 0;
}

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

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

相关文章

不同版本QT使用qmake时创建QML项目的区别

不同版本QT使用qmake时创建QML项目的区别 文章目录 不同版本QT使用qmake时创建QML项目的区别一、QT5新建QML项目1.1 目录结构1.2 .pro 文件内容1.3 main.cpp1.4 main.qml 二、QT6新建QML项目2.1 目录结构2.2 .pro文件内容2.3 main.cpp2.4 main.qml 三、两个版本使用资源文件的区…

鼠标响应突然不灵敏的检查方法

鼠标突然响应缓慢或者失灵&#xff0c;如下检测步骤&#xff1a; 1、首先排查电源问题&#xff0c;更换电池或者充电&#xff1b; 2、观察光标移动响应、鼠标左键响应、鼠标右键响应、鼠标滚轮等操作&#xff0c;哪些正常&#xff0c;哪些异常。 2、把鼠标接到别的机器上实验…

[Kubernetes]3. k8s集群Service详解

在上一节讲解了k8s 的pod,deployment,以及借助pod,deployment来部署项目,但会存在问题: 每次只能访问一个 pod,没有负载均衡自动转发到不同 pod访问还需要端口转发Pod重创后IP变了,名字也变了针对上面的问题,可以借助Service来解决,下面就来看看Service怎么使用 一.Service详…

✺ch5——纹理贴图

目录 加载纹理图像文件纹理坐标在着色器中使用纹理&#xff1a;采样器变量和纹理单元纹理贴图&#xff1a;示例程序多级渐远纹理贴图各向异性过滤环绕和平铺透视变形材质——更多OpenGL细节补充说明 纹理贴图是在栅格化的模型表面上覆盖图像的技术。 它是为渲染场景添加真实感的…

functools.partial:Python中灵活函数部分应用的工具

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;functools.partial是一个强大的工具&#xff0c;它提供了一种部分应用函数的方式&#xff0c;能够在创建新函数时固定部分参数&#xff0c;从而在后续调用中减少需要传递的参数数量。…

Python四种配色方案,适合科研的配色

1、Plasma&#xff08;等高线图颜色&#xff09;2、Inferno&#xff08;黑热图颜色&#xff09;3、Cividis&#xff08;较好的配色方案&#xff0c;适用于色盲&#xff09;4、Viridis&#xff08;绿色主导的配色方案&#xff09; 下面这四种配色是不需要指定的&#xff0c;Pyth…

element组件库的日期选择器如何限制?

本次项目中涉及到根据日期查找出来的数据进行调整,所以修改的数据必须是查找范围内的数据.需要对调整数据的日期进行限制,效果如下: 首先我们使用了element 组件库的日期选择器,其中灌完介绍, picker-options中函数disabledDate可以设置禁用状态,代码如下: <el-date-pickerv…

关于GPU使用过程中的若干问题

1.CUDA异常 问题描述&#xff1a;运行torch.cuda.is_available() 报错&#xff1a;cuda unknown error - this may be due to an incorrectly set up environment解决方案&#xff1a;重启 2.nvidia驱动版本不匹配 问题描述&#xff1a;运行nvidis-smi 报错&#xff1a;Fa…

个人用户的数据之美:数据可视化助力解读

数据可视化是一种强大的工具&#xff0c;不仅可以为企业和专业人士提供见解&#xff0c;也对个人用户带来了许多实际的帮助。下面我就以一个数据可视化从业者的视角&#xff0c;来谈谈数据可视化对个人用户的益处&#xff1a; 首先对于个人用户来说&#xff0c;数据可视化可以让…

Nodejs 第二十五章(http)

“http” 模块是 Node.js 中用于创建和处理 HTTP 服务器和客户端的核心模块。它使得构建基于 HTTP 协议的应用程序变得更加简单和灵活。 创建 Web 服务器&#xff1a;你可以使用 “http” 模块创建一个 HTTP 服务器&#xff0c;用于提供 Web 应用程序或网站。通过监听特定的端…

python接口自动化测试--requests使用和基本方法封装

之前学习了使用jmeterant做接口测试&#xff0c;并实现了接口的批量维护管理(大概500多条用例)&#xff0c;对“接口”以及“接口测试”有了一个基础了解&#xff0c;最近找了一些用python做接口测试的资料&#xff0c;一方面为了学习下如何使用python进行接口测试(如何做出一个…

抖店需要多少资金?如何开通?具体流程如下!

我是电商珠珠 新手开抖店最关心的就是资金问题&#xff0c;在网上关于抖店的资金多少的都有&#xff0c;几百几千的都有。 各个回答都不一样。 另外一个问题就是怎么开通&#xff0c;今天我就来给大家详细的讲一下。 一、资金 入驻抖店需要办理一张个体工的营业执照&#…

Unity中URP下的顶点偏移

文章目录 前言一、实现思路二、实现URP下的顶点偏移1、在顶点着色器中使用正弦函数&#xff0c;实现左右摇摆的效果2、在正弦函数的传入参数中&#xff0c;加入一个扰度值&#xff0c;实现不规则的顶点偏移3、修改正弦函数的振幅 A&#xff0c;让我们的偏移程度合适4、修改正弦…

Linux/Windows IP | Team基础管理

引言 IP&#xff08;Internet Protocol&#xff09; 定义&#xff1a; IP&#xff08;Internet Protocol&#xff09;是网络传输数据的协议&#xff0c;负责在网络中唯一标识和定位设备&#xff0c;并提供数据传输的基础。功能&#xff1a; 允许计算机在网络上相互通信和交换…

VMware Ubuntu虚拟机忘记密码

​​原文 https://blog.csdn.net/ezconn/article/details/89328024​​​​​​​ 前言&#xff1a; 在VMware运行Ubuntu虚拟机时&#xff0c;开机之后忘记密码怎么办&#xff1f; 环境&#xff1a;Ubuntu版本&#xff1a;ubuntu-16.04.6-server-amd64&#xff1b;VMware版本…

乐理基础-弱起小节、弱起

弱起小节的定义&#xff1a; 1.音乐不是从强拍开始的&#xff0c;是从弱拍或次强拍开始的。 2.弱起小节会省去前面没有音乐的部分&#xff0c;它是不完整的小节&#xff0c;它的拍数是不够的。如图1 弱起小节的作用&#xff1a; 强拍经常要作为 和弦出现 和 变化的地方&#xf…

德人合科技 | 防止公司电脑文件数据资料外泄,自动智能透明加密保护系统

【透明加密软件】——防止公司电脑文件数据资料防止外泄&#xff0c;自动智能透明加密保护内部核心文件、文档、图纸、源代码、音视频等资料&#xff01; PC端访问地址&#xff1a; www.drhchina.com &#x1f31f; 核心功能&#xff1a; 透明加密&#xff1a;采用高级加密算…

EasyExcel合并相同内容单元格及动态标题功能的实现

一、最初版本 导出的结果&#xff1a; 对应实体类代码&#xff1a; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentLoopMerge; import com.al…

全链路压力测试:解析其主要特点

随着信息技术的飞速发展和云计算的普及&#xff0c;全链路压力测试作为一种关键的质量保障手段&#xff0c;在软件开发和系统部署中扮演着至关重要的角色。全链路压力测试以模拟真实生产环境的压力和负载&#xff0c;对整个业务流程进行全面测试&#xff0c;具有以下主要特点&a…

Nginx网站服务详解(Nginx服务的主配置文件 ——nginx.conf)

目录 一、全局配置的六个模块简介 二、Nginx配置文件的详解 1&#xff09;全局配置模块 2&#xff09;I/O 事件配置 3&#xff09;HTTP 配置 4&#xff09;web服务监听设置 5&#xff09;其他设置 location常见配置指令&#xff1a;“root、alias、proxy_pass 对比&a…