C数据结构:堆(实现、排序)

目录

堆是什么?

堆有什么用?

堆的实现

堆的数据结构

堆的初始化

堆的插入

向上调整

堆的删除

向下调整

获取堆顶数据

获取堆的大小

判断堆是否为空

堆的销毁

堆排序   

完整代码


堆是什么?

堆是二叉树的一种

二叉树的特点是每个父节点最多只有两个子节点

树这种结构已经不属于线性表中的一类了

树这种数据结构就类似于现实生活中的树,但它是反过来的,根节点在最上面

堆属于一种特殊的完全二叉树

它除了最后一层的节点可以不是满的,其他都必须是满的,但最后一层必须向左排列

堆分为大堆和小堆

大堆的特点是根节点的值最大,且它以下的每个节点都必须是这个特点(大堆)

小堆的特点是根节点的值最小,且它以下的每个节点都必须是这个特点(小堆)

堆有什么用?

小堆的堆顶每个都是最小的,大堆的堆顶每个都是最大的

那么我们可以很轻易的获取到最大的和最小的数据,在上亿个数据中取K个最大的或者最小的都是很轻易的

堆的实现

堆的实现我们可以使用链表,也可以使用顺序表

若是链表则需要定义两个指针一个指向左孩子一个指向右孩子

typedef struct Heap
{
    int data;
    struct Heap* left;
    struct Heap* right;
}Heap;

但是现在对于堆这种特殊的完全二叉树而言,我们可以直接使用顺序表存储

因为我们可以从顺序表的下标中找到一个规律

parent节点 = (child - 1) / 2

child 节点   = parent * 2 + 1

所以虽然看上去树的物理结构是一个顺序表,但其实我们可以想象出来它是一个树形

物理结构:

树形结构:

外面的数字代表它们的在数组中对应的下表,里面的数字代表该节点当前存储的数据

例如我们要求下标为2(值为3)的孩子有谁,那么根据公式child = parent * 2 + 1

可以得到第一个孩子下标为5,那么第二个孩子则是第一个孩子加1所以下表为6

下面开始实现

堆的数据结构

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

这里使用的是一个指针a动态开辟空间,静态的数组在现实中没有什么价值,缺点多 

堆的初始化

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

	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

初始化可以先给a开辟一块空间,也可以先置空

 初始capacity和size当然是0 

堆的插入

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

	if (php->_size == php->_capacity)
	{
		int newcapacity = php->_capacity == 0 ? 4 : php->_capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->_a = tmp;
		php->_capacity = newcapacity;
	}

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

	AdjustUp(php->_a, php->_size - 1);
}

首先上来的都是常规的扩容逻辑,判断空间是否已满,若满了则需要增容,增容需要检查空间开辟是否成功,最后插入

跟顺序表扩容逻辑相同

区别就在最后的AdjustUp函数,向上调整函数

向上调整

若我们本来就是一个堆,那么贸然的插入一个值,极有可能会导致该堆已经不是一个堆

所以我们需要让最后一个节点向上调整成为一个堆

 

如上图插入了一个值为3的节点,本来是一个小堆,结果被破坏了,这时候我们需要让3向上调整

向上调整规则:

先跟父亲节点比较,若小于父亲节点,则需要跟父亲节点交换

交换过后,再次跟父亲节点比较,3大于2,那么向上调整结束

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

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 HeapPop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);

	Swap(&php->_a[0], &php->_a[php->_size - 1]);
	php->_size--;

	AdjustDown(php->_a, php->_size, 0);
}

删除我们删除的是根节点的值,也就是数组中下标为0的值

不能使用以前的方法让数组中其他的值--覆盖掉下标为0的值

因为这样会让原本差根节点就成为堆的结构瞬间瓦解

我们只能重新建堆,这样做时间复杂度就会比较高

所以建议采取下面的方式

我们需要先跟最后一个节点的值交换

然后删除最后一个就行了

但是我们交换了第一个数据,我们需要对第一个数据向下调整,这样才能重新变成一个堆

向下调整

和向上调整思路差不多,若是需要小堆

我们需要先找到两个孩子中较小的那一个,然后跟双亲节点比较

若双亲节点的值大于孩子节点的值,我们要交换

然后循环往下向下调整即可

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

首先求出第一个孩子的下标

在循环中,若第二个孩子节点的值小于第一个孩子的值,则child++,就能找到两个孩子节点中小的那一个

然后判断是双亲节点小还是孩子节点小,若孩子小于双亲,则交换,否则跳出循环

获取堆顶数据

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

	return php->_a[0];
}

直接返回下标为0的值即可 

获取堆的大小

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

	return php->_size;
}

直接返回size 

判断堆是否为空

int HeapEmpty(Heap* php)
{
	assert(php);

	return php->_size == 0;
}

判断size是否为0 

堆的销毁

void HeapDestory(Heap* php)
{
	assert(php);

	free(php->_a);
	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

释放动态开辟的空间,并将指针置为NULL

capacity和size置为0

这样我们的堆就写好了

堆排序   

void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

 首先需要先将这个数组建堆

建堆可以使用向上调整或者向下调整

这里使用的是向下调整,因为它不需要对最后一层调整,因为最后一层的节点是最多的,所以时间就少很多

向下调整建堆我们只需要从倒数第二层的最后一个节点开始向下调整,调整到第一个节点即可

如果我们需要数组是升序,那么我们需要建小堆,降序,那么需要建大堆

堆排的思想是先让堆顶最大(或最小的元素放到最后),然后把最后一个节点排除在外将其他节点看成一个堆,重新找出第二大(或第二小的元素放到倒数第二个位置),以此往复即可将数组顺序排好 

具体是找最大还是最小主要是看堆,建堆则主要看向下调整算法的符号 

所以从最后一个节点开始,跟第一个节点交换,也就是找到了最大的(或者最小的)那个数据

这时候就已经不是一个堆了,那么我们就需要用向下调整算法让第一个节点向下调整,使之重新成为一个堆,让最后的end--(也就是将我们刚刚找到的最大的数据或最小的数据排除在外),循环结束时我们的排序也就做完了

完整代码

Heap.h

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

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

// 堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
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);
// 堆排序
void HeapSort(int* a, int n);

Heap.c

#include "Heap.h"

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

	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

void HeapDestory(Heap* php)
{
	assert(php);

	free(php->_a);
	php->_a = NULL;
	php->_capacity = php->_size = 0;
}

void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

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 HeapPush(Heap* php, HPDataType x)
{
	assert(php);

	if (php->_size == php->_capacity)
	{
		int newcapacity = php->_capacity == 0 ? 4 : php->_capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(php->_a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->_a = tmp;
		php->_capacity = newcapacity;
	}

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

	AdjustUp(php->_a, php->_size - 1);
}

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 HeapPop(Heap* php)
{
	assert(php);
	assert(php->_size > 0);

	Swap(&php->_a[0], &php->_a[php->_size - 1]);
	php->_size--;

	AdjustDown(php->_a, php->_size, 0);
}

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

	return php->_a[0];
}

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

	return php->_size;
}

int HeapEmpty(Heap* php)
{
	assert(php);

	return php->_size == 0;
}

void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	int end = n - 1;
	while (end)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

Test.c

#include "Heap.h"

void TestHeap1()
{
	int a[] = { 1,2,5,8,9,3,6,4,7,1,2 };
	Heap hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HeapPush(&hp, a[i]);
	}

	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	HeapDestory(&hp);
}

void TestHeap2()
{
	int a[] = { 1,6,8,0,7,5,4,2,9,3 };
	HeapSort(a, 10);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

int main()
{
	//TestHeap1();
	TestHeap2();

	return 0;
}

完 

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ragef

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

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

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

打赏作者

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

抵扣说明:

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

余额充值