算法给小码农堆魂器--铁血柔情

本文详细介绍了数据结构中的堆,特别是大根堆的实现,包括堆的性质、结构体定义、初始化、销毁、打印、插入、删除等操作。堆是完全二叉树,用于高效地获取最大值。代码示例展示了C语言实现的大根堆数据结构,包括向上调整、向下调整等关键算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构中的堆不同于操作系统中的堆(操作系统中的堆是用来存储动态内存的),数据结构中的堆是数据的存储方式。数据结构中的堆是完全二叉树

既然堆是完全二叉树的形式存储的那么就非常适合用数组的方式来表示

堆的概念及结构

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

大堆:树中一个树及子树中,任何一个父亲都大于等于孩子

小堆:树中一个树及子树中,任何一个父亲都小于等于孩子

堆的性质

堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树**

堆的结构(这里实现大堆)

image-20211108081113661

既然还是数组的结构的话就还是顺序表的处理方式,数组指针,size,capacity。虽然物理上我们是用顺序表的方式来表示,但是他实际上表示的数据是完全二叉树。

堆的结构体
typedef int HPDataType;

typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
堆初始化函数HeapInit

就是一个指向NULL的数组,size 和 capacity都为零

//堆初始化函数
void HeapInit(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}
堆销毁函数HeapDestroy

由于数组是动态开辟的,所以用完后需要销毁的,不然会内存泄漏

//堆销毁函数
void HeapDestroy(HP* hp)
{
	assert(hp);
	free(hp->a);
	hp->size = hp->capacity = 0;
}
堆打印函数HeapPrint

可以想象成一种快速调试,类似于单片机中的串口打印看数据收发情况

//堆打印函数
void HeapPrint(HP* hp)
{
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}
向上调整函数AdjustUp

为了不影响数据的存储形式(大堆还得是大堆),插入数据就不能破坏大堆的形式,我们需要把堆插入函数中的数据调整给剥离出来

我们可以看到插入的这个数据对其他的节点并没有什么影响,有影响的只是这个节点到根这条路径上的节点,如何解决对这条路径的影响呢,我们可以形象的看到仅仅是在这条路径上进行向上调整

通过parent = (child-1)/2 找到父亲节点,与之进行比较,然后父亲小就交换位置(大堆),然后交换后就在找上面的父亲节点,直到找到父亲大于孩子,就不交换了

image-20211108115306980

//向上调整函数
void AdjustUp(HPDataType* a, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[parent] < a[child])//父亲小于孩子就交换(大堆)
		{
			a[parent] = a[parent] ^ a[child];
			a[child] = a[parent] ^ a[child];
			a[parent] = a[parent] ^ a[child];
			//交换好后重新称呼一下孩子与父亲
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}

	}
}
堆插入函数HeapPush

image-20211108163302928

//堆插入函数(要保持原来形式,大堆还是大堆,小堆就还是小堆)
void HeapPush(HP* hp, HPDataType x)
{
	assert(hp);
	//判断扩容
	if (hp->size == hp->capacity)
	{
		//容量给新的容量
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		//扩容
		HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		//增容失败
		if (!tmp)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		//增容成功
		hp->a = tmp;
		hp->capacity = newcapacity;		
	}
	//放数据
	hp->a[hp->size] = x;
	hp->size++;
	//实现大堆
	//这个部分的向上调整其他地方也用的到就把他剥离出来
	AdjustUp(hp->a, hp->size - 1);//child下标
}
判断堆是否为空函数HeapErmpy
//判断堆是否为空函数
bool HeapErmpy(HP* hp)
{
	assert(hp);
	return hp->size == 0;
}
返回堆大小函数HeapSize
//返回堆大小函数
int HeapSize(HP* hp)
{
	assert(hp);
	return hp->size;
}

下面还会用到交换函数,上面也有那么我们不妨把他剥离出来封装一下,就不需要重复写了

交换函数Swap
//交换函数
void Swap(HPDataType* px, HPDataType* py)
{
	*px = *px ^ *py;
	*py = *px ^ *py;
	*px = *px ^ *py;
}
向下调整函数AdjustDown

image-20211108223143528

//向下调整函数
void AdjustDown(HPDataType* a, int size, int parent)
{
	assert(a);
	//创建一个孩子变量,有两个孩子就在这个上加1就行
	int child = parent * 2 + 1;
	while (child< size)
	{
		//选大孩子
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child++;
		}
		//大的孩子还大于父亲就交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
	}
}
堆删除函数HeapPop

我们可以认为假想根和堆的最后一个元素交换后,把最后一个删除,然后再对堆进行操作,你会发现,我们没有破坏原来的整体结构

image-20211108223334078

//堆删除函数(删除的是堆顶数据也就是取最值)
//还有不可能一直删的所以我们需要一个判空函数
void HeapPop(HP* hp)
{
	assert(hp);
	assert(!HeapErmpy(hp));
	//根和堆最后一个元素交换
	Swap(&hp->a[0],&hp->a[hp->size-1]);
	//把最后一个删除,就是我们想要删除的元素
	hp->size--;
	//向下调整
	AdjustDown(hp->a,hp->size,0);
}

代码

Heap.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;

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

//堆初始化函数
extern void HeapInit(HP* hp);
//堆销毁函数
extern void HeapDestroy(HP* hp);
//向上调整函数
extern void AdjustUp(HPDataType* a,int child);
//堆插入函数
extern void HeapPush(HP* hp,HPDataType x);
//堆打印函数
extern void HeapPrint(HP* hp);
//向下调整函数
extern void AdjustDown(HPDataType* a, int size, int parent);
//堆删除函数
extern void HeapPop(HP* hp);
//判断堆是否为空函数
extern bool HeapErmpy(HP* hp);
//交换函数
extern void Swap(HPDataType* px, HPDataType* py);
//返回堆大小函数
extern int HeapSize(HP* hp);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Heap.h"

//堆初始化函数
void HeapInit(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}
//堆销毁函数
void HeapDestroy(HP* hp)
{
	assert(hp);
	free(hp->a);
	hp->size = hp->capacity = 0;
}

//向上调整函数
void AdjustUp(HPDataType* a, int size, int child)
{
	assert(a);
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[parent] < a[child])//父亲小于孩子就交换(大堆)
		{
			Swap(&a[parent], &a[child]);
			//交换好后重新称呼一下孩子与父亲
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}

	}
}

//堆插入函数(要保持原来形式,大堆还是大堆,小堆就还是小堆)
void HeapPush(HP* hp, HPDataType x)
{
	assert(hp);
	//判断扩容
	if (hp->size == hp->capacity)
	{
		//容量给新的容量
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		//扩容
		HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);
		//增容失败
		if (!tmp)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		//增容成功
		hp->a = tmp;
		hp->capacity = newcapacity;		
	}
	//放数据
	hp->a[hp->size] = x;
	hp->size++;
	//实现大堆
	//这个部分的向上调整其他地方也用的到就把他剥离出来
	//向上调整一下堆的数据形式,使他还是大堆的形式
	AdjustUp(hp->a, hp->size, hp->size - 1);
}
//堆打印函数
void HeapPrint(HP* hp)
{
	assert(hp);
	int i = 0;
	for (i = 0; i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}
//判断堆是否为空函数
bool HeapErmpy(HP* hp)
{
	assert(hp);
	return hp->size == 0;
}
//返回堆大小函数
int HeapSize(HP* hp)
{
	assert(hp);
	return hp->size;
}
//交换函数
void Swap(HPDataType* px, HPDataType* py)
{
	*px = *px ^ *py;
	*py = *px ^ *py;
	*px = *px ^ *py;
}
//向下调整函数
void AdjustDown(HPDataType* a, int size, int parent)
{
	assert(a);
	//创建一个孩子变量,有两个孩子就在这个上加1就行
	int child = parent * 2 + 1;
	while (child< size)
	{
		//选大孩子
		if (child + 1 < size && a[child] < a[child + 1])
		{
			child++;
		}
		//大的孩子还大于父亲就交换
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//堆删除函数(删除的是堆顶数据也就是取最值)
//还有不可能一直删的所以我们需要一个判空函数
void HeapPop(HP* hp)
{
	assert(hp);
	assert(!HeapErmpy(hp));
	//根和堆最后一个元素交换
	Swap(&hp->a[0],&hp->a[hp->size-1]);
	//把最后一个删除,就是我们想要删除的元素
	hp->size--;
	//向下调整
	AdjustDown(hp->a,hp->size,0);
}


test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Heap.h"

void test()
{
	int a[] = { 70,56,30,25,15,75 };
	HP hp;
	HeapInit(&hp);
	int i = 0;
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	HeapPop(&hp);
	HeapPrint(&hp);
	HeapDestroy(&hp);
}


int main()
{
	test();
	return 0;
}
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码农UU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值