广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C语言数据结构二叉树之堆的实现和堆排序详解
  • 888
分享到

C语言数据结构二叉树之堆的实现和堆排序详解

2024-04-02 19:04:59 888人浏览 独家记忆
摘要

目录一、本章重点二、堆2.1堆的介绍2.2堆的接口实现三、堆排序一、本章重点 堆的介绍堆的接口实现堆排序 二、堆 2.1堆的介绍 一般来说,堆在物理结构上是连续的数组结构,在逻辑结构

一、本章重点

  • 堆的介绍
  • 堆的接口实现
  • 堆排序

二、堆

2.1堆的介绍

一般来说,堆在物理结构上是连续的数组结构,在逻辑结构上是一颗完全二叉树

但要满足

  • 每个父亲节点的值都得大于孩子节点的值,这样的堆称为大堆。
  • 每个父亲节点的值都得小于孩子节点的值,这样的堆称为小堆。

那么以下就是一个小堆。

百度百科:

堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。

若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。 

下面序列是堆的是( )。 

A.97,56,38,66,23,42,12 //不是大堆也不是小堆,即不是堆。

B.23,86,48,3,35,39,42 //不是大堆也不是小堆,即不是堆。

C.05,56,20,23,40,38,29  //不是大堆也不是小堆,即不是堆。

D.05,23,16,68,94,72,71,73  //是小堆

只有D是堆而且是小堆,因此答案选D。

D的逻辑结构:

 父亲节点和孩子节点的数组下标有以下关系:

  • left_child=(parent+1)*2
  • right_child=(parent+2)*2
  • parent=(child-1)/2(这里的child左孩子和右孩子都适用)

以上就不做证明了,不过我们可以验证一下,以上图D的逻辑结构为例,16的parent下标是2,72的下标是5,71的下标是6,满足left_child=(parent+1)*2、right_child=(parent+2)*2、parent=(child-1)/2。

有序一定是堆,堆不一定有序。

同时堆顶的数组是整个数组最大的数或者整个数组最小的数。

2.2堆的接口实现

第一件事我们就是要创建堆,实际就是创建一个数组,这里用动态数组。

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	size_t size;
	size_t capacity;
}HP;

堆创建好之后,我们需要对它进行初始化。

第一个接口:

void Heapinit(HP* PHP);

轻车熟路,将堆中的a置为NULL,size和capacity置为0。

或者这里可以设置capacity不为0的初始值也是可以的。

参考代码:

void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

我们对堆进行初始化之后,也要在最后销毁堆。

第二个接口:

void HeapDestroy(HP* php)

销毁堆,即销毁一个动态数组

参考代码:

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

现在我们可以考虑往堆中插入数据了,要求插入新元素之后还是堆。

第三个接口:

void HeapPush(HP* php, HPDataType x)

堆没有要求在哪个位置插入新元素,可以在任意的位置插入新元素,但要保证插入新元素之后还是堆。

由于数组在头部还是在中间位置的插入复杂度是O(N),并且插入后不一定是堆了。

因此我们考虑的是直接在数组尾部插入新元素,然后用一个函数去调整数组的顺序使得它还是一个堆。

那么核心代码就是这个调整算法

先来看这一个堆,插入新元素后该如何进行调整。

 我们在数组的最后插入22,原堆是一个小堆,此时我们需要从下往上去调整各个父亲节点,使得该堆还是一个小堆。

换句话说:我们只需要调整下面有彩色的节点顺序。

交换过程:如果孩子节点小于父亲节点,那么将它们交换,然后迭代。

如果孩子节点大于父亲节点就跳出循环。

迭代过程:将父亲节点的下标赋值给孩子节点的下标,然后重新计算父亲节点的下标,计算方法:parent=(child-1)/2。

参考代码:

void AdjustUp(HPDataType* a, size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
        //如果孩子小于父亲,则交换    
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
        //孩子大于父亲,则结束调整
		else
		{
			break;
		}
	}
}
void HeapPush(HP* php, HPDataType x)
{
	assert(php);
    //动态数组,空间不够要扩容
 
	if (php->size == php->capacity)
	{
		size_t newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		HPDataType* tmp = realloc(php->a, sizeof(HPDataType)* newCapacity);
		if (tmp == NULL)
		{
			printf("realloc failed\n");
			exit(-1);
		}
 
		php->a = tmp;
		php->capacity = newCapacity;
	}
    //尾插数据
	php->a[php->size] = x;
	++php->size;
 
	// 向上调整,控制保持是一个小堆
	AdjustUp(php->a, php->size - 1);
}

上面是多个数据的插入,那么如果插入第一个数据,这个函数还能帮助我们把数据插入堆中吗?

答案是肯定的。

既然有Push数据到堆,自然有从堆中删除元素了。

这里的删除不同于栈和队列的删除,这里指的是将堆顶的数据删除,删除之后堆还是一个堆。为什么只实现删堆顶的数据,因为简单实用,这个接口是为后面的堆排序做准备的。

第四个接口:

void HeapPop(HP* php)

思路比较简单:将数组第一个元素删除,然后保持它还是一个小堆。

怎么删除第一个数据呢?

这里的考虑是将数组第一个元素和数组最后一个交换,交换之后尾删掉最后一个元素,达成删除第一个元素的效果,复杂度是O(N),这里可以提一下,这种头删的方式是改变了数组元素的相对顺序的。

删除之后我们要做调整,使得堆还是小堆。

那么怎么调整呢?

以下是一个小堆

 头删之后

 如何调整它,使得它还是一个小堆?

这里的思路是:向下调整算法,首先parent=73,然后选出它子节点最小的值,然后它们之间交换,交换之后,将子节点看作新的父亲节点,继续向下调整,直到父亲节点的左孩子不存在。

参考代码:

void AdjustDown(HPDataType* a, size_t size, size_t root)
{
	size_t parent = root;
	size_t child = parent * 2 + 1;
	while (child < size)
	{
		// 1、选出左右孩子中小的那个
		if (child + 1 < size && a[child+1] < a[child])
		{
			++child;
		}
 
		// 2、如果孩子小于父亲,则交换,并继续往下调整
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

这里需要注意的是,为什么循环的结束条件不是右孩子不存在呢?

因为右孩子不存在时,也可能要进行交换。

比如:

 还需要注意的是左孩子存在右孩子不一定存在

if (a[child+1] > a[child])
{
	++child;
}

直接这样写a[child+1]可能会越界,因此要加上child + 1 < size,保证child + 1 <= size-1。

参考代码:

void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
    //将数组第一个元素和最后一个元素交换然后删除最后一个元素,达到头删的目的。
	Swap(&php->a[0], &php->a[php->size - 1]);
	--php->size;
    //向下调整算法
	AdjustDown(php->a, php->size, 0);
}

其他接口补充:

由于比较简单,理解起来不费劲,因此这里直接给出。

参考代码:

bool HeapEmpty(HP* php)//判断堆是否为空
{
	assert(php);
 
	return php->size == 0;
}
 
size_t HeapSize(HP* php)//堆的元素个数
{
	assert(php);
 
	return php->size;
}
 
HPDataType HeapTop(HP* php)//取堆顶数据
{
	assert(php);
	assert(php->size > 0);
 
	return php->a[0];
}

三、堆排序

 堆排序:利用堆顶节点是整个数组的最大值或者最小值的特点,可以达到排序的目的。

比如我们要将1、5、2、4、8、6、10排成升序

可以将这几个元素依次入堆,使得这些数据变成小堆。

然后我们可以取堆的第一个元素,它是整个数组最小的元素,要排升序,那么我们就需要将它排在第一个位置,然后删除堆顶元素,由于我们的删除接口的作用是:删除堆顶元素,并保持堆还是小堆,那么我们调用删除接口之后,再取堆顶元素,将它排在第二个位置,依次继续下去,我们就能将这些数据排成升序了。

参考代码:

void HeapSort(int* a, int size)
{
	HP hp;
	HeapInit(&hp);
    //建小堆
	for (int i = 0; i < size; ++i)
	{
		HeapPush(&hp, a[i]);
	}
    
    //不断取堆顶元素进行排序
	size_t j = 0;
	while (!HeapEmpty(&hp))
	{
		a[j] = HeapTop(&hp);
		j++;
		HeapPop(&hp);
	}
    //销毁堆,防止内存泄露
	HeapDestroy(&hp);
}

这里的堆排序的空间复杂度是O(N),因为在堆区开辟了一个N个元素大小的堆空间。

堆排序看起来挺复杂的,那么它的时间复杂度是什么呢?

建小堆:0(N)

HeapPop()一次执行的是:头删堆顶元素(O(1)),然后依次向下比较,比较的次数是高度次,因为是完全二叉树,比较的时间复杂度是O(logN)。

因此执行一次HeapPop的时间复杂度是O(logN)。

那么不断取堆顶元素进行排序,取了N个元素,调用了N次HeapPop(),时间复杂度是O(N*logN)。

总的时间复杂度是O(N)+O(N*logN),当N很大时,加的O(N)可以忽略。

实际时间复杂就是:O(N*logN)

空间复杂度:O(N)

那么堆排序的时间复杂度是O(N*logN)。

相比于冒泡排序的O(N*N)。

堆排序显然效率更高。

如果N等于100万,冒泡要执行1万亿次,而堆排序执行2千万次,效率可想而知!

到此这篇关于C语言数据结构二叉树之堆的实现和堆排序详解的文章就介绍到这了,更多相关C语言 堆排序内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C语言数据结构二叉树之堆的实现和堆排序详解

本文链接: https://www.lsjlt.com/news/145634.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C语言数据结构二叉树之堆的实现和堆排序详解
    目录一、本章重点二、堆2.1堆的介绍2.2堆的接口实现三、堆排序一、本章重点 堆的介绍堆的接口实现堆排序 二、堆 2.1堆的介绍 一般来说,堆在物理结构上是连续的数组结构,在逻辑结构...
    99+
    2022-11-13
  • C语言数据结构之堆排序详解
    目录1.堆的概念及结构2.堆的实现2.1 堆的向下调整算法2.2 堆的向上调整算法2.3 建堆(数组)2.4 堆排序2.5 堆排序的时间复杂度1.堆的概念及结构 如果有一个关键码的集...
    99+
    2022-11-13
  • C语言堆与二叉树的顺序结构与实现
    目录一. 二叉树的顺序结构二. 堆的概念及结构三. 堆的实现四. 堆排序(具有缺陷型)一. 二叉树的顺序结构 普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全...
    99+
    2022-11-13
  • 【数据结构】二叉树-堆实现及其堆的应用(堆排序&topK问题)
    文章目录 一、堆的概念及结构二、堆的实现1.结构的定义2.堆的初始化3.堆的插入4.堆的向上调整5.堆的删除6.堆的向下调整7.取出堆顶元素8.返回堆的元素个数9.判断堆是否为空10.打印堆中...
    99+
    2023-09-05
    数据结构 php 开发语言
  • C语言植物大战数据结构二叉树堆
    目录前言堆的概念创建结构体初始化结构体销毁结构体向堆中插入数据1.堆的物理结构和逻辑结构2.完全二叉树下标规律3.插入数据思路依次打印堆的值删除堆顶的值判断堆是否为空求堆中有几个元素...
    99+
    2022-11-13
  • C语言数据结构之堆、堆排序的分析及实现
    目录 1.堆的概念结构及分类1.2堆的分类1.2.1 大堆1.2.2 小堆2. 堆的主要接口3.堆的实现3.1 堆的初始化 HeapInit3.2 堆的销毁 HeapDes...
    99+
    2022-11-13
  • C语言数据结构之二叉树详解
    目录1. 树概念及结构1.1树概念1.2树的表示2. 二叉树概念及结构2.1概念2.2数据结构中的二叉树2.3特殊的二叉树2.4二叉树的存储结构2.5二叉树的性质3. 二叉树顺序结构...
    99+
    2022-11-13
  • C语言数据结构之堆排序的优化算法
    目录1.堆排序优化算法1.1建堆的时间复杂度1.1.1 向下调整建堆:O(N)1.1.2 向上调整建堆:O(N*logN)1.2堆排序的复杂度1.2.1原堆排序的时间复杂度...
    99+
    2022-11-13
  • Java数据结构之二叉排序树的实现
    目录1 二叉排序树的概述2 二叉排序树的构建2.1 类架构2.2 查找的方法2.3 插入的方法2.4 查找最大值和最小值2.5 删除的方法3 二叉排序树的总结1 二叉排序树的概述 本...
    99+
    2022-11-13
  • C语言数据结构中堆排序的分析总结
    目录一、本章重点 二、堆2.1堆的介绍(三点)2.2向上调整2.3向下调整2.4建堆(两种方式)三、堆排序一、本章重点  堆向上调整向下调整堆排序 二、堆 2.1...
    99+
    2022-11-13
  • C语言 深入解读数据结构之堆的实现
    堆的概念与结构 概念:如果有一个关键码的集合K={ k0,k1 ,k2 ,…,kn-1 },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足K i<=K ...
    99+
    2022-11-12
  • C++数据结构之二叉搜索树的实现详解
    目录前言介绍实现节点的实现二叉搜索树的查找二叉搜索树的插入二叉搜索树的删除总结前言 今天我们来学一个新的数据结构:二叉搜索树。 介绍 二叉搜索树也称作二叉排序树,它具有以下性质: 非...
    99+
    2022-11-13
  • Go语言数据结构之二叉树可视化详解
    目录题目源代码做题思路扩展左右并列展示上下并列展示总结回顾题目 以图形展示任意二叉树,如下图,一个中缀表达式表示的二叉树:3.14*r²*h/3 源代码 package ...
    99+
    2022-11-11
  • C语言详解如何实现堆及堆的结构与接口
    目录一、堆的结构及实现(重要)1.1 二叉树的顺序结构1.2 堆的概念及结构1.3 堆的实现1.3.1 堆的向下调整算法1.3.2 向下调整算法的时间复杂度1.3.3 堆的创建(向下...
    99+
    2022-11-13
  • C语言数据结构详细解析二叉树的操作
    目录二叉树分类二叉树性质性质的使用二叉树的遍历前序遍历中序遍历后序遍历层序遍历求二叉树的节点数求二叉树叶子结点个数求二叉树的最大深度二叉树的销毁二叉树分类 满二叉树 除最后一层无任何...
    99+
    2022-11-13
  • C++数据结构之搜索二叉树的实现
    目录零.前言1.概念2.作用3.迭代实现(1)查找(2)插入(3)删除4.递归实现(1)查找(2)插入(3)删除5.key/value模型的应用(1)对应查找(2)判断出现次数6.总...
    99+
    2022-11-13
  • 数据结构TypeScript之二叉查找树实现详解
    目录树的结构特点面向对象方法封装二叉查找树(迭代版)二叉查找树的定义构造函数基本单元:二叉查找树节点主体:二叉查找树增加节点查找节点删除节点二叉树的遍历树的结构特点 树是一种有层次...
    99+
    2023-01-30
    TypeScript数据结构二叉查找树 TypeScript数据结构
  • Java数据结构之平衡二叉树的实现详解
    目录定义结点结构查找算法插入算法LL 型RR 型LR 型RL 型插入方法删除算法概述实例分析代码完整代码定义 动机:二叉查找树的操作实践复杂度由树高度决定,所以希望控制树高,左右子...
    99+
    2022-11-13
  • C语言数据结构堆的基本操作实现
    目录1.基本函数实现a.代码1(向下调整)b.代码2(向上调整)c.代码3(交换)2.建堆 3.插入数据4. 删除数据5.获取堆顶的数据6.堆的数据个数7.判空8.打印9.销毁10....
    99+
    2022-11-12
  • Java数据结构之最小堆和最大堆的原理及实现详解
    目录一、前言二、堆的数据结构三、堆的代码实现1. 实现介绍2. 入堆实现3. 出堆实现4. 小堆实现5. 大堆实现一、前言 堆的历史 堆的数据结构有很多种体现形式,包括;2-3堆、B...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作