0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

堆的实现思路

科技绿洲 来源:指尖动听知识库 作者:指尖动听知识库 2023-11-24 16:02 次阅读

什么是堆?

  • 堆是一种 基于树结构的数据结构,它是一棵二叉树 ,具有以下两个特点:
  • 堆是一个完全二叉树,即除了最后一层,其他层都是满的,最后一层从左到右填满。
  • 堆中每个节点都满足堆的特性,即父节点的值要么等于或者大于(小于)子节点的值。

1.1 堆的分类

堆一般分为两类: 大堆和小堆

  • 大堆中,父节点的值大于或等于子节点的值,
  • 小堆中,父节点的值小于或等于子节点的值。

堆的主要应用是在排序和优先队列中。

以下分别为两个堆(左大堆,右小堆):

图片


Part2 堆的实现思路

2.1 使用什么实现?

图片

逻辑结构如上, 然而这仅仅是我们想像出来的而已,而实际上的堆的物理结构是一个完全二叉树 通常是 用数组实现的 。如下:

图片

对此,这就要引申出一个问题?我们该如何分辨父节点以及子节点呢?如下:

2.2 怎么分辨父节点以及子节点?

通常我们的数组下标为“0”处即为根节点,也就是说我们一定知道一个父节点!并且我们也有“计算公式”来计算父节点以及子节点。(先记住,后面实现有用!!!)也就是说我们也可以通过公式从每一个位置计算父节点以及子节点!如下:

图片

图片

图片

2.3 总体实现思路

先建立一个结构体,由于堆的结构实际上是一颗完全二叉树,因此我们的 结构跟二叉树一样即可! 接着,想想我们的堆需要实现的功能? 构建、销毁、插入、删除、取堆顶的数据、取数据个数、判空 。(⊙o⊙)…基本的就这些吧哈~

接着按照 定义函数接口->实现各个函数功能->测试测试->收工(-_^) o(  ̄▽ ̄ )ブ

Part3** 堆的实现**

3.1 结构体以及接口的实现

typedefint HPDataType;

typedefstruct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

3.2 堆的两种建堆方式

在实现以上的接口之前,我们必须要知道堆的两种建堆方式!!!

并且仅仅通过调整建堆方式的<和>符号,我们就可以轻易控制大小堆,具体看代码注释!

建堆有两种方式,分别是 自底向上建堆以及自顶向下建堆。 具体简介如下:

  1. 自底向上建堆:自底向上建堆是指按照原始数组顺序依次插入元素,然后对每个插入的元素执行向上调整的操作,使得堆的性质保持不变。这种方法需要对每个元素都进行调整操作,时间复杂度为 O(nlogn)。
  2. 自顶向下建堆:自顶向下建堆是指从堆顶开始,对每个节点执行向下调整操作,使得堆的性质保持不变。这种方法需要从根节点开始递归向下调整,时间复杂度为 O(n)。因此,自顶向下建堆的效率比自底向上建堆要高。

以上两种建堆方式,实际上是基于两种调整方法,接下来将详细介绍:

向上调整方法

堆的向上调整方法将新插入的节点从下往上逐层比较,如果当前节点比其父节点大(或小,根据是大根堆还是小根堆),则交换这两个节点。一直向上比较,直到不需要交换为止。这样可以保证堆的性质不变。

具体步骤如下:

  • 将新插入的节点插入到堆的最后一位。
  • 获取该节点的父节点的位置,比较该节点与其父节点的大小关系。
  • 如果该节点比其父节点大(或小,根据是大根堆还是小根堆),则交换这两个节点。
  • 重复步骤2-3,直到不需要交换为止,堆的向上调整完成。
  • 堆的向上调整的时间复杂度为O(logn),其中n为堆的大小。

一图让你了解~:

图片

实现如下:

void swap(HPDataType* s1, HPDataType* s2)
{
	HPDataType temp = *s1;
	*s1 = *s2;
	*s2 = temp;
}

void Adjustup(HPDataType* a, int child)//向上调整
{
	int 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;
		}

	}
}
向下调整方法

堆的向下调整方法是指将某个节点的值下放至其子节点中,以维护堆的性质的过程。

假设当前节点为 i,其左子节点为 2i+1,右子节点为 2i+2,堆的大小为 n

则向下调整的步骤如下

  • 从当前节点 i 开始,将其与其左右子节点中较小或较大的节点比较,找出其中最小或最大的节点 j。
  • 如果节点 i 小于等于(或大于等于,取决于是最小堆还是最大堆)节点 j,则说明它已经满足堆的性质,调整结束;否则,将节点 i 与节点 j 交换位置,并将当前节点 i 更新为 j。
  • 重复执行步骤 1 和步骤 2,直到节点 i 没有子节点或已经满足堆的性质。

一图让你了解~:

图片

实现如下:

void swap(HPDataType* s1, HPDataType* s2)
{
	HPDataType temp = *s1;
	*s1 = *s2;
	*s2 = temp;
}

void Adjustdown(HPDataType* a, int n, int parent)//向下调整
{
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])//找出两个孩子中较大的那个,此为大堆,如果要实现小堆则 改 >
		{
			++child;
		}

		if (a[child] > a[parent])//此为大堆,如果要实现小堆则 改 >
		{
			swap(&a[child], &a[parent]);

			parent = child;
			child = parent * 2 + 1;

		}
		else
		{
			break;
		}
	}
}

3.3 堆的构建

void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	assert(a);

	hp- >_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (!hp- >_a)
	{
		perror("malloc fail");
		exit(-1);
	}

	hp- >_capacity = hp- >_size = n;

	//将a中的元素全部转移到堆中
	memcpy(hp- >_a, a, sizeof(HPDataType) * n);

	//建堆
	for (int i = 1; i < n; i++)
	{
		Adjustup(hp- >_a, i);//按向上调整,此建立大堆
	}

}

本文的构建方法是 通过传递一个数组以及传递一个数组大小来构建的 ,里面包括了堆结构体的初始化操作,基本的建堆方式则是通过向上调整方法建堆。


3.4 堆的销毁

void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp- >_a);
	hp- >_a = NULL;

	hp- >_capacity = hp- >_size = 0;
}

就正常的销毁操作?大家应该都懂(确信) (o°ω°o)


3.5 堆的插入

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

	if (hp- >_capacity == hp- >_size)//扩容
	{
		int newcapacity = hp- >_capacity == 0 ? 4 : hp- >_capacity * 2;
		HPDataType* new = (HPDataType*)realloc(hp- >_a, sizeof(HPDataType) * newcapacity);
		if (!new)
		{
			perror("realloc fail");
			exit(-1);
		}

		hp- >_a = new;
		hp- >_capacity = newcapacity;
	}

	hp- >_a[hp- >_size++] = x;

	Adjustup(hp- >_a, hp- >_size - 1);

}

实现是对于堆的空间进行判断,不够则是扩容操作,当然也有初始化的意思,接着是通过向上调整的方式插入操作。


3.6 堆的删除(较重要)

void HeapPop(Heap* hp)//先将最后一个数与堆顶交换,然后再让size--,再进行向下调整
{
	assert(hp);

	swap(&hp- >_a[0], &hp- >_a[hp- >_size - 1]);
	hp- >_size--;

	Adjustdown(hp- >_a, hp- >_size, 0);

}

进行删除操作,我们当然是删除堆顶啦,这个删除操作先将最后一个数与堆顶交换,然后再让size--,再进行向下调整。

一图让你了解~:

图片


3.7 取堆顶的数据

HPDataType HeapTop(Heap* hp)//取堆顶
{
	assert(hp);
	assert(hp- >_size > 0);

	return hp- >_a[0];
}

3.8 堆的数据个数

int HeapSize(Heap* hp)//堆大小
{
	assert(hp);

	return hp- >_size;
}

3.9 堆的判空

int HeapEmpty(Heap* hp)//判堆空
{
	assert(hp);

	return hp- >_size==0;
}

Part4 总体代码

4.1pile.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 01
#include< stdio.h >
#include< stdlib.h >
#include< string.h >
#include< assert.h >

typedefint HPDataType;

typedefstruct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

4.2pile.c

#include"pile.h"


void swap(HPDataType* s1, HPDataType* s2)
{
	HPDataType temp = *s1;
	*s1 = *s2;
	*s2 = temp;
}

void Adjustup(HPDataType* a, int child)//向上调整
{
	int 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 Adjustdown(HPDataType* a, int n, int parent)//向下调整
{
	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])//找出两个孩子中较大的那个,此为大堆,如果要实现小堆则 改 >
		{
			++child;
		}

		if (a[child] > a[parent])//此为大堆,如果要实现小堆则 改 >
		{
			swap(&a[child], &a[parent]);

			parent = child;
			child = parent * 2 + 1;

		}
		else
		{
			break;
		}
	}
}


void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	assert(a);

	hp- >_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (!hp- >_a)
	{
		perror("malloc fail");
		exit(-1);
	}

	hp- >_capacity = hp- >_size = n;

	//将a中的元素全部转移到堆中
	memcpy(hp- >_a, a, sizeof(HPDataType) * n);

	//建堆
	for (int i = 1; i < n; i++)
	{
		Adjustup(hp- >_a, i);//按向上调整,此建立大堆
	}

}

void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp- >_a);
	hp- >_a = NULL;

	hp- >_capacity = hp- >_size = 0;
}

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

	if (hp- >_capacity == hp- >_size)//扩容
	{
		int newcapacity = hp- >_capacity == 0 ? 4 : hp- >_capacity * 2;
		HPDataType* new = (HPDataType*)realloc(hp- >_a, sizeof(HPDataType) * newcapacity);
		if (!new)
		{
			perror("realloc fail");
			exit(-1);
		}

		hp- >_a = new;
		hp- >_capacity = newcapacity;
	}

	hp- >_a[hp- >_size++] = x;

	Adjustup(hp- >_a, hp- >_size - 1);

}

void HeapPop(Heap* hp)//先将最后一个数与堆顶交换,然后再让size--,再进行向下调整
{
	assert(hp);

	swap(&hp- >_a[0], &hp- >_a[hp- >_size - 1]);
	hp- >_size--;

	Adjustdown(hp- >_a, hp- >_size, 0);

}

HPDataType HeapTop(Heap* hp)//取堆顶
{
	assert(hp);
	assert(hp- >_size > 0);

	return hp- >_a[0];
}

int HeapSize(Heap* hp)//堆大小
{
	assert(hp);

	return hp- >_size;
}

int HeapEmpty(Heap* hp)//判堆空
{
	assert(hp);

	return hp- >_size==0;
}

4.3test.c

#include"pile.h"


void test()
{
	Heap hp;
	int arr[] = { 1,6,2,3,4,7,5 };
	HeapCreate(&hp, arr, sizeof(arr) / sizeof(arr[0]));
	//HeapPush(&hp, 10);
	printf("%dn", HeapSize(&hp));
	while (!HeapEmpty(&hp))
	{
		printf("%d %d n", HeapTop(&hp), HeapSize(&hp));
		HeapPop(&hp);
	}

	printf("%dn", HeapSize(&hp));
	HeapDestory(&hp);
	
	HeapSort(arr, sizeof(arr) / sizeof(arr[0]));

	printf("n");
}

int main()
{
	test();
	return0;
}

4.4 测试结果

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7648

    浏览量

    148521
  • 数据结构
    +关注

    关注

    3

    文章

    564

    浏览量

    39905
  • 元素
    +关注

    关注

    0

    文章

    47

    浏览量

    8372
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12240
收藏 人收藏

    评论

    相关推荐

    python学习:三个测试库的装饰器实现思路

    在 Python 中实现参数化测试的几个库,并留下一个问题: 它们是如何做到把一个方法变成多个方法,并且将每个方法与相应的参数绑定起来的呢? 我们再提炼一下,原问题等于是:在一个类中,如何使用装饰
    的头像 发表于 09-27 11:44 2866次阅读
    python学习:三个测试库的装饰器<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    ADC多次采样的实现思路

    ADC扫描采样若干通道,数据保存在指定缓冲区,连续采样若干次之后触发中断,然后读取采样数据处理。
    发表于 09-09 12:54 1841次阅读

    立创·梁山派开发板-21年电赛F题-送药小车实现思路

    基本要求 1. 根据走廊上的标识信息自动识别,寻径将药品送到指定病房,投影要在门口区域内, 2. 到了指定病房后,点亮红色指示灯,等待卸载药品。 3. 人工卸掉药品后,小车自动熄灭红色指示灯,开始返回。 4. 自动返回药房,点亮绿色指示灯。
    的头像 发表于 08-08 09:44 504次阅读
    立创·梁山派开发板-21年电赛F题-送药小车<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    I2C读写时序分析和实现思路

    上篇推文对I2C总线的特性进行了介绍和描述。对于开发者而言,最重要的是编码I2C的读写时序驱动。本篇推文主要总结和分享I2C总线主机端通信的编程实现思路,并不对应特定MCU的硬件I2C外设,此处需要加以区分。
    发表于 10-01 16:54 385次阅读
    I2C读写时序分析和<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    芯片信息安全,不同ISA架构的实现思路

    /IEC 27400:2022等。然而对于不同的芯片架构而言,实现安全的方式往往也不尽相同,比如Arm和RISC-V。   Arm TrustZone 技术   经过多年的发展,Arm的TrustZone技术已经被应用在数十亿颗应用处理器上,可以说这一安全设计保护着我们身边各类设备的代码和资料。因
    的头像 发表于 04-18 00:09 1997次阅读

    检错纠错的有关概念和实现思路

    检错纠错的有关概念和实现思路  数据在计算机系统内加工、存取和传送的过程中可能产生错误。为减少和避免这类错误,一方面是精心选择各种电路,改进生产工艺
    发表于 10-13 16:41 1511次阅读
    检错纠错的有关概念和<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    供电厂的各种负载设备等效电路大全

    概述●理解交流供电的特殊性●理解PF和THD●PPFC原理及实现思路●APFC原理及实现思路理解交流供电的特
    的头像 发表于 11-06 15:43 4726次阅读
    供电厂的各种负载设备等效电路大全

    Python中参数化测试的实现思路解析

    通常而言,一个测试方法就是一个最小的测试单元,其功能应该尽量地原子化和单一化。
    发表于 11-08 09:06 595次阅读

    基于STM32的温湿度检测和无线传输的设计与实现思路

    实现的大体思路1. DHT11传感器:单总线协议编程PS:连接到STM32开发板时,一定要记住不得将DHT11的VCC和GND短接,只要短接一下下就会烧掉、废了用不了。2. 无线传输
    发表于 12-27 18:42 15次下载
    基于STM32的温湿度检测和无线传输的设计与<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    环形缓冲区的实现思路

    单片机程序开发一般都会用到UART串口通信,通过通信来实现上位机和单片机程序的数据交互。通信中为了实现正常的收发,一般都会有对应的发送和接收缓存来暂存通信数据。这里使用环形缓冲区的方式来设计数据收发的缓存,即缓冲区溢出后,从缓冲区数组的起始索引处重新进行数据的存储,这样可
    的头像 发表于 01-17 15:07 1236次阅读

    RGMII接口转GMII接口的实现思路

    RGMII接口是双沿采样时钟,数据位宽为4bit,而GMII接口是单沿采样时钟,数据位宽是8bit。
    发表于 06-09 11:23 1403次阅读

    图像流AXI-Stream生成BMP文件的实现思路

    实现上,由于bmp除去文件头后也只是把图像流数据按顺序放而已
    的头像 发表于 06-27 11:20 648次阅读
    图像流AXI-Stream生成BMP文件的<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    UVC bulk传输实现思路

    前段时间有个读者咨询UVC bulk 传输实现,接着这个机会重新梳理一遍UVC bulk 传输实现思路,同时对比ISO 与 Bulk 实现不同。
    的头像 发表于 09-25 10:00 2513次阅读
    UVC bulk传输<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    数字前端设计的x.5分频实现思路

    第一个脉冲很容易实现。计数器2’b00 -- 2’b01 – 2’b10无限循环,最高位就是每三个周期出现一次的脉冲。第二个脉冲要用到一个negedge DFF。两个脉冲OR一下,输出就是1.5分频。
    的头像 发表于 12-14 15:26 235次阅读
    数字前端设计的x.5分频<b class='flag-5'>实现</b><b class='flag-5'>思路</b>

    什么是动态线程池?动态线程池的简单实现思路

    因此,动态可监控线程池一种针对以上痛点开发的线程池管理工具。主要可实现功能有:提供对 Spring 应用内线程池实例的全局管控、应用运行时动态变更线程池参数以及线程池数据采集和监控阈值报警。
    的头像 发表于 02-28 10:42 184次阅读