文章目录

  • 一、堆的概念及结构
  • 二、堆的实现
    • 1、结构的定义
    • 2、堆的初识化
    • 3、堆的插入
    • 4、堆的向上调整
    • 5、堆的删除
    • 6、堆的向下调整
    • 7、取出堆顶的元素
    • 8、返回堆的元素个数
    • 9、判断堆是否为空
    • 10、打印堆中的数据
    • 11、堆的销毁
  • 三、完整代码
    • 1、Heap.h
    • 2、Heap.c
    • 3、test.c

一、堆的概念及结构

如果有一个关键码的集合 K = {k0 , k1 , k2 , … , kn-1} ,把它的所有元素按完全二叉树的顺序存储方式存储在一 个一维数组中 ,并满足: Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >=K2i+2) i = 0 , 1 , 2… ,则称为小堆 ( 或大堆) 。(即双亲比孩子的数值小(大)——小(大)堆)将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆只有两种,即大堆和小堆,大堆就是父亲结点数据大于儿子结点数据,小堆则反之。

堆的性质:堆中某个节点的值总是不大于或不小于其父节点的值;堆总是一棵完全二叉树


二、堆的实现

1、结构的定义

由于是堆的元素按完全二叉树的顺序存储方式存储在一位数组中的,所以堆的结构和顺序表的结构一样。

//符号和结构的声明
#define DEF_SIZE 5
#define CRE_SIZE 2
typedef int HPDataType;typedef struct Heap
{HPDataType* data;int size;int capacity;
}HP;

2、堆的初识化

堆的初识化和顺序表的初始化一样。

//堆的初始化
void HeapInit(HP* php)
{assert(php);php->data = (HPDataType*)malloc(sizeof(HPDataType) * DEF_SIZE);if (php->data == NULL){perror("malloc fail");exit(-1);}php->size = 0;php->capacity = DEF_SIZE;
}

3、堆的插入

堆的插入有两个需要注意的地方:

1、由于堆只会在尾部插入元素,所以我们不需要将 CheckCapacity 单独封装一个函数;

2、由于堆要求在插入元素之后仍保持堆的结构,即保持小根堆/大根堆,所以我们需要对堆进行向上调整,向上调整的过程其实也就是建堆的过程。

//堆的插入--需要保证插入之后仍然保持堆的结构
void HeapPush(HP* php, HPDataType x)
{assert(php);//检查容量if (php->size == php->capacity){HPDataType* ptr = (HPDataType*)realloc(php->data, sizeof(HPDataType) * php->capacity * CRE_SIZE);if (ptr == NULL){perror("realloc fail");exit(-1);}php->data = ptr;php->capacity *= CRE_SIZE;}//插入元素php->data[php->size] = x;php->size++;//保持堆结构--向上调整AdjustUp(php->data, php->size - 1);
}

4、堆的向上调整

这里我们以小根堆为例,如图:假设现在我们已经有了一个小根堆,现在我们往堆中 (堆尾) 插入一个元素,那么可能会出现两种情况:

1、插入的元素大于父节点,此时我们的堆仍保持小根堆结构,所以不需要改动;比如我们往堆中插入30;

2、插入的元素小于父节点;这种情况又可以分为两种:一是插入的节点虽然小于父节点,但是大于父节点的父节点,这种情况我们只需要交换父节点和该节点,使得堆保存小根堆的结构即可,比如我们插入20;二是该节点不仅小于父节点,还小于父节点的父节点,这种情况下我们就需要把该节点不断往上调整,直到把堆调整为小根堆,最坏的情况是该节点被调整为根节点,比如我们插入10;

//交换两个节点
void Swap(HPDataType* p1, HPDataType* p2)
{assert(p1 && p2);HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整--小根堆
void AdjustUp(HPDataType* data, int child)
{assert(data);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;}}
}

如果我们需要建大根堆,只需要把交换的条件修改一下即可。

//当子节点大于父节点时交换
if (data[child] > data[parent])

5、堆的删除

对于堆的删除有明确的规定:我们只能删除堆顶的元素;但是头删之后存在两个问题:

1、顺序表头删需要挪动数据,效率低下;

2、头删之后堆中各节点的父子关系完全被破坏;

对于上面的这些问题,我们有如下解决办法:

1、我们在删除之前先将堆顶和堆尾的元素交换,然后让size–,这样相当于删除了堆顶的元素,且效率达到了O(1);

2、由于我们把堆尾元素交换到了堆顶,堆的结构遭到了破坏,所以设计一个向下调整算法来让保持堆的结构;

//删除堆顶的元素--需要保证删除之后仍然保持堆的结构
void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));//首先交换堆顶和堆尾的元素Swap(&php->data[0], &php->data[php->size - 1]);//删除堆尾的元素php->size--;//保持堆结构--向下调整AdjustDown(php->data, php->size, 0);
}

6、堆的向下调整

堆向下调整的思路和向上调整刚好相反 (我们还是以小根堆为例):1、找出子节点中较小的节点;2、比较父节点与子节点,如果父节点大于子节点则交换两个节点;3、交换之后,原来的子节点成为新的父节点,然后继续 1 2 步骤,直到调整为堆的结构。

//向下调整
void AdjustDown(HPDataType* data, int n, int parent)
{assert(data);int minchild = parent * 2 + 1;//当子节点调整到堆尾时结束循环while (minchild < n){//找出较小的子节点if (minchild + 1 < n && data[minchild + 1] < data[minchild]){minchild += 1;}//如果父节点大于较小的子节点就交换if (data[parent] > data[minchild]){Swap(&data[parent], &data[minchild]);//迭代parent = minchild;minchild = parent * 2 + 1;}//否则直接跳出循环else{break;}}
}

和向上调整类似,如果我们想要调整为大堆,也只需要改变交换条件:

//找出较大的子节点
if (minchild + 1 < n && data[minchild + 1] > data[minchild])
//如果父节点小于较小的子节点就交换
if (data[parent] < data[minchild])

7、取出堆顶的元素

//取堆顶的元素
HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(php));return php->data[0];
}

8、返回堆的元素个数

//堆的元素个数
int HeapSize(HP* php)
{assert(php);return php->size;
}

9、判断堆是否为空

//堆的判空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}

10、打印堆中的数据

//打印堆中的数据
void HeapPrint(HP* php)
{assert(php);int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->data[i]);}printf("\n");
}

11、堆的销毁

//堆的销毁
void HeapDestory(HP* php)
{assert(php);free(php->data);php->capacity = php->size = 0;
}

三、完整代码

1、Heap.h

#pragma once//头文件的包含
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>//符号和结构的声明
#define DEF_SIZE 5
#define CRE_SIZE 2
typedef int HPDataType;typedef struct Heap
{HPDataType* data;int size;int capacity;
}HP;//函数的声明
//堆的初始化
void HeapInit(HP* php);
//堆的销毁
void HeapDestory(HP* php);
//堆的插入
void HeapPush(HP* php, HPDataType x);
//删除堆顶的元素
void HeapPop(HP* php);
//取堆顶的元素
HPDataType HeapTop(HP* php);
//堆的元素个数
int HeapSize(HP* php);
//堆的判空
bool HeapEmpty(HP* php);
//打印堆中的数据
void HeapPrint(HP* php);

2、Heap.c

#define _CRT_SECURE_NO_WARNINGS 1#include "Heap.h"//堆的初始化
void HeapInit(HP* php)
{assert(php);php->data = (HPDataType*)malloc(sizeof(HPDataType) * DEF_SIZE);if (php->data == NULL){perror("malloc fail");exit(-1);}php->size = 0;php->capacity = DEF_SIZE;
}//堆的销毁
void HeapDestory(HP* php)
{assert(php);free(php->data);php->capacity = php->size = 0;
}//交换两个节点
void Swap(HPDataType* p1, HPDataType* p2)
{assert(p1 && p2);HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整--小根堆
void AdjustUp(HPDataType* data, int child)
{assert(data);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 HeapPush(HP* php, HPDataType x)
{assert(php);//检查容量if (php->size == php->capacity){HPDataType* ptr = (HPDataType*)realloc(php->data, sizeof(HPDataType) * php->capacity * CRE_SIZE);if (ptr == NULL){perror("realloc fail");exit(-1);}php->data = ptr;php->capacity *= CRE_SIZE;}//插入元素php->data[php->size] = x;php->size++;//保持堆结构--向上调整AdjustUp(php->data, php->size - 1);
}//向下调整
void AdjustDown(HPDataType* data, int n, int parent)
{assert(data);int minchild = parent * 2 + 1;//当子节点调整到堆尾时结束循环while (minchild < n){//找出较小的子节点if (minchild + 1 < n && data[minchild + 1] < data[minchild]){minchild += 1;}//如果父节点大于较小的子节点就交换if (data[parent] > data[minchild]){Swap(&data[parent], &data[minchild]);//迭代parent = minchild;minchild = parent * 2 + 1;}//否则直接跳出循环else{break;}}
}//删除堆顶的元素--需要保证删除之后仍然保持堆的结构
void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));//首先交换堆顶和堆尾的元素Swap(&php->data[0], &php->data[php->size - 1]);//删除堆尾的元素php->size--;//保存堆结构--向下调整AdjustDown(php->data, php->size, 0);
}//取堆顶的元素
HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(php));return php->data[0];
}//堆的元素个数
int HeapSize(HP* php)
{assert(php);return php->size;
}//堆的判空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}//打印堆中的数据
void HeapPrint(HP* php)
{assert(php);int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->data[i]);}printf("\n");
}

3、test.c

#define _CRT_SECURE_NO_WARNINGS 1#include "Heap.h"int main()
{int a[] = { 27,15,19,18,28,34,65,49,25,37 };HP hp;//堆的初始化HeapInit(&hp);//插入元素int i = 0;int len = sizeof(a) / sizeof(a[0]);for (i = 0; i < len; i++){HeapPush(&hp, a[i]);}HeapPrint(&hp);//删除堆顶元素HeapPop(&hp);HeapPrint(&hp);//取出堆顶元素HPDataType top = HeapTop(&hp);printf("%d\n", top);//堆排序for (i = 0; i < len - 1; i++){printf("%d ", HeapTop(&hp));HeapPop(&hp);}//堆的销毁HeapDestory(&hp);return 0;
}

大家也可以去我的 Gitee 仓库中获取完整代码:Heap/Heap · 野猪佩奇/日常学习 - 码云 - 开源中国 (gitee.com)


【数据结构】二叉树 -- 堆相关推荐

  1. 数据结构,堆和栈和队列的概念

    数据结构,堆和栈和队列的概念 1 什么是数据结构 数据结构是计算机存储,组织数据的反复改.数据结构是指相互之间存在的一种或多种特定关系的数据元素集合. 2 数据结构的逻辑结构 1 集合结构,元素都是孤 ...

  2. 数据结构之堆的插入、取值、排序(细致讲解+图片演示)

    数据结构之堆(Heap):插入.取值.排序. 堆是一种数据结构,分为最小堆和最大堆,可以用二叉树来表示. 在二叉树的任意的一个三角结构中(一个父节点,两个子节点),需要满足以下两个条件: 1.父节点要 ...

  3. 数据结构——二叉树总结

    数据结构-二叉树总结 写在前面 二叉树遍历 递归实现先.中.后序遍历 非递归遍历 先序非递归 中序非递归 后序非递归 层次遍历 二叉树还原 先序中序建树 后序中序建树 层次中序建树 二叉树应用 二叉查 ...

  4. 数据结构:堆 的详解

    堆 文章目录 堆 堆的概念及结构 堆的性质 堆的实现 向下调整算法(小根堆) 代码 向上调整算法 代码 堆的创建 方法一(向下调整算法) 方法二(向上调整算法) 建堆的时间复杂度 堆的模拟实现 堆的增 ...

  5. 3. 数据结构--二叉树 BST AVL树 Huffman

    数据结构–二叉树 KEY:(不敢相信没有堆-) 二叉树的定义及其主要特征 ☑️ 二叉树的顺序存储结构和链式存储结构实现 二叉树的遍历及应用 二叉排序(查找.检索)树 (BST) 平衡的二叉检索树- A ...

  6. 算法(4)数据结构:堆

    1.0 问题描述 实现数据结构:堆. 2.0 问题分析 堆一般使用数组来表示,其中某个节点下标i的两个子节点的下标为 2i+1 和 2i+2.堆是一棵完全二叉树. 堆有3种基本操作:创建,插入,删除. ...

  7. 数据结构 -- 二叉树

          这篇文章介绍的是经典的数据结构--二叉树,在这篇文章里介绍了几乎二叉树的所有操作.       二叉树给我们最重要的印象莫过于递归,因为这棵树就是递归的,所以,我在解决各个问题时大部分都用 ...

  8. 数据结构 - 二叉树 - 面试中常见的二叉树算法题

    数据结构 - 二叉树 - 面试中常见的二叉树算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图 ...

  9. (十)数据结构之“堆”

    数据结构之"堆" 堆是什么? JS中的堆 堆的应用 第K个最大元素 LeetCode:215.数组中的第K个最大元素 LeetCode:347.前K个高频元素 LeetCode:2 ...

  10. 数据结构——二叉树的递归算法

    二叉树的结构定义: typedef struct BiNode {TElemType data;struct BiNode *lchild;struct BiNode *rchild; }BiNode ...

最新文章

  1. 2020年度“中国神经科学重大进展”获奖名单【附成果介绍】
  2. Android最佳性能实践(二)——分析内存的使用情况
  3. 电脑f2还原系统步骤_使用冰点还原电脑每次重启都会还原,打造一个百毒不侵的系统...
  4. 【47.92%】【hdu 5763】Another Meaning
  5. The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
  6. python扫描端口hack_一款集http端口扫描和目录批量扫描为一体的自动化工具
  7. 两张图让你快速读懂JVM字节码指令
  8. python按综合、销量排序抓取100页的淘宝商品列表信息
  9. CES Asia展华为秀肌肉,布局智能互联生态
  10. 深入学习图数据库语言Gremlin 系列文章链接汇总
  11. 牛客网JavaScript V8在线编程输入输出
  12. 细说强网杯Web辅助
  13. 世界上最神奇的24堂课-----第一课 内心世界,新的力量
  14. 九招教你完全了解液晶拼接屏
  15. 猜帽子颜色问题(阿里巴巴面试题)
  16. csapp hello的一生
  17. vnc改ip_修改vnc server
  18. 中型公司网络架构拓扑与详解
  19. 微软黑屏,360坐收渔利
  20. HTML5大屏版性能测试报告

热门文章

  1. Cocos 入门教程
  2. 【基础01】二进制、八进制、十进制、十六进制的概念及转换关系
  3. 浅谈图像处理方向的就业前景
  4. 生态透水砖加工制作技术资料配方方法
  5. 用于业务的精炼js工具函数(浏览器环境)
  6. python微信库有哪些_GitHub - zwczou/weixin-python: 微信SDK - 包括微信支付,微信公众号,微信登陆,微信消息处理等...
  7. 学习效果不理想怎么办?怎样提高
  8. SQL基础教程 数据库和SQL
  9. 《响应式web设计》读书笔记(一)入门
  10. 人工智能库兹韦尔的“奇点理论”有一天是否会变成现实