欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > C语言实现堆结构及Top - K问题求解

C语言实现堆结构及Top - K问题求解

2025/3/16 15:53:06 来源:https://blog.csdn.net/nplplus/article/details/146284773  浏览:    关键词:C语言实现堆结构及Top - K问题求解

目录

​编辑

一、引言 

二、代码整体结构 

三、代码详细分析 

(一)头文件包含与宏定义 

(二)堆数据结构定义 

(三)堆的基本操作实现 

堆初始化( HPinit  函数) 

交换函数( Swap  函数) 

向上调整( ADjustUp  函数) 

插入操作( HPPush  函数) 

向下调整( ADjustDown  函数) 

删除操作( HPPop  函数) 

获取堆顶元素( HeapTop  函数) 

判断堆是否为空( HeapEmpty  函数) 

获取堆的大小( HeapSize  函数) 

堆销毁( HPDestory  函数) 

(四)Top - K问题相关函数 

生成测试数据( Creattopk  函数) 

找出Top - K元素( Printtopk  函数) 

(五)主函数 

​四、总结 


一、引言
 


在数据处理和算法实现中,是一种非常重要的数据结构,常用于优先队列、排序等场景。本文将详细介绍一段C语言代码,该代码实现了堆数据结构,并利用堆解决了Top - K问题,即从大量数据中找出最大的K个元素
 


二、代码整体结构
 


代码主要分为以下几个部分:
 
头文件包含与宏定义:引入必要的头文件,并使用宏定义消除一些函数的安全警告。
 
堆数据结构定义:定义堆的数据类型和相关操作函数的声明。
 
堆的基本操作实现:包括堆的初始化、插入、删除、获取堆顶元素等操作。
 
Top - K问题相关函数:生成测试数据的函数和找出Top - K元素的函数。
 
主函数:程序的入口,调用相关函数完成任务。
 


三、代码详细分析
 


(一)头文件包含与宏定义
 


c#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <time.h>


 
 #define _CRT_SECURE_NO_WARNINGS  用于消除在使用一些旧版C函数(如  fopen 、 fscanf  等)时编译器产生的安全警告。后面依次引入了标准输入输出头文件、标准库头文件、断言头文件、布尔类型头文件和时间头文件,为后续的文件操作、内存分配、条件判断和随机数生成等功能提供支持。
 


(二)堆数据结构定义
 


 
定义了一个  HPDateType  类型(目的是提高代码的可操作性),这里简单地将其定义为  int  类型,表示堆中存储的数据类型。然后定义了一个结构体  HP  来表示堆,其中  a  是一个指向堆数据的指针, size  表示当前堆中元素的个数, capacity  表示堆的容量。
 


(三)堆的基本操作实现
 


堆初始化( HPinit  函数)
 


cvoid HPinit(HP* php)
{assert(php);php->a = (HPDateType*)malloc(sizeof(HPDateType) * 4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}


 
 
函数用于初始化一个堆。首先通过  assert  宏确保传入的堆指针不为空。然后使用  malloc  函数为堆分配初始容量为4的内存空间,并检查分配是否成功。如果分配失败,使用  perror  打印错误信息并返回。最后将堆的元素个数初始化为0,容量初始化为4。
 


交换函数( Swap  函数)
 


cvoid Swap(HPDateType* e1, HPDateType* e2)
{HPDateType tmp = *e1;*e1 = *e2;*e2 = tmp;
}


 
用于交换两个堆元素的值,是堆调整过程中的辅助函数。(提高便利性,函数中多次出现交换操作)
 


向上调整( ADjustUp  函数)
 


cvoid ADjustUp(HPDateType* 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;}}
}


 
当向堆中插入一个新元素时,需要将新元素向上调整以维护堆的性质(这里实现的是大顶堆,即父节点的值大于等于子节点的值)。函数从新插入元素的位置开始,通过比较新元素与其父元素的值,如果新元素更大,则交换它们的位置,直到满足堆的性质。
 


插入操作( HPPush  函数)
 


cvoid HPPush(HP* php, HPDateType x)
{assert(php);if (php->size == php->capacity){int* tmp = (HPDateType*)realloc(php->a, sizeof(HPDateType) * php->capacity * 2);if (tmp == NULL){perror("relloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;ADjustUp(php->a, php->size - 1);
}


 
向堆中插入一个新元素。首先检查堆是否已满,如果已满则使用  realloc  函数将堆的容量扩大为原来的两倍,并检查内存重新分配是否成功。然后将新元素插入到堆的末尾,并调用  ADjustUp  函数将新元素向上调整,以维护堆的性质。
 


向下调整( ADjustDown  函数)
 


cvoid ADjustDown(HPDateType* 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;}}
}


 
当删除堆顶元素或对堆进行其他需要重新调整的操作时,使用向下调整函数。函数从指定的父节点位置开始,比较父节点与其子节点的值,如果存在更大的子节点,则将父节点与该子节点交换位置,并继续向下调整,直到满足堆的性质。
 


删除操作( HPPop  函数)
 


cvoid HPPop(HP* php)
{assert(php);assert(!HeapEmpty(php));Swap(&php->a[0], &php->a[php->size - 1]);php->size--;ADjustDown(php->a, php->size, 0);
}


 
删除堆顶元素。首先确保堆指针不为空且堆不为空。然后将堆顶元素与堆的最后一个元素交换位置,减少堆的元素个数,并调用  ADjustDown  函数从堆顶开始向下调整,以维护堆的性质。
 


获取堆顶元素( HeapTop  函数)
 


cHPDateType HeapTop(HP* php)
{assert(php);return php->a[0];
}


 
返回堆顶元素,前提是堆指针不为空。
 


判断堆是否为空( HeapEmpty  函数)
 


cbool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}


 
判断堆是否为空,返回  true  表示堆为空, false  表示堆不为空。
 


获取堆的大小( HeapSize  函数)
 


cint HeapSize(HP* php)
{assert(php);return php->size;
}


 
返回堆中当前元素的个数。
 


堆销毁( HPDestory  函数)
 


cvoid HPDestory(HP* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = php->size = 0;
}


 
释放堆所占用的内存空间,并将堆的相关属性(指针、容量和元素个数)设置为初始状态。
 


(四)Top - K问题相关函数
 


生成测试数据( Creattopk  函数)
 


cvoid Creattopk()
{int n = 10000;srand((unsigned int)time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen");return;}for (size_t i = 0; i < n; i++){int x = rand() % 10000;fprintf(fin, "%d\n", x);}fclose(fin);
}


 
该函数用于生成包含10000个随机整数(范围在0到9999之间)的测试数据,并将这些数据写入名为  data.txt  的文件中。首先设置随机数种子,然后打开文件,如果文件打开失败则打印错误信息并返回。接着通过循环生成随机数并写入文件,最后关闭文件。
 


找出Top - K元素( Printtopk  函数)
 


cvoid Printtopk(const char* file, int k)
{// 建立小堆int* topk = (int*)malloc(sizeof(int) * k);assert(topk);// 直接使用传入的 file 参数打开文件FILE* fout = fopen(file, "r");if (fout == NULL){perror("foopen error");return;}// 读出前 K 个建立小堆for (int i = 0; i < k; i++){fscanf(fout, "%d", &topk[i]);}for (int i = (k - 1 - 1) / 2; i >= 0; i--){ADjustDown(topk, k, i);}// 将剩余 n-k 个依次遍历int val = 0;int ret = fscanf(fout, "%d", &val);while (ret != EOF){if (val > topk[0]){topk[0] = val;ADjustDown(topk, k, 0);}ret = fscanf(fout, "%d", &val);}// 打印格式调整,使用空格分隔for (int i = 0; i < k; i++){printf("%d ", topk[i]);}printf("\n");free(topk);fclose(fout);
}


 
函数用于从指定文件中找出最大的K个元素。首先分配一个大小为K的数组  topk  用于存储Top - K元素。然后打开文件,如果文件打开失败则打印错误信息并返回。接着读取文件中的前K个元素并构建一个小顶堆(因为要找出最大的K个元素,使用小顶堆可以保证堆顶是这K个元素中的最小值,方便后续比较)。之后遍历文件中剩余的元素,如果某个元素大于堆顶元素,则将堆顶元素替换为该元素,并重新调整小顶堆。最后打印出这K个最大的元素,并释放内存和关闭文件。

因为大小堆存储空间复杂度大堆O(N)\log N,小堆O(N),
 



(五)主函数
 


cint main()
{// 移除注释掉的测试代码Creattopk();// 修正文件名Printtopk("data.txt", 10);return 0;
}


 
主函数是程序的入口,先调用  Creattopk  函数生成测试数据,然后调用  Printtopk  函数从生成的数据文件中找出最大的10个元素并打印出来,最后返回0表示程序正常结束。
 

四、总结
 


通过上述代码,我们实现了堆数据结构的基本操作,并利用堆解决了Top - K问题。堆作为一种高效的数据结构,在很多实际场景中都有广泛应用,理解其原理和实现方式对于提升编程能力和算法设计能力具有重要意义。同时,在代码编写过程中,要注意内存管理、错误处理等细节,以确保程序的健壮性和稳定性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词