堆的基本实现

一、堆的概念

在提出堆的概念之前,首先要了解二叉树的基本概念

一颗二叉树是节点的有限集合,该集合:

1、或者为空;

2、或者由一个根节点加上两颗分别称为左子树和右子树的两颗子树构成;

 堆就是一颗完全二叉树;

堆有两种:小堆和大堆

堆在内存上的存储是数组形式的(物理结构);我们认为想象成链式结构(逻辑结构)

通过数组结构去实际存储,有这样的规律:每个父节点的下标乘以2加1就是左孩子节点,加2就是右孩子节点;无论是左孩子还是右孩子,其下标减去1再 /2 就是父节点的下标,这就是通过数组存储堆(完全二叉树)的优点。


 

 二、堆实现的相关头文件

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
#include<errno.h>
//堆有两种:大堆、小堆
typedef int HPDataType;typedef struct Heap
{HPDataType* arr;int size;int capacity;
}Heap;//堆的构建
void HeapInit(Heap* php);//堆的销毁
void HeapDestroy(Heap* php);//向上调整
void AdjustUp(Heap* php, int child);
//堆的插入
void HeapPush(Heap* php, HPDataType x);//向下调整
void AdjustDown(Heap* php, int parent, int size);
//堆的删除
void HeapPop(Heap* php);//取出堆顶的数据
HPDataType HeapTop(Heap* php);//堆的数据个数
int HeapSize(Heap* php);//堆的判空
bool HeapEmpty(Heap* php);

堆是完全二叉树,所以用数组存储可以方便访问子节点和父节点;


三、堆基本接口的实现(大堆)

1、堆的构建(初始化)

void HeapInit(Heap* php)
{assert(php);php->arr = NULL;php->size = php->capacity = 0;
}

2、堆的销毁

//堆的销毁
void HeapDestroy(Heap* php)
{assert(php);if (php->arr == NULL){free(php->arr);}php->arr = NULL;php->size = php->capacity = 0;
}

3、堆的插入

void HeapPush(Heap* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;HPDataType* tmp = (HPDataType*)realloc(php->arr,sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc failed");exit(1);}php->arr = tmp;php->capacity = newcapacity;}php->arr[php->size++] = x;//向上调整AdjustUp(php->arr,php->size-1);
}

堆只有大堆和小堆之分,插入数据和顺序表一样,关键是插入数据之后要对数组里面的数据进行调整;

插入数据用到向上调整

//向上调整
void AdjustUp(HPDataType* arr,int child)
{int parent = (child - 1) / 2;while (child>0){if (arr[child] > arr[parent]){//交换数组里面的值Swap(&arr[child], &arr[parent]);//继续比较大小要将child和parent的值向上移动child = parent;parent = (child - 1) / 2;}else{//之前的数据都是一个一个建好的大堆//父节点的值比子节点的大,停止break;}}
}
void Swap(HPDataType* x, HPDataType* y)
{HPDataType tmp = *x;*x = *y;*y = tmp;
}

每次插入进堆的数据都是孩子节点(形参名称),向上调整的原因就是每个子树的父亲节点理论上是要大于孩子节点的,但是插入的数据可能要比父节点大,所以在向上调整函数里面要先通过孩子节点的下标找到对应的父节点,之后再比较看是否要交换,直到父亲节点大于子节点;

循环结束的条件是child>0,当child为0的时候说明已经调整到根节点的位置了。

4、堆的删除

//堆的删除
void HeapPop(Heap* php)
{assert(php && php->size>0);//删除是删除的是堆顶的数据,若是直接让数组整体往前移动 堆被打乱 要重新建堆 时间复杂度高;//方法:交换堆首位的数据,让size--,再从堆顶开始向下调整Swap(&php->arr[0],& php->arr[php->size - 1]);php->size--;AdjustDown(php->arr, 0, php->size - 1);
}

堆的删除删除的是根节点的数据,也就是数组里面下标为0的数据;如果直接移动数组下标删除,那么新数组就不再是一个堆,此时要重新建堆,代价过大;

采用这种方式:每次进行删除之前先交换堆首尾的数据,之后再让size--,就访问不到原来的根节点,此时得到的新数组,除了根节点处的数据,它的子树都是大堆;此时进行向下调整算法,把这个不应该是根节点的数据向下调整,把下面的数据里面最大的调整到根节点处;

向下调整算法

void AdjustDown(HPDataType* arr, int parent, int size)//size指向的是最后一个有效数据
{int child = parent * 2 + 1;//定义为左孩子while (child <= size){if (child+1<=size && arr[child] < arr[child + 1])//当最后一个子树只有左孩子时,child+1越界{child = child + 1;//若是右孩子较大,那么就定义成右孩子}//总是大的调到上面去if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

向下调整是从下标为0处开始调整的,这个0下标位置就是父节点,通过乘以2加上1的办法找到子节点,但是要找到子节点里面较大的那个所以上面代码就有了child处和child+1处数据大小的比较,若是父节点小于子节点就交换;每次调整完都刷新父节点和子节点的下标,以便一直往下面调整直到父节点的数据要大于子节点的数据就停止;

循环结束的条件是child<size,这里的size是数组里面最后一个有效数的下标;

5、堆顶数据、堆的数据个数、堆的判空

//取出堆顶的数据
HPDataType HeapTop(Heap* php)
{assert(php && php->size>0);return php->arr[0];
}//堆的数据个数
int HeapSize(Heap* php)
{assert(php);return php->size;
}//堆的判空
bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

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

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

相关文章

mybatis-plus实现分页功能

第一步&#xff1a;添加mybatis-plus为分页所使用的拦截器插件 &#xff08;不用这个的话sql里面的limit关键字无法实现&#xff0c;也就没办法实现查询操作&#xff09; 代码&#xff1a; Configuration public class mybatis_plus_config {Beanpublic MybatisPlusIntercept…

python-数水果(赛氪OJ)

[题目描述] 已知水果的种类共有 M 种&#xff0c;给出长度为 N 的序列&#xff0c;每个数字表示的是它是哪种水果。求每种水果各有多少个&#xff0c;按照对应编号从小到大的顺序输出。输入&#xff1a; 输入共两行&#xff1a;第一行包含两个整数 N,M(1 < N,M < 10000)&…

解决Firefox代理身份验证弹出窗口问题:C#和Selenium实战指南

引言 在使用Selenium和C#进行网页抓取时&#xff0c;遇到代理服务器的身份验证弹出窗口是一个常见的问题。这不仅会中断自动化流程&#xff0c;还会导致抓取任务失败。本文将提供一个实战指南&#xff0c;帮助开发者解决这个问题&#xff0c;并介绍如何在代码中设置代理IP、Us…

x-cmd mod | x man - man 命令增强

目录 简介例子1. 使用 fzf 列出当前系统上所有的 man 文档2. 显示 ssh 的 man 文档。如果不存在则显示搜索3. 显示 ssh 的 tldr 文档4. 使用交互式 UI 列出包含 "disk" 的 man 文档 使用选项子命令x man --explainx man --fzf 简介 man 模块的主要目的是提升用户查找…

【TypeScript学习打卡第一天】

介绍、常用类型 一、介绍1.概念2.TypeScript 为什么要为 JS 添加类型支持&#xff1f;3.ts的优势 二、ts初体验1.安装编译 TS 的工具包2.编译并运行 TS 代码3.简化运行 TS 的步骤 三、常用类型1.类型注解2.常用基础类型概述(1) 原始类型(2) 数组类型(3) 联合类型(4) 类型别名(5…

第三届人工智能、物联网和云计算技术国际会议(AIoTC 2024,9月13-15)

第三届人工智能、物联网与云计算技术国际会议(AIoTC 2024)将于2024年9月13日-15日在中国武汉举行。 本次会议由华中师范大学伍伦贡联合研究院与南京大学联合主办、江苏省大数据区块链与智能信息专委会承办、江苏省概率统计学会、江苏省应用统计学会、Sir Forum、南京理工大学、…

JDK新特性(Lambda表达式,Stream流)

Lambda表达式&#xff1a; Lambda 表达式背后的思想是函数式编程&#xff08;Functional Programming&#xff09;思想。在传统的面向对象编程中&#xff0c;程序主要由对象和对象之间的交互&#xff08;方法调用&#xff09;构成&#xff1b;而在函数式编程中&#xff0c;重点…

postman给全部接口添加请求头数据(如token)

如果给没有一个接口添加请求头token就太慢了&#xff0c;如下图。可以点击所有接口的所属的目录。点击“Scripts”&#xff0c;点击Pre-request按钮。加入代码&#xff1a; pm.request.addHeader("Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI111pXVCJ9.eyJjbGFpbXMiOnsiaW…

Nginx负载均衡策略

upstream机制提供了负载均衡的功能,可以讲请求负载分担到集群服务器的某个服务器上 打包时候到时一个8085 一个8090 一个8095 nohup /usr/local/develop/jdk-17.0.10/bin/java -Xmx256m -Xms256m -jar nginx-demo-8085.jar > server8085.log 2>&1 & nohup /u…

56_Redis简单命令

一、引言 1.1 数据库压力过大 由于用户量增大&#xff0c;请求数量也随之增大&#xff0c;数据压力过大 一个请求的url 背后可能有有4-5个 sql的操作 每秒钟 qps&#xff08;并发数&#xff09; 1000 背后的sql操作 4000-5000mysql 单机并发量读写 8000-10000 &#x…

鸿蒙配置Version版本号,并获取其值

app.json5中配置版本号&#xff1a; 获取版本号&#xff1a; bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION).then((bundleInfo) > {let versionName bundleInfo.versionName; //应用版本号}).catch((error: BusinessE…

【Vulnhub系列】Vulnhub_DC-1靶场渗透(原创)

【Vulnhub系列靶场】Vulnhub_DC-1靶场渗透 原文转载已经过授权 原文链接&#xff1a;Lusen的小窝 - 学无止尽&#xff0c;不进则退 (lusensec.github.io) 一、环境准备 1、在百度网盘中下载DC-1靶场。DC-1靶场受virtual box 的影响&#xff0c;在VM中直接打开是扫描不到IP 的…

jmeter录制

1、添加代理服务器 添加方法&#xff1a;“测试计划”右键 -> 添加 -> 非测试元件 -> HTTP代理服务器 2、添加线程组 添加方法&#xff1a;“测试计划”右键->添加->线程&#xff08;用户&#xff09;->线程组 3、配置http代理服务器 &#xff08;1&a…

第三方软件测试报告可做哪些用途?

1. 评估软件质量&#xff1a;第三方软件测试报告通过对软件的各项性能指标进行测试和分析&#xff0c;能够客观地评估软件的质量水平。这份报告可以为软件的开发团队提供反馈&#xff0c;帮助他们发现和修复潜在的问题&#xff0c;提高软件的质量和稳定性。 2. 验证软件功能&a…

<数据集>手机识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;16172张 标注数量(xml文件个数)&#xff1a;16172 标注数量(txt文件个数)&#xff1a;16172 标注类别数&#xff1a;1 标注类别名称&#xff1a;[Phone] 使用标注工具&#xff1a;labelImg 标注规则&#xff1a;…

探索分布式光伏运维系统的组成 需要几步呢?

前言 随着光伏发电的不断发展&#xff0c;对于光伏发电监控系统的需求也日益迫切&#xff0c;“互联网”时代&#xff0c;“互联网”的理念已经转化为科技生产的动力&#xff0c;促进了产业的升级发展&#xff0c;本文结合“互联网”技术提出了一种针对分散光伏发电站运行数据…

c#调用python代码,实现读取npy的数据并显示图像

本例子实现的功能是&#xff1a; 根据stat.npy、ops.npy两个npy文件的内容&#xff0c;显示图形 1. 用python代码实现读取两个文件&#xff0c;文件名为read_npy.py&#xff0c;代码如下&#xff1a; import numpy as npdef read_npy_files(stat_file, ops_file):stat np.lo…

NSL-KDD入侵检测系统的设计与实现系列预告

每日进阶-基于机器学习的入侵检测系统——打怪升级之道 在当今的数字时代&#xff0c;网络安全不仅是防御&#xff0c;更是主动出击。你是否想知道如何用机器学习技术设计一套入侵检测系统&#xff08;IDS&#xff09;&#xff0c;让黑客无所遁形&#xff1f;本系列文章将为您揭…

【全志H616开发】Linux守护进程

文章目录 守护进程简介基本特点创建一个守护进程通常涉及以下步骤&#xff1a;进程查看指令&#xff1a; 守护进程开发代码示例&#xff1a; 开机自动启动 守护进程 简介 Linux Daemon&#xff08;守护进程&#xff09;是运行在后台的一种特殊进程。它独立于控制终端并且周期性…