堆-----数据结构

news2024/11/20 7:37:06

引言

什么是堆?堆是一种特殊的数据结构(用数组表示的树)。

为什么要使用到堆?比如一场比赛,如果使用擂台赛的方式来决出冠军(实力第一),就很难知道实力第二的队伍是什么了。

但是下图能很清楚的表示各队伍的强弱关系。

堆的特点

上图就是一个最大堆,解释:每一个圆都是一个节点,数字代表着键值,其中95是93和92的父节点,93和92是95的子节点,93和92是兄弟节点(父节点为同一个),根节点就是键值最大的节点,为95,最后一个节点是87,最后一个左子节点也是87。

最大堆的特点

满足以下三点

1.每个节点最多可以有两个子节点。

2.根节点的键值是所有堆节点键值中最大者,且每个节点的值都大于其子(孩子)节点。

3.除了根节点没有兄弟节点,最后一个左子节点可以没有兄弟节点,其他节点必须有兄弟节点。

最好是自己理解,不用强记 。其中有一点要注意:A的兄弟节点的子节点可能大于A,相当于在比赛中,其中一个小组都是弱队,那么一个弱队却可以闯入半决赛一样。

最小堆的特点的话就将第二点中的大改为小就可以了,其他的特点一样。这里讨论的是最大堆

数组形式表示

父节点和子节点的关系:

i 的左子节点:2i+1 ,右子节点:2i+2

i 的父节点:(i-1)/2

i 是从 0 开始的

再将上图的堆转换为数组的形式,如下图:

 这就是一个最大堆的数组表示形式。

在数组中快速创建堆

也就是怎么把任意一个数组变成最大/小堆。

我们先把堆的最小单位拿出来,如下图:

他不是一个最大堆,如果要变成最大堆,只需要父节点是95,子节点分别是86、88就可以了(86和95交换)。而一个最大堆是由若干个这个最小单位组成的,所以第一步就是将所有的堆的最小单位变成最大堆。

1、首先先找到最后一个节点的父节点,找出该节点的最大子节点与自己比较,如果大于自身,就交换两个节点。

2、继续移动到上一个父节点(也就是下标 -1 的地方),重复做第一步的比较操作,直到遍历所有的父节点。

当我们移动完所有的父节点,那最大堆就形成了吗?还疏忽了一个地方。例如当移动到某个父节点时,如下图:

最开始父节点是88,与子节点95交换了,那父节点就是95,95 的子节点就是 88,那88一定大于他的子节点吗?很显然这个答案是不一定,因为 88 的子节点只满足小于之前的父节点 95,所以还需要向下调整,直到子节点都小于父节点。

3、对每次移动中,变成子节点的节点,向下调整,也就是判断他与子节点是否满足最大堆的特点,不满足还要继续移动节点(向下调整),满足的话就接着下个父节点。

4、所有的节点交换完毕,最大堆构建完成。

堆的算法实现

堆数据结构的定义

#define DEFAULT_CAPCITY 120 //默认的堆容量

typedef struct _Heap
{
	int* arr;		//存储堆元素的数组
	int size;		//堆中元素的个数
	int capcity;	//堆的容量
}Heap;

//函数声明
void buildHeap(Heap& heap);
bool inset(Heap& heap, int value);
bool initHeap(Heap& heap, int* orginal, int size);
void adjustDown(Heap& heap, int i);
void adjustUp(Heap& heap, int i);

堆的初始化 

bool initHeap(Heap& heap,int *orginal,int size) 
{
    //orginal 是指向数组的指针,而这个数组是我们要传入堆的数组

	int capcity = DEFAULT_CAPCITY > size ? DEFAULT_CAPCITY : size; //取size和默认容量的最大值
	
	heap.arr = new int[capcity];
	if (!heap.arr) return false; //申请失败

	heap.capcity = capcity;

	if (size > 0) //size合法
	{
		memcpy(heap.arr, orginal, sizeof(int) * size);
		heap.size = size;
		//建堆
		buildHeap(heap);
	}

	return true;
}

堆的创建

//建堆,从最后一个父节点逐个向前调整所有的父节点(直到根节点),确保每一个父节点都是一个最大堆
//那么,整体上就是一个最大堆
void buildHeap(Heap& heap)
{
	int i = (heap.size - 2) / 2; //因为下标从0开始,heap.size-1就得到下标,再结合公式就是这个式子

	for (; i >= 0; i--)
	{
		adjustDown(heap, i); //向下调整包含了构建最大堆,如果感到困惑,先看向下调整函数
	}
}

堆的向下调整函数

void adjustDown(Heap& heap, int i)
{
	int temp = heap.arr[i]; //保存父节点的键值
	int parent = 0 ,child = 0;

	for (parent = i; (2 * parent + 1) < heap.size; parent = child) 
	{
		child = 2 * parent + 1; //先指向左子节点
		
		//指向两个子节点中最大的节点
		if (child + 1 < heap.size && heap.arr[child] < heap.arr[child + 1])
		{
			child = child + 1;
		}
        
		if (temp >= heap.arr[child])
		{
			break; //无需向下调整
		}
		else
		{
			heap.arr[parent] = heap.arr[child];
			heap.arr[child] = temp;
		}
		
	}
}

堆的插入新元素

1、插入新的元素到最大堆的尾部,也就是数组的后面

2、插入的元素可能会破环这个最大堆,需要重新调整,和父节点比较,如果比父节点大,就交换两个节点……重复直到新节点比父节点小或者新节点变为根节点(调整到位)。

设计两个函数,一个是插入,一个是向上调整。

bool insert(Heap& heap, int value)
{
	if (heap.size == heap.capcity) //堆空间不足
	{
		return false;
	}

	int i = heap.size ; //指向新加元素的下标
	heap.arr[heap.size++] = value;
	adjustUp(heap , i);
	return true;
}
void adjustUp(Heap& heap, int i)
{
	if (i <= 0 || i >= heap.size) return;

	while (i > 0)
	{
		int parent = (i - 1) / 2;
		if (parent >= 0) // 父节点没越界
		{
			if (heap.arr[parent] < heap.arr[i])
			{
				int temp = heap.arr[i];
				heap.arr[i] = heap.arr[parent];
				heap.arr[parent] = temp;
				i = parent;
			}
			else
			{
				break; //无需调整
			}
		}
		else
		{
			break; //父节点出界
		}
	}
}

看到这,你会发现堆的创建还有一种方式,也就是将数组的元素一个一个插入,也能得到最大堆。

源代码

#include <iostream>

using namespace std;




#define DEFAULT_CAPCITY 120 //默认的堆容量

typedef struct _Heap
{
	int* arr;		//存储堆元素的数组
	int size;		//堆中元素的个数
	int capcity;	//堆的容量
}Heap;

void buildHeap(Heap& heap);
bool insert(Heap& heap, int value);
bool initHeap(Heap& heap, int* orginal, int size);
void adjustDown(Heap& heap, int i);
void adjustUp(Heap& heap, int i);

//初始化堆
bool initHeap(Heap& heap,int *orginal,int size) 
{
    //orginal 是指向数组的指针,而这个数组是我们要传入堆的数据

	int capcity = DEFAULT_CAPCITY > size ? DEFAULT_CAPCITY : size; //取size和默认容量的最大值
	
	heap.arr = new int[capcity];
	if (!heap.arr) return false;

	heap.capcity = capcity;

	if (size > 0)
	{
		memcpy(heap.arr, orginal, sizeof(int) * size);
		heap.size = size;
		//建堆
		buildHeap(heap);
	}

	return true;
}

//建堆,从最后一个父节点逐个向前调整所有的父节点(直到根节点),确保每一个父节点都是一个最大堆
//那么,整体上就是一个最大堆
void buildHeap(Heap& heap)
{
	int i = (heap.size - 2) / 2; //因为下标从0开始,heap.size-1就得到下标

	for (; i >= 0; i--)
	{
		adjustDown(heap, i);
	}
}

void adjustDown(Heap& heap, int i)
{
	int temp = heap.arr[i]; //父节点的键值
	int parent = 0 ,child = 0;

	for (parent = i; (2 * parent + 1) < heap.size; parent = child)
	{
		child = 2 * parent + 1;
		
		//指向两个子节点中最大的节点
		if (child + 1 < heap.size && heap.arr[child] < heap.arr[child + 1])
		{
			child = child + 1;
		}

		if (temp >= heap.arr[child])
		{
			break; //无需向下调整
		}
		else
		{
			heap.arr[parent] = heap.arr[child];
			heap.arr[child] = temp;
		}
		
	}
}

//堆——插入新元素
bool insert(Heap& heap, int value)
{
	if (heap.size == heap.capcity)
	{
		return false;
	}

	int i = heap.size ;
	heap.arr[heap.size++] = value;
	adjustUp(heap , i);
	return true;
}

void adjustUp(Heap& heap, int i)
{
	if (i <= 0 || i >= heap.size) return;

	while (i > 0)
	{
		int parent = (i - 1) / 2;
		if (parent >= 0) // 父节点没越界
		{
			if (heap.arr[parent] < heap.arr[i])
			{
				int temp = heap.arr[i];
				heap.arr[i] = heap.arr[parent];
				heap.arr[parent] = temp;
				i = parent;
			}
			else
			{
				break; //无需调整
			}
		}
		else
		{
			break; //父节点出界
		}
	}
}
int main(void)
{
	Heap heap;
	int orginalArr[] = { 1,2,3,87,93,82,92,86,95 };

	if (!initHeap(heap, orginalArr, sizeof(orginalArr) / sizeof(int)))
	{
		cout << "初始化堆失败!" << endl;
		exit(-1);
	}

	for (int i = 0; i < heap.size; i++)
	{
		printf("%d\n",heap.arr[i]);
	}
	puts("");
	insert(heap, 100);
	for (int i = 0; i < heap.size; i++)
	{
		printf("%d\n", heap.arr[i]);
	}
	return 0;
}

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

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

相关文章

Simulink 最基础教程(三)常用模块

3.1源模块 1&#xff09;clock 这个模块的输出是 y(t)t。很多信号都是和时间 t 相关的&#xff0c;例如正弦波信号&#xff0c;可以写成 sin(w*t) 的形式。虽然软件也提供了正弦波模块&#xff0c;但如果用 clock 模块三角运算模块&#xff0c;对初学者而言&#xff0c;也是很好…

QT_day3

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到新的界面中 如果账号和密码不匹配&#…

科技资讯|2023全球智能手表预估出货1.3亿块,智能穿戴提升AI功能

根据集邦咨询公布的最新报告&#xff0c;受全球经济低迷影响&#xff0c;2023 年全球智能手表出货量预估为 1.3 亿块。苹果以超过 30% 的份额领先&#xff0c;其次是三星&#xff08;接近 10%&#xff09;、华为、Garmin、Fitbit 等。 报告认为苹果、三星和华为等主要智能手表…

智能新零售管理系统哪个好?亿发数字化收银系统提供商,可定制

在数字化时代的背景下&#xff0c;传统收银系统已经无法满足商家不断增加的业务需求。因此&#xff0c;出现了智能门店收银系统&#xff0c;该系统旨在为商家提供更加智能、高效的解决方案&#xff0c;满足商家的个性化需求&#xff0c;帮助中小型商家提高数字化运营能力。 1、…

运筹优化 | Python调用Gurobi求解线性规划 | 代码解析

需要求解的线性规划 from gurobipy import *定义了一个线性松弛问题&#xff0c;并用Gurobi求解 initial_LP Model(initial LP) # 定义变量initial_LP&#xff0c;调用Gurobi的Model&#xff0c;选择Initial Programming&#xff08;整数规划&#xff09;模型 x {} # 创建一个…

TCP/IP模型五层协议

TCP/IP模型五层协议 认识协议 约定双方进行的一种约定 协议分层 降低了学习和维护的成本&#xff08;封装&#xff09;灵活的针对这里的某一层协议进行替换 四/五层协议 五层协议的作用 应用层 应用层常见协议 应用层常见协议概览 基于TCP的协议 HTTP&#xff08;超…

【C++】命名空间和using namespace std的注意事项

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

2022年下半年 软件设计师 上午试卷(22题—40题)

对高级语言源程序进行编译或解释的过程中需要进行语法分析&#xff0c;递归子程序分析属于 &#xff08;22&#xff09; 的分析法。 &#xff08;22&#xff09; A. 自上而下 B. 自下而上 C. 从左至右 D. 从右至左 注意字眼”递归“&#xff0c;自上而下的语法分析方法是一种自…

深入理解 Netty FastThreadLocal

作者&#xff1a;vivo 互联网服务器团队- Jiang Zhu 本文以线上诡异问题为切入点&#xff0c;通过对比JDK ThreadLocal和Netty FastThreadLocal实现逻辑以及优缺点&#xff0c;并深入解读源码&#xff0c;由浅入深理解Netty FastThreadLocal。 一、前言 最近在学习Netty相关的…

【C++面向对象】1. 类、对象

文章目录 【 1. 类 & 对象的定义 】1.1 类的定义1.2 对象的定义 【 2. 类的成员 】2.1 数据成员2.2 成员函数类的内部定义成员函数类的外部定义成员函数成员函数的访问实例 【 3. 类的访问修饰符 】3.1 public 公有成员3.2 private 私有成员3.3 protected 保护成员3.4 继承…

【Excel】WPS单元格快速转换表格字母大小写

使用WPS Office打开表格&#xff0c;选择需要处理的单元格或单元格区域。 依次点击「会员专享」选项卡 —>「智能工具箱」。 再点击「格式」—>「大小写」&#xff0c;选择一种大小写转换方式即可。

测试开发之自动化篇 —— 使用Selenium IDE录制脚本!

今天&#xff0c;我们开始介绍基于开源Selenium工具的Web网站自动化测试。 Selenium包含了3大组件&#xff0c;分别为&#xff1a;1. Selenium IDE 基于Chrome和Firefox扩展的集成开发环境&#xff0c;可以录制、回放和导出不同语言的测试脚本。 2. WebDriver 包括一组为不同…

AI智能视频监控系统解锁新场景:大型音乐节活动视频监控方案

随着近日音乐节的大火&#xff0c;越来越多的人喜欢参加音乐节进行放松娱乐。音乐节通常都会聚集大量人群&#xff0c;为了确保参与者的安全&#xff0c;在音乐节期间设置智能视频监控系统可以帮助管理人员及时发现和应对任何潜在危险或紧急情况&#xff0c;可以保障参与者的生…

重磅升级!官网全新改版上线啦~

新布局、新风格 新体验、新服务 棱镜七彩新版官网 正式上线啦&#xff01; 各版块功能全新升级 为广大用户提供更优质的服务体验&#xff01; 重构版块信息&#xff0c;用户需求一键直达 新官网结构大焕新&#xff0c;升级后的官网根据用户需求进行了更合理的设置与规划…

IJCAI2023【基于双曲空间探索的非独立同分布联邦学习】

1、介绍汇报的主题及汇报者 2、粗略介绍面临的挑战及出发点 3、介绍一下预备知识 4、解决方案 5、总览 6、实验设置 7、实验 8、结论

代码检查的方式有哪几种?

代码检查是软件开发过程中的关键环节&#xff0c;可以帮助发现和纠正潜在的错误和问题。以下是几种常见的代码检查方式&#xff1a; 1. 人工代码检查&#xff1a; 这是最基本和常见的方式&#xff0c;由开发人员手动检查代码。这种方式依赖于开发人员的经验和专业知识&#xf…

Linux远程管理协议

&#xff08;RFB、RDP、Telnet和SSH&#xff09;是Linux的远程管理协议。提到远程管理&#xff0c;通常指的是远程管理服务器&#xff0c;而非个人计算机。个人计算机可以随时拿来用&#xff0c;服务器通常放置在机房中&#xff0c;用户无法直接接触到服务器硬件&#xff0c;只…

JavaScript 操作浏览器和HTML文档/JavaScript 操作对象

JavaScript 操作浏览器和HTML文档 框架 浏览器对象模型&#xff08;BOM&#xff09; windownavigatorscreenlocation 文档对象模型&#xff08;DOM&#xff09; document 操作对象间的关系 window对象是浏览器的顶层对象&#xff0c;它包含了浏览器窗口的各种属性和方法。w…

数据库表设计及优化初步——项目中的数据库表究竟怎么设计?如何提高查询效率?

前言 我们都知道数据库设计有以下三大范式&#xff0c;但实际应用中真的是按照这三大范式来设计吗&#xff1f; 本篇博客尝试阐述项目中数据库表的设计&#xff0c;以及查询优化的方法。 第一范式&#xff1a; 原子&#xff0c;列信息不可再分&#xff1b; 第二范式&#…

培训机构招生电子传单制作教程:突出核心竞争力的方法

随着科技的不断发展&#xff0c;现在的招生宣传也变得越来越电子化。其中&#xff0c;电子传单就是一种非常有效的宣传方式。下面就让我们来学习如何制作具有吸引力的培训机构招生电子传单。 首先&#xff0c;我们需要进入乔拓云后台&#xff0c;并登录。在登录后&#xff0c;我…