数据结构之堆

C语言实现堆及其运用



一、堆的概念及结构

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
堆的性质:
1.堆中某个节点的值总是不大于或不小于其父节点的值
2.堆总是一棵完全二叉树

并且值得注意的是 堆的逻辑结构和物理结构截然不同
如图:
在这里插入图片描述
并且这个结构还有一个非常特殊的规律
父子间下标规律:
父亲找孩子:leftchild = parent * 2 + 1 ------ rightchild = parent * 2 + 2
孩子找父亲:parent = (child - 1) / 2

这个规律极其重要 它帮助了我们控制堆

这时聪明的小伙伴应该已经猜到了堆应该是用数组来实现的
至于为什么用数组 可以参考两者之间时间和空间的复杂度的区别

二、堆的实现

1.堆节点的定义

根据堆总是一棵完全二叉树的这一特征 我们给出堆节点的定义

typedef int HeapDataType;

typedef struct heap
{
	HeapDataType* a;//存储的数据
	int size;//堆的大小
	int capacity;//堆的最大容量
}heap;

2.堆的创建

void HeapCreate(heap* php, HeapDataType* a, int n)

{
	assert(php);
	HeapDataType* tmp = (HeapDataType*)malloc(sizeof(HeapDataType) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	php->a = tmp;
	php->size = 0;
	php->capacity = n;
}

3.堆的销毁

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

	free(php->a);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

4.堆的插入

注意:这里我们实现的堆是从尾部插入的所以我们用向上调整法 并且实现的是小堆序

void HeapPush(heap* php, HeapDataType x)
{
	assert(php);

	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;

		HeapDataType* tmp = (HeapDataType*)realloc(php->a, sizeof(HeapDataType) * newcapacity);

		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}

	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size, php->size - 1);

}

5.向上调整和向下调整

无论的向上调整还是向下调整 我们都需要满足调整前的结构是一个合法的堆

void AdjustUp(HeapDataType* a, int n, 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(HeapDataType* a, int n, int parent)
{

	int child = parent * 2 + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])
		{
			child++;
		}

		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);

			parent = child;
			child = child * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

6.堆的删除

这里需要额外注意的是删除是指删除堆顶数据,并且堆顶数据不能够直接删除,如果直接删除就会破坏堆原有的结构,导致整个结构无法使用,所以我们将堆的最后一个数据与堆顶数据交换 变相将堆顶数据删除 然后让堆顶数据向下调整 保持原有的大堆/小堆结构

void HeapPop(heap* php)
{
	assert(php);

	assert(!HeapEmpty(php));

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

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

7.取堆顶数据

HeapDataType HeapTop(heap* php)
{
	assert(php);

	return php->a[0];
}

8.堆的数据个数

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

	return php->size;
}

9.堆的判空

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

	return php->size == 0 ? 1 : 0;
}

三、堆的运用

这里我们主要介绍堆排序以及TopK问题

1.堆排序

堆排序的原理其实很简单,因为堆就是用数组来实现的,所以需要把这个数组变成大堆或者小堆
但由于堆只规定父子之间的关系,并不在意兄弟之间数据的大小,单单只建堆是完成不了排序的
但堆顶的数据一定是整个堆中最大或者最小的那个,由此我们可以将堆顶的数据与堆的最后一个数据(假设为第N个)进行调换,然后将前N-1个数据进行向下调整 循环操作N-1次 完成排序
根据以上操作 我们不难得出以下结果
升序———建大堆
降序———建小堆

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

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

2.TopK问题

TopK问题是指在N个数中获得K个最大或者最小的数
如果我们用普通方法必定需要进行排序,当N很大时,这时的时间与空间的复杂度会变得很高,排序的成本很大
所以我们可以建一个大小为K的大堆或者小堆 当数据比堆顶数据大或者小的时候选择入堆,而当走完N个数据之后
堆中的数据就是我们需要的K个数据

void DataCreat(int n)
{
	srand((unsigned)time(NULL));
	FILE* fp = fopen("data.txt", "w");

	int i = 0;

	for (i = 0; i < n; i++)
	{
		fprintf(fp, "%d\n", rand() % 10000);//这里我们随机生成数据
	}
	fclose(fp);
}



void PrintTopK(int* a, int k)
{
	
	for (int i = 0; i < k; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void TestTopk()
{
	//创造随机数
	int N = 1000;
	DataCreat(N);
	
	int K = 10;
	HeapDataType* kminheap = (HeapDataType*)malloc(sizeof(HeapDataType) * K);
	if (kminheap == NULL)
	{
		perror("malloc file");
		return;
	}


	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen file");
		return;
	}

	for (int i = 0; i < K; i++)
	{
		fscanf(fp, "%d", &kminheap[i]);
	}

	for (int i = (K - 2)/2; i >= 0; i--)
	{
		AdjustDown(kminheap, K, i);
	}

	for (int i = K; i < N; i++)
	{
		int val = 0;
		fscanf(fp, "%d", &val);

		if (val > kminheap[0])
		{
			kminheap[0] = val;
			AdjustDown(kminheap, K, 0);
		}
	}
	fclose(fp);

	PrintTopK(kminheap, K);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值