【数据结构】二叉树 -- 堆
文章目录
- 一、堆的概念及结构
- 二、堆的实现
- 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 什么是数据结构 数据结构是计算机存储,组织数据的反复改.数据结构是指相互之间存在的一种或多种特定关系的数据元素集合. 2 数据结构的逻辑结构 1 集合结构,元素都是孤 ...
- 数据结构之堆的插入、取值、排序(细致讲解+图片演示)
数据结构之堆(Heap):插入.取值.排序. 堆是一种数据结构,分为最小堆和最大堆,可以用二叉树来表示. 在二叉树的任意的一个三角结构中(一个父节点,两个子节点),需要满足以下两个条件: 1.父节点要 ...
- 数据结构——二叉树总结
数据结构-二叉树总结 写在前面 二叉树遍历 递归实现先.中.后序遍历 非递归遍历 先序非递归 中序非递归 后序非递归 层次遍历 二叉树还原 先序中序建树 后序中序建树 层次中序建树 二叉树应用 二叉查 ...
- 数据结构:堆 的详解
堆 文章目录 堆 堆的概念及结构 堆的性质 堆的实现 向下调整算法(小根堆) 代码 向上调整算法 代码 堆的创建 方法一(向下调整算法) 方法二(向上调整算法) 建堆的时间复杂度 堆的模拟实现 堆的增 ...
- 3. 数据结构--二叉树 BST AVL树 Huffman
数据结构–二叉树 KEY:(不敢相信没有堆-) 二叉树的定义及其主要特征 ☑️ 二叉树的顺序存储结构和链式存储结构实现 二叉树的遍历及应用 二叉排序(查找.检索)树 (BST) 平衡的二叉检索树- A ...
- 算法(4)数据结构:堆
1.0 问题描述 实现数据结构:堆. 2.0 问题分析 堆一般使用数组来表示,其中某个节点下标i的两个子节点的下标为 2i+1 和 2i+2.堆是一棵完全二叉树. 堆有3种基本操作:创建,插入,删除. ...
- 数据结构 -- 二叉树
这篇文章介绍的是经典的数据结构--二叉树,在这篇文章里介绍了几乎二叉树的所有操作. 二叉树给我们最重要的印象莫过于递归,因为这棵树就是递归的,所以,我在解决各个问题时大部分都用 ...
- 数据结构 - 二叉树 - 面试中常见的二叉树算法题
数据结构 - 二叉树 - 面试中常见的二叉树算法题 数据结构是面试中必定考查的知识点,面试者需要掌握几种经典的数据结构:线性表(数组.链表).栈与队列.树(二叉树.二叉查找树.平衡二叉树.红黑树).图 ...
- (十)数据结构之“堆”
数据结构之"堆" 堆是什么? JS中的堆 堆的应用 第K个最大元素 LeetCode:215.数组中的第K个最大元素 LeetCode:347.前K个高频元素 LeetCode:2 ...
- 数据结构——二叉树的递归算法
二叉树的结构定义: typedef struct BiNode {TElemType data;struct BiNode *lchild;struct BiNode *rchild; }BiNode ...
最新文章
- 2020年度“中国神经科学重大进展”获奖名单【附成果介绍】
- Android最佳性能实践(二)——分析内存的使用情况
- 电脑f2还原系统步骤_使用冰点还原电脑每次重启都会还原,打造一个百毒不侵的系统...
- 【47.92%】【hdu 5763】Another Meaning
- The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
- python扫描端口hack_一款集http端口扫描和目录批量扫描为一体的自动化工具
- 两张图让你快速读懂JVM字节码指令
- python按综合、销量排序抓取100页的淘宝商品列表信息
- CES Asia展华为秀肌肉,布局智能互联生态
- 深入学习图数据库语言Gremlin 系列文章链接汇总
- 牛客网JavaScript V8在线编程输入输出
- 细说强网杯Web辅助
- 世界上最神奇的24堂课-----第一课 内心世界,新的力量
- 九招教你完全了解液晶拼接屏
- 猜帽子颜色问题(阿里巴巴面试题)
- csapp hello的一生
- vnc改ip_修改vnc server
- 中型公司网络架构拓扑与详解
- 微软黑屏,360坐收渔利
- HTML5大屏版性能测试报告
热门文章
- Cocos 入门教程
- 【基础01】二进制、八进制、十进制、十六进制的概念及转换关系
- 浅谈图像处理方向的就业前景
- 生态透水砖加工制作技术资料配方方法
- 用于业务的精炼js工具函数(浏览器环境)
- python微信库有哪些_GitHub - zwczou/weixin-python: 微信SDK - 包括微信支付,微信公众号,微信登陆,微信消息处理等...
- 学习效果不理想怎么办?怎样提高
- SQL基础教程 数据库和SQL
- 《响应式web设计》读书笔记(一)入门
- 人工智能库兹韦尔的“奇点理论”有一天是否会变成现实