二叉堆的简单板子+理解+例题

news2024/11/28 19:49:47

首先,我们先要了解堆是什么?

堆:是一种高级树状数据结构,是一种完全二叉树。

(完全二叉树指的是,除了叶子节点,每个节点均有左右两个子节点的树状结构)

而,二叉堆是堆的最常见的实现方式。

二叉堆又可以分为:大根堆,小根堆。(可以用c++ 的 stl实现)
大根堆:每一个节点,大于等于其子节点。(从堆顶到堆底不严格递增)

小根堆:每一个节点,小于等于其子节点。(从堆顶到堆底不严格递减)

那么对于二叉堆,我们是需要手动去实现一些它的一些基本操作。

  1. 向下调整
  2. 向上调整
  3. 插入一个元素
  4. 求堆中最大值/最小值(堆顶)
  5. 删除堆中最大值/最小值

下面先实现最大堆/大根堆的操作:

1.使用vector容器实现:

//定义一个最大堆,先给里面装填一个空元素,使得后续插入的元素下标一一对应:
//意思是,第一个数的下标就是 1,而不是0 ,同时也是 size() -1 
vector<int > big(1);

2.向上调整

void upp(int pos) {
    while (pos > 1) {                        // 循环直到节点到达堆顶
        if (big[pos] > big[father]) {        // 如果当前节点的值大于其父节点的值
            swap(big[pos], big[father]);     // 交换当前节点与父节点的值
        }
        else break;                          // 如果不满足最大堆性质,终止循环
        pos = father;                        // 更新当前节点的位置为父节点
    }
}

3.向下调整

void down(int pos) {
    int size = big.size();                    // 获取堆的大小
    while (2*pos <= size-1) {                 // 当前节点有至少一个子节点时循环
        int son;
        if (rson <= size - 1 and big[lson] < big[rson]) {       // 如果当前节点有右子节点且右子节点的值大于左子节点的值
            son = rson;                                         // 则选取右子节点作为子节点
        }
        else son = lson;                                        // 否则选取左子节点作为子节点
        if (big[pos] < big[son])swap(big[son], big[pos]);        // 如果当前节点的值小于子节点的值,则交换它们的位置
        else break;                                             // 如果不满足最大堆性质,终止循环
        pos = son;                                              // 更新当前节点的位置为子节点
    }
}

4.插入一个数:

void insert(int val) {
    big.push_back(val);              // 将元素 val 添加到堆的末尾

    upp(big.size() - 1);             // 调用 upp 函数,以维护最大堆性质
}

5.删除最大值:

void earse_big() {
    if (big.size() > 1) {                   // 如果堆中有至少两个元素
        big[1] = big[big.size() - 1];       // 将第一个元素用最后一个元素覆盖
        big.pop_back();                     // 删除最后一个元素
        down(1);                            // 对堆顶元素进行向下调整,以满足最大堆性质
    }
}

6.返回最大值:

int get_max() {
    if (big.size() > 1) {
        return big[1];
    }
}

以上就是二叉堆中的最大堆实现的过程。

不过在实际的写题中,我们不需要每次手写一个二叉堆。

可以直接用现成的stl 容器,priority_queue;

下面简单介绍以下stl的用法:

priority_queue<int>  bigheap;  //priority_queue 默认大根堆
priority_queue<int, vector<int>, greater<int> > littleheap; // 如果要定义小根堆,就要写全参数
// priority_queue 的参数为: 数据类型、容器类型、定义类型。 
//如果是小根堆, 我们在第三个参数那里改成: greater<int> 
//如果是大根堆:完整的写法就是: priority_queue<int , vector<int> , less<int> >  堆的名字

然后,下面是一些堆的函数:

priority_queue<int>  big;
priority_queue<int, vector<int>, greater<int> > little;

int main() {

	big.push(1); //插入的同时自动调整位置
	big.pop();//删除堆顶元素
	big.top()//返回堆顶元素 最大值/最小值

}

下面给一道堆的模板题:

P3378 【模板】堆 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3378答案:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
#include<list>
using namespace std;




#define ll long long
#define lson pos<<1
#define rson (pos<<1)|1
#define father pos>>1
const int N = 1e6 + 7;
//二叉堆:
// 最大堆,最小堆
// 最大堆要满足一个性质:任意一个节点,如果它的子节点存在的话,这个节点的值是要大于等于它的子节点的任意的值
// 
// 1、向下调整的函数
// 2、向上调整的函数
// 3、向一堆数据中插入一个元素
// 4、在一堆数据中删除一个元素(最大值)
// 5、求出一堆数据里面的最大值。
//
//
vector<int > heap(1);
void heapup(int pos) { //node 指的是vector下标


	while (pos > 1) {

		if (heap[pos] < heap[father]) {
			swap(heap[pos], heap[father]);
		}
		else {
			break;
		}
		pos = father;
	}


}

//第一个问题: 为什么heapdown函数中 循环的条件要取等
void heapdown(int pos) {


	int size = heap.size(); //实际上堆里面的元素为 size-1, size指的是一个空的下标
	

	while ( lson < size) {
		int son;


		if (rson<size and heap[lson] > heap[rson]) {
			son = rson;
		}
		else son = lson;


		if (heap[pos] < heap[son])break;

		else {
			swap(heap[pos], heap[son]);
		}


		pos = son;
	}
}


void insert(int val) {
	

	heap.push_back(val);
	int size = heap.size();
	heapup(size - 1);


}


int get_min() {


	return heap[1];


}


void earse_min() {

	if (heap.size() > 1) {

		heap[1] = heap[heap.size() - 1];
		heap.pop_back();
		heapdown(1);

	}

}


bool empty() {


	if (heap.size() > 1)return true;
	else return false;
}

int main() {
	
	int n;
	cin >> n;
	while (n--) {
		int op;
		cin >> op;
		if (op == 1) {
			int x;
			cin >> x;
			insert(x);
		}
		else if (op == 2) {
			cout<<get_min();
			cout << '\n';
		}
		else {
			earse_min();
		}
	}
}

下面讲一下,二叉堆的综合运用:

1. 对顶堆

先给一个模板题,来看看对顶堆的使用场景

P1801 黑匣子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1801然后,来介绍一下对顶堆:

堆顶堆由两个堆组成,一个大根堆,一个小根堆。

比如一遍往堆里插入元素,一遍问第i大的元素是哪个?

我们可以这样写:

它问第i大的元素是哪个?

我们就可以构造出一个这样的形状

 不断往小根堆中插入元素,直到插满i个元素,此后的话,执行这样一个操作:

先往小根堆里插入元素,然后取出小根堆的堆顶,加入上面的大根堆,然后删除小根堆的堆顶。

这样就实现了一个目的:

小根堆内的元素仍然是i个,但在新元素插入后,调整了大小关系,仍然使得小根堆的堆顶的元素是当前的第i大的元素(即时现在有超过i个元素,大于第i大的元素,都被放到了大根堆里)

如果i开始变化,如i变成i+1,那么我们直接把当前大根堆的堆顶的元素加入小根堆中,这个元素一定会在小根堆的堆顶,然后我们在删除大根堆的堆顶,使之调整结构

那么对于求第i小的元素,我们也是同样的道理,只要下面放大根堆,上面放小根堆,维护大根堆内的元素为i个,那么大根堆的堆顶就是在当前两个堆中,第i小的那个元素:

上题解:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
#include<map>
#include<set>
#include<queue>
#include<numeric>
#include<iomanip>
#include<stack>
#include<list>
using namespace std;




#define ll long long
#define lson pos<<1
#define rson (pos<<1)|1
#define father pos>>1
const int N = 2e6 + 7;
priority_queue<int>  bigheap;
priority_queue<int, vector<int>, greater<int> > littleheap;


int a[N];//元素
int opt[N];//操作
int main() {
	int m, n; //元素个数,操作个数
	cin >> m >> n;
	for (int i = 1; i <= m; i++) {
		cin >> a[i];
	}
	for (int j = 1; j <= n; j++) {
		cin >> opt[j];
	}

	int tot = 1, j = 1;

	for (int i = 1; i <= m; i++) {

		bigheap.push(a[i]);

		if (bigheap.size() >= tot) {
			littleheap.push(bigheap.top());
			bigheap.pop();
		}
		

		while (i == opt[j]) {

			cout << littleheap.top() << endl;

			
			bigheap.push(littleheap.top());
			
			
			littleheap.pop();
			j++;
			tot++;
		}
	}
}

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

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

相关文章

爱吃饼干的小白鼠2023年终总结

目录 前言 学习生活经历 回顾2023 参加活动 回顾点点滴滴 展望2024 2024年新的起点和目标 前言 大家好&#xff0c;我是爱吃饼干的小白鼠。今天刚好是2024年1月1日&#xff0c;时间飞逝&#xff0c;2023年过的飞快&#xff0c;我已经入驻CSDN有一年了&#xff0c;这一年…

计算机组成原理——中央处理器cpu21-40

18、某计算机的指令流水线由4个功能段组成&#xff0c;指令流经各功能段的时间&#xff08;忽略各功能段之间的缓存时间&#xff09;分别为90ns、80ns、70ns和60ns&#xff0c;则该计算机的CPU时钟周期至少是多少。A A、 90ns     B、 80ns C、 70ns     D、 60ns …

dll文件和exe文件的区别和关系

dll文件 DLL(Dynamic Link Library)文件为动态链接库文件&#xff0c;又称"应用程序拓展"&#xff0c;是软件文件类型。在Windows中&#xff0c;许多应用程序并不是一个完整的可执行文件&#xff0c;它们被分割成一些相对独立的动态链接库&#xff0c;即DLL文件&…

把类成员函数作为参数传递给thread类......

(1)把类成员函数作为参数传递给thread类 一般地&#xff0c;在调用类的非静态函数时&#xff0c;编译器会隐式添加一参数&#xff0c;它是所操作对象的地址&#xff0c; 用于绑定对象和成员函数&#xff0c;并且位于所有其他实际参数之前。例如&#xff0c;类example具有成员函…

CCNP课程实验-Route_Path_Control_CFG

目录 实验条件网络拓朴需求 基础配置需求实现1.A---F所有区用Loopback模拟&#xff0c;地址格式为&#xff1a;XX.XX.XX.XX/32&#xff0c;其中X为路由器编号。根据拓扑宣告进对应协议。A1和A2区为特例&#xff0c;A1&#xff1a;55.55.55.0/24&#xff0c;A2&#xff1a;55.55…

Linux驱动学习—设备树及设备树下的platform总线

1、什么是设备树&#xff1f; 设备树是一种描述硬件资源的数据结构。他通过bootloader将硬件资源传给内核&#xff0c;使得内核和硬件资源 描述相对独立。 2、设备树的由来 2.1 平台总线的由来 要想了解为什么会有设备树&#xff0c;设备树是怎么来的&#xff0c;我们就要先…

71内网安全-域横向网络传输应用层隧道技术

必备知识点&#xff1b; 代理和隧道技术的区别&#xff1f; 代理主要解决的是网络访问问题&#xff0c;隧道是对过滤的绕过&#xff0c; 隧道技术是为了解决什么 解决被防火墙一些设备&#xff0c;ids&#xff08;入侵检测系统&#xff09;进行拦截的东西进行突破&#xff0…

了解.NET 通用主机

写在前面 .NET 通用主机负责应用启动和生存期管理&#xff0c;主机是封装应用资源和生存期功能的对象&#xff0c;通用主机可用于其他类型的 .NET 应用程序&#xff0c;如控制台应用&#xff1b;.NET 通用主机基于类库Microsoft.Extensions.Hosting 来实现&#xff0c;本文记录…

保护Word或Excel的几种方法,总有一种满足你的需求

你已经在Microsoft Word或Excel中创建了一个重要或机密文件,你希望将其保密或至少保持安全。也许你想确保只有你和某些人可以阅读或编辑它。也许你想限制某人可以对文件进行的修改类型。你甚至可以向读者保证这是最终版本。如果你知道在Word和Excel中使用哪些工具以及它们是如…

安装Node修改Node镜像地址搭建Vue脚手架创建Vue项目

1、安装VSCode和Node 下载VSCode Visual Studio Code - Code Editing. Redefined 下载Node Node.js (nodejs.org) 检验是否安装成功&#xff0c;WinR,输入cmd命令&#xff0c;使用node -v可以查看到其版本号 2、修改镜像地址 安装好node之后&#xff0c;开始修改镜像地址 …

UntiyShader(五)属性、内置文件和变量

目录 一、如何使用属性 例子 ShaderLab中的属性的类型和Cg中的变量的类型之间的匹配关系 二、Unity提供的内置文件和变量 内置的包含文件 内置的变量 一、如何使用属性 在一开始我们提到过&#xff0c;材质和UnityShader之间有着密切的练习&#xff0c;我们可以通过材质面…

前后台分离开发

前后台分离开发 简介 前后台分离开发&#xff0c;就是在项目开发过程中&#xff0c;对于前端代码的开发由专门的前端开发人员负责&#xff0c;后端代码则由后端开发人员负责&#xff0c;这样可以做到分工明确、各司其职&#xff0c;提高开发效率&#xff0c;前后端代码并行开…

OpenOCD简介和下载安装(Ubuntu)

文章目录 OpenOCD简介OpenOCD软件模块OpenOCD源码下载OpenOCD安装 OpenOCD简介 OpenOCD&#xff08;Open On-Chip Debugger&#xff09;开放式片上调试器 OpenOCD官网 https://openocd.org/&#xff0c;进入官网点击 About 可以看到OpenOCD最初的设计是由国外一个叫Dominic Ra…

Qt高质量的开源项目合集

文章目录 1.Qt官网下载/文档2.第三方开源 1.Qt官网下载/文档 Qt Downloads Qt 清华大学开源软件镜像站 Qt 官方博客 2.第三方开源 记录了平常项目开发中用到的第三方库&#xff0c;以及一些值得参考的项目&#xff01; Qt AV 基于Qt和FFmpeg的跨平台高性能音视频播放框…

这本书没有一个公式,却讲透了数学的本质

这本书没有一个公式&#xff0c;却讲透了数学的本质&#xff01; 《数学的雨伞下&#xff1a;理解世界的乐趣》。一本足以刷新观念的好书&#xff0c;从超市到对数再到相对论&#xff0c;娓娓道来。对于思维空间也给出一个更容易理解的角度。 作者&#xff1a;米卡埃尔•洛奈 …

汇川PLC(H5U):伺服电机点动控制

一、基本概念理解 伺服电机旋转一圈的脉冲数 伺服电机上都装有一个编码器&#xff0c;这个编码器用于产生脉冲&#xff0c;常见伺服电机转一圈一般是产生10000个脉冲。相当于电机转360&#xff0c;产生了10000个脉冲&#xff0c;那旋转1就产生10000/360 27.7个脉冲。同理&am…

SDG大数据平台简介

联合国可持续发展目标&#xff08;Sustainable Development Goals&#xff09;缩写SDGs&#xff0c;是联合国制定的17个全球发展目标&#xff0c;在2000-2015年千年发展目标&#xff08;MDGs&#xff09;到期之后继续指导2015-2030年的全球发展工作。&#xff08;摘自百度&…

ROS安装PR2

一、PR2介绍 PR2是Willow Garage公司设计的机器人平台&#xff0c;也是目前科研领域经常用到的机器人之一。PR2有两条手臂&#xff0c;每条手臂七个关节&#xff0c;手臂末端是一个可以张合的夹爪&#xff1b;PR2依靠底部的四个轮子移动&#xff0c;在头部、胸部、肘部、夹爪上…

第 378 场周赛 解题报告 | 珂学家 | 分类讨论场

前言 整体评价 感觉是分类讨论场&#xff0c;t3用二分&#xff0c;是因为二分不会错&#xff0c;直接分类讨论容易WA. t4一开始看错题了&#xff0c;T_T, 看成翻转&#xff0c;写了半天StringHash, 还用上双hash&#xff0c;共8个StringHash。 重排的话&#xff0c;其实统计…

搜索引擎推广的实践技巧提升你的品牌影响力-华媒舍

搜索引擎推广是一种有效提升品牌影响力的推广策略。通过关键词优化、广告创意设计、定向投放和数据分析与优化等实践技巧&#xff0c;可以提高品牌的知名度、点击率和转化率。在实施引擎霸屏推广之前&#xff0c;还需对实践效果进行评估&#xff0c;以确保推广策略的有效性和适…