C语言数据结构易错知识点(3)(堆、堆排序)

1.堆的本质:完全二叉树

堆在物理结构上是顺序结构,实现方式类似于顺序表(数组);但在逻辑结构上是树形结构,准确来说堆是一棵完全二叉树。因为堆的实现在代码上和顺序表有很高的相似度,所以在写代码时,我们要仔细理解堆的逻辑结构,理解每步操作对该二叉树的影响,我们也可以画画简图,更加直观地感受堆的相关操作的含义。

9c533143795f41fdb8fe967f067d1630.png

2.向上调整与向下调整使用场景

当数据插入堆或从堆顶移除后,都要对堆进行相关的调整,使其成为一个新堆。调整方式分为向上调整和向下调整,它们有不同应用场景

①向上调整:尾部插入数据时使用。当在尾部加入一个新的数据时,这个数据可能要和父节点交换后才能形成大(小)堆,交换过程中除了新插入的节点可能不满足堆的规则以外,其余节点都不需要调整。

函数参数参考:void AdjustUp(HPDataType* arr, int child);

下面举个例子:

c7e6b00301204162b1474f63f8bc04c7.png

 

对于这个小堆,这里新插入了一个0,需要进行调整,我们这时选择向上调整,即沿着祖先向上调整,其余部分不调整

8be48f0eb9a94de8994a40060dbc6d7d.png

 

②向下调整:当数据从堆顶移除建堆时使用

函数参数参考:void AdjustDown(HPDataType* arr, int size, int parent);

a.从堆顶移除数据时向下调整:先交换首元素和尾元素,并用顺序表的方式让size--,使得最后一个元素被删除,再向下调整直到成堆

96fb274916ae40d3a68920b7684c0e6a.png

b.建堆:建堆一般来说有两种方式,一种是向上调整,每次新插入一个元素后向上调整直到成堆;另一种是向下调整,在加入所有数据后从倒数第二行开始向下调整直到成堆。一般来说,我们采用向下调整,因为对于建堆而言,向下调整的时间复杂度近似为O(N-logN)或O(N),而向上调整的时间复杂度为O(NlogN)下面简单讲解一下

向上调整建堆:共有N个元素,每次向上调整logN次,时间复杂度O(logN)

ad616cf9e09f4ca49f7f64f0c1111d11.png

向下调整建堆:第一眼看上去虽然也是O(NlogN),但多观察就发现调整次数远远小于向上调整

521084a4937d4022b90eb1242dfd975b.png

经过详细计算可知比较准确的时间复杂度是O(N-logN),可以再近似于O(N),但我们不用特别纠结于如何精确计算时间复杂度,而是要像上面两张图那样学会粗略地分析和计算

3.堆排序:如果一来就学习堆排序的话,其实会很困难,但如果学会了堆的相关操作,掌握向上和向下调整之后,堆排序就非常简单了。

我们需要用到的知识点就只有:(子节点-1) / 2 == 父节点,父节点 * 2 + 1 == 左孩子结点,父节点 * 2 + 2 == 右孩子结点,堆删除的思路,向下调整。

堆删除的思路对堆排序来说很重要,关键在于首元素和尾元素的交换。 

堆排序关键思路:当我们想让数组从小到大排序,则建立大堆;从大到小排序,则建立小堆。当我们通过大堆找到最大值后,将其与尾元素交换,然后size--达到伪删除的目的(顺序表的删除也可视为一种伪删除,即数据没有被销毁,只是访问不到),重复建堆并交换,次大的数据在最大的数据之前,以此类推......

堆排序的实现注意事项:我们实现堆排序的时候,不要像实现堆那样开辟一整块空间,再push数据、建堆等操作。我们直接在数组上建堆,因为堆排序的物理结构归根到底还是顺序表,只要熟悉堆的操作,我们直接通过下标进行访问修改即可。

Topk问题:这类问题的关键就是要建立k个元素的堆,从小到大排序,则建立大堆;从大到小排序,则建立小堆,从第k + 1个数开始每次删去堆顶元素,再push一个数至堆顶,再向下调整。满足条件的数沉入堆底,不会被删除。理解了这里,那么Topk问题就迎刃而解了。

4.堆和堆排序实现代码:

Heap.h

#pragma once

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap
{
	HPDataType* arr;

	int size;

	int capacity;

}Heap;


void HeapInit(Heap* p);

void HeapDesTroy(Heap* p);

void HPDataTypeSwap(HPDataType* x1, HPDataType* x2);

void AdjustUp(HPDataType* arr, int child);

void AdjustDown(HPDataType* arr, int size, int parent);

void HeapPush(Heap* p, HPDataType x);

void HeapPop(Heap* p);

bool HeapEmpty(Heap* p);

int HeapSize(Heap* p);

HPDataType HeapTop(Heap* p);

void HeapSort(HPDataType* arr, int size);

Heap.c

#pragma once

#include "Heap.h"


void HeapInit(Heap* p)
{
	assert(p);

	p->capacity = p->size = 0;

	p->arr = NULL;
}


void HeapDesTroy(Heap* p)
{
	assert(p);

	free(p->arr);

	p->arr = NULL;

	p->capacity = p->size = 0;
}

void HPDataTypeSwap(HPDataType* x1, HPDataType* x2)
{
	assert(x1 && x2);

	HPDataType tmp = *x1;

	*x1 = *x2;

	*x2 = tmp;
}


void AdjustUp(HPDataType* arr, int child)
{
	assert(arr);

	int parent = (child - 1) / 2;

	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			HPDataTypeSwap(&arr[child], &arr[parent]);

			child = parent;

			parent = (child - 1) / 2;
		}

		else
		{
			break;
		}
	}
}

void AdjustDown(HPDataType* arr, int size, int parent)
{
	assert(arr);

	int child = parent * 2 + 1;

	if (parent * 2 + 2 < size)
	{
		child = arr[child] < arr[child + 1] ? child : child + 1;
	}

	while (child < size)
	{
		if (arr[child] < arr[parent])
		{
			HPDataTypeSwap(&arr[child], &arr[parent]);

			parent = child;

			child = parent * 2 + 1;

			if (parent * 2 + 2 < size)
			{
				child = arr[child] < arr[child + 1] ? child : child + 1;
			}
		}

		else
		{
			break;
		}
	}
		
}

void HeapPush(Heap* p, HPDataType x)
{
	assert(p);

	if (p->capacity == p->size)
	{
		p->capacity = p->capacity == 0 ? 4 : 2 * p->capacity;

		HPDataType* tmp = (HPDataType*)realloc(p->arr, sizeof(HPDataType) * p->capacity);

		assert(tmp);

		p->arr = tmp;
	}

	p->arr[p->size++] = x;

	AdjustUp(p->arr, p->size - 1);
}

void HeapPop(Heap* p)
{
	assert(p);

	HPDataTypeSwap(&p->arr[0], &p->arr[p->size - 1]);

	p->size--;

	AdjustDown(p->arr, p->size, 0);
}

bool HeapEmpty(Heap* p)
{
	assert(p);

	return p->size == 0;
}

int HeapSize(Heap* p)
{
	assert(p);

	return p->size;
}

HPDataType HeapTop(Heap* p)
{
	assert(p);

	if (p->size == 0)
	{
		printf("空堆,无数据\n");

		assert(p->size != 0);
	}

	return p->arr[0];
}



void HeapSort(HPDataType* arr, int size)
{
	assert(arr);

	int parent = (size - 2) / 2;

	while (parent >= 0)
	{
		AdjustDown(arr, size, parent);

		parent--;
	}

	int sz = size;

	while (--size)
	{
		HPDataTypeSwap(&arr[0], &arr[size]);

		AdjustDown(arr, size, 0);
	}

	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

}

堆测试代码:


#include "Heap.h"

//小堆的相关功能实现

int main()
{
	Heap p;

	HeapInit(&p);

	HPDataType arr[] = { 10, 9, 8, 7, 4, 5, 6, 2, 1, 3, 12, 11, 13, 15, 14, 16, 19, 17, 18, 20 };

	for (int i = 0; i < sizeof(arr) / sizeof(HPDataType); i++)
	{
		HeapPush(&p, arr[i]);
	}

	while (HeapEmpty(&p) != true)
	{
		printf("%d ", HeapTop(&p));

		HeapPop(&p);
	}

	HeapDesTroy(&p);

	return 0;
}

5395f9d0f4f2494d8fd52a7566d6cc9f.png

堆排序测试代码:


#include "Heap.h"

//从大到小

int main()
{
	HPDataType arr[] = { 10, 9, 8, 7, 4, 5, 6, 2, 1, 3, 12, 11, 13, 15, 14, 16, 19, 17, 18, 20 };

	HeapSort(arr, sizeof(arr) / sizeof(HPDataType));

	return 0;
}

d2a8814235ea4f2f91bd6948fb23e5a7.png

 

 

 

 

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值