【数据结构】堆(超详细)

文章目录

  • 前言
  • 堆的概念及结构
  • 堆的实现
    • 堆的向下调整算法(建小堆为例)
    • 堆的向上调整算法(建小堆为例)
    • 堆的初始化
    • 销毁堆
    • 堆的插入
    • 堆的删除(规定删堆顶的数据)
    • 取堆顶元素
    • 判断堆是否为空
    • 获取堆的个数
  • 完整代码(包括测试代码)
    • Heap.h
    • Heap.c
    • test.c

前言

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

堆的概念及结构

在这里插入图片描述

【大根堆和小根堆】:

根结点最大的堆叫做大根堆,树中所有父亲都大于或等于孩子。

根结点最小的堆叫做小根堆,树中所有父亲都小于或等于孩子。
在这里插入图片描述

堆的实现

首先新建一个工程:

Heap.h(堆的类型定义、接口函数声明、引用的头文件)
Heap.c(堆接口函数的实现)
test.c(主函数、测试栈各个接口功能)

完整的代码放在后面(包括测试代码),这里就不会展示测试的效果图。大家可以自己别敲边按测试代码测试。图解会写的很详细的,么么😙

堆的向下调整算法(建小堆为例)

现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。
向下调整算法有一个前提:左右子树必须是一个堆,才能调整
int arr[] = {27,15,19,18,28,34,65,49,25,37};
在这里插入图片描述

思想:
1.选出左右孩子较小的一个值,
2.然后和父亲进行比较,如果比父亲小就进行交换,并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。如果比父亲大,则停止向下调整,此时该树就成小堆。

//交换函数
void Swap(HDataType* p1, HDataType* p2)
{HDataType tmp = *p1;*p1 = *p2;*p2 = tmp;}//向下调整算法
void AdjustDown(HDataType* a, int size, int parent)
{//假设左孩子小int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child + 1] < a[child])//确保有右孩子,如果右孩子小,更新到右边{++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);//更新父亲孩子下标,parent = child;child = child * 2 + 1;}else // 如果父亲小于孩子,说明已经为小堆了,停止调整{break;}}}

堆的向上调整算法(建小堆为例)

当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。
假设在这个堆int arr[] = {15,18,19,25,28,34,65,49,27,37}的末尾插入16.
在这里插入图片描述

思想:
1.将要插入孩子的值与父亲的值比较。
2.若插入孩子的值比父亲的值小,则交换插入孩子的值与父亲的值,并将父亲的位置当作新的插入孩子的值继续进行向上调整。若插入孩子的值比父亲的值大,则停止向上调整,此时该树就成小堆。


//交换函数
void Swap(HDataType* p1, HDataType* p2)
{HDataType tmp = *p1;*p1 = *p2;*p2 = tmp;}//向上调整算法
void AdjustUp(HDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}
}

堆的初始化

//初始化堆
void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}

销毁堆

//销毁堆
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = 0;php->capacity = 0;
}

堆的插入

数据插入时是插入到数组的末尾,即树形结构的最后一层的最后一个结点,所以插入数据后我们需要运用堆的向上调整算法对堆进行调整,使其在插入数据后仍然保持堆的结构。

//堆的插入
void HPPush(HP* php, HDataType x)
{assert(php);//检查空间是否足够插入,不够则扩容if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HDataType* tmp = (HDataType*)realloc(php->a, sizeof(HDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newcapacity;}//插入元素php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);// 从插入的元素开始,进行向上调整,保持它依然是堆
}

堆的删除(规定删堆顶的数据)

堆的删除,删除的是堆顶的元素,但是这个删除过程可并不是直接删除堆顶的数据,
1.将堆顶的数据与最后一个结点的数据交换,
2.删除堆种最后一个元素,此时左子树和右子树还是小堆
3.再对堆进行向下调整

//堆的删除(规定删堆顶的数据)
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, 0);// 从根节点开始,对剩下元素进行向下调整成大堆,保持它依然是堆
}

取堆顶元素

//取堆顶元素
HDataType HPTop(HP* php)
{assert(php);assert(php->size > 0);//确保堆里有数据return php->a[0];//返回堆顶数据
}

判断堆是否为空

//判断堆是否为空
bool HPEmpty(HP* php)
{assert(php);return php->size == 0;//判断堆中数据是否为0
}

获取堆的个数

//获取堆的个数
int HPSize(HP* php)
{assert(php);return php->size;//返回堆中数据个数
}

完整代码(包括测试代码)

Heap.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>typedef  int HDataType;
typedef struct Heap
{HDataType* a;int size;int capacity;}HP;//交换函数
void Swap(HDataType* p1, HDataType* p2);
//向上调整算法
void AdjustUp(HDataType* a, int child);
//向下调整算法
void AdjustDown(HDataType* a, int size, int parent);//初始化堆
void HPInit(HP* php);
//销毁堆
void PHDestroy(HP* php);
//堆的插入
void HPPush(HP* php, HDataType x);
//堆的删除(规定删堆顶的数据)
void HPPop(HP* php);
//取堆顶元素
HDataType HPTop(HP* php);
//判断堆是否为空
bool HPEmpty(HP* php);
//获取堆的个数
int HPSize(HP* php);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"//小堆
void Swap(HDataType* p1, HDataType* p2)
{HDataType tmp = *p1;*p1 = *p2;*p2 = tmp;}//向下调整算法
void AdjustDown(HDataType* a, int size, int parent)
{//假设左孩子小int child = parent * 2 + 1;while (child < size){	if (child + 1 < size && a[child + 1] < a[child])//确保有右孩子,如果错了,更新到右边{++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = child * 2 + 1;}else{break;}}}
//向上调整算法
void AdjustUp(HDataType* a, int child)//可以用递归写,没必要,用循环就可以
{int parent = (child - 1) / 2;while (child > 0){if (a[child] < a[parent]){Swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}else{break;}}}
//初始化堆
void HPInit(HP* php)
{assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}//销毁堆
void HPDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = 0;php->capacity = 0;}//堆的插入
void HPPush(HP* php, HDataType x)
{assert(php);if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HDataType* tmp = (HDataType*)realloc(php->a, sizeof(HDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}//堆的删除(规定删堆顶的数据)
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, 0);
}//取堆顶元素
HDataType 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

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"int main()
{int a[] = { 4,3,7,9,1,5,8,2,8 };int sz = sizeof(a) / sizeof(a[0]);HP hp;HPInit(&hp);for (int i = 0; i < sz; i++){HPPush(&hp, a[i]);}//int k = 5;//while (k--)//{//	printf("%d\n", HPTop(&hp));//	HPPop(&hp);//}while (!HPEmpty(&hp)){printf("%d ", HPTop(&hp));HPPop(&hp);}printf("\n");return 0;
}

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

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

相关文章

【计算机毕业设计】基于SSM++jsp的高校专业信息管理系统【源码+lw+部署文档+讲解】

目录 第1章 绪论 1.1 课题背景 1.2 课题意义 1.3 研究内容 第2章 开发环境与技术 2.1 MYSQL数据库 2.2 JSP技术 2.3 SSM框架 第3章 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统流程 3.2.1 操作流程 3.2.2 登录流程 3.2.3 删除信息流…

springboot通过 EasyExcel.read()方法解析csv(excel)文件中的数据用list接收

springboot通过 EasyExcel.read&#xff08;&#xff09;方法解析csv&#xff08;excel&#xff09;文件中的数据用list接收 文章目录 前言一、EasyExcel是什么&#xff1f;二、使用步骤1.引入库2.接收数据的实体类3.处理字典值ExcelDictConverter4.把文件中的数据解析出来放入…

Java面试八股之一个char类型变量能不能存储一个中文字符

Java中一个char类型变量能不能存储一个中文字符&#xff1f;为什么&#xff1f; Java中一个char类型变量可以存储一个中文字符。原因如下&#xff1a; Unicode编码支持&#xff1a;Java语言采用Unicode字符集作为其内建字符编码方式。Unicode是一种广泛接受的字符编码标准&am…

摸鱼大数据——Linux搭建大数据环境(集群免密码登录和安装Hadoop)二

集群设置免密登录 克隆node1虚拟机的前置条件&#xff1a;node1虚拟机存在且处于关闭状态 1.克隆出node2虚拟机 1.node1虚拟机: 右键 -> "管理" -> "克隆" 2.图形化弹窗中: "下一页"->"下一页"->选择"创建完整克隆&…

简单问题汇总

一、vector和list 1.vector vector是可变大小数组的序列容器&#xff0c;拥有一段连续的内存空间&#xff0c;并且起始地址不变&#xff0c;因此能高效的进行随机存取&#xff0c;时间复杂度为o(1)&#xff1b;但因为内存空间是连续的&#xff0c;所以在进行插入和删除操作时…

本地运行《使用AMD上的SentenceTransformers构建语义搜索》

Building semantic search with SentenceTransformers on AMD — ROCm Blogs 这篇博客解释了如何在Sentence Compression数据集上训练SentenceTransformers模型来执行语义搜索。使用BERT基础模型&#xff08;不区分大小写&#xff09;作为基础的变换器&#xff0c;并应用Huggi…

GPT-4o:全面深入了解 OpenAI 的 GPT-4o

GPT-4o&#xff1a;全面深入了解 OpenAI 的 GPT-4o 关于 GPT-4o 的所有信息ChatGPT 增强的用户体验改进的多语言和音频功能GPT-4o 优于 Whisper-v3M3Exam 基准测试中的表现 GPT-4o 的起源追踪语言模型的演变GPT 谱系&#xff1a;人工智能语言的开拓者多模式飞跃&#xff1a;超越…

Threejs 学习笔记 | 灯光与阴影

文章目录 Threejs 学习笔记 | 灯光与阴影如何让灯光照射在物体上有阴影LightShadow - 阴影类的基类平行光的shadow计算投影属性 - DirectionalLightShadow类平行光的投射相机 聚光灯的shadow计算投影属性- SpotLightShadow类聚光灯的投射相机 平行光 DirectionalLight聚光灯 Sp…

生活服务商家拥抱数字化,鸿运果系统加速“服务生意数字化”进程

在数字化转型的大潮中&#xff0c;生活服务商家正积极拥抱变革&#xff0c;以适应新的市场环境和消费者需求。鸿运果系统作为专业的“服务生意”数字化解决方案提供商&#xff0c;正助力商家加速数字化转型&#xff0c;推动行业向智能化、个性化服务转型。 数字化转型的背景 …

C++ 多态的相关问题

目录 1. 第一题 2. 第二题 3. inline 函数可以是虚函数吗 4. 静态成员函数可以是虚函数吗 5. 构造函数可以是虚函数吗 6. 析构函数可以是虚函数吗 7. 拷贝构造和赋值运算符重载可以是虚函数吗 8. 对象访问普通函数快还是访问虚函数快 9. 虚函数表是什么阶段生成的&…

宿舍管理系统代码详解(主页面)

本篇将对管理系统的主页面的代码进行详细的介绍。 目录 一、主页面前端代码 1.样式展示 2.代码详解 &#xff08;1&#xff09;template部分 &#xff08;2&#xff09;script部分 &#xff08;3&#xff09;路由导航守卫 &#xff08;4&#xff09;在vue中引用vue 一、主页…

iOS--底层学习--GCD的简单认识

iOS--底层学习--GCD的简单认识 前言什么是GCDGCD的优点GCD中的任务和队列任务队列 GCD的使用队列的创建和获取任务的创建队列嵌套任务和队列中的一些要点 GCD线程间的通信从后台线程切换到主线程通过队列传递数据使用Dispatch Group进行线程间协调 GCD的方法dispatch_barrier_a…