蓝桥杯备考:堆和priority queue(优先级队列)

堆的定义

heap堆是一种特殊的完全二叉树,对于树中的每个结点,如果该结点的权值大于等于孩子结点的权值,就称它为大根堆,小于等于就叫小根堆,如果是大根堆,每个子树也是符合大根堆的特征的,如果是小根堆,每课子树符合小根堆特征

堆的存储

顺序存储,按照层序遍历编号依次存放在数组里面,变量n负责标记元素个数,存储固然简单,但是我们的 题目一般不会那么好心直接给一个标准的堆,一般只是随便给我们一组数,这组数还原出来并不一定是堆结构,我们可以用数组存下来这组数,然后把数组调整称堆,也可以依次把这些数插入到堆里面

向上调整算法 

向上调整,当我们插入一个数的时候,我们需要进行向上调整

算法原理(大根堆,小根堆同理):如果插入的数权值小于等于父亲结点,那我们不变,如果插入的数是大于父亲结点的,我们就要让它和父亲结点换位置

不断重复这个操作,直到此数变成根结点或者权值小于等于父亲结点的时候为止

比如我们尝试调整一下这棵树

void up(int child)
{int parent = child / 2;while (child != 1 && heap[parent] < heap[child]){swap(heap[parent], heap[child]);child = parent;parent = child / 2;}}

向下调整算法

当我们需要删除一个结点的时候,我们只能删除堆顶的元素,这时候,我们交换堆底和堆顶元素,然后对交换上来的堆顶进行向下调整算法,如果新的堆顶的两个孩子都小于等于它,那么就不用操作,因为我的其他子树都维持着大根堆的特征,如果堆顶元素的两个孩子的较大结点大于我们的堆顶元素,此时我们就要让他和最大的孩子交换,重复此操作,直到换到叶子结点或者该结点大于等于左右孩子为止

void down(int parent)
{int child = parent * 2;if (heap[child + 1] > heap[child]) child = child + 1;while (child <= n && heap[parent] < heap[child]){swap(heap[parent], heap[child]);parent = child;child = parent * 2;}}

由于我们的向上调整算法和向下调整算法最坏的情况都是执行二叉树的深度次,又因为二叉树的深度是log(n+1) 所以我们的向上调整算法和向下调整算法就是O(log N)

堆数据结构的创建

创建

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int heap[N], n;

插入

插入的时候我们就放在n下标的下一个位置,然后把新的n下标传进去进行向上调整即可

代码;

void push(int x)
{heap[++n] = x;up(n);
}

删除

删除的时候我们要先交换堆顶和堆底元素,然后删除堆底元素n--,

然后对堆顶元素进行向下调整

void pop()
{swap(heap[n], heap[1]);n--;down(1);
}

查询堆顶元素

int top()
{return heap[1];
}

时间复杂度是O(1)

查询有效元素个数

int size()
{return n;
}

进行测试,把这个数组插入到堆里面,键堆,依次取出堆顶元素,就是个降序的过程

总代码

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int heap[N], n;void up(int child)
{int parent = child / 2;while (child != 1 && heap[parent] < heap[child]){swap(heap[parent], heap[child]);child = parent;parent = child / 2;}}
void down(int parent)
{int child = parent * 2;if (heap[child + 1] > heap[child]) child = child + 1;while (child <= n && heap[parent] < heap[child]){swap(heap[parent], heap[child]);parent = child;child = parent * 2;}}
void push(int x)
{heap[++n] = x;up(n);
}
void pop()
{swap(heap[n], heap[1]);n--;down(1);
}int top()
{return heap[1];
}
int size()
{return n;
}
int main()
{int a[10] = { 1, 41, 23, 10, 11, 2, -1, 99, 14, 0 };for (int i = 0; i < 10; i++){push(a[i]);}while (size()){cout << top() << " ";pop();}return 0;
}

priority queue优先级队列的用法(初阶,进阶)

在我们的stl中,有一个现成的堆的数据结构,叫做优先级队列,有了它,我们就不用再自己手写一个堆的数据结构了

堆可以存储内置类型比如说int

堆也可以存储字符串类型

堆也可以存储自定义类型比如结构体

每一种存储方式都对应一种写法

我们先来介绍普通的存储内置类型的优先级队列

#include <iostream>
#include <queue>
using namespace std;int a[10] = { 1, 41, 23, 10, 11, 2, -1, 99, 14, 0 };int main()
{priority_queue <int> heap;//这种简单的写法默认就是大根堆for (int i = 0; i < 10; i++){heap.push(a[i]);}while (heap.size()){cout << heap.top() << " ";heap.pop();}return 0;
}

首先是这种简单的写法,这种简单的写法默认就是大根堆

如果你想实现正经八本的一个堆的话,我们有一个模式

priority_queue<数据类型,数据实现方式,比较方式(判断大/小根堆)>

	priority_queue<int, vector<int>, less<int>> q1;//大根堆
	priority_queue<int, vector<int>, greater<int>> q2;//小根堆

这个需要我们特殊的记一下,大根堆用less(当第一个数小于第二个数是返回true,相当于我们sort里的比较函数一样),小根堆用greater

测试结果

测试代码

#include <iostream>
#include <queue>
using namespace std;int a[10] = { 1, 41, 23, 10, 11, 2, -1, 99, 14, 0 };
void test()
{priority_queue<int, vector<int>, less<int>> q1;//大根堆priority_queue<int, vector<int>, greater<int>> q2;//小根堆for (int i = 0; i < 10; i++){q1.push(a[i]);q2.push(a[i]);}while (q1.size()){cout << q1.top() << " ";q1.pop();}cout << endl;while (q2.size()){cout << q2.top() << " ";q2.pop();}cout << endl;
}
int main()
{priority_queue <int> heap;//这种简单的写法默认就是大根堆/*for (int i = 0; i < 10; i++){heap.push(a[i]);}while (heap.size()){cout << heap.top() << " ";heap.pop();}*/test();return 0;
}

如果我们要存double和long long 只要把int改一下就行了

接下来我们来说一下结构体类型怎么建堆

如果想实现结构体类型的建堆,我们只需要在结构体内部重载小于号,如果返回b<x.b的话,就是大根堆,如果返回b>x,b的话就是小根堆

struct node {int x, y, z;bool operator < (const node& e)const {return y < e.y;//大根堆}
};
void test2()
{priority_queue <node> heap5;for (int i = 1; i <= 5; i++){heap5.push({ i + 5,i + 1,i + 2 });}while (heap5.size()){node t = heap5.top(); heap5.pop();cout << t.x << " " << t.y << " " << t.z << endl;}
}

可以看到这就是大根堆

把重载的返回值改成大于就变成小根堆了,我们就不再放代码了,只要改一个符号就行

堆和priority优先级队列的算法题

1.模板题

这是一道模板题,我们先用自己手写的堆实现一下

#include <iostream>
using namespace std;const int N = 1e6+10;
int heap[N],n;
void up(int child)
{int parent = child/2;while(parent >=1 && heap[parent]>heap[child]){swap(heap[parent],heap[child]);child = parent;parent = child/2;}
}
void down(int parent)
{int child = parent * 2;while(child <= n){if(child + 1 <= n && heap[child+1] < heap[child])++child;if(heap[parent] < heap[child]) return;swap(heap[parent],heap[child]);parent = child;child = parent*2;}
}
void push(int x)
{heap[++n] = x;up(n);
}
void pop()
{swap(heap[1],heap[n]);n--;down(1);
}
int main()
{int T;cin >> T;while(T--){int op;cin >> op;if(op == 1){int t;cin >> t;push(t); }else if(op == 2){cout << heap[1] << endl;}else{pop();}}return 0;
}

接着用我们stl的优先级队列来实现一下

#include <iostream>
#include <queue>
using namespace std;
priority_queue<int,vector<int>,greater<int>> heap1;
int main()
{int n;cin >> n;while(n--){int op;cin >> op;if(op == 1){int x;cin >> x;heap1.push(x);}else if(op == 2)cout << heap1.top() << endl;elseheap1.pop();}return 0;
}

2.第k小(topk问题)

我们要维护这个优先级队列,让优先级队列里只有k个元素,然后堆顶就是我们的第k小,比如1,2,3 3就是第三小,如果要找第k小的话,实际上就是k个元素的时候的最大的值,也就是k个元素的时候的堆顶,比如1 2 3 3就是第k小,如果你插入4 5 6 1 2 3 4 5 6 最小的还是3,所以我们依次删除堆顶,留下 1 2 3 3 当堆顶的时候就是第三小  再比如 1 2  5  插入了3  那 我们删除最大的堆顶 留下 1 2 3 3也就是第三小了

#include <iostream>
#include <queue>
using namespace std;
priority_queue <int> heap1;
const int N = 2e5+10;
int a[N];int main()
{int n,m,k;cin >> n >> m >> k;for(int i = 0;i<n;i++){cin >> a[i];heap1.push(a[i]);}while(heap1.size() > k){int t = heap1.top();heap1.pop();}while(m--){int op;cin >> op;if(op == 1){int x;cin >> x;heap1.push(x);while(heap1.size() > k){heap1.pop();}}if(op == 2){if(heap1.size() < k)cout << -1 << endl;elsecout << heap1.top() << endl;}}}

3.除2!

如果遇到求和,我们一定要记得考虑用long long

#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
int n,k;
ll sum;
priority_queue <int> heap;
int main()
{cin >> n >> k;for(int i = 1;i<=n;i++){int x;cin >> x;sum+=x;if(x%2 == 0){heap.push(x);}}while(heap.size() && k--){int t = heap.top()/2;heap.pop();sum-=t;if(t%2 == 0){heap.push(t);}}cout << sum << endl;return 0;
}

4.最小函数值(结构体堆+单调性)

这道题我们要用构造一个结构体元素的堆,根据单调性,我们不需要一次性把所有的函数式前m个元素算出来,我们只需要先把代入值为1的值算出来,push成一个堆,然后把堆顶打印出来,把这个堆顶元素的代入值变为2再push进去,接下来再打印下一个堆顶即可

所以我们结构体要存下函数值(建堆的条件)还要存第几个函数表达式,并存下代入值x

#include <iostream>
#include <queue>
using namespace std;
const int N = 1e4+10;
int a[N],b[N],c[N];
int n,m;struct node
{int f;int num;int x;bool operator <(const node& y) const{return f > y.f;//前一个元素大于后一个元素 }	
};
int calc(int i,int x)
{return a[i]*x*x + b[i]*x +c[i];
}
priority_queue <node> q;
int main()
{cin >> n >> m;for(int i = 1;i<=n;i++){cin >> a[i] >> b[i] >> c[i];}//先把代入值为1的元素push进去,然后依次出堆顶//每次出堆顶都代入这个表达式代入的下一个值//这个循环一共循环m次 for(int i = 1;i<=n;i++){q.push({calc(i,1),i,1});}while(q.size() && m--){node t = q.top();q.pop();cout << t.f << " ";int x1 = t.x+1; int num1 = t.num;q.push({calc(num1,x1),num1,x1});}return 0;
}

5.序列合并(结构体堆+单调性)

这道题和上一道题非常的类似

我们这么想,先让第一个序列里最小的值和第二个序列分别相加,push进入小根堆,删除堆顶再push进第一个序列第二个小的值和第二个序列那个值相加的值,直到打印完前n个值

#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5+10;
int a[N],b[N];
typedef long long ll;
struct node{ll sum;int i;int j;bool operator <(const node& n) const{return sum > n.sum;}};
priority_queue <node> heap;
int main()
{int n; cin >> n;for(int i = 1;i<=n;i++)	cin >> a[i];for(int i = 1;i<=n;i++)	cin >> b[i];for(int i = 1;i<=n;i++){heap.push({a[i]+b[1],i,1});}for(int i = 1;i<=n;i++){node t = heap.top(); heap.pop();ll sum1 = t.sum,i1 = t.i,j1=t.j;cout << sum1 << " ";heap.push({a[i1]+b[j1+1],i1,j1+1});}return 0;} 

6.舞蹈课(双链表存数据,结构体元素组成的堆)

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

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

相关文章

到底应不应该使用@Builder

大多数同学使用 Builder 无非就是为了链式编程&#xff0c;然而 Builder 并不是链式编程的最佳实践&#xff0c;它会额外创建内部类&#xff0c;存在继承关系时还需要使用 SuperBuilder 注解&#xff0c;设置默认值时也需要额外的 Builder.Default 去设置默认值&#xff0c;无疑…

微软官方Windows 10系统ISO镜像文件下载指南

简介 什么是ISO镜像文件 ISO镜像文件是一种特殊的数字文件格式&#xff0c; 精确复制了物理光盘的所有内容和结构 。这种文件通常用于存储完整的操作系统安装程序或其他大型软件包&#xff0c;便于在网络上传输和长期保存。ISO文件的核心优势在于其高度的完整性和可靠性&…

RabbitMQ-消息可靠性以及延迟消息

目录 消息丢失 一、发送者的可靠性 1.1 生产者重试机制 1.2 生产者确认机制 1.3 实现生产者确认 &#xff08;1&#xff09;开启生产者确认 &#xff08;2&#xff09;定义ReturnCallback &#xff08;3&#xff09;定义ConfirmCallback 二、MQ的持久化 2.1 数据持久…

fgets、scanf存字符串应用

题目1 夺旗&#xff08;英语&#xff1a;Capture the flag&#xff0c;简称 CTF&#xff09;在计算机安全中是一种活动&#xff0c;当中会将“旗子”秘密地埋藏于有目的的易受攻击的程序或网站。参赛者从其他参赛者或主办方偷去旗子。 非常崇拜探姬的小学妹最近迷上了 CTF&am…

【C语言系列】深入理解指针(1)

前言 总所周知&#xff0c;C语言中指针部分是非常重要的&#xff0c;这一件我们会介绍指针相关的内容&#xff0c;当然后续我还会出大概4篇与指针相关的文章&#xff0c;来深入的讲解C语言指针部分&#xff0c;希望能够帮助到指针部分薄弱或者根本不会的程序员们&#xff0c;后…

力扣面试150 串联所有单词的子串 分组滑动窗口

Problem: 30. 串联所有单词的子串 参考题解 滑动窗口 class Solution {public List<Integer> findSubstring(String s, String[] words) {int n s.length(), m words.length, w words[0].length();// 统计 words 中「每个目标单词」的出现次数Map<String, Integ…

CSS笔记01

黑马程序员视频地址&#xff1a; 前端Web开发HTML5CSS3移动web视频教程https://www.bilibili.com/video/BV1kM4y127Li?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes 目录 引入方式 CSS特性 继承性 层叠性 优先级 Emmet写法 …

django应急物资管理系统

Django应急物资管理系统是一种高效、智能的管理系统&#xff0c;旨在应对自然灾害、事故灾难等突发事件&#xff0c;确保救援物资能够及时、准确地调配到需要的地方。 一、系统背景与意义 在现代社会&#xff0c;各类突发事件频繁发生&#xff0c;对人民生命财产安全构成严重…

管理口令安全和资源(二)

DBMS_METADATA DBMS_METADATA 是 Oracle 数据库中的一个包&#xff0c;它提供了用于管理数据库元数据的工具和过程。元数据是关于数据的数据&#xff0c;它描述了数据库的结构&#xff0c;包括表、视图、索引、存储过程、用户和其他数据库对象的信息。DBMS_METADATA 包允许用户…

安路FPGA开发工具TD:问题解决办法 及 Tips 总结

安路科技&#xff08;Anlogic&#xff09;是一家专注于高性能、低功耗可编程逻辑器件&#xff08;FPGA&#xff09;设计和生产的公司。其提供的开发工具TD&#xff08;TangDynasty&#xff09;是专门为安路FPGA系列产品设计的集成开发环境&#xff08;IDE&#xff09;。以下是对…

Java常用时间类

JDK7的时间类 1&#xff1a;Date类 2&#xff1a;SimpleDateFormat类 3&#xff1a;Calendar类 JDK8的时间类 1&#xff1a;Zoneld类 2&#xff1a;Instant类 3&#xff1a;ZoneDateTime 4&#xff1a;LocalDate 5&#xff1a;LocalTime 6&#xff1a;LocalDateTime …

模块化架构与微服务架构,哪种更适合桌面软件开发?

前言 在现代软件开发中&#xff0c;架构设计扮演着至关重要的角色。两种常见的架构设计方法是模块化架构与微服务架构。它们各自有独特的优势和适用场景&#xff0c;尤其在C#桌面软件开发领域&#xff0c;模块化架构往往更加具有实践性。本文将对这两种架构进行对比&#xff0…

Java开发提效秘籍:巧用Apache Commons IO工具库

一、引言 在 Java 开发的广袤领域中&#xff0c;输入输出&#xff08;I/O&#xff09;操作宛如一座桥梁&#xff0c;连接着程序与外部世界&#xff0c;从文件的读取与写入&#xff0c;到网络数据的传输&#xff0c;I/O 操作无处不在&#xff0c;其重要性不言而喻。然而&#xf…

使用 Helm 安装 Redis 集群

在 Kubernetes 集群中使用 Helm 安装 Redis 集群可以极大地简化部署和管理 Redis 的过程。本文将详细介绍如何使用 Helm 安装 Redis 集群&#xff0c;并提供一些常见问题的解决方案。 前提条件 Kubernetes 集群。&#xff08;略&#xff09;已安装 Helm 工具。搭建了存储类nf…

算法刷题笔记——图论篇

这里写目录标题 理论基础图的基本概念图的种类度 连通性连通图强连通图连通分量强连通分量 图的构造邻接矩阵邻接表 图的遍历方式 深度优先搜索理论基础dfs 与 bfs 区别dfs 搜索过程深搜三部曲所有可达路径广度优先搜索理论基础广搜的使用场景广搜的过程 岛屿数量孤岛的总面积沉…

C 语言的void*到底是什么?

一、void* 的类型任意性 void* 是一种通用指针类型。它可以指向任意类型的数据。例如&#xff0c;它可以指向一个整数&#xff08;int&#xff09;、一个浮点数&#xff08;float&#xff09;、一个字符&#xff08;char&#xff09;或者一个结构体等。在C语言中&#xff0c;当…

Redis延迟队列详解

以下是对 Redis 延迟队列的详细解释&#xff1a; 一、什么是 Redis 延迟队列 Redis 延迟队列是一种使用 Redis 实现的消息队列&#xff0c;其中的消息在被消费之前会等待一段时间&#xff0c;这段时间就是延迟时间。延迟队列常用于一些需要延迟处理的任务场景&#xff0c;例如订…

利用免费GIS工具箱实现高斯泼溅切片,将 PLY 格式转换为 3dtiles

在地理信息系统&#xff08;GIS&#xff09;和三维数据处理领域&#xff0c;不同数据格式有其独特应用场景与优势。PLY&#xff08;Polygon File Format&#xff09;格式常用于存储多边形网格数据&#xff0c;而 3DTiles 格式在 Web 端三维场景展示等方面表现出色。将 PLY 格式…

【数据分析】02- A/B 测试:玩转假设检验、t 检验与卡方检验

一、背景&#xff1a;当“审判”成为科学 1.1 虚拟场景——法庭审判 想象这样一个场景&#xff1a;有一天&#xff0c;你在王国里担任“首席审判官”。你面前站着一位嫌疑人&#xff0c;有人指控他说“偷了国王珍贵的金冠”。但究竟是他干的&#xff0c;还是他是被冤枉的&…

ZooKeeper 核心知识全解析:架构、角色、节点与应用

1.ZooKeeper 分布式锁怎么实现的 ZooKeeper 是一个高效的分布式协调服务&#xff0c;它提供了简单的原语集来构建更复杂的同步原语和协调数据结构。利用 ZooKeeper 实现分布式锁主要依赖于它的顺序节点&#xff08;Sequential Node&#xff09;特性以及临时节点&#xff08;Ep…