当前位置: 首页 > news >正文

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  函数)
 


c
  
void 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  函数)
 


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


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


向上调整( ADjustUp  函数)
 


c
  
void 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  函数)
 


c
  
void 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  函数)
 


c
  
void 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  函数)
 


c
  
void 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  函数)
 


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


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


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


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


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


获取堆的大小( HeapSize  函数)
 


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


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


堆销毁( HPDestory  函数)
 


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


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


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


生成测试数据( Creattopk  函数)
 


c
  
void 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  函数)
 


c
  
void 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),
 



(五)主函数
 


c
  
int main()
{
    // 移除注释掉的测试代码

    Creattopk();
    // 修正文件名
    Printtopk("data.txt", 10);

    return 0;
}
 


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

四、总结
 


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

相关文章:

  • C#opencv 遍历图像中所有点 不在圆范围内的点变为黑色,在圆范围内的保持原色
  • Gemini 2.0 Flash:AI 图像生成的革命性突破!
  • c++三级(枚举问题)
  • 练习题:87
  • Go vs Rust vs C++ vs Python vs Java:谁主后端沉浮
  • Mybatis——基础操作、动态SQL
  • 【STM32】USART串口收发HEX数据包收发文本数据包
  • 2024年12月CCF-GESP编程能力等级认证C++编程三级真题解析
  • Vue前端项目部署到宝塔面板的详细过程
  • VSCode通过SSH免密远程登录Windows服务器
  • 搭建Spring Boot Admin监控系统
  • 【SpringBoot】最佳实践——JWT结合Redis实现双Token无感刷新
  • 写时拷贝技术
  • 第三周日志-周末看书(3)
  • 计算机组成原理常用的计数单位(性能指标的计算)持续更新
  • 02自动化测试常用函数
  • 多页pdf转长图
  • 《重新审视深度部分标签学习中的一致性正则化》2022年ICML论文精读
  • 寄生虫仿生算法:基于寄生虫特征的算法设计
  • c语言整理
  • 国际博物馆日|在辽宁省博物馆遇见敦煌
  • 上海天文馆走进徐家汇书院,XR沉浸式天文科普体验再推力作
  • 新时代,新方志:2025上海地方志论坛暨理论研讨会举办
  • 国家统计局公布2024年城镇单位就业人员年平均工资情况
  • 深圳南澳码头工程环评将再次举行听证会,项目与珊瑚最近距离仅80米
  • 曾犯强奸罪教师出狱后办教培机构?柳州鱼峰区教育局回应