数据结构:堆的三部曲 (一)堆的实现

堆的实现

  • 1.堆的结构
    • 1.1堆的定义理解
  • 2.堆的实现(以小根堆为例)
    • 2.1 堆结构体的定义
    • 2.2 堆的插入
      • 交换函数
      • 向上调整算法
      • 插入函数的代码
    • 2.3 堆的删除
      • 向下调整算法:
      • 删除函数的代码:
    • 2.4其他操作
  • 3.测试以及完整源代码实现
    • 3.1测试代码
  • 3.2完整代码实现
    • heap.c
    • heap.c
    • main.c

1.堆的结构

1.1堆的定义理解

堆的逻辑结构为一颗完全二叉树,堆对于数据存储有一定的要求:这这棵树的任意一颗子树的根节点的值小于等于(或大于等于)其孩子节点的值。我们将根节点最大的堆叫做小堆,把根节点最大的堆叫做大堆。
堆的存储结构结构为数组,我们将堆的元素存储在一个数组之中。由于堆是完全二叉树,所以其节点下标之间满足以下关系:
parent = (child-1) / 2
leftChild = parent2 + 1
rightChild = parent
2 + 2 = leftChild + 1

堆的结构如图:
在这里插入图片描述

注意:堆规定的是父亲和孩子之间值的关系,并没有规定左右孩子值之间的关系。比如下图,我们对于15这个父节点的左右孩子大小没有要求,两种情况都可以,因此我们只需要维护父亲和孩子的关系即可。
在这里插入图片描述

2.堆的实现(以小根堆为例)

2.1 堆结构体的定义

由于堆是使用数组来存储的,因此堆的结构定义和顺序表相同,首先需要定义一个数据类型的指针,然后还需要定义int类型的size和capacity,用来记录当前有多少个节点以及当前最多能存储多少节点,当空间不足时我们就可以扩容操作。

typedef int HpDataType;
typedef struct Heap
{HpDataType* a;int size;int capacity;
}HP;

2.2 堆的插入

堆的插入的前提是插入前的二叉树是堆,因此插入数据后只需要保持其父亲节点和孩子节点的关系。由于使用数组存储,因此在size位置插入后,就需要开始调整使插入后仍为堆。

交换函数

void Swap(HpDataType* x, HpDataType* y)
{HpDataType tmp = *x;*x = *y;*y = tmp;
}

向上调整算法

由于是从插入的那个孩子处开始调整,所以需要传入当时的下标。
child即为最后一个节点的下标。由于需要维护每一个父亲和孩子的关系,因此需要用到循环。如果新插入的节点的值比父节点的值小,那么交换它们的值,如果比其的父节点的值大,那么插入节点后的堆仍为小堆,不需要调整,退出循环。考虑最坏情况,如果插入的节点比第一个节点的值都小,那么就需要一直交换,当最后一个节点也调整后,不满足条件从而退出循环,那么这个最坏的结果结束后的child即为最终的循环结束条件,此时child为0,因此循环进行条件为child>0,结束条件为child<=0。

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 = (parent - 1) / 2;}else{break;}}
}

插入函数的代码

void HPPush(HP* hp, HpDataType x)
{assert(hp);int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;//空间如果不足则扩容if (hp->capacity == hp->size){HpDataType* tmp = (HpDataType*)realloc(hp->a, newcapacity*sizeof(HpDataType));if (tmp == NULL){perror("realloc failed");exit(-1);}hp->a = tmp;hp->capacity = newcapacity;}hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);
}

2.3 堆的删除

堆的删除为删除堆顶元素。
如果我们直接使用数据移动覆盖的删除方法,那么基本所有的父子关系将会被打乱,这样堆就会被完全破坏,而顺序表删除最后一个元素很容易,因此我们可以将第一个节点和最后一个节点交换,再删除最后一个节点,这样不仅更加简便,而且根节点的左右子树仍为堆,我们只需要将根节点向下调整即可调整为堆。

向下调整算法:

那么如何实现向下调整算法呢?
我们需要让每一颗子树的根节点都为该树的最小值,由于堆没有规定左右孩子之间的关系,因此如果需要向下调整交换时,需要判断该节点的左右孩子的最小值。如果不需要交换,即该节点比孩子节点小,那么就证明此时为堆,结束循环。考虑最坏情况,直到交换到叶子节点才能结束,那么如何判断叶子节点呢?叶子节点是没有孩子的节点,也就是其孩子的下标超出了size的大小,因此通过child和size的大小可以判断是否为叶子节点。

void AdjustDown(HpDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){//假设左孩子小,如果假设错误则交换if (child + 1 < size && a[child] > a[child + 1]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

删除函数的代码:

void HPPop(HP* hp)
{assert(hp);assert(hp->size > 0);Swap(&hp->a[0], hp->a[hp->size - 1]);hp->size--;AdjustDown(hp->a, hp->size, 0);
}

2.4其他操作

2.4.1取堆顶元素

int HPTop(HP* hp)
{assert(hp);return hp->a[0];
}

2.4.2堆的节点个数

int HPSize(HP* hp)
{assert(hp);return hp->size;
}

2.4.3堆是否为空判断

bool IsEmpty(HP* hp)
{assert(hp);return hp->size == 0;
}

3.测试以及完整源代码实现

3.1测试代码

int main()
{int a[] = { 3,5,2,7,9,4,1 };HP hp;HPinit(&hp);//建堆for (int i = 0; i < sizeof(a) / sizeof(int); i++){HPPush(&hp, a[i]);}//打印堆for (int i = 0; i < hp.size; i++){printf("%d ", hp.a[i]);}printf("\n");//类似堆排序while (!IsEmpty(&hp)){printf("%d ", HPTop(&hp));HPPop(&hp);}printf("\n");//类似top  k//int k = 3;//while (k--)//{//	printf("%d ", HPTop(&hp));//	HPPop(&hp);//}HPDestroy(&hp);return 0;
}

运行结果如下:
在这里插入图片描述

3.2完整代码实现

heap.c

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int HpDataType;
typedef struct Heap
{HpDataType* a;int size;int capacity;
}HP;void HPinit(HP* hp);
void HPDestroy(HP* hp);
void HPPush(HP* hp, HpDataType x);
void HPPop(HP* hp);
int HPTop(HP* hp);
bool IsEmpty(HP* hp);
int HPSize(HP* hp);

heap.c

//小堆的实现
void HPinit(HP* hp)
{assert(hp);hp->a = NULL;hp->size = hp->capacity = 0;
}void HPDestroy(HP* hp)
{assert(hp);free(hp->a);hp->a = NULL;hp->capacity = hp->size = 0;
}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 = (parent - 1) / 2;}else{break;}}
}void HPPush(HP* hp, HpDataType x)
{assert(hp);if (hp->capacity == hp->size){int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;HpDataType* tmp = (HpDataType*)realloc(hp->a, newcapacity * sizeof(HpDataType));if (tmp == NULL){perror("realloc failed");exit(-1);}hp->a = tmp;hp->capacity = newcapacity;}hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1);
}void AdjustDown(HpDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){//假设左孩子小,如果假设错误则交换if (child + 1 < size && a[child] > a[child + 1]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HPPop(HP* hp)
{assert(hp);assert(hp->size > 0);Swap(&hp->a[0], &hp->a[hp->size - 1]);hp->size--;AdjustDown(hp->a, hp->size, 0);
}int HPTop(HP* hp)
{assert(hp);return hp->a[0];
}bool IsEmpty(HP* hp)
{assert(hp);return hp->size == 0;
}int HPSize(HP* hp)
{assert(hp);return hp->size;
}

main.c

#include "heap.h"int main()
{int a[] = { 3,5,2,7,9,4,1 };HP hp;HPinit(&hp);for (int i = 0; i < sizeof(a) / sizeof(int); i++){HPPush(&hp, a[i]);}printf("建堆如下:");for (int i = 0; i < hp.size; i++){printf("%d ", hp.a[i]);}printf("\n");printf("依次取堆顶元素:");while (!IsEmpty(&hp)){printf("%d ", HPTop(&hp));HPPop(&hp);}printf("\n");//int k = 3;//while (k--)//{//	printf("%d ", HPTop(&hp));//	HPPop(&hp);//}HPDestroy(&hp);return 0;
}

以上就是本次说有内容,谢谢观看。

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

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

相关文章

typore自定义删除线快捷键

打开高级设置 设置快捷键 重新打开typore

Java实现短信发送业务

1、业务需求 发送短信功能是一个很普遍的需求&#xff0c;比如验证码&#xff0c;快递单号&#xff0c;通知信息一类。 而在Java中实现短信功能相对简单&#xff0c;只需要调用短信服务商提供的API。接下来以阿里云为例&#xff0c;介绍如何实现短信发送功能&#xff0c;其他短…

x-cmd pkg | bat - cat 命令的现代化替代品

目录 简介首次用户功能特点进一步阅读 简介 bat 是 cat 命令的替代品&#xff0c;对 cat 命令进行功能扩展&#xff0c;如语法高亮、自动分页等&#xff0c;为用户提供更友好的显示和定制选项。对于需要在终端频繁查看文本内容的用户&#xff0c;推荐用 bat。 首次用户 使用 …

xinference

xinference Xorbits Inference&#xff08;xinference&#xff09;是一个性能强大且功能全面的分布式推理框架。可用于大语言模型&#xff08;LLM&#xff09;&#xff0c;语音识别模型&#xff0c;多模态模型等各种模型的推理。通过 Xorbits Inference&#xff0c;你可以轻松地…

计算机基础面试题 |01.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

linux 休眠唤醒中设备、总线、用户进程、内核线程调试分析流程

一、suspending consoles打印 代码位置&#xff1a;Kernel/power/suspend.c 函数调用流程&#xff1a;devices_and_enter(suspend_state_t state) --> suspend_console(); void suspend_console(void) { if (!console_suspend_enabled) 注释这一行&#xff0c;可以看到…

企业级依赖管理: 深入解读 Maven BOM

一、背景 当开发者在一个大型项目中使用 Maven 进行依赖管理时&#xff0c;项目往往会包含多个模块或子项目&#xff0c;并且这些模块会共享相同的依赖项。但是&#xff0c;不同模块可能会独立地指定各自的依赖版本&#xff0c;这可能导致以下问题&#xff1a; 依赖版本不一致…

头歌:实验十六 matplotlib数据可视化

第1关 各省gdp的和生成条状图 import pandas import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt matplotlib.rcParams[font.family]SimHei matplotlib.rcParams[font.sans-serif] [SimHei] datapandas.read_excel("test/各省GDP.xlsx",dtype…

在多Module项目中,给IDEA底部选项卡区域添加Services选项卡

一般一个spring cloud项目中大大小小存在几个十几个module编写具体的微服务项目。此时&#xff0c;如果要调试测需要依次启动各个项目比较麻烦。 idea其实提供了各module的启动管理工具了&#xff0c;可以快速启动和关闭各个服务&#xff0c;也能批量操作&#xff0c;比如一次…

25. 数组作为函数参数

写代码时&#xff0c;我们会将数组作为参数传给函数 冒泡排序&#xff1a; 两两相邻的元素进行比较&#xff0c;可能的话进行交换 一趟冒泡排序会将一个元素放在其最后应该在的位置 10个数字只需9趟&#xff0c;第一趟10个数字待排序&#xff0c;9对比较&#xff1b;第二趟…

计算机科学速成课【学习笔记】(1)——计算机早期历史

本集课程B站链接&#xff1a; 【计算机科学速成课】[40集全/精校] - Crash Course Computer Science_哔哩哔哩_bilibili【计算机科学速成课】[40集全/精校] - Crash Course Computer Science共计40条视频&#xff0c;包括&#xff1a;1. 计算机早期历史-Early Computing、2. 电…

【2024最新版】neo4j安装配置

neo4j安装 写在最前面下载配置环境&#xff08;还是不行&#xff1f;&#xff09;启动neo4jpython中调用 写在最前面 之前我安装过&#xff0c;还写了一篇笔记 结果意外发现没有了&#xff0c;而且和之前安装的步骤不一样了&#xff0c;因此再次记录安装过程 下载 https://ne…

vue3安装vue-json-viewer实现json格式化

一、安装插件 直接看效果&#xff1a;json格式化 安装版本不宜过旧也不宜过新&#xff0c;针对vue3安装这个版本即可 yarn add vue-json-viewer;"dependencies": {"vue-json-viewer": "3",}, 二、使用插件 <script> import JsonViewe…

Vue中的keep-alive缓存组件的理解

<keep-alive> 是一个抽象组件&#xff0c;用于将其内部的组件保留在内存中&#xff0c;而不会重新渲染。这意味着当组件在<keep-alive> 内部切换时&#xff0c;其状态将被保留&#xff0c;而不是被销毁和重新创建。 <keep-alive>用来缓存不经常变化的组件&am…

HLS 2017.4 导出 RTL 报错:ERROR: [IMPL 213-28] Failed to generate IP.

软件版本&#xff1a;HLS 2017.4 在使用 HLS 导出 RTL 的过程中产生如下错误&#xff1a; 参考 Xilinx 解决方案&#xff1a;https://support.xilinx.com/s/article/76960?languageen_US 问题描述 DESCRIPTION As of January 1st 2022, the export_ip command used by Vivad…

机器学习笔记 - 基于Python的不平衡数据的欠采样技术

一、简述 随着从不同的来源生成和捕获大量数据。尽管信息量巨大,但它往往反映了现实世界现象的不平衡分布。数据不平衡的问题不仅仅是统计上的挑战,它对数据驱动模型的准确性和可靠性具有深远的影响。 以金融行业欺诈检测为例。尽管我们希望避免欺诈,因为其具有高度破坏性,…

在Mac上恢复SD卡数据的 6 个有效应用程序

慌&#xff01;SD卡里的照片和视频不小心删了&#xff0c;Mac设备上还恢复不了数据&#xff01; 遇到这种情况&#xff0c;你需要的是一款可靠的Mac适用的SD卡恢复软件。我们为你准备了一份最佳的SD卡恢复软件列表&#xff0c;并且还有详细的评论。另外&#xff0c;我们还会给…

深入解析 Spring 的 @Autowired:自动装配的魔法与细节

Autowired 是 Spring 框架中的一个重要注解&#xff0c;用于自动装配 bean 依赖。Spring 通过 Autowired 可以自动将匹配的 bean 注入到需要的地方&#xff0c;如属性、构造函数或 setter 方法等。 下面是 Autowired 注解的详细说明&#xff1a; 作用 自动装配&#xff1a;A…

FTP简介及搭建计算机端口的介绍

目录 一. FTP的简介 二. FTP的主要作用 三. 搭建FTP服务器 3.1 开启防火墙 3.2 创建组 3.3 创建用户 3.4 用户绑定组 3.5 安装FTP服务器 3.6 配置FTP服务器 3.7 配置FTP文件夹的权限 3.8 连接测试 3.8.1 服务器本机测试 3.8.2 外部服务器测试 3.8.3 借助工具MobalXterm 四…

让电脑变得更聪明——用python实现五子棋游戏

作为经典的棋类游戏&#xff0c;五子棋深受大众喜爱&#xff0c;但如果仅实现人与人的博弈&#xff0c;那程序很简单&#xff0c;如果要实现人机对战&#xff0c;教会计算机如何战胜人类&#xff0c;那就不是十分容易的事了。本文我们先从简单入手&#xff0c;完成五子棋游戏的…