首页 > 其他分享 >【C】堆的实现及应用(Tok问题、排序)

【C】堆的实现及应用(Tok问题、排序)

时间:2023-11-07 21:01:02浏览次数:32  
标签:php int void Tok HPDataType 应用 child 排序 size

文章介绍了堆的基本定义以及代码实现,并针对常用的TopK问题、排序方法做了应用模拟,同时对有可能出现的疑问做了解释说明,希望可以帮助到大家,如有错误,还望不吝赐教。

一、基本知识

在百度百科中,基本定义如下: image.png简陋一些讲,就是一棵完全二叉树,其中任意节点的值都大于(小于)它的孩子节点,称为大根(小根)堆; 由于完全二叉树常采用顺序结构存储,故堆也通常采用顺序结构的数组实现。

二、堆的实现(以小堆为例)

a.结构体实现

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
// 初始化
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = 0;
	php->size = 0;
}

b.堆的调整

向下调整算法

插入数据后,依次与孩子比较,慢慢“沉底”。 运行步骤:1、确定调整的位置;2、比较其孩子大小;3、若存在孩子比它小,则将最小的孩子与它交换;若不存在,则退出调整,表明此时已调整完成;4、继续调整:从交换后的位置继续2、3步骤。 前提:左右子树必须是小堆;why:若其左右子树存在不为小堆的情况,则交换后,上一层有可能不满足小堆条件,从而导致调整失效,如下例: image.png用途:常用于堆顶插入数据时,对堆的调整。 代码如下:

void AdjustDown(HPDataType* a, int n, int parent)
{
	// 初值给左孩子
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 右孩子更小,child指向右孩子
		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 AdjustUp(HPDataType* a, int child)
{
	assert(a);
	// 找父亲
	int parent = (child - 1) / 2;
	// child大于0,表示还有父节点未参与比较
	while (child > 0)
	{
		if (a[child] < a[parent])
		{
			// 子结点比父节点小,交换值,继续走
			Swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		// 父节点>子结点,根据小根堆定义,往上的结点都大于此结点,于是退出循环
		else
		{
			break;
		}
	}
}

c.堆的基本操作

插入数据

void HeapPush(HP* php, HPDataType x)
{
	assert(php);
	// 空间不足,调整空间
	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}

		php->a = tmp;
		php->capacity = newcapacity;
	}
	// 尾部插入数据
	php->a[php->size++] = x;
	// 向上调整
	AdjustUp(php->a, php->size - 1);
}
// 以数组a中数据建堆
void HeapInitArray(HP* php, int* a, int n)
{
	assert(php);
	assert(a);

	php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (php->a == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	php->size = n;
	php->capacity = n;

	memcpy(php->a, a, sizeof(HPDataType) * n);
	// 建堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(php->a, i);
	}
}

删除数据

从堆顶删除最小的数;

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	// 将堆顶数据放置到最后
	Swap(&php->a[php->size - 1], &php->a[0]);
	php->size--;

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

其余基本操作

// 打印堆
void HeapPrint(HP* php)
{
	assert(php);

	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}
// 销毁堆
void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}
// 堆顶数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);

	return php->a[0];
}
// 堆判空
bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

三、堆的应用

1、TopK问题求解

存在大量数据时,需要求前k个最大或最小的数据,当数据量太大时,排序有可能会出错(数据量太大以至于不能直接加载入内存中),此时可以应用堆来解决。

步骤:

1、取前k个数据建堆; 2、依次读取随后数据,将满足要求的进堆; 3、读取完所有数据,堆中即为前k个所需的数据。

问题:当需要前k个最大的数据时,建小堆还是大堆?

建小堆。小堆建立后,堆顶即为堆中最小的数据,此时若有数据比它大,入堆即可,依次替换便可保证堆中的数据慢慢刷新为前k个最大的数; 而若建大堆,堆顶数据为堆中最大的数据,此时新数据与堆顶数据比较不能得到新数据是否满足要求,因为不能确定它是否大于堆中所有的数据。 代码如下:

 // 模拟数据
void CreateNDate()
{
	// 造数据
	int n = 10000;
	srand(time(0));

	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		exit(-1);
	}
	for (size_t i = 0; i < n; i++)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}

	fclose(fin);
}

// 实现代码
void PrintTopK(const char* filename, int k)
{
	// 建堆
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		printf("fopen failed");
		exit(-1);
	}
	int* minheap = (int*)malloc(sizeof(int) * k);
	if (minheap == NULL)
	{
		printf("malloc failed");
		exit(-1);
	}
	// 将前k个数输入到堆中
	for (size_t i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);
	}
	// 调整为小堆,完全二叉树中若有n个节点,则其最后一个节点的序号为(k-2)/2,
	for (int i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(minheap, k, i);
	}

	// 将剩下的元素依次与堆顶元素比较,大则入堆
	int x = 0;
	while (fscanf(fout, "%d", &x) != EOF)
	{
		if (x > minheap[0])
		{
			// 入堆
			minheap[0] = x;
			AdjustDown(minheap, k, 0);
		}
	}
	for (size_t i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");

	free(minheap);
	fclose(fout);
}

2、堆排序

实现步骤,将原有数据调整为堆之后,依次将堆顶数据至于末尾,再依次调整。

建堆时,为何从(n-2)/2处开始调整?

最后一个分支节点,要么子树为空,要么子树只有一个节点,所以满足堆的要求,因此i从最后一个分支节点开始往前调整。令最后一个分支节点的下标为i,两种情况求解分别如下: 总共n个节点

  • 只有左子树 左子树下标为$2i+1$,所以有$2i+1+1 = n$,故$i = (n-2)/2$,由完全二叉树性质可得,只有左子树时,节点总数n为偶数。
  • 左右子树都有 右子树下标为$2i+2$,有$2i+2+1 = n$,故$i=(n-3)/2$,又由完全二叉树性质可得,此时节点总数n为奇数,所以$(n-3)/2 = (n-2)/2$。

由上,从$i=(n-2)/2$时开始调整。

代码如下:

void Heapsort(int* a, int n)
{
	// 降序,考虑建小堆,然后依次执行类似pop操作,将堆顶置于数组尾部
	// 将原有数据调整为堆,向下调整建堆
	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	// 排序
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

标签:php,int,void,Tok,HPDataType,应用,child,排序,size
From: https://blog.51cto.com/u_15423682/8239591

相关文章

  • hashmap的小应用---投票去旅游
    在学习了map之后,使用简单的hashmap进行简单的全班同学投票旅游地点packagecom.itheima.myMap;importjava.util.*;importjava.util.function.BiConsumer;publicclassText2{publicstaticvoidmain(String[]args){//模拟投票Randomra=newRandom......
  • sharding分表应用笔记(一)——分表数据源配置
    sharding分表应用笔记(一)——分表数据源配置目录sharding分表应用笔记(一)——分表数据源配置1前言2配置2.1相关依赖2.2命名空间配置2.2.1引入sharding命名空间2.2.2物理数据源配置2.2.3分表数据源配置3外部链接1前言应用背景:物理数据源只有一个;对于部分数据量大的表实行......
  • golang中 String bytes rune 和 字符概念与应用
    一、引入问题-为何打印s[0]没有打印‘你’字符packagemainimport"fmt"funcmain(){ s:="你" fmt.Println(s[0]) fmt.Printf("%s\n",s[0])}output%!s(uint8=228)首先需要知道go中编码格式和String类型,Go内置的utf-8编码格式。二、utf-8编码与Unicode......
  • 数字化智慧工地平台的应用层面分析
    智慧工地在应用层面能够带来怎样的优势?数字化给各行各业所带来的改变,在早些年间突出自动这一流程。但随着科技的发展,让人们也愈发了解可视化操作所带来的优势。智慧工地的诞生,相当于为建筑施工带来了一套较为完整的数字化流程,能够完善施工环节中的各部分内容。接下来,就让我们详细了......
  • PyQt5-如何设置应用和窗口的图标?控件的提示信息如何设置?
    (PyQt5-如何设置应用和窗口的图标?控件的提示信息如何设置?)1如何设置应用和窗口的图标?1.1导入需要的包QApplication类是PyQt5的应用程序类;QMainWindow类是一个主窗口类;QIcon类用于创建和管理图标;ctypes是python的一个函数库,提供和C语言兼容的数据类型,可以直接调用动态链接......
  • Python的应用领域
    web开发Python在web开发上有很多框架Django、Flask、Tornado等众多框架在我国豆瓣、美团、知乎都使用Python做基础的设施建设数据分析和科学计算:Python有着众多的第三方库的支持 方便帮助数据分析人员去完成数据分析和可视化的操作 人工智能和机器学习可使用第三方库可以......
  • DRF的过滤和排序
    搜索组件、过滤排序组件'''排序:fromrest_framework.filtersimportOrderingFilter按id正序倒叙排序,按price正序倒叙排列使用:http://127.0.0.1:8000/course/free/?ordering=-id配置类:filter_backends=[OrderingFilter]配置字段:ordering_fields=['id','price&......
  • 岩土工程铁路桥梁监测中智能振弦传感器的应用方案
    岩土工程铁路桥梁监测中智能振弦传感器的应用方案智能振弦传感器是近年来岩土工程和桥梁监测领域的重要技术之一。它具有高灵敏度、高精度、高可靠性等优点,并且能够实时对结构物振动进行监测和分析。本文针对岩土工程铁路桥梁监测中智能振弦传感器的应用方案进行探讨和分析。 ......
  • Util应用框架基础(五) - 异常处理
    本节介绍Util应用框架如何处理系统错误.概述系统在运行过程中可能发生错误.系统错误可以简单分为两类:系统异常系统本身出现的错误.业务异常不满足业务规则出现的错误.如何处理系统异常如果发生系统异常,大多数情况下,你除了记录异常日志外,可能无法处理它们.一个......
  • 常见数组的排序算法的特点
    假设这些排序算法想得到一个升序序列,长度为n。参考https://blog.csdn.net/qq_53414724/article/details/125016223https://zhuanlan.zhihu.com/p/602971700冒泡排序冒泡排序从头开始寻找相邻的元素,找到较大的元素放于后面,一直到数组末尾。循环n-1轮,每一轮都从0开始,但结束位置......