数据与结构--堆

news2024/11/30 8:39:11

堆的概念

:如果有一个关键码的集合K={k0,k1,k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足ki<=k2i+1且ki<=k2i+2(或满足ki>=k2i+1且ki>=k2i+2),其中i=0,1,2,…,则称该集合为堆。

  1. 堆序性质:在堆中,父节点的值要么大于等于(最大堆)或小于等于(最小堆)其子节点的值。这个性质是堆的核心特征。

  2. 完全二叉树结构:堆通常是一棵完全二叉树,即除了最底层外,其他层的节点都是满的,而且最底层的节点都集中在最左边。这意味着在堆中插入和删除节点时,树的形状会发生变化,但始终保持完全二叉树的性质。

  3. 最大堆和最小堆:堆分为最大堆和最小堆。

    • 最大堆:每个父节点的值都大于等于其子节点的值。根节点是堆中的最大值。
    • 最小堆:每个父节点的值都小于等于其子节点的值。根节点是堆中的最小值。

堆的结构 

大根堆示例

小根堆示例

堆的向下调整算法

现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。

 但是,使用向下调整算法需要满足一个前提
 若想将其调整为小堆,那么根结点的左右子树必须都为小堆。
 若想将其调整为大堆,那么根结点的左右子树必须都为大堆。

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

代码如下:

//交换函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//堆的向下调整(小堆)
void AdjustDown(int* a, int n, int parent)
{
	//child记录左右孩子中值较小的孩子的下标
	int child = 2 * parent + 1;//先默认其左孩子的值较小
	while (child < n)
	{
		if (child + 1 < n&&a[child + 1] < a[child])//右孩子存在并且右孩子比左孩子还小
		{
			child++;//较小的孩子改为右孩子
		}
		if (a[child] < a[parent])//左右孩子中较小孩子的值比父结点还小
		{
			//将父结点与较小的子结点交换
			Swap(&a[child], &a[parent]);
			//继续向下进行调整
			parent = child;
			child = 2 * parent + 1;
		}
		else//已成堆
		{
			break;
		}
	}
}

那么建堆的时间复杂度又是多少呢?
 当结点数无穷大时,完全二叉树与其层数相同的满二叉树相比较来说,它们相差的结点数可以忽略不计,所以计算时间复杂度的时候我们可以将完全二叉树看作与其层数相同的满二叉树来进行计算。

堆的向上调整算法

当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。 

代码实现

//交换函数
void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}

//堆的向上调整(小堆)
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)//调整到根结点的位置截止
	{
		if (a[child] < a[parent])//孩子结点的值小于父结点的值
		{
			//将父结点与孩子结点交换
			Swap(&a[child], &a[parent]);
			//继续向上进行调整
			child = parent;
			parent = (child - 1) / 2;
		}
		else//已成堆
		{
			break;
		}
	}
}

 堆的实现

初始化堆

// 初始化堆
HeapNode* initializeHeap() {
    return nullptr; // 返回空指针表示空堆
}

判断堆是否为空

// 判断堆是否为空
bool isEmpty(HeapNode* heap) {
    return heap == nullptr;
}

这个函数简单地检查堆是否为空,如果堆是空的则返回true,否则返回false。

插入元素

// 向堆中插入元素
HeapNode* insertElement(HeapNode* heap, int val) {
    HeapNode* newNode = new HeapNode(val); // 创建新节点
    if (isEmpty(heap)) {
        return newNode; // 如果堆为空,新节点即为根节点
    } else {
        // 找到最后一个节点
        HeapNode* temp = heap;
        while (temp->left != nullptr && temp->right != nullptr) {
            temp = temp->left; // 堆是一个完全二叉树,所以优先插入左子节点
        }
        // 插入新节点作为最后一个节点的左子节点
        if (temp->left == nullptr) {
            temp->left = newNode;
        } else {
            temp->right = newNode;
        }
        return heap;
    }
}

这个函数首先创建一个新节点,然后判断堆是否为空。如果堆为空,新节点即为根节点。如果堆不为空,函数会找到最后一个节点,然后将新节点插入为其左子节点(优先插入左子节点)或右子节点。

删除元素

// 从堆中删除元素
HeapNode* deleteElement(HeapNode* heap, int val) {
    if (heap == nullptr) {
        std::cout << "Heap is empty." << std::endl;
        return heap; // 如果堆为空,直接返回
    }

    // 先找到要删除的节点及其父节点
    HeapNode* parent = nullptr;
    HeapNode* nodeToDelete = heap;
    while (nodeToDelete != nullptr && nodeToDelete->value != val) {
        parent = nodeToDelete;
        if (val < nodeToDelete->value) {
            nodeToDelete = nodeToDelete->left;
        } else {
            nodeToDelete = nodeToDelete->right;
        }
    }

    // 如果未找到要删除的节点
    if (nodeToDelete == nullptr) {
        std::cout << "Element not found in heap." << std::endl;
        return heap;
    }

    // 如果要删除的节点有两个子节点
    if (nodeToDelete->left != nullptr && nodeToDelete->right != nullptr) {
        // 找到要删除节点的右子树中最小的节点
        HeapNode* minRight = nodeToDelete->right;
        while (minRight->left != nullptr) {
            minRight = minRight->left;
        }
        // 用最小右节点的值替换要删除的节点的值
        nodeToDelete->value = minRight->value;
        // 删除最小右节点
        heap = deleteElement(heap, minRight->value);
        return heap;
    }

    // 如果要删除的节点是叶子节点或只有一个子节点
    if (nodeToDelete->left == nullptr) {
        if (parent != nullptr) {
            if (parent->left == nodeToDelete) {
                parent->left = nodeToDelete->right;
            } else {
                parent->right = nodeToDelete->right;
            }
        } else {
            heap = nodeToDelete->right;
        }
        delete nodeToDelete;
        return heap;
    }

    if (nodeToDelete->right == nullptr) {
        if (parent != nullptr) {
            if (parent->left == nodeToDelete) {
                parent->left = nodeToDelete->left;
            } else {
                parent->right = nodeToDelete->left;
            }
        } else {
            heap = nodeToDelete->left;
        }
        delete nodeToDelete;
        return heap;
    }

    return heap;
}

删除元素代码解释

这段代码实现了从堆中删除元素的功能。让我来解释一下:

  1. 首先,我们检查堆是否为空,如果为空则输出错误信息并直接返回。

  2. 接着,我们使用循环来找到要删除的节点以及其父节点。循环条件是当前节点不为空且当前节点的值不等于待删除的值,根据待删除的值和当前节点值的比较结果来决定往左子树还是右子树走。

  3. 如果我们找到了要删除的节点:

    • 如果要删除的节点有两个子节点,则我们需要找到其右子树中的最小节点(即右子树中的最左下角的节点),将其值替换到待删除的节点中,然后递归地删除最小节点。

    • 如果要删除的节点是叶子节点或只有一个子节点,则我们将其子节点链接到其父节点上,并删除待删除的节点。

  4. 最后,我们返回调整后的堆。

打印元素

// 打印堆中的元素
void printHeap(HeapNode* heap) {
    if (isEmpty(heap)) {
        std::cout << "Heap is empty." << std::endl;
        return;
    }
    // 使用中序遍历打印堆中的所有元素
    printHeap(heap->left);
    std::cout << heap->value << " ";
    printHeap(heap->right);
}

销毁堆

// 销毁堆
void destroyHeap(HeapNode* heap) {
    if (heap != nullptr) {
        destroyHeap(heap->left); // 递归销毁左子树
        destroyHeap(heap->right); // 递归销毁右子树
        delete heap; // 释放当前节点内存
    }
}

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

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

相关文章

综合性练习(验证码案例)

目录 一、需求 二、准备工作 三、约定前后端交互接口 1、需求分析 2、接口定义 四、Hutool工具介绍 1、引入依赖 2、测试使用Hutool生成验证码 五、实现服务器端代码 代码解读&#xff1a; 六、调整前端页面代码 七、运行测试 随着安全性的要求越来越高&#xff0c…

数据库 MySQL 四种事务隔离级别代码演示 -- 读未提交;读已提交;可重复读;串行化

前提 # 设置数据库隔离级别 SET SESSION TRANSACTION ISOLATION LEVEL 隔离级别;# 查询事务隔离级别 select transaction_isolation;事务处理的分离水平对应的数据整合情况&#xff1a; 隔离级别非提交读取&#xff08;脏读&#xff09;不可重复读取幻读READ UNCOMMITED√√√…

【JVM】Class文件的格式

目录 概述 Class文件的格式 概述 Class文件是JVM的输入&#xff0c;Java虚拟机规范中定义了Class文件的结构。Class文件是JVM实现平台无关、技术无关的基础。 1:Class文件是一组以8字节为单位的字节流&#xff0c;各个数据项目按顺序紧凑排列 2:对于占用空间大于8字节的数据…

Geopandas以及CMakeList程序编写技巧

Geopandas官方文档 Geopandas官方文档 reset_index()函数 pandas库中的reset_index()函数是用于重新设置数据框索引的方法。 例如&#xff1a;当我对于文件数据进行了一系列操作后&#xff0c;例如设置了索引set_index&#xff0c;那么会导致数据的索引框发生变化&#xff…

Linux网络编程】传输层中的TCP和UDP(UDP篇)

【Linux网络编程】传输层中的TCP和UDP&#xff08;UDP篇&#xff09; 目录 【Linux网络编程】传输层中的TCP和UDP&#xff08;UDP篇&#xff09;传输层再谈端口端口号范围划分认识知名端口号netstatiostatpidofxargs UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲数据UDP使…

08 - hive的集合函数、高级聚合函数、炸裂函数以及窗口函数

目录 1、集合函数 1.1、size&#xff1a;集合中元素的个数 1.2、map&#xff1a;创建map集合 1.3、map_keys&#xff1a; 返回map中的key 1.4、map_values: 返回map中的value 1.5、array 声明array集合 1.6、array_contains: 判断array中是否包含某个元素 1.7、sort_a…

最佛系的打字练习软件——TL(TypeLetters)

有朋友开玩笑说&#xff1a;TL&#xff08;TypeLetters&#xff09;是最佛系的打字练习软件。真的吗&#xff1f;仔细看&#xff0c;好像还真有这么点意思&#xff1a; 无竞速&#xff1a;TL没有速度显示&#xff0c;初学者逐个认清键位&#xff0c;无需关心速度&#xff0c;中…

不训练也能给模型加上各种超能力?

之前我写过一篇 Sakana 与 Jamba (qq.com) Sakana也好,Jamba也罢,其实都是模型合并的一种比较好的项目实践,今天我们可以讨论一下普通开发者是否能像做一个项目一样,合并多个模型,达到自己想要的效果(GPU的连载后面写,东西太多,再给2篇也不知道能不能写完 ) 不同于Sakan…

Apifox 教程:如何实现跨语言调用(Java、PHP、Python、Go 等)

在一些特定场景下&#xff0c;比如需要在 Apifox 中对文件进行读写、加密、转换格式或者进行其它业务的操作时&#xff0c;仅使用 Apifox 内置的 JS 类库可能无法满足业务需求&#xff0c;这时&#xff0c;就可以借助「外部程序」作为解决方案。 外部程序是保存在「外部程序目…

嵌入式 - GPIO编程简介

An Introduction to GPIO Programming By Jeff Tranter Wednesday, June 12, 2019 编者按&#xff1a;本 2019 年博客系列是 ICS 最受欢迎的系列之一&#xff0c;现已更新&#xff08;2022 年 12 月&#xff09;&#xff0c;以确保内容仍然准确、相关和有用。 本博客是 Integr…

【Mac】Ghost Buster Pro(苹果电脑内存清理专家) v3.2.5安装教程

软件介绍 Ghost Buster pro是一款针对Mac系统的电脑清理和优化工具&#xff0c;可以帮助用户清理系统垃圾、修复注册表错误、卸载不需要的软件、管理启动项等&#xff0c;从而提高系统性能和稳定性。 安装教程 1.打开镜像包&#xff0c;拖动「Ghost Buster Pro」到应用程序中…

当代 Qt 正确的 安装方法 及 多版本切换

此文写于 20240511 首先去网站Index of /official_releases/online_installers下载一个安装器 安装器有什么用? 可以浏览安装版本 安装组件 安装器版本越能 能装的东西越多 现在只能选Qt5 和 Qt6 至于你公司用的Qt4 我也没招 见招时再拆招 安装器 默认国外源 可以换国内…

sql函数--10---In 多个字段

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.普通 in语句查询sqlMyBatis错误写法正确写法ListString[] 2.In多个字段sql案例脚本实例&#xff1a;错误写法&#xff1a;正确写法&#xff1a; MyBatis XML 写法…

[图解]SysML和EA建模住宅安全系统-02

1 00:00:00,900 --> 00:00:02,690 这个就是一个块定义图了 2 00:00:03,790 --> 00:00:04,780 简称BDD 3 00:00:05,610 --> 00:00:08,070 实际上就是UML里面的类图 4 00:00:08,080 --> 00:00:09,950 和组件图的一个结合体 5 00:00:13,150 --> 00:00:14,690 我…

YOLOv9最新改进系列:融合空间信息关注机制(SimAM)于YOLOv9网络,在通道之间和空间位置之间建立更加准确的关联,助力YOLOv9有效涨点!!!

YOLOv9最新改进系列&#xff1a;融合空间信息关注机制&#xff08;SimAM&#xff09;于YOLOv9网络&#xff0c;在通道之间和空间位置之间建立更加准确的关联,助力YOLOv9有效涨点&#xff01;&#xff01;&#xff01; 在此之前&#xff01;先恭喜两位家人&#xff01;&#xf…

中国当代最具影响力的人物颜廷利:死神(死亡)并不可怕,可怕的是…

中国当代最具影响力的人物颜廷利&#xff1a;死神&#xff08;死亡&#xff09;并不可怕&#xff0c;可怕的是… 在中国优秀传统文化之中&#xff0c;汉语‘巳’字与‘四’同音&#xff0c;在阿拉伯数字里面&#xff0c;通常用‘4’来表示&#xff1b; 作为汉语‘九’字&#x…

鸿蒙开发-ArkTS语言-XML

鸿蒙开发-UI-web 鸿蒙开发-UI-web-页面 鸿蒙开发-ArkTS语言-基础类库 鸿蒙开发-ArkTS语言-并发 鸿蒙开发-ArkTS语言-并发-案例 鸿蒙开发-ArkTS语言-容器 鸿蒙开发-ArkTS语言-非线性容器 文章目录 前言 一、XML概述 二、XML生成 三、XML解析 1.解析XML标签和标签值 2.解析XML属性…

引擎:主程渲染

一、引擎发展 二、引擎使用 1.游戏渲染流程 2.3D场景编辑器操作与快捷键 3.节点的脚本组件 脚本介绍 引擎执行流程 物体节点、声音组件\物理组件\UI组件、脚本组件 暴露变量到面板 4.节点的查找 基本查找 this.node&#xff1a;挂载当前脚本的节点A&#xff1b; this.nod…

APP未上架应用市场,微信商户如何轻松开通APP支付?

在移动互联网时代&#xff0c;APP作为企业与用户之间的重要桥梁&#xff0c;其支付功能的完善与否直接关系到用户体验和企业的营收。然而&#xff0c;有些企业可能面临APP尚未上架应用市场的情况&#xff0c;这时如何开通微信APP支付功能呢&#xff1f;&#xff08;需要超快捷高…

端午节线上活动方案怎么写?

一年一端午&#xff0c;一岁一安康。 如果您想组织端午活动&#xff0c;却不知道如何安排&#xff0c;可以看看何策网&#xff0c;有很多案例参考&#xff0c;仿造模板修改即可。 下面分享一个线上端午节活动策划方案&#xff0c;希望能帮到你&#xff01; 端午节作为祭祖祈…