当前位置: 首页 > news >正文

堆的简单介绍

首先介绍一下完全二叉树的概念:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。(也就是深度为K的二叉树前K-1层是满二叉树,第K层时不是满二叉树但是叶子节点从左往右依次有序排列)

堆的概念:

堆的存储方式是使用数组,类似于顺序表。如下图所示:可以得出一些关系,

1. 父亲节点的下标==孩子节点的下标-1/2,如0=(1-1)/2(0的左孩子);0=(2-1)/2(0的右孩子);

2. 左孩子节点的下标==父亲的下标*2+1。如1=0*2+1;右孩子的下标等于左孩子的下标+1

下面开始讲解堆的实现

堆的实现,Heap.c文件:

前面说到堆的底层实际就是数组,类似于顺序表,所以同样是利用结构体定义一个堆结构。

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>typedef int HPDataType;typedef struct Heap
{HPDataType* _data;int _size;int _capacity;
}Heap;// 堆的构建(初始化)
void HeapInit(Heap* php, HPDataType* arr, int num);// 堆的销毁
void HeapDestroy(Heap* php);
// 堆的插入
void HeapPush(Heap* php, HPDataType x);// 堆顶的删除
void HeapPop(Heap* php);// 堆顶数据
HPDataType HeapTop(Heap* php);// 堆的向下调整
void AdjustDown(HPDataType* _data, int num, int root);// 传址--交换两个数
void Swap(HPDataType* a, HPDataType* b);

堆的实现文件:Heap.c文件

首先讲清楚向下调整代码,很重要:

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

如下图所示:

代码:

#define _CRT_SECURE_NO_WARNINGS 
#include "Heap.h"// 打印堆
void HeapPrint(Heap* php)
{for (int i = 0;i < php->_size;i++){printf("%d ", php->_data[i]);}printf("\n");
}// 传址--交换两个数
void Swap(HPDataType* a, HPDataType* b)
{HPDataType temp = *a;*a = *b;*b = temp;
}// 小堆的向下调整,前提:左右子树都是小堆,即所有父亲小于左右孩子
// 大堆的向下调整,前提:左右子树都是大堆,即所有父亲大于左右孩子
void AdjustDown(HPDataType* _data, int num, int root)
{int parent = root;int child = parent * 2 + 1; // 左孩子while (child < num)   {// 找出左右孩子中小的那一个;//if (child + 1 < num && _data[child + 1] > _data[child]) // 注意越界访问(建大堆)if (child+1 < num && _data[child + 1] < _data[child]) // 注意越界访问,child+1是右孩子,完全二叉树最后的一个父亲节点他可能没有右孩子为避免非法访问,必须加入这个条件{++child;}// 如果小的那个孩子小于父亲则交换//if (_data[child] > _data[parent]) // 建大堆if (_data[child] < _data[parent]){Swap(&_data[child], &_data[parent]);parent = child;            // 交换之后把孩子节点(它又是它的孩子的父亲)的位置给给父亲child = parent * 2 + 1;    // 重新计算孩子的孩子节点的下标}else // 父亲小于孩子就跳出循环{break;}}
}// 堆的构建(初始化)
void HeapInit(Heap* php, HPDataType* arr, int num)
{assert(php);// 将传入的数组(不是堆的数组)拷贝到我们的数组内// 申请空间HPDataType* temp = (HPDataType*)malloc(sizeof(HPDataType) * num);if (temp == NULL){perror("malloc fali");exit(-1);}php->_data = temp;// 使用memcpy函数拷贝,第一个参数是目标空间起始地址,第二个是数据源的地址,第三个是一次拷贝多少个字节的内容memcpy(php->_data, arr, sizeof(HPDataType) * num);php->_size = num;php->_capacity = num;// 构建堆 (从最后一个叶子节点的父亲开始调整逐步往上调整子树成为小堆,// i=0时根节点的左右字数已经是小堆,此时将根节点调整至满足小堆即可)// for (int i = num;i >= 0;i--) 从最后一个节点开始比较也行只是叶子节点没有孩子没必要调整for (int i = (num - 1 - 1) / 2;i >= 0;i--)  // num-1是数组的最后一个下标,然后套到求父亲节点的公式{AdjustDown(php->_data, php->_size, i);}
}

注意:最开始给的数组未必左右子树都是堆,所以从最后一个叶子节点的父亲开始向下调整,如下图所示,根节点15的左右子树都不是堆;所以要从下标4的位置开始调整,4-3-2-1-0就调整完毕。这里展示的除了0下标和4下标不是堆,其他1-2-3下标都是小堆。

堆的插入、销毁、删除堆顶数据,获得堆顶数据:(向上调整刚好也可以参考上图,插入元素10)

// 向上调整
void AdjustUp(HPDataType* _data, int num, int child)
{int parent = (child - 1) / 2;// 孩子和父亲比较大小while (child > 0){if (_data[child] < _data[parent]){Swap(&_data[child], &_data[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}// 堆的销毁
void HeapDestroy(Heap* php)
{assert(php);free(php->_data);php->_data = NULL;php->_size = php->_capacity = 0;
}
// 堆的插入
void HeapPush(Heap* php, HPDataType x)
{assert(php);// 插入数据要检查空间是否足够if (php->_size == php->_capacity){php->_capacity *= 2;   // 2倍数扩容HPDataType* temp = (HPDataType*)realloc(php->_data, sizeof(HPDataType) * php->_capacity);if (temp == NULL){perror("realloc fail");exit(-1);}php->_data = temp;}php->_data[php->_size] = x;php->_size++;// 向上调整,将插入的数据和它的父亲以及父亲的父亲……作比较AdjustUp(php->_data, php->_size, php->_size - 1);
}// 堆顶的删除
void HeapPop(Heap* php)
{assert(php);assert(php->_size > 0);// 让堆顶的数据和最后一个数据交换,然后size-1,然后在向下调整重新构建堆Swap(&php->_data[0], &php->_data[php->_size - 1]);php->_size--;AdjustDown(php->_data, php->_size, 0);// 此时左右子树都是堆,只需要从0的位置向下调整即可
}// 堆顶数据
HPDataType HeapTop(Heap* php)
{assert(php);assert(php->_size > 0);return php->_data[0]; // 直接返回堆顶的数据即可
}

建堆的测试文件:test.c:

#define _CRT_SECURE_NO_WARNINGS 
#include "Heap.h"// 数据降序:排小堆
// 数据升序:排大堆
// 堆排序
void Heapsort(HPDataType* a, int n)
{for (int i = (n - 1 - 1) / 2;i >= 0;i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){// 小堆后就选好了最小的放在第一个位置,将它和最后一个位置换掉,然后对前n-1个数据再排小堆Swap(&a[0], &a[end]);// 选次小的……AdjustDown(a, end, 0);--end;}
}int main()
{int arr[] = { 27,15,19,18,28,34,65,49,25,37 };Heap hp;HeapInit(&hp, arr, sizeof(arr) / sizeof(int));HeapPrint(&hp);HeapPush(&hp, 13);HeapPrint(&hp);HeapDestroy(&hp);//Heapsort(arr, sizeof(arr) / sizeof(int));return 0;
}

结论:本文介绍了完全二叉树和堆的概念及其实现。完全二叉树是效率很高的数据结构,其每个节点都与满二叉树对应。堆使用数组存储,实现父子节点间的下标关系。文章详细讲解了堆的实现过程,包括初始化、插入、删除、调整等核心操作,重点分析了向下调整算法(要求左右子树已满足堆性质)和向上调整算法(用于插入操作)。同时提供了堆排序的实现思路,通过不断调整堆结构实现数据有序排列。代码示例展示了堆的各种操作及测试流程,包括堆的构建、插入元素、删除堆顶等。

http://www.lryc.cn/news/580306.html

相关文章:

  • 智链万物:人工智能驱动的产业智能化革命
  • 使用 C++/Faiss 加速海量 MFCC 特征的相似性搜索
  • Python(28)Python循环语句指南:从语法糖到CPython字节码的底层探秘
  • 解决el-select数据类型相同但是显示数字的问题
  • 【Project】基于kafka的高可用分布式日志监控与告警系统
  • C#扩展方法全解析:给现有类型插上翅膀的魔法
  • CMake基础:条件判断详解
  • 探索 Ubuntu 上 MongoDB 的安装过程
  • [Cyclone] 哈希算法 | SIMD优化哈希计算 | 大数运算 (Int类)
  • 【大模型】到底什么是Function Calling和MCP,以及和ReAct推理的关系是什么?
  • 若 VSCode 添加到文件夹内右键菜单中显示
  • 03_性能优化:让软件呼吸更顺畅
  • ABB焊接机器人智能节气仪
  • App爬虫工具篇-appium配置
  • AWS WebRTC:通过shell分析viewer端日志文件
  • 查看linux中steam游戏的兼容性
  • 权电阻网络DAC实现电压输出型数模转换Multisim电路仿真——硬件工程师笔记
  • C++构造和折构函数详解,超详细!
  • Linux基本命令篇 —— uname命令
  • 第二章-AIGC入门-开启AIGC音频探索之旅:从入门到实践(6/36)
  • 利用 AI 打造的开发者工具集合
  • 一个简单的分布式追踪系统
  • 指针篇(7)- 指针运算笔试题(阿里巴巴)
  • 物联网软件层面的核心技术体系
  • 论文解读:《DeepGray:基于灰度图像和深度学习的恶意软件分类方法》
  • 优象光流模块,基于python的数据读取demo
  • 新能源汽车功率级测试自动化方案:从理论到实践的革命性突破
  • 区块链技术在物联网(IoT)中的核心应用场景
  • SQL Server 进阶语法实战:从动态透视到存储过程的深度应用(第四课)
  • 高档宠物食品对宠物的健康益处有哪些?