【数据结构】实现堆

news2024/9/22 13:42:31

大家好,我是苏貝,本篇博客带大家了解堆,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 一. 堆的概念及结构
  • 二. 堆的实现
    • 堆的结构体
    • 初始化
    • 销毁
    • 插入数据
    • 删除数据(默认删除堆顶即根节点)
    • 显示堆顶(即根节点)的值
    • 是否为空
    • 堆的大小
  • 三. 模块化代码实现
    • Heap.h
    • Heap.c
    • Test.c
    • 结果演示

一. 堆的概念及结构

如果有一个关键码的集合K,把它的所有元素按 完全二叉树 的顺序存储方式存储在一个一维数组中,并满足:任意一个双亲结点的值<=孩子节点的值(或任意一个双亲结点的值>=孩子节点的值),则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

堆的性质

  1. 堆中某个节点的值总是不大于(大堆)或不小于(小堆)其双亲节点的值;
  2. 堆总是一棵完全二叉树。

在这里插入图片描述


二. 堆的实现

上面我们说过,现实中我们通常使用顺序结构的数组来存储堆,现在让我们来实现一下小堆

1

堆的结构体

我们用数组来存储堆,而且一般都使用动态数组

typedef int HPDataType;

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

2

初始化

因为函数的实参是HP类型变量的地址,不可能为空,所以对它断言,下面的接口同理。

void HPInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

3

销毁

因为数组a是动态开辟的数组,所以要在程序结束前将它释放

void HPDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

4

插入数据

在插入数据之前,我们要判断是否需要增容,增容的条件是size==capacity。判断完成后,在堆的最后面(即数组的最后面)插入数据。数据插入完成后,我们还要保证这是一个小堆,所以要对比这个数据和它的双亲结点的值:如果它的值要大于双亲结点的值,那位置就不要动;如果它的值要小于双亲结点的值,为了让这个堆还是小堆,我们要将它和双亲结点互换位置,直到它的值要大于双亲结点的值或者它已经是根节点

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

void HPPush(HP* php, HPDataType x)
{
	assert(php);

	//是否需要增容
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			//exit(-1);
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}

	//插入
	php->a[php->size] = x;
	php->size++;
    //插入之后,如果堆的性质遭到破环,交换与双亲结点的位置
	AdjustUp(php->a, php->size - 1);
}

下面是交换与双亲结点的位置的代码

void swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = 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;
		}
	}
}

5

删除数据(默认删除堆顶即根节点)

删除必须保证堆中有元素,所以对堆的大小断言,下面的显示根节点同理。
注意:AdjustDown函数中的第一个if语句是想要找到左右孩子节点中较小的一个,但不一定有右孩子节点,所以要保证child + 1 < size

在这里插入图片描述

在这里插入图片描述

void AdjustDown(HPDataType* a, int size)
{
	int parent = 0;
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}


//删除(默认删除的是堆顶,即根节点)
void HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size);
}

6

显示堆顶(即根节点)的值

HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}

7

是否为空

bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

8

堆的大小

int HPSize(HP* php)
{
	assert(php);

	return php->size;
}

三. 模块化代码实现

Heap.h

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>


typedef int HPDataType;

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


//初始化
void HPInit(HP* php);
//销毁
void HPDestroy(HP* php);
//插入
void HPPush(HP* php, HPDataType x);
//删除(默认删除的是堆顶,即根节点)
void HPPop(HP* php);
//显示堆顶元素
HPDataType HPTop(HP* php);
//是否为空
bool HPEmpty(HP* php);
//堆的大小
int HPSize(HP* php);

Heap.c

#include"Heap.h"


//初始化
void HPInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}


//销毁
void HPDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}


void swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = 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;
		}
	}
}


//插入
void HPPush(HP* php, HPDataType x)
{
	assert(php);

	//是否需要增容
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			//exit(-1);
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}

	//插入
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
}


void AdjustDown(HPDataType* a, int size)
{
	int parent = 0;
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] < a[child])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}


//删除(默认删除的是堆顶,即根节点)
void HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, php->size);
}

//
//3.将堆顶元素向下调整到满足堆特性为止



//显示堆顶元素
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}


//是否为空
bool HPEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}


//堆的大小
int HPSize(HP* php)
{
	assert(php);

	return php->size;
}

Test.c

#include"Heap.h"

int main()
{
	HP hp;
	int a[] = { 7,6,5,4,3,2,1 };
	HPInit(&hp);
	for (int i = 0; i < 7; i++)
	{	
		HPPush(&hp, a[i]);
	}
	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}
	HPDestroy(&hp);
	return 0;
}

结果演示

在这里插入图片描述


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

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

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

相关文章

基于Mahout实现K-Means聚类

需求分析 需要对数据集进行预处理&#xff0c;选择合适的特征进行聚类分析&#xff0c;确定聚类的数量和初始中心点&#xff0c;调用Mahout提供的K-Means算法进行聚类计算&#xff0c;评估聚类结果的准确性和稳定性。同时&#xff0c;需要对Mahout的使用和参数调优进行深入学习…

c++|内存管理

c|内存管理 C/C内存分布strlen 和 sizeof的区别 c语言动态内存管理方式malloccallocrealloc例题 c管理方式new/delete操作内置类型new/delete操作自定义类型证明 new 和 delete 的底层原理operator new与operator delete函数operator new 和 operator delete的 用法构造函数里面…

基于 Vue3打造前台+中台通用提效解决方案(下)

47、通用组件 - 倒计时组件 特惠部分存在一个倒计时的功能,所以我们需要先处理对应的倒计时模块,并把它处理成一个通用组件。 那么对于倒计时模块我们又应该如何进行处理呢? 所谓倒计时,其实更多的是一个时间的处理,那么对于时间的处理,此时我们就需要使用到一个第三方…

Socks5代理协议:原理、应用与优势

在计算机网络中&#xff0c;代理协议是一种用于转发客户端请求的机制。Socks5是其中一种广泛使用的代理协议。它主要工作在传输层和应用层之间&#xff0c;位于OSI参考模型的第五层&#xff08;会话层&#xff09;。其设计初衷是为了帮助授权用户突破防火墙限制&#xff0c;获取…

20240304-2-计算机网络

计算机网络 知识体系 Questions 1.计算机网络分层的优点和缺点 优点 各层之间是独立的&#xff1b;灵活性好&#xff1b;结构上可分割开&#xff1b;易于实现和维护&#xff1b;能促进标准化工作。 缺点&#xff1a; 降低效率&#xff1b;有些功能会在不同的层次中重复出现&…

2000-2021年全国各省市城乡平均受教育年限数据(分城镇和农村)(含原始数据+计算过程+计算结果)

2000-2021年全国各省市城乡平均受教育年限数据&#xff08;分城镇和农村&#xff09; 1、时间&#xff1a;2000-2021年 2、范围&#xff1a;全国及31省 3、来源&#xff1a;人口与就业统计年鉴 4、指标包括&#xff1a;城乡平均受教育年限 、6岁以上总人口 未上过学、…

自动化神器 Playwright 的 Web 自动化测试解决方案!

Playwright认识 3. Playwright环境搭建 Playwright简介&#xff1a; 2020年&#xff0c;微软&#xff08;Microsoft&#xff09;开源了一个名为Playwright的工具&#xff0c;与Selenium一样入门简单&#xff0c;支持多语言&#xff08;Python、Java、Node.js、.NET&#xff0…

Java8,函数式编程应用:

持续更新中&#xff1a; 函数式(Functional)接口 什么是函数式(Functional)接口 只包含一个抽象方法的接口&#xff0c;称为函数式接口。 你可以通过 Lambda 表达式来创建该接口的对象。&#xff08;若 Lambda 表达式 抛出一个受检异常(即&#xff1a;非运行时异常)&#xff0c…

Linux学习:初识Linux

目录 1. 引子&#xff1a;1.1 简述&#xff1a;操作系统1.2 学习工具 2. Linux操作系统中的一些基础概念与指令2.1 简单指令2.2 ls指令与文件2.3 cd指令与目录2.4 文件目录的新建与删除指令2.5 补充指令1&#xff1a;2.6 文件编辑与拷贝剪切2.7 文件的查看2.8 时间相关指令2.9 …

为什么TestNg会成为Java测试框架的首选?还犹豫什么,看它!

上一篇自动化测试我们大概了解了测试的目标、测试的技术选型以及搭建平台的目标及需求&#xff0c;也确定了自动化测试方案以testNg作为整个测试流程贯穿的基础支持框架&#xff0c;那么testNg究竟有什么特点&#xff1f;本篇开始我们来详细的学习testNg这个测试框架。 为什么要…

软件设计师8--输入输出技术

软件设计师8--输入输出技术 考点1&#xff1a;输入输出技术数据传输控制方式中断处理过程例题&#xff1a; 考点1&#xff1a;输入输出技术 数据传输控制方式 √ 程序控制&#xff08;查询&#xff09;方式&#xff1a;分为无条件传送和程序查询方式两种。方法简单&#xff0…

MySQL篇—执行计划之覆盖索引Using index和条件过滤Using where介绍(第三篇,总共三篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

python实现手机号归属地查询

手机上突然收到了某银行的短信提示&#xff0c;看了一下手机的位数&#xff0c;正好是11位。我一想&#xff0c;这不就是标准的手机号码吗&#xff1f;于是一个想法涌上心头——用python的库实现查询手机号码归属地查询自由。 那实现的效果如下&#xff1a; 注&#xff1a;电…

yolov7添加spd-conv注意力机制

一、spd-conv是什么&#xff1f; SPD-Conv&#xff08;Symmetric Positive Definite Convolution&#xff09;是一种新颖的卷积操作&#xff0c;它主要应用于处理对称正定矩阵&#xff08;SPD&#xff09;数据。在传统的卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;…

【java数据结构】模拟二叉树的链式结构之孩子表示法,掌握背后的实现逻辑

&#x1f4e2;编程环境&#xff1a;idea &#x1f4e2;树结构&#xff0c;以及叶子&#xff0c;结点&#xff0c;度等一些名词是什么意思&#xff0c;本篇不再赘述。 【java数据结构】模拟二叉树的链式结构之孩子表示法&#xff0c;掌握背后的实现逻辑 1. 认识二叉树1.1 二叉树…

桂院校园导航 | 云上高校导航 云开发项目 二次开发教程 2.0

Gitee代码仓库&#xff1a;桂院校园导航小程序 GitHub代码仓库&#xff1a;GLU-Campus-Guide 演示视频 【校园导航小程序】2.0版本 静态/云开发项目 演示 云开发项目 2.0版本 升级日志 序号 板块 详情 1 首页 重做了首页&#xff0c;界面更加高效和美观 2 校园页 新增…

Python判断结构20个实例

基本理论基础 Python中的选择判断结构是一种编程中常用的控制结构&#xff0c;它用于根据条件的真假决定程序的执行路径。选择判断结构有多种类型&#xff0c;包括if语句、if-else语句、if-elif-else语句以及嵌套的选择结构。 首先&#xff0c;我们来介绍最常见的if语句。if语…

浅谈WPF之Binding数据校验和类型转换

在WPF开发中&#xff0c;Binding实现了数据在Source和Target之间的传递和流通&#xff0c;就像现实生活中的一条条道路&#xff0c;建立起了城镇与城镇之间的衔接&#xff0c;而数据校验和类型转换&#xff0c;就像高速公路之间的收费站和安检站。那在WPF开发中&#xff0c;如何…

引入本地图片报错:require is not defined

文章目录 问题分析1. 原始写法2. 最初的解决方案3. 尝试使用 require 引入4. 封装方法进行解析引入图片 问题 Vue3 Vite 使用本地图片报错&#xff1a;require is not defined 分析 1. 原始写法 刚开始我是这样写的&#xff0c;数据是这样定义的&#xff0c;但是数据没出…