首页 > 其他分享 >数据结构-堆-详解

数据结构-堆-详解

时间:2024-09-09 10:54:44浏览次数:6  
标签:php parent int 详解 child 数据结构 节点 size

数据结构-堆-详解

1.性质

堆是一种特殊的完全二叉树,其父节点总是不大于/不小于 子节点。
相比于普通二叉树,堆更适合用顺序结构的数组存储。

大根堆

其中任一父节点都不小于子节点。
逻辑结构:
在这里插入图片描述
储存结构:

在这里插入图片描述

小根堆

其中任一父节点都不大于子节点。
逻辑结构:
在这里插入图片描述
储存结构:
在这里插入图片描述

2.实现

2.1struct Heap、HeapInit、HeapDestroy

堆的结构体、堆的初始化、堆的销毁。
从上面的大小根堆可以看出,在实现上是一个顺序表,而逻辑上是二叉树
因此,这几步与顺序表几乎完全一致:

typedef int HeapDataType;
typedef struct Heap
{
	HeapDataType* a;
	int size;
	int capacity;
}Heap;

void HeapInit(Heap* php)
{
	assert(php);
	php->a = (HeapDataType*)malloc(sizeof(HeapDataType) * 4);
	if (!php->a)
	{
		perror("HeapInit::malloc");
		return;
	}
	php->size = 0;
	php->capacity = 4;
}

void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}

2.2HeapPush

插入元素。

void HeapPush(Heap* php, HeapDataType x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		HeapDataType* tmp = (HeapDataType*)realloc(php->a,sizeof(HeapDataType) * php->capacity * 2);
		if (!tmp)
		{
			perror("HeapDataType::realloc");
			return;
		}
		php->a = tmp;
		php->capacity *= 2;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a,php->size-1);
}

先判断是否需要扩容,再插入。
但这是个堆,因此需要对数据进行调整。
此处以大根堆为例,使用向上调整法AdjustUp

AdjustUp

大根堆需要满足其中任一父节点都不小于子节点。
当插入一个节点后,可以将其与父节点比较,不满足条件则交换,最多调到根。

void AdjustUp(HeapDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (parent >= 0)
	{
		if (a[child] > a[parent])
			Swap(a + child, a + parent);
		else
			break;
		child = parent;
		parent = (child - 1) / 2;
	}
}//有小bug

由公式得:父节点下标parent = (child - 1) / 2
进入循环:

  • 如果不满足条件,则交换Swap
void Swap(HeapDataType* a,HeapDataType* b)
{
	assert(a && b);
	HeapDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

这里又封装了一个函数,因为这部分需要用到交换的地方还挺多的。

  • 如果满足条件,由于堆的性质,可直接break结束循环。

一个小bug:需注意的是循环结束条件parent >= 0,当运行时,会发现程序能跑,没出问题。
但可以分析一下:如果child已经为根,即child == 0,算一下parent(0 - 1)/2 = 0
下图的情景出现了!
在这里插入图片描述
当然,这里的bug是能改的,用child作为判断条件即可:

//while (parent >= 0)
while(child>0)

2.3HeapPop

删除堆顶元素。

堆顶元素为堆的最大/小值,因此,删除堆顶元素才具有一定意义。

挪动删除,
是不行的:
在这里插入图片描述
两个原因:

  • 效率低下
  • 父子关系被打乱

正确方法:

void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	Swap(php->a, php->a + php->size - 1);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

这里,先将最前的与最后的交换(Swap),
然后删除最后一个元素(size--),
最后,向下调整(AdjustDown)。
在这里插入图片描述

AdjustDown

void AdjustDown(HeapDataType* a, int n, int parent)
{
	assert(a);
	int child = parent * 2 + 1;
	while (child < n)
	{
		if ((child + 1) < n && a[child] < a[child + 1])
			child++;
		if (a[parent] < a[child])
			Swap(a + parent, a + child);
		else
			break;
		parent = child;
		child = parent * 2 + 1;
	}
}

这里传了三个参数,其中parent为需要调整的父节点的位置,此处传0
while循环,会在child下标小于堆大小n的情况下继续执行,这表示还有子节点存在。
由于是大根堆,向下调整时,先选出子节点中的较大值,再与父节点比较,满足则break出循环,不满足条件则交换,继续循环。

2.4HeapTop、HeapSize、HeapEmpty

取堆顶元素、堆的大小、判空。

HeapDataType HeapTop(Heap* php)
{
	assert(php);
	return php->a[0];
}

int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

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

3.应用

3.1堆排

即使用堆进行排序。
给一个数组,要使用堆排,前提是必须有个堆,因此第一步为建堆。
那么,建大堆还是小堆?怎么建堆?
建什么堆,这里有个规律:

  • 升序建大堆
  • 降序建小堆

如何建堆,有两种方法:

  • 使用向上调整法,插入建堆
  • 使用向下调整法,调整建堆

建堆

向上调整法

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = 0; i < n; i++)
	{
		AdjustUp(a, i);
	}
	//排序
}

向下调整法
使用向下调整法建堆,需满足左、右子树必须是堆
随便给的数组肯定不能满足此条件,因此不能从根节点开始建堆,需要找倒数第一个非叶子节点:
在这里插入图片描述
叶节点是堆,空节点也是堆,因此,从第一个非叶子节点开始使用向下调整法,不断调整直到根节点。

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	//排序
}

在实际使用中,通常使用向下调整法建堆,因为向上调整法的时间复杂度为O(N*logN),而向下调整法的时间复杂度为O(N)

排序

这里使用的是堆删除的思想来排序。
现在假设排升序,并且已经建好了一个大堆:
在这里插入图片描述
在这里插入图片描述
Pop一下,最大的就跑最后去了,然后重复此过程,就能排成升序。
这也验证了:

  • 升序建大堆
  • 降序建小堆

完整代码:

void HeapSort(int* a, int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	//排序
	int size = n;
	while (size--)
	{
		Swap(a, a + size);
		AdjustDown(a, size, 0);
	}
}

3.2TopK问题

即在很多数中找出最…K个 数。
这里的很多一般是真的很多,因此不能在对全部数据进行排序,因为浪费空间。
解决方法是用这些数的前K个建个堆,在将其余满足条件的数插入堆中。

建堆:

  • 求最大,建小堆
  • 求最小,建大堆

插入:

依次将其余的数与堆顶的数进行比较,根据需要进行替换,最后堆中的K个数即为所求。


希望本篇文章对你有所帮助!并激发你进一步探索数据结构的兴趣!

本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

相关文章:
数据结构-二叉树-基础知识

标签:php,parent,int,详解,child,数据结构,节点,size
From: https://blog.csdn.net/2401_86587256/article/details/141835459

相关文章

  • 算法与数据结构——堆
    堆堆(heap)是一种满足特定条件的完全二叉树,主要分为两种类型:小顶堆(minheap):任意节点的值≤其子节点的值大顶堆(maxheap):任意节点的值≥其子节点的值堆作为完全二叉树的一个特例,具有以下特性:最底层节点靠左填充,其他层的节点都被填满我们将二叉树的根节点称为“堆顶”,将......
  • C++ iostream、iomanip 头文件详解
    C++iostream、iomanip头文件详解首先,我们来看看iostream。相信大家都知道iostream,这个库可以说是最常用的几个库之一了。iostream库提供了输入输出流。比如cin、cout,都是在iostream里的。所以,我们经常会用到iostream这个库。iostream这个名字很好理解,InputOutputStream,输......
  • C/C++中extern函数使用详解
    extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。此外extern也可用来进行链接指定目录一、定义和声明的区别二、extern用法2.1extern函数2.2extern变量2.3在C++文件中调用C方式编译的函数三、通俗讲......
  • 14、Flink SQL 的 事件时间详解
    事件时间事件时间允许程序按照数据中包含的时间来处理,这样可以在有乱序或者晚到的数据的情况下产生一致的处理结果,它可以保证从外部存储读取数据后产生可以复现(replayable)的结果。事件时间可以让程序在流式和批式作业中使用同样的语法,在流式程序中的事件时间属性,在批式程......
  • Java基础—运算符篇(从0到1完整详解,附有代码+案例)
    文章目录运算符分类:2.1.算术运算符2.1.1基本算数运算2.1.2复合算数运算2.1.3类型转换2.1.4“+”的三种情况2.2自增自减运算符2.3赋值运算符2.4关系运算符2.5逻辑运算符2.6短路逻辑运算符2.7三元运算符2.8运算的优先级运算符分类:赋值运算符:=算术运算符:+-......
  • Python 错误 AttributeError 解析,实际错误实例详解
    文章目录前言Python错误AttributeError:_csv.readerobjectHasNoAttributeNext修复Python中的AttributeError:'_csv.reader'objecthasnoattribute'next'错误Python错误AttributeError:‘_io.TextIOWrapper‘objectHasNoAttribute‘Sp......
  • exit与_exit详解,并于进程间的关系
    文章目录1.`exit`函数定义语法参数特点示例2.`_exit`函数定义语法参数特点示例3.主要区别4.与进程间的关系进程的创建与终止示例结论1.exit函数定义exit是一个标准库函数,定义在<stdlib.h>头文件中。它用于正常或异常地终止程序,并执行一些清理操作。......
  • ADB安装及使用详解(非常详细)从零基础入门到精通,看完这一篇就够了
    文章目录一、ADB简介1、什么是adb2、为什么要用adb二、准备工具1、下载adb2、配置环境变量3、连接4、电脑打开cmd窗口三、ADB命令详解1、基本命令2、权限命令3、建立连接4、apk操作指令5、文件操作指令6、日志操作指令7、系统操作指令题外话==如何入门学习网络......
  • 数据结构题目 第一章
    题目1、数据结构被形式的定义为(K,R),其中K是( )的有限集合,R是K上关系的有限集合。A.算法  B.数据元素 C.数据操作 D.逻辑结构2、数据元素是数据的最小单位。 ( )3、存储数据时,通常不仅需要存储各数据元素的值,而且还要存储( )。A.数据的处理方法 B.数据元素的类型......
  • 负重10Kg多旋翼行业无人机技术详解
    随着无人机技术的不断成熟与应用领域的持续拓展,负重型多旋翼无人机已成为行业应用中的重要力量。特别是能够承载10Kg以上载荷的无人机,凭借其卓越的挂载能力、灵活的操控性以及广泛的适用性,在物流运输、农业植保、电力巡检、环境监测等多个领域展现出巨大潜力。本文将从机架与结......