有趣的数据结构——堆

一.什么是堆?

字如其形,它是一种完全二叉树,就像这样:

不过应该反过来看,像个小山一样:

最上方的出发点叫做堆顶,分出两个枝干叫做子叶,完全二叉树。

堆中节点的值都大于等于(或小于等于)其子节点的值,堆中如果节点的值都大于等于其子节点的值,我们把它称为大顶堆,如果都小于等于其子节点的值,我们将其称为小顶堆。

二.向上调整算法和向下调整算法。

1.理论依据

由于堆只有左右子树所以不难通过计算找到它的“父亲”和“孩子”。

父子节点下标有个规律关系:

1.leftchild=parent*2+1  (奇数)

2rightchild=parent*2+2  (偶数)

int parent=(child-1)/2

通过这个我们可以轻松找到“父亲下标”。

2.代码实现

在创建堆时,我们加入size和capacity变量实时记录并扩容,用数组a储存数据

typedef struct Heap {
	heapdatatype* a;
	int size;
	int capacity;
}Heap;

放入数据与顺序表一样,直接尾插,只不过在最后需要使用“向上调整算法”

void HeapPush(Heap* php, heapdatatype x)
{
	assert(php);
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		heapdatatype* temp = (heapdatatype*)realloc(php->a, sizeof(heapdatatype) * newcapacity);
		if (temp == NULL)
		{
			perror("realloc fail");
		}
		php->a = temp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	Adjustup(php->a, php->size-1);
}

“向上调整算法”

思路是将数组最后加入的元素下标传入,通过反复寻找父亲然后比较,调整至合适的位置。

如图加入数据10:

经过调整后:

void Swap(heapdatatype* p1, heapdatatype* p2)
{
	heapdatatype tem = *p1;
	*p1 = *p2;
	*p2 = tem;
}
void Adjustup(heapdatatype* 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;
	}

}

我们添加几个数据试试:

#include"Heap.h"
int main()
{
	int arr[20] = { 99,78,43,23,14,29,77,44,0,88,22,18,43,55,1,2,3,4,78,5 };
	Heap FirstHeap;
	HeapInit(&FirstHeap);
	for (int i = 0; i < 20; i++)
	{
		HeapPush(&FirstHeap, arr[i]);
	}
	for (int i = 0; i < 20; i++)
	{
		printf("%4d", FirstHeap.a[i]);
	}

	
	HeapDestroy(&FirstHeap);
	return 0;
}

我们将数据用堆的形状表示看看:

完全符合小堆。

“向下调整算法”

如果需要删除数据则需要搭配向下调整算法。

为了保持小堆结构,我们采用将堆末与堆顶元素互换,从而删掉堆末,再通过向下调整算法恢复结构:

通过寻找孩子,比较大小,交换数据,完成调整:

void HeapPop(Heap* php)
{
	assert(php);
	assert(php->size > 0);

	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;

	AdjustDown(php->a, 0,php->size);

}

void AdjustDown(heapdatatype* a, int parent ,int size)
{
	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;
		}
	}
}

需要注意的是,我们将size传了进来,方便判断找到最后的孩子并跳出循环。

同样尝试一下:

删除了15个数据,仍然满足“小堆结构”

3.top K问题

TopK算法是一种用于找到数据集中最大或最小的K个元素的算法。

这里我们就可以借助堆排序完成,首先生成一个大小为K的小队顶。

接着,从第k+1个元素开始扫描,和堆顶(堆中最小的元素)比较,如果被扫描的元素大于堆顶,则替换堆顶的元素,并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。

这种解决方式大量节省了空间和时间。是不是很有趣呢,大家可以下来尝试一下。

感谢阅读,留个三联交个朋友。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

强sir的世界

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

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

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

打赏作者

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

抵扣说明:

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

余额充值