【数据结构】—堆详解(手把手带你用C语言实现)

news2025/1/23 10:36:26

                                       食用指南:本文在有C基础的情况下食用更佳 

                                       🔥这就不得不推荐此专栏了:C语言

                                       ♈️今日夜电波:水星—今泉愛夏

                                                                1:10 ━━━━━━️💟──────── 4:23
                                                                    🔄   ◀️   ⏸   ▶️    ☰ 

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍 


目录

❤️什么是堆?

        堆的分类 

💛堆的实现思路

        使用什么实现?

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

        总体实现思路

💜堆的实现

        结构体以及接口的实现 

        💯堆的两种建堆方式(调整方法)究极无敌重要!!!

        向上调整方法   

        向下调整方法 

        堆的构建

        堆的销毁 

        堆的插入

        ⭕️堆的删除 (较重要)

        取堆顶的数据

        堆的数据个数

        堆的判空

💚总体代码


❤️什么是堆?

        堆是一种基于树结构的数据结构,它是一棵二叉树,具有以下两个特点:

1. 堆是一个完全二叉树,即除了最后一层,其他层都是满的,最后一层从左到右填满。

2. 堆中每个节点都满足堆的特性,即父节点的值要么等于或者大于(小于)子节点的值。

        堆的分类 

        堆一般分为两类:大堆和小堆。大堆中,父节点的值大于或等于子节点的值,而小堆中,父节点的值小于或等于子节点的值。堆的主要应用是在排序和优先队列中。

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


💛堆的实现思路

        使用什么实现?

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

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

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

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

                        parent=\left (child-1 \right )/ 2

                        lchild=parent* 2+1

                        rchild=parent*2+2

        总体实现思路

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

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

         


💜堆的实现

        结构体以及接口的实现 

typedef int HPDataType;

typedef struct 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);

        💯堆的两种建堆方式(调整方法)究极无敌重要!!!

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

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

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

        1. 自底向上建堆:自底向上建堆是指按照原始数组顺序依次插入元素,然后对每个插入的元素执行向上调整的操作,使得堆的性质保持不变。这种方法需要对每个元素都进行调整操作,时间复杂度为 O(nlogn)。

        2. 自顶向下建堆:自顶向下建堆是指从堆顶开始,对每个节点执行向下调整操作,使得堆的性质保持不变。这种方法需要从根节点开始递归向下调整,时间复杂度为 O(n)。因此,自顶向下建堆的效率比自底向上建堆要高。

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

        向上调整方法   

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

        具体步骤如下:

        1.将新插入的节点插入到堆的最后一位。

        2.获取该节点的父节点的位置,比较该节点与其父节点的大小关系。

        .如果该节点比其父节点大(或小,根据是大根堆还是小根堆),则交换这两个节点。

        4.重复步骤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

        则向下调整的步骤如下:

  1. 从当前节点 i 开始,将其与其左右子节点中较小或较大的节点比较,找出其中最小或最大的节点 j。

  2. 如果节点 i 小于等于(或大于等于,取决于是最小堆还是最大堆)节点 j,则说明它已经满足堆的性质,调整结束;否则,将节点 i 与节点 j 交换位置,并将当前节点 i 更新为 j。

  3. 重复执行步骤 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;
		}
	}
}

        堆的构建

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;
}

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


        堆的插入

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);

}

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

         一图让你了解~


        取堆顶的数据

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;
}

💚总体代码

        pile.h

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

typedef int HPDataType;

typedef struct 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);

        pile.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;
}

        test.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("%d\n", HeapSize(&hp));
	while (!HeapEmpty(&hp))
	{
		printf("%d %d \n", HeapTop(&hp), HeapSize(&hp));
		HeapPop(&hp);
	}

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

	printf("\n");
}

int main()
{
	test();
	return 0;
}

        测试结果:


                    感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                         给个三连再走嘛~  

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

掌控你的Mac——用Bookshelf Library简化文件管理

Bookshelf Library for Mac是一款高效的文件索引管理工具&#xff0c;它可以帮助你轻松整理和查找Mac上的所有文档和书籍。下面我们来看看这款工具的五个特点。 安装&#xff1a;Bookshelf Library for Mac(文件索引管理工具)v6.3.4激活版 第一&#xff0c;Bookshelf Library…

【广州华锐互动】工业零件拆装VR培训:无需前往现场,提高学习效率

工业零件拆装VR培训是一种新兴的培训方式&#xff0c;通过虚拟现实技术将设备拆解过程进行模拟&#xff0c;让学员在虚拟环境中进行实际操作和学习。这种培训方式具有许多益处&#xff0c;本文将对其进行详细阐述。 首先&#xff0c;工业零件拆装VR培训可以提高学员的学习效率。…

好用的软件测试框架有哪些?测试框架的作用是什么?

软件测试框架是现代软件开发过程中至关重要的工具&#xff0c;它可以帮助开发团队更加高效地进行测试和验证工作&#xff0c;从而大大提高软件质量和用户体验。 一、好用的软件测试框架 1. Selenium&#xff1a;作为一种开源的自动化测试框架&#xff0c;Selenium具有功能强大…

【Jmeter】什么是BeanShell?

一、什么是BeanShell&#xff1f; BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器&#xff0c;JMeter性能测试工具也充分接纳了BeanShell解释器&#xff0c;封装成了可配置的BeanShell前置和后置处理器&#xff0c;分别是 BeanShell Pre…

快速打造BI大屏 激活各行业数据价值

BI的概念普遍认为最早由Gartner公司提出&#xff0c;简单可理解为基于现代企业经营理论与信息应用技术系统对信息、数据进行挖掘、分析和处理&#xff0c;最终辅助商业决策的一个企业服务解决方案。 在企业数字化进程中&#xff0c;这样的解决方案主要以信息技术系统为底座&am…

力扣 -- 673. 最长递增子序列的个数

小算法&#xff1a; 通过一次遍历找到数组中最大值出现的次数&#xff1a; 利用这个小算法求解这道题就会非常简单了。 参考代码&#xff1a; class Solution { public:int findNumberOfLIS(vector<int>& nums) {int nnums.size();vector<int> len(n,1);auto…

23.Xaml Frame控件---->导航控件

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

leetcode330. 按要求补齐数组(java)

按要求补齐数组 题目描述贪心算法代码演示 题目描述 难度 - 困难 leetcode - 330. 按要求补齐数组 给定一个已排序的正整数数组 nums &#xff0c;和一个正整数 n 。从 [1, n] 区间内选取任意个数字补充到 nums 中&#xff0c;使得 [1, n] 区间内的任何数字都可以用 nums 中某几…

演讲实录:大模型时代,我们需要什么样的AI算力系统?

当前&#xff0c;“百模大战”带来了算力需求的爆发&#xff0c;AI芯片产业也迎来巨大机遇&#xff0c;“创新架构开源生态”正在激发多元AI算力产品百花齐放。面对新的产业机会&#xff0c;AI算力产业链亟需通过上下游协作共同把握机遇。 近日&#xff0c;浪潮信息AI&HPC…

Unity Shader顶点数据疑问

1&#xff09;Unity Shader顶点数据疑问 2&#xff09;Unity 2018发布在iOS 16.3偶尔出现画面不动的问题 3&#xff09;安卓游戏启动后提示“应用程序异常” 这是第352篇UWA技术知识分享的推送&#xff0c;精选了UWA社区的热门话题&#xff0c;涵盖了UWA问答、社区帖子等技术知…

MCU芯片测试:性能指标测试痛点是什么?ATECLOUD能否解决?

MCU芯片测试指标的核心是性能指标&#xff0c;包括处理器性能、存储器容量和读写速度&#xff0c;外设性能等。芯片测试对自动化测试的要求很高&#xff0c;ATECLOUD-IC不仅解决了传统测试方法的问题&#xff0c;而且也可以满足芯片测试的高要求&#xff0c;高效地完成MCU芯片性…

详解qsort函数的使用及模拟实现qsort函数

目录 引言&#xff1a; 1. qsort函数简介&#xff1a; &#x1f388;qsort函数原型&#xff1a; &#x1f388;函数参数介绍&#xff1a; &#x1f388;比较函数(compar)的编写&#xff1a; &#x1f388;(补充) void*类型的指针&#xff1a; 2.qsort函数示例&#xff1a;…

java将excel中用例写到world中【搬代码】

首先创建用例 例如&#xff1a; 运行代码: 预期结果&#xff1a; 实际结果&#xff1a;与预期结果不符合&#xff0c;哪位大佬有代码传授一下啊&#xff0c;实在是不知道咋写了 代码&#xff1a; package com.znzdh.qitagongju; import com.spire.doc.*; import com.spire…

安徽省图书馆典藏《乡村振兴振兴战略下传统村落文化旅游设计》许少辉八一新著

安徽省图书馆典藏《乡村振兴振兴战略下传统村落文化旅游设计》许少辉八一新著

InstallShield打包升级时不覆盖原有文件的解决方案

一个.NET Framework的Devexpress UI Windows Form项目&#xff0c;用的InstallShield&#xff0c;前些个版本都好好的&#xff0c;最近几个版本突然就没法更新了&#xff0c;每次更新的时候都覆盖不了原文件&#xff0c;而且这样更新后第一次打开程序&#xff08;虽然是老程序&…

ReID网络:MGN网络(5): 一点延伸: 缩减特征维度, 提高匹配效率

1. MGN网络输出 在实际使用MGN时&#xff0c;是将网络尾部的特征进行导出&#xff0c;并进行相关的相似性度量。 如图1所示&#xff0c;MGN特网络在析出特征的结尾处执行了concat操作。如果每一条特征维度是256的话&#xff0c;那么MGN最终输出将是2048维的特征。 图1 MGN特征…

香港汇丰银行开户

作为香港较知名的银行之一&#xff0c;汇丰银行提供了丰富的金融服务&#xff0c;包括个人银行服务、企业银行服务以及国际银行服务等。对于需要在香港开展业务或投资的人来说&#xff0c;开立一个汇丰银行账户是必不可少的。那么&#xff0c;开立汇丰银行账户需要哪些资料&…

医院用泛微构建数字化信创平台,人财物、医教研、文事会统一管理

近年来&#xff0c;国家多次发布政策&#xff0c;驱动医疗行业的智慧化转型。医疗行业作为重点领域&#xff0c;正在大力推进信创发展&#xff0c;数字化和信创将共同助力医院的高质量发展。 国家卫健委发布的《医院智慧管理分级评估标准体系&#xff08;试行&#xff09;》、…

【算法训练-数组 五】【二分查找】:旋转排序数组的最小数字、旋转排序数组的指定数字

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【数组的二分查找】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为…

fast-lio2添加wheel在发散时轨迹约束

fast-lio2主要算法为迭代误差卡尔曼滤波算法&#xff0c;imu作为预测&#xff0c;点云更新&#xff0c;当点云发散时输出位姿异常&#xff0c;漂移很大&#xff0c;后端在融合出现崩溃情况&#xff0c;加入轮速计约束发散时位姿。 1.订阅wheel话题 2.发散检测&#xff0c;检测…