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

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/461486.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

机试:偶数分解

题目描述: 代码示例: #include <bits/stdc.h> using namespace std; int main(){ // 算法思想1:遍历小于该偶数的所有素数,存入数组中,遍历数组找出两个数之和等于偶数的数int n;cout << "输入样例" << endl;cin >> n;int nums[n];int k …

Android 地图 判断一点是否在某区域内

问题 Android 地图 判断一点是否在某区域内 详细问题 笔者进行Android项目开发&#xff0c;接入高德地图绘制区域后&#xff0c;需要判断一点是否在某区域内&#xff0c;如何实现 代码 mMapView.getMap().addPolygon(polygonOptions).contains(latLng)代码含义解释 这段代…

【C#】【SAP2000】读取SAP2000中frame单元列表到Grasshopper中

private void RunScript(bool build, ref object p1, ref object p2, ref object Profile, ref object stressRatio, ref object temperatureLoad, ref object displacement, ref object frameList){if (build true){// 声明变量int ret;int Numit 0;int[] ObjType new int[…

在Vue中使用wangeditor创建富文本编辑器的完整指南

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Jmeter+Ant 接口自动化环境配置指南

一 、Jmeter安装与配置 https://blog.csdn.net/tester_sc/article/details/80746405 注&#xff1a;Jmeter5.0的环境变量配置与4.0或历往老版本有部分小差异&#xff0c;笔者用的Jmeter 5.0 二 、Ant的安装与配置 # Ant下载地址(下载到指定目录后&#xff0c;进行解压到当前…

链表详解-leetcode203.移除链表元素

链表 移除链表元素 题目&#xff1a; 题意&#xff1a;删除链表中等于给定值 val 的所有节点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 示例 2&#xff1a; 输入&#xff1a;head [], val 1 输出&#xff1a;[…

C++中的多值返回:解锁函数返回值的神奇力量

C中的多值返回&#xff1a;解锁函数返回值的神奇力量 在C编程中&#xff0c;有时候我们需要从函数中返回多个值。虽然C中的函数通常只能返回一个值&#xff0c;但有几种技术和惯用法可以实现返回多个值的效果。本文将介绍C中实现多值返回的几种常用方法&#xff0c;包括引用、指…

F5怎么样?保障AI服务的安全性和交付

伴随着人工智能时代的快速发展&#xff0c;AI已成为企业数字化转型的得力工具&#xff0c;比如为用户提供更好的服务&#xff0c;降低企业成本等。与此同时&#xff0c;AI技术的应用也会带来应用安全等方面的新风险&#xff0c;可见其有着双刃剑效应。作为一家提供多云应用安全…

react-面试题

一、组件基础 1. React 事件机制 <div onClick{this.handleClick.bind(this)}>点我</div> React并不是将click事件绑定到了div的真实DOM上&#xff0c;而是在document处监听了所有的事件&#xff0c;当事件发生并且冒泡到document处的时候&#xff0c;React将事…

差分逻辑电平 — LVDS、CML、LVPECL、HCSL互连

前言 首先了解差分逻辑电平、单端逻辑电平的基础知识 地址&#xff1a;常见的逻辑电平_常用的逻辑电平-CSDN博客 注&#xff1a; ECL >> PECL >> LVPECL演变&#xff1b; ECL速度快&#xff0c;驱动能力强&#xff0c;噪声小&#xff0c;但是功耗大&#xff0c;使…

STM32-位带操作及位带别名区

这里写自定义目录标题 一、位带操作的基本含义及作用二、以STM32为例三、位带别名区和位带区(寄存器地址位地址)的转换关系四、使用例程 一、位带操作的基本含义及作用 位带别名区的设计主要是为了**方便对位带区单个比特位进行读写操作**。在某些应用场景下&#xff0c;需要频…

手把手学会pycharm中使用git

学习教程&#xff1a;http://【git pycharm的使用 连接github】 https://www.bilibili.com/video/BV1sv4y1D7SW/?share_sourcecopy_web&vd_source1a32dd27a726236a74603cf06b7302aa 1. 上传到本地仓库 &#xff08;1&#xff09;直接上传本地仓库 &#xff08;2&#xf…

OpenOFDM接收端信号处理流程

Overview — OpenOFDM 1.0 documentation 本篇文章为学习OpenOFDM之后的产出PPT&#xff0c;仅供学习参考。 ​​​​​​​

easyExcel 导入、导出Excel 封装公共的方法

文档包含三部分功能 1、easyExcel 公共导出list<对象>方法&#xff0c;可以自定义excel中第一行和样式 2、easyExcel 导入逻辑&#xff0c;结合spring Validator 验证导入数据是否符合规范 3、easyExcel 自定义导出 list<map> 、 list<对象> &#xff08;可…

Redis中的缓存设计

缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c;缓存层和存储层都不会命中&#xff0c;通常处于容错的考虑&#xff0c;如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c;失去了缓存保护后端存储的意义。…

Java两周半速成之路(第十六天)

一、网络编程 1.概述&#xff1a; 就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换 2.网络模型 3.网络参考模型图 4.网络通信三要素 4.1IP地址 InetAddress类的使用&#xff1a; 注意&#xff1a;通过API查看&#xff0c;此类没有构造方法&#xff0c;如…

精读《精通 console.log》

1 引言 本周精读的文章是 Mastering JS console.log like a Pro&#xff0c;一起来更全面的认识 console 吧&#xff01; 2 概述 & 精读 console 的功能主要在于控制台打印&#xff0c;它可以打印任何字符、对象、甚至 DOM 元素和系统信息&#xff0c;下面一一介绍。 c…

力扣-20. 有效的括号(回顾知识哈希表,栈)

给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有…

QT中dumpcpp以及dumpdoc使用

qt中调用COM的方式方法有四种&#xff0c;参考解释在 Qt 中使用 ActiveX 控件和 COM (runebook.dev) 介绍dumpcpp的使用方法Qt - dumpcpp 工具 (ActiveQt) (runebook.dev)&#xff1a; 在安装好了的qt电脑上&#xff0c;通过powershell窗口来实现&#xff0c;powershell比cmd要…

C语言从入门到熟悉------第五阶段

结构体 结构体很重要&#xff0c;一定要掌握。但是在很多C语言书籍中结构体的内容讲得非常少&#xff0c;因为从结构体开始&#xff0c;后面介绍的内容已经超出C语言基础的范畴&#xff0c;属于C高级编程部分了。仅仅具备前面的知识是远远不够的&#xff0c;因为在实际编程中&…