完全二叉树与堆(包含STL堆的用法)

news2024/11/16 0:17:00

完全二叉树

完全二叉树为一类特殊的二叉树,高度为h的完全二叉树满足如下条件:
(1)所有叶结点都出现在第h或h-1层;
(2)第h-1层的所有叶结点都在非叶结点右边
(3)除h-1层的最右非叶结点可能有一个左分支外,其余非叶结点都有两个分支

特点:
(1)叶结点只能出现在最底两层,且最底层的叶结点都集中在二叉树的左部;
(2)完全二叉树中至多有1个度为1的结点,如果有,则该结点只有左孩子;
(3)深度为h的完全二叉树的前h-1层一定是满二叉树。

性质:
具有n个结点的完全二叉树的深度为[\log_{2}n]+1。区中,运算符[x]为对x向下取整。

堆 

现实应用中,经常会遇到频繁地在一组动态数据中获取最大值或最小值的问题。这类问题可以在每次操作时对数据进行排序后获得,但这种方法的时间开销比较大。下面介绍一种可以高效解决这类问题的数据结构--堆。堆有多种形式,包括二叉堆、d-堆、左右堆、斜堆、斐波那契堆、 配对堆等。下面主要介绍二叉堆,为表述简洁,将二叉堆简称为堆。

(动态数据是指需要经常对数据进行更新操作的数据,更新操作包括插入、删除和修改数据)

堆的定义

堆(Heap) 是一类特殊类型的完全二叉树,其每个结点的键值都比其孩子结点的值大(小),因此,堆的根结点的键值为所有结点键值的最大(小)值。有两种类型的堆:如果根结点的键值为所有结点键值的最大值,则称为最大堆大顶堆;如果根结点的键值为所有结点键值的最小值,则称为最小堆小顶堆

下面介绍大顶堆的性质和操作方法,小顶堆只要稍作修改即可。

#include<iostream>
using namespace std;
typedef int datatype;

//一步操作:在数组heap的前n个元素所构成的完全二叉树中,将以cur为根结点的子树变为大顶堆
/*
	从最后一个非叶结点开始(这样才能进行与孩子结点的比较),从后往前考虑每个结点
	将这每个结点为根结点部分的子树转化为大顶堆(从部分先开始转化为大顶堆)

	需要进行以下的操作:
	1.孩子结点的比较,从大的入手 
	2.父与子结点的比较
	3.若还能向下比较,则注意一开始根结点的数值保存,应在while循环之外

	注意:
	因为每一次转化之前,部分都进行过大顶堆的转换,所以只需要保存每次的最顶上的值,
	依次向下轮换就行,另外注意while循环中cur和i的更新变化
	(i的变化是由于完全二叉树的性质,cur的变化是为了将子节点的较大值赋给父节点,tmp一直保留原先的根结点)
*/
void heap_adjust(datatype heap[], int n, int cur) {
	int i = 0, tmp = heap[cur]; //tmp保留的是根结点数值
	while (i = 2 * cur + 1, i < n) { //i为cur的做孩子
		if (i + 1 < n && heap[i + 1] > heap[i])i++; //孩子结点的比较,heap[i]为较大的孩子结点
		if (heap[i] <= tmp)break;
		heap[cur] = heap[i]; //结点i的值上浮到其父节点
		cur = i; //更新cur
	}
	heap[cur] = tmp;
}
 
//将数组heap的前n个元素所构成的完全二叉树转化为大顶堆
void heap_make(datatype heap[], int n) {
	for (int i = n / 2 - 1; i >= 0; i--)  //由于结点编号从0开始,第一个非叶结点为n/2
		heap_adjust(heap, n, i);
}
 
/*
	插入结点:
	向堆插入一个新的结点时,需要确定其插入的位置,以保证在插入该结点后新的二叉树仍保持堆得特性。
	方法是先将新的结点加入堆的末尾,然后从新的结点出发,自底向上调整二叉树
	思想同转化二叉树
*/
//向堆heap中插入一个值为k的结点,n为堆中元素的个数
void heap_push(datatype heap[],  int k) {
	int cur = 0, pa = 0;
	int n = sizeof(heap) / sizeof(datatype);
	cur = n, n++; //新加入的结点加到heap的末尾,并将其设置为当前结点
	while (cur > 0) {
		pa = (cur - 1) / 2; //结点为cur的父节点
		if (heap[pa] >= k)break;
		heap[cur] = heap[pa]; //当父节点不大于当前结点时,将父节点的值下沉
		cur = pa; //当前结点指向父节点
	}
	heap[cur] = k;
}

/*
	删除结点
	采用对结构的应用通常是为了快速获取一组动态数据中的最大值或最小值,即获取堆的根结点的值,
	因此,删除堆中的结点也只考虑删除对的根结点。

	方法:
	删除大顶堆根结点的方法是将堆的最后一个结点的值取代根结点的值,并删除最后一个结点,
	然后从根节点出发采用heap_adjust算法将完全二叉树转化为大顶堆
*/
//删除大顶堆heap的根结点,n为堆中元素的数量
void heap_pop(int heap[],int &n) { //n为引用类型,删除堆顶后,堆的元素会减1
	
	heap[0] = heap[n - 1];
	n--;
	heap_adjust(heap, n, 0);
}

int main() {
	datatype a[] = { 2,3,5,1,4,8,3,9,6 };

	//转化为堆
	heap_make(a, 9);
	/*for (int i = 0; i <= 8; i++)
		cout << a[i] << endl;*/
	
	//插入值
	/*heap_push(a, 7);
	for (int i = 0; i <= 8; i++)
		cout << a[i] << endl ;
	cout << endl;*/

	//删除值
	int n = sizeof(a) / sizeof(datatype);
	heap_pop(a,n);
	for (int i = 0; i <= 8; i++)
		cout << a[i] << endl;
}

实例展示:(配合上面的代码)

数组转化为大顶堆: 

 大顶堆插入值:

 大顶堆删除值

  STL中的堆——priority_queue

        普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。优先队列是指元素被赋予优先级,在访问元素时,只能访问具有最高优先级的元素,删除元素时也只能删除具有最高优先级的元素。由于每次都获取最高优先级的元素,因此优先队列在底层一般采用堆存储结构,而元素的排序规则是根据优先级从高到低排序。
        STL中集成了优先队列适配器容器prority-queue,支持从一个集合中快速地查找以及删除具有最大值或最小值的元素。priority_queue 可以基于容器deque或vector实现,其默认容器为deque,在使用过程中也可以将容器设置为vector。可分为两种:最小优先队列,适合查找和删除最小元素,类似于小顶堆;最大优先队列,适合查找和删除最大元素,类似于大项堆。在创建priority_queue时默认大顶堆。

使用priority_queue时必须添加头文件

#include<queue>

priority_queue类型的对象定义方法

        一般情况下,priority_queue 有3个类模板参数:第一个模板参数为数据类型,第二个模板参数为基础容器,默认为 deque<类型>类型;第三个模板参数为元素比较的方式,有两种方式:greater<类型>和 less<类型>,默认为 less<类型>。


        如果元素比较方式使用 less<类型>,则在用户自定义类型中必须定义“小于“运算符重
载;如果使用greater<类型>,则在用户自定义类型中必须定义“大于”运算符重载。

priority_queue的操作

和队列基本操作相同:

  • top 访问队头元素
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

在使用top和pop函数前,一般都需要采用empty函数判断当前优先队列是否为空。

priority_queue实例

1.基本类型例子

#include<iostream>
#include <queue>
using namespace std;
int main()
{
    //对于基础类型 默认是大顶堆
    priority_queue<int> a;
    //等同于 priority_queue<int, vector<int>, less<int> > a;

    priority_queue<int, vector<int>, greater<int> > c;  //这样就是小顶堆,并指定基础类型为vector

    priority_queue<string> b;

    for (int i = 0; i < 5; i++)
    {
        a.push(i);
        c.push(i);
    }

    while (!a.empty())
    {
        cout << a.top() << ' ';
        a.pop();
    }
    cout << endl;

    while (!c.empty())
    {
        cout << c.top() << ' ';
        c.pop();
    }
    cout << endl;

    b.push("abc");
    b.push("abcd");
    b.push("cbd");
    while (!b.empty())
    {
        cout << b.top() << ' ';
        b.pop();
    }
    cout << endl;
    return 0;
}

 2.pari的比较,先比较第一个元素,第一个相等比较第二个

#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main() 
{
    priority_queue<pair<int, int> > a;
    pair<int, int> b(1, 2);
    pair<int, int> c(1, 3);
    pair<int, int> d(2, 5);
    a.push(d);
    a.push(c);
    a.push(b);
    while (!a.empty()) 
    {
        cout << a.top().first << ' ' << a.top().second << '\n';
        a.pop();
    }
}

3. 对于自定义类型

#include <iostream>
#include <queue>
using namespace std;

//方法1
struct tmp1 //运算符重载<
{
    int x;
    tmp1(int a) {x = a;}
    bool operator<(const tmp1& a) const
    {
        return x < a.x; //大顶堆
    }
};

//方法2
struct tmp2 //重写仿函数
{
    bool operator() (tmp1 a, tmp1 b) 
    {
        return a.x < b.x; //大顶堆
    }
};

int main() 
{
    tmp1 a(1);
    tmp1 b(2);
    tmp1 c(3);
    priority_queue<tmp1> d;
    d.push(b);
    d.push(c);
    d.push(a);
    while (!d.empty()) 
    {
        cout << d.top().x << '\n';
        d.pop();
    }
    cout << endl;

    priority_queue<tmp1, vector<tmp1>, tmp2> f;
    f.push(c);
    f.push(b);
    f.push(a);
    while (!f.empty()) 
    {
        cout << f.top().x << '\n';
        f.pop();
    }
}

总结: 

        优先队列是一种非常有用的数据结构,哈夫曼算法、Dijkstra最短路径算法、Prim最小生成树算法等都采用优先队列实现,另外A*搜索算法和操作系统的线程调度也都使用优先队列。

参考:

1.c++优先队列(priority_queue)用法详解_吕白_的博客-CSDN博客_priority_queue 

2.C++中STL用法超详细总结_list_HUST_Miao-DevPress官方社区 (csdn.net) 

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

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

相关文章

AAAI 2023|模拟人脑场景感知过程,套娃Transformer讲故事能力更上一层楼

原文链接&#xff1a;https://www.techbeat.net/article-info?id4467 作者&#xff1a;seven_ 视频字幕生成目前已成为工业界AI创作领域非常火热的研究话题&#xff0c;这一技术可以应用在短视频的内容解析和讲解中&#xff0c;AI讲故事的技术已经越来越成熟。而在学术界&…

13、ThingsBoard-如何发送告警邮件

1、概述 很多时候,我们使用thingsboard的时候,会遇到比如一个设备触发了告警,如何将设备的告警消息定义成邮件模板,然后通知租户或者客户管理员,管理员进行处理,这样的需求是非常重要的。 2、实现的步骤 要实现这个需求我总结了几步: 2.1、设备上报的参数与阈值进行…

基于关键点检测的病患步态检测及分析方法

在临床工作中&#xff0c;对患有神经系统或骨骼肌肉系统疾病而可能影响行走能力的患者需要进行步态分析&#xff0c;以评定患者是否存在异常步态以及步态异常的性质和程度 步态评定临床意义 1、评估患者是否存在异常步态以及步态异常的性质和程度 2、为分析异常步态原因和矫正异…

看我们应用性能监控如何几秒钟定位慢访问跟因

背景 某汽车集团的汽车配件电子图册系统是其重要业务系统。最近业务部门反映&#xff0c;汽车配件电子图册调用图纸时&#xff0c;出现访问慢现象。 某汽车集团总部已部署NetInside流量分析系统&#xff0c;使用流量分析系统提供实时和历史原始流量。本次分析重点针对汽车配件…

二进制?十进制!

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 给定两个十进制整数 : AAA,BBB 你需要把它们的二进制形式以十进制的运算法则相加输出结果。 例如&#xff1a; A3,B2A 3 , B 2A3,B2 的时候&#xff0c;AAA 的二进制表示是 : 111111 , BB…

Linux部署Nexus通过Maven推送及拉取代码

&#x1f60a; 作者&#xff1a; 一恍过去&#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390&#x1f38a; 社区&#xff1a; Java技术栈交流&#x1f389; 主题&#xff1a; Linux部署Nexus通过Maven推送及拉取代码⏱️ 创作时间&#xff1a; 2023年…

myBaits Expert Wheat Exome — 从多个小麦品种中富集超过250Mb的高可信度的外显子

myBaits Expert Wheat Exome 与国际小麦基因组测序联盟(IWGSC)合作开发&#xff0c;使用了IWGSC发布的中国春基因组和注释信息。靶向六倍体小麦中完整的高置信度且有基因注释的外显子区域,能够全面、统一、可靠地深入覆盖大干15 Gb的小麦基因组中超过250 Mb的CDS及其邻近区域。…

硅烷聚乙二醇活性酯;Silane-PEG-NHS;溶于大部分有机溶剂。仅供科研实验使用,不用于诊治

英文名称&#xff1a;Silane-PEG-NHS&#xff0c;Silane-PEG-SCM 中文名称&#xff1a;硅烷聚乙二醇活性酯 分子量&#xff1a;1k&#xff0c;2k&#xff0c;3.4k&#xff0c;5k&#xff0c;10k&#xff0c;20k。。。 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;…

数组常用方法总结 (5) :find / findIndex / filter

find 与前边讲过的 some 类似&#xff0c;用于检测数组的每一项是否符合限定条件。只要遇到一个符合条件的&#xff0c;就会停止循环。在循环中&#xff0c;如果是简单数组&#xff0c;数据不会被改变&#xff0c;如果是对象数组&#xff0c;数据会改变。如果停止了循环&#…

音频(七)——数字麦克风和模拟麦克风(DMIC/AMIC)

数字麦克风与模拟麦克风(DMIC/AMIC) 麦克风(mic)&#xff1a;是将声音信号转换为电信号的能量转换器件&#xff0c;也就是用来采集你说话的声音扬声器(speaker)&#xff1a;是一种把电信号转变为声信号的换能器件&#xff0c;就是把对方说话产生的电信号转换成声音播放出来。简…

比较C++在for循环中的i++和++i以及i++的O2优化的效率:++i真的比i++快吗

比较C在for循环中的i和i以及i的O2优化的效率&#xff1a;i真的比i快吗 前言 对i和i的争论褒贬不一&#xff0c;不知从何时起&#xff08;大概是学C的时候老师就是这么教的&#xff09;我的习惯是在for循环中使用i而不是i for (int i 0; i < n; i) // 典但是看到一些博客…

再说多线程(五)——死锁

在前面四节中&#xff0c;我们一直没有讨论多线程程序的一个负面问题——死锁&#xff0c;有了一定的基础&#xff0c;现在是时候研究一下死锁了。死锁一定是出现在多线程程序中&#xff0c;单线程是不可能造成死锁的&#xff0c;因为你不可能同时加两把锁。死锁有个简单的例子…

《软件工程》课程四个实验的实验报告(《可行性研究与项目计划》《需求分析》《系统设计》《系统实现》)

实验1《可行性研究与项目计划》 实验学时&#xff1a; 2 实验地点&#xff1a; 任意 实验日期&#xff1a; 12月15日 一、实验目的 了解&#xff1a;软件项目可行性研究及项目计划的基本原理与方法&#xff1b;掌握&#xff1a;Visio等工具进行可…

【尚硅谷】Java数据结构与算法笔记06 - 算法复杂度详解

文章目录一、算法的时间复杂度1.1 度量算法执行时间的两种方法1.1.1 事后统计1.1.2 事前估算1.2 时间频度1.2.1 基本介绍1.2.2 举例说明&#xff1a;基本案例1.2.3 举例说明&#xff1a;忽略常数项1.2.4 举例说明&#xff1a;忽略低次项1.2.5 举例说明&#xff1a;忽略系数1.3 …

WebServer传输大文件致客户端自动关闭

程序运行在云服务器上, Ubuntu 20.04LTS系统&#xff0c;用浏览器测试能正常打开页面&#xff0c;请求一般的html文本和几十kb的小图片无问题&#xff0c;接着放了一个1.63MB&#xff08; 1714387字节&#xff09;的网上找的图过去&#xff0c;客户端图没加载完就自动断连了&am…

如何搭建一个专业的企业知识库

当客户跟你达成合作关系后&#xff0c;需要持续的关系维护&#xff0c;在一定的销售点&#xff0c;定期和客户沟通&#xff0c;据调查&#xff0c;赢得一个新客户的成本可能是保留一个现有客户的5到25倍&#xff0c;作为营销策略&#xff0c;客户服务支持必须满足他们的期望。建…

Linux小黑板(7):再谈动静态

"我看到&#xff0c;久违的晴朗啊"一、什么是动静态库在本栏目前面的篇幅也提到过这个概念&#xff0c;因此本小节就小小地回顾一番。在linux下:静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。动态库(.so):程序在运行的时候才去链接动态库的代码&am…

【npm报错】解决invalid json response body at https://registry.npmjs.org

报错信息&#xff1a; npm ERR! code FETCH_ERROR npm ERR! errno FETCH_ERROR npm ERR! invalid json response body at https://registry.npmjs.org/riophae%2fvue-treeselect reason: Invalid response body while trying to fetch https://registry.npmjs.org/riophae%2f…

从粪便菌群移植到下一代有益菌:Anaerobutyricum soehngenii为例

谷禾健康 我们知道&#xff0c;肠道微生物群对人类健康和福祉很重要&#xff0c;调节宿主代谢&#xff0c;塑造免疫系统并防止病原体定植。 通过粪便微生物群移植&#xff08;FMT&#xff09;恢复平衡多样的微生物群&#xff0c;已成为研究疾病发病机制中微生物群因果关系的潜在…

Spring Cloud Gateway 之限流

文章目录一、常见的限流场景1.1 限流的对象1.2 限流的处理方式1.3 限流的架构二、常见的限流算法2.1 固定窗口算法&#xff08;Fixed Window&#xff09;2.2 滑动窗口算法&#xff08;Rolling Window 或 Sliding Window&#xff09;2.3 漏桶算法&#xff08;Leaky Bucket&#…