数据结构第十一弹---堆

这篇具有很好参考价值的文章主要介绍了数据结构第十一弹---堆。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、堆的概念及结构

堆就是以二叉树的顺序存储方式来存储元素,同时又要满足父亲结点存储数据都要大于等于儿子结点存储数据(也可以是父亲结点数据都要小于等于儿子结点数据)的一种数据结构。堆只有两种即大堆和小堆大堆就是父亲结点数据大于等于儿子结点数据,小堆则反之。

数据结构第十一弹---堆,数据结构,c语言,算法

2、堆的性质

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

3、堆的调整算法

3.1、向下调整算法

现在我们给出一个数组,逻辑上看作一棵完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。

数据结构第十一弹---堆,数据结构,c语言,算法
但是,使用向下调整算法需要满足一个前提:
 若想将其调整为小堆,那么根结点的左右子树必须都为小堆。
 若想将其调整为大堆,那么根结点的左右子树必须都为大堆。


数据结构第十一弹---堆,数据结构,c语言,算法

向下调整算法的基本思想(小堆):
 1.从根结点处开始,选出左右孩子中值较小的孩子。
 2.让小的孩子与其父亲进行比较。
 若小的孩子比父亲还小,则该孩子与其父亲的位置进行交换。并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
 若小的孩子比父亲大,则不需处理了,调整完成,整个树已经是小堆了。

代码实现

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}
void AdjustDown(HPDataType* a, int size, int parent)
{
	//1.假设左孩子为小的数据
	int child = parent * 2 + 1;
	while (child < size)
	{
	//2.如果左孩子>右孩子 则将右孩子赋值
	//有可能只有左孩子 所以加条件
	//以下未有左右孩子且左孩子>右孩子情况,则将child++
		if (child + 1 < size && a[child] > a[child + 1])
		{
			child++;
		}
		//3.将孩子与父亲进行比较 如果孩子小则交换
		//然后将父亲和孩子移动到下一个位置
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

交换数值函数注意
数据结构第十一弹---堆,数据结构,c语言,算法
使用堆的向下调整算法需要满足其根结点的左右子树均为大堆或是小堆才行,那么如何才能将一个任意树调整为堆呢?
 答案很简单,我们只需要从倒数第一个非叶子结点开始,从后往前,按下标,依次作为根去向下调整即可。
数据结构第十一弹---堆,数据结构,c语言,算法

向下调整算法的时间复杂度:
数据结构第十一弹---堆,数据结构,c语言,算法

根据计算可知,向下调整算法的时间复杂度为O(N)。

3.2、向上调整算法

当我们在一个堆的末尾插入一个数据后,需要对堆进行调整,使其仍然是一个堆,这时需要用到堆的向上调整算法。

数据结构第十一弹---堆,数据结构,c语言,算法

向上调整算法的基本思想(小堆):
 1.将目标结点与其父结点比较。
 2.若目标结点的值比其父结点的值小,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是小堆了。

数据结构第十一弹---堆,数据结构,c语言,算法

但是,使用向上调整算法需要满足一个前提:
 若想将其调整为小堆,那么原来的数据为小堆。
 若想将其调整为大堆,那么原来的数据为大堆。


代码实现

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}
void AdjustUp(HPDataType* p, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (p[parent] > p[child])
		{
			Swap(&p[parent], &p[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

向上调整算法时间复杂度
数据结构第十一弹---堆,数据结构,c语言,算法

因此向上调整算法的时间复杂度为O(N*logN)。

4、堆的实现

实现一个数据结构的第一步需要创建一个工程。(下图为vs 2022)
数据结构第十一弹---堆,数据结构,c语言,算法
Heap.h(堆的类型定义、接口函数声明、引用的头文件)
Heap.c(堆接口函数的实现)
test.c (主函数、测试顺序表各个接口功能)

4.1、头文件包含和结构定义

以下是实现堆可能用到的头文件。

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

堆的结构定义

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;//存放数据的动态数组
	int size;     //有效数据个数
	int capacity; //数组容量
}HP;

4.2、初始化

void HeapInit(HP* php)
{
	assert(php);//断言避免出现空指针
	php->a = NULL;
	php->capacity = php->size = 0;
}

4.3、销毁

void HeapDestory(HP* php)
{
	assert(php);
	free(php->a);//释放动态数组
	php->size = php->capacity = 0;
}

4.4、插入数据

插入数据的同时需要保证插入之后的数据依然是堆,由于插入数据之前的所有数据是堆,可以使用向上调整数据进行调整。
数据结构第十一弹---堆,数据结构,c语言,算法

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//1.检查容量
	if (php->size == php->capacity)
	{
		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);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	//2.插入数据
	php->a[php->size] = x;
	php->size++;

	//3.调整数据
	AdjustUp(php->a, php->size-1);
}

测试
数据结构第十一弹---堆,数据结构,c语言,算法

4.5、删除数据 删除堆顶

删除堆顶元素,首先需要有数据,通过断言判断,有数据的情况下先将堆顶元素和数组尾的数据进行交换,然后将size–,因为除了堆顶元素不满足堆结构之外,其他都满足,所以使用向下调整数据算法。

void HeapPop(HP* php)
{
	assert(php);
	//有数据才删除
	assert(php->size > 0);
	//1.将首位数据交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	//2.删除尾数据
	php->size--;
	//3.向下调整
	AdjustDown(php->a, php->size, 0);
}

测试
数据结构第十一弹---堆,数据结构,c语言,算法

4.6、获取堆顶元素

根据堆的定义可知,堆顶元素就是数组首元素,返回首元素即可。

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

测试
数据结构第十一弹---堆,数据结构,c语言,算法

4.7、获取有效数据个数

根据堆的结构设计,size就是堆的有效数据个数,返回size即可。

size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}

测试
数据结构第十一弹---堆,数据结构,c语言,算法

4.8、判断是否为空

根据堆结构的设计,size代表堆的有效数据个数,size等于0则为空,不等于0则不为空。

bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

数据结构第十一弹---堆,数据结构,c语言,算法

5、代码汇总

5.1、Heap.h

#pragma once     //防止头文件重复包含
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;//存放数据的数组
	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);
//获取有效数据个数
size_t HeapSize(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//两个元素交换
void Swap(HPDataType* a, HPDataType* b);

//向上调整数据 小堆
void AdjustUp(HPDataType* p, int child);
//向下调整算法 小堆
void AdjustDown(HPDataType* a, int size, int parent);

5.2、Heap.c

#define _CRT_SECURE_NO_WARNINGS
#include "Heap.h"
//初始化 小堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = php->size = 0;
}
//销毁
void HeapDestory(HP* php)
{
	assert(php);
	free(php->a);
	php->size = php->capacity = 0;
}
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}
log N
//向上调整数据 小堆
void AdjustUp(HPDataType* p, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (p[parent] > p[child])
		{
			Swap(&p[parent], &p[child]);
			child = parent;
			parent = (parent - 1) / 2;
		}
		else
		{
			break;
		}
	}
}


//插入数据
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	//1.检查容量
	if (php->size == php->capacity)
	{
		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);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	//2.插入数据
	php->a[php->size] = x;
	php->size++;

	//3.调整数据
	AdjustUp(php->a, php->size-1);
}

//向下调整算法 小堆
void AdjustDown(HPDataType* a, int size, int parent)
{
	//1.假设左孩子为小的数据
	int child = parent * 2 + 1;
	while (child < size)
	{
	//2.如果左孩子>右孩子 则将右孩子赋值
	//有可能只有左孩子 所以加条件
	//以下未有左右孩子且左孩子>右孩子情况,则将child++
		if (child + 1 < size && a[child] > a[child + 1])
		{
			child++;
		}
		//3.将孩子与父亲进行比较 如果孩子小则交换
		//然后将父亲和孩子移动到下一个位置
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


//删除数据 删除堆顶数据
void HeapPop(HP* php)
{
	assert(php);
	//有数据才删除
	assert(php->size > 0);
	//1.将首位数据交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	//2.删除尾数据
	php->size--;
	//3.向下调整
	AdjustDown(php->a, php->size, 0);
}
//取堆顶元素
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}
size_t HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

总结

本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!文章来源地址https://www.toymoban.com/news/detail-778666.html

到了这里,关于数据结构第十一弹---堆的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构与算法(十一) 排序算法一

    int nArray[] = { 8,5,3,2,7 };如下一个数组,现对其进行从小到大排序 选择排序:将小的依次放在前面 具象化如下: void swap(int *nSValue,int *nDValue) 交换函数  {     int nTempValue = 0;     nTempValue = *nSValue;     *nSValue = *nDValue;     *nDValue = nTempValue; } void selectSort(int *pArr, int nCount) 选

    2024年01月20日
    浏览(31)
  • JAVAEE初阶相关内容第十一弹--多线程(进阶)

    目录 一、常见的锁策略 1乐观锁VS悲观锁 1.1乐观锁 1.2悲观锁 2.轻量级锁VS重量级锁 2.1轻量级锁 2.2重量级锁 3.自旋锁VS挂起等待锁 3.1自旋锁 3.2挂起等待锁 4.互斥锁VS读写锁 4.1互斥锁 4.2读写锁 5.公平锁VS非公平锁 5.1公平锁 5.2非公平锁 6.可重入锁VS不可重入锁 6.1可重入锁 6.2不可

    2024年02月07日
    浏览(29)
  • 数据结构与算法十一 图的入门

    在现实生活中,有许多应用场景会包含很多点以及点点之间的连接,而这些应用场景我们都可以用即将要学习的图这种数据结构去解决。 地图: 我们生活中经常使用的地图,基本上是由城市以及连接城市的道路组成,如果我们把城市看做是一个一个的点,把道路看做是一条

    2024年02月04日
    浏览(76)
  • 数据结构作业—第十三周---- Prim算法 Kruskal算法 Dijkstra算法

    (只看点,不看边,适合边较多的图,即 稠密图 )       是一种按权值的递增次序选择合适的边来构造最小生成树的方法;( 稀疏图 ) 适合带权有向图和带权无向图求单源最短路径; 不适合含负取值的图,求最短路径; 1 . 单选题 简单 7分 对于有n个顶点的带权连通图

    2024年02月15日
    浏览(41)
  • 【夜深人静学数据结构与算法 | 第十篇】动态规划

    目录 前言: 动态规划: 常见应用: 解题步骤:  动态规划的简化步骤: 案例: 509. 斐波那契数 - 力扣(LeetCode) 70. 爬楼梯 - 力扣(LeetCode) 62. 不同路径 - 力扣(LeetCode) 总结:         本文我们将为大家讲解一下动态规划的理论知识,并且会讲解几道力扣的经典例题。

    2024年02月11日
    浏览(50)
  • 深入理解数据结构第一弹——二叉树(1)——堆

    前言: 在前面我们已经学习了数据结构的基础操作:顺序表和链表及其相关内容,今天我们来学一点有些难度的知识—— 数据结构中的二叉树 ,今天我们先来学习 二叉树中堆 的知识,这部分内容还是非常有意思的,下面我们就开始慢慢学习 准备工作:本人习惯将文件放在

    2024年04月17日
    浏览(37)
  • 【数据结构入门精讲 | 第十七篇】一文讲清图及各类图算法

    在上一篇中我们进行了的并查集相关练习,在这一篇中我们将学习图的知识点。 下面介绍几种在对图操作时常用的算法。 深度优先搜索(DFS)是一种用于遍历或搜索树、图等数据结构的基本算法。该算法从给定的起点开始,沿着一条路径直到达到最深的节点,然后再回溯到

    2024年02月03日
    浏览(41)
  • 【夜深人静学习数据结构与算法 | 第十二篇】动态规划——背包问题

      目录  前言:  01背包问题: 二维数组思路: 一维数组思路: 总结:       在前面我们学习动态规划理论知识的时候,我就讲过要介绍一下背包问题,那么今天我们就来讲解一下背包问题。 在这里我们只介绍 01背包 ,至于分组背包和混合背包这种的已经属于竞赛级别的

    2024年02月12日
    浏览(45)
  • 数据结构:二叉树经典例题(单选题)-->你真的掌握二叉树了吗?(第一弹)

    朋友们、伙计们,我们又见面了,本期来给大家解读一下有关二叉树的经典例题,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏: C语言:从入门到精通 数据结构专栏: 数据结构 个  人  主  页 : stackY、 目录 前言: 一、 二、

    2024年02月10日
    浏览(34)
  • C语言数据结构与算法

    冒泡排序 例题 顺序表下的 冒泡排序 注意:冒泡排序 稳定,最多执行n(n-1)/2次 选择排序不稳定,平均比较次数n(n-1)/2 直接插入排序,是在有序基础上,速度最快且稳定的排序方法。 希尔排序是 不稳定的 顺序查找 二分查找(非递归) 二分查找(递归) 数组 链表 查询 快 慢

    2024年02月06日
    浏览(61)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包