详解数据结构中的堆

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


本篇文章主要讲解如何构建一个堆(本文讲的是二叉堆),堆排序以及TOP-K问题。

1.构建堆

首先,储存堆我们用到的是数组,我们把它封装为一个结构体

typedef int HDataType;

typedef struct Heap
{
	HDataType* arr;
	int size;
	int capacity;
}Heap;

size是数组里面有效数据的个数,capacity是数组的容量大小。

虽然堆在物理结构上是一个数组,但在逻辑结构上我们把它想象做一个完全二叉树,如下图
详解数据结构中的堆,数据结构,算法,c语言
这样我们可以通过下标来访问这棵树。
下面是通过父亲访问孩子:
我们定义父节点的下标为parent,那左孩子节点的下标就是parent * 2 + 1,右孩子节点的下标是parent * 2 + 2,比如上图中的26 下标是1,1 * 2 + 1是下标为3的左孩子36。
下面是通过孩子访问父亲:
定义孩子下标为child,如果child是左孩子,那么(child - 1) / 2就可以得到父节点的下标,如果child是右孩子的话,本来我们应该是(child - 2) / 2,因为如果减一除二的话可能会得到一个小数,比如(4 - 1) / 2 = 1.5,但这是我们数学上的算法,在C语言里,整数除法是向下取整的,因此结果还是1,不是1.5,所以无论是左右孩子我们都可以减一除二得到父节点的下标。

另外,堆分为大堆和小堆,大堆满足父节点的数不小于子节点,小堆满足父节点不大于子节点。

1.1 向下调整算法

要想构建一个堆,我们得先学习向下调整算法,假设我们现在有一个堆,左子树和右子树都是小堆,只有堆顶不满足小堆的性质,比如这棵树
详解数据结构中的堆,数据结构,算法,c语言
我们只需要把堆顶的数据调到合适的位置,它就是一个小堆了,我们可以这样做:先找出堆顶的左右孩子中小的那一个,再让它和堆顶数据比较,如果孩子小,就让它和父节点进行交换,比如这里的29是父节点,它的左右孩子是26和16,其中小的是16,然后16和29比,孩子小,就让16和29进行交换
详解数据结构中的堆,数据结构,算法,c语言
此时我们交换的是右孩子和父节点,左边没有动过,因此左边依然是小堆,我们只需要改右边就可以了,现在新的29是我们下一个要进行比较的父节点,找出它的左右孩子中小的那一个,当然这里只有左孩子20,比较得父节点大,再交换左孩子和父节点
详解数据结构中的堆,数据结构,算法,c语言
此时,我们已经成功得到了一个小堆。
下面是代码实现(建小堆,如果要建大堆改掉一些小于号就可以),参数中arr是要调整的数组,n是数组的大小,root是堆顶的下标

void AdjustDown(HDataType* arr, int n, int root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child] < arr[child + 1]) //child + 1 < n 防止越界
		{
			child = child + 1;
		}
		//到这child下标是两个孩子中小的呢个的下标
		if (arr[parent] < arr[child])
		{
			swap(&arr[parent], &arr[child]);
		}
		else 
		{
			break;
		}
		parent = child;
		child = 2 * parent + 1;
	}
}

但这样调整只适合左子树和右子树都已经是堆的情况啊,那一般的情况又怎么办呢?
其实,一棵树的每个叶节点可以看做只有它自己的一个堆,因为只有自己,所以它已经满足堆的条件
详解数据结构中的堆,数据结构,算法,c语言
比如这里的52,11,35,15,73。
如果我们对29进行向下调整算法,算法就会把29调到合适的位置,使得这个堆变成小堆,调完之后我们再调27,又可以把这个堆调成小堆,接下来继续往回走,再调46,直到把所有父节点都调完,整个堆就变成了小堆。
因为我们是用数组维护的堆,所以上面这个遍历父节点的过程我们可以通过下标减1来遍历。那怎么找到第一个父节点29呢,前面我们提到了size是数组里有效数据的个数,所以最后一个数据的下标就是size - 1,(size - 1)/ 2就是最后一个父节点的下标。

下面是利用向下调整建堆的代码

	for (int i = (size -1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, size, i);
	}

我们构建堆一般是放在堆的初始化函数里面的,堆的初始化一般就是给你一个数组,把数组里面的元素弄成堆。
ph是我们堆的结构体指针,使用前记得先创建一个结构体变量,这里是把它的地址传进去,s是我们要修改成堆的数组,n是数组的大小

void HeapInit(Heap* ph, HDataType* s, int n)
{
	ph->arr = (HDataType*)malloc(sizeof(HDataType) * n);
	memcpy(ph->arr, s, sizeof(HDataType) * n);//把s里面的数据复制到我们的数组里面
	ph->capacity = ph->size = n;

	//构建堆
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(ph->arr, n, i);
	}
}

1.2 构建堆的时间复杂度分析

分析建堆的时间复杂度就是看它运行了多少次,向下调整算法每次需要移动树的高度-1次,设高度为h
详解数据结构中的堆,数据结构,算法,c语言建堆的时间复杂度就是每一层的节点个数乘以移动的次数,再把每一层相加,设和为S,即
详解数据结构中的堆,数据结构,算法,c语言

求这个和我们用到错位相减法
详解数据结构中的堆,数据结构,算法,c语言同时,h = logN(默认以2为底),所以时间复杂度为O(N - logN),因为logN很小,所以也就是O(N)

2.堆排序

2.1 堆排序的实现

堆排序就是用堆这个数据结构来对一些数据进行排序,如果我们有一个小堆,那么堆顶的数就是最小的数,我们需要再找一个次小的数,该怎么找呢?
我们可以这样做:把堆顶的数和数组中最后一个数互换,然后把数组长度减1,认为最后一个数不是数组里面的,这样再对新的堆顶进行向下调整构建新的堆,这个时候的堆顶就是次小的数,再重复之前的操作,直到数组里只有一个数据,这个时候数组里面的数据就排好序了。
需要注意,这种排序 建大堆排升序,建小堆排降序。
代码实现:

void HeapSort(HDataType* arr,int n)
{
	//构建堆
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
	//开始排序
	int end = n - 1;   //数组最后一个数的下标
	while (end > 0)
	{
		swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}

2.2 堆排序的时间复杂度

堆排序的执行次数是数据个数乘以向下调整的时间复杂度,即
O(N * logN),就算加上建堆的时间复杂度O(N),N 和 N * logN相比,N也比较小,所以直接取O(N * logN)

3. TOP-K问题

这类问题是给出N个数,求出最小(最大)的前K个数,或者是第K小(大)的数。
这中问题用堆可以很好地解决,我们用N个数中的前K个数构建一个大小为K的堆,如果要最大的前K个数,就建小堆,然后再让堆顶的数和剩下的N-K个数一一比较,如果堆顶的数小,就把堆顶的数换成大的,再继续比较,最终堆里面的K个数就是最大的前K个数。
这种方法优点是可以解决数据太多内存放不下的问题,同时时间复杂度很小,为O(N*logK)。文章来源地址https://www.toymoban.com/news/detail-827888.html

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

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

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

相关文章

  • 初识Go语言25-数据结构与算法【堆、Trie树、用go中的list与map实现LRU算法、用go语言中的map和堆实现超时缓存】

      堆是一棵二叉树。大根堆即任意节点的值都大于等于其子节点。反之为小根堆。   用数组来表示堆,下标为 i 的结点的父结点下标为(i-1)/2,其左右子结点分别为 (2i + 1)、(2i + 2)。 构建堆   每当有元素调整下来时,要对以它为父节点的三角形区域进行调整。 插入元素

    2024年02月12日
    浏览(59)
  • 【数据结构】由完全二叉树引申出的堆的实现

    关于“堆”,百度百科上是这么说的: ——————————引自百度百科 由上面可知,我们可以将堆理解成一个数组,也可以理解成一个完全二叉树。 其实由于完全二叉树的特殊性,其本身就可以使用一个数组来存储。 在之前的二叉树的实现中,我们已经知道完全二叉树

    2024年02月08日
    浏览(43)
  • 【数据结构和算法初阶(C语言)】复杂链表(随机指针,随机链表的复制)题目详解+链表顺序表结尾

    目录  1.随机链表的复制 1.2题目描述  1.3题目分析 1.4解题: 2.顺序表和链表对比 2.1cpu高速缓存利用率 3.结语 一个长度为  n  的链表,每个节点包含一个额外增加的随机指针  random   该指针可以指向链表中的任何节点或空节点。        构造这个链表的  深拷贝 。 深拷贝

    2024年03月10日
    浏览(87)
  • 数据结构--》掌握数据结构中的排序算法

            当我们面对海量数据时,如何高效地将其排序是数据结构领域中一个重要的问题。排序算法作为其中的关键部分,扮演着至关重要的角色。         无论你是初学者还是进阶者,本文将为你提供简单易懂、实用可行的知识点,帮助你更好地掌握排序算法在数据

    2024年02月08日
    浏览(43)
  • 数据结构--》掌握数据结构中的查找算法

            当你需要从大量数据中查找某个元素时,查找算法就变得非常重要。         无论你是初学者还是进阶者,本文将为你提供简单易懂、实用可行的知识点,帮助你更好地掌握查找在数据结构和算法中的重要性,进而提升算法解题的能力。接下来让我们开启数据

    2024年02月08日
    浏览(57)
  • 【数据结构】C语言结构体详解

    目录 前言 一、结构体的定义 二、定义结构体变量 三、结构体变量的初始化 四、使用typedef声明新数据类型名 五、指向结构体变量的指针 总结 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助。 💡本文由Filotimo__✍️原创,首发于CSDN📚。 📣如需转

    2024年02月04日
    浏览(49)
  • 数据结构(五)数据结构与算法中的经典题

    本文是在原本数据结构与算法闯关的基础上总结得来,加入了自己的理解和部分习题讲解。至此数据结构介绍已完结,后续会把数据结构算法题系列更完。 原活动链接 邀请码: JL57F5 根据要求完成题目 Q1. (单选)以下哪些数据结构支持随机访问? A. 数组 B. 单链表 C. 双向链表

    2024年01月20日
    浏览(44)
  • 数据结构与算法——数据结构有哪些,常用数据结构详解

    数据结构是学习数据存储方式的一门学科,那么,数据存储方式有哪几种呢?下面将对数据结构的学习内容做一个简要的总结。 数据结构大致包含以下几种存储结构: 线性表,还可细分为顺序表、链表、栈和队列; 树结构,包括普通树,二叉树,线索二叉树等; 图存储结构

    2024年02月15日
    浏览(63)
  • R语言中的常用数据结构

    目录 R对象的基本类型 R对象的属性 R的数据结构 向量 矩阵 数组 列表 因子 缺失值NA 数据框 R的数据结构总结 R语言可以进行探索性数据分析,统计推断,回归分析,机器学习,数据产品开发 R语言对象有五种基本类型,分别是字符,数值,整数,复数,逻辑 你可能有疑问,整

    2024年04月12日
    浏览(30)
  • 数据结构——排序算法(C语言)

    本篇将详细讲一下以下排序算法: 直接插入排序 希尔排序 选择排序 快速排序 归并排序 计数排序 排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某写的大小,按照递增或递减0排列起来的操作。 稳定性的概念 假定在待排序的记录序列中,存在多个

    2024年02月08日
    浏览(66)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包