【数据结构与算法】——堆(补充)

前言

上一篇文章讲解了堆的概念和堆排序,本文是对堆的内容补充

主要包括:堆排序的时间复杂度、TOP

这里写目录标题

正文

堆排序的时间复杂度

前文提到,利用堆的思想完成的堆排序的代码如下(包含向下调整):

c 复制代码
#include <stdio.h>

// 交换两个整数的值
void Swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

// 大顶堆向下调整
void AdjustDown(int* arr, int parent, int n)
{
    int child = parent * 2 + 1; // 左孩子
    while (child < n)
    {
        // 保障右孩子存在且右孩子更大
        if (child + 1 < n && arr[child] < arr[child + 1])
        {
            child++;
        }
        if (arr[child] > arr[parent])
        {
            // 调整
            Swap(&arr[child], &arr[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

// 堆排序
void HeapSort(int* arr, int n)
{
    // 建堆------向下调整算法建堆
    for (int i = (n - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(arr, i, n);
    }
    // 堆排序
    int end = n - 1;
    while (end > 0)
    {
        Swap(&arr[0], &arr[end]);
        AdjustDown(arr, 0, end);
        end--;
    }
}

// 打印数组
void PrintArray(int* arr, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main()
{
    int arr[] = { 4, 1, 3, 2, 16, 9, 10, 14, 8, 7 };
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("排序前的数组: ");
    PrintArray(arr, n);

    HeapSort(arr, n);

    printf("排序后的数组: ");
    PrintArray(arr, n);

    return 0;
}

那么我们如何计算他的时间复杂度呢?
1.建堆过程

堆排序的算法中,首先先是向下调整算法

需要移动结点总的移动步数为:每层结点个数*向下调整次数

列式为:

很明显,这是高中学过的等比数列,利用错位相减法可以算出T(n)

结果是:

向下调整算法建堆时间复杂度为:O(n)

同样我们可以算出向上排序的时间复杂度为:

向上调整算法建堆时间复杂度为:O(n ∗ log2n)

堆排序的时间复杂度为 O(n log n),以下是具体分析:

排序阶段

每次将堆顶元素(最大值)与堆尾元素交换,然后对剩余 (n-1, n-2, \dots, 1) 个元素重新调整堆。每次调整堆的时间为 (O(log n))(堆的高度为 (log n)),共进行 (n-1) 次交换和调整,总时间为

最后汇总

建堆阶段 (O(n)) 和排序阶段 (O(n \log n)) 中,(O(n \log n)) 是主导项,因此堆排序的总时间复杂度为 (O(n log n))

TOP-K

TOP-K问题:即求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。

比如崩坏 星穹铁道活跃度最高的240万个玩家(这只是例子)

对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了

(可能数据都不能⼀下⼦全部加载到内存中)。最佳的⽅式就是⽤堆来解决,基本思路如下:

1)⽤数据集合中前K个元素来建堆

前k个最⼤的元素,则建⼩堆

前k个最⼩的元素,则建⼤堆

2)⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素

将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素

代码如下

1.先把前面学过的代码拿过来,这里刚好可以复习一下如何用向下调整法建堆

c 复制代码
//求最大k个数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

//堆的结构
typedef int HPDataType;
typedef struct Heap
{
    HPDataType* arr;
    int size;    //有效数据个数
    int capacity;//空间大小
}HP;

void Swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
void AdjustDown(HPDataType* arr, int parent, int n)
{
    int child = parent * 2 + 1;//左孩子
    while (child < n)
    {
        //保障右孩子
        if (child + 1 < n && arr[child] < arr[child + 1])
        {
            child++;
        }
        if (arr[child] > arr[parent])
        {
            //调整
            Swap(&arr[child], &arr[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

2.这里没有数据,我们先创造100000个随机数

c 复制代码
void CreatNdata()
{
    //造数据
    int n = 100000;
    srand(time(0));

    const char* file = "data.txt";
    FILE* fin = fopen(file, "w");
    if (fin == NULL)
    {
        perror("fopen fail!");
        return;
    }

    for (int i = 0; i < n; i++)
    {
        int x = (rand() + 1) % n;
        fprintf(fin, "%d\n", x);
    }
    fclose(fin);
}

并且建立"data.txt"文本文件存放,,该文件位置与代码位置一致,打开方法为

右击文件名

点这个

就可以看到这个文件了,选择记事本打开

3.具体写法

1) 输入k值

2)以只读的形式打开文件

3)动态分布一个大小为k的数组

4)先读到k个数据并存到minheap[]

  • 运用 fscanf 函数从文件中读取前 k 个整数,并将它们存储到 minheap 数组里。
  • 从最后一个非叶子节点开始,通过AdjustDown函数对这 k 个数据进行调整,构建一个最小堆。
    5)读取剩余元素并更新最小堆
  • 利用 while 循环持续从文件中读取剩余的数据,直到文件结束(EOF 表示文件结束)。
  • 对于每个读取到的数 x,若它比堆顶元素 minheap[0] 大,就把堆顶元素替换为 x,接着调用 AdjustDown 函数重新调整堆,保证堆仍然是最小堆。

6)关闭文件

c 复制代码
void topk()
{
    //最大的前多少个数
    printf("请输入k>");
    int k = 0;
    scanf("%d", &k);

    const char* file = "data.txt";
    FILE* fout = fopen(file, "r");
    if (fout == NULL)
    {
        perror("fopen error");
        return;
    }
    int val = 0;
    int* minheap = (int*)malloc(sizeof(int) * k);
    if (minheap == NULL)
    {
        perror("malloc fail");
        return;
    }

    for (int i = 0; i < k; i++)
    {
        fscanf(fout, "%d", &minheap[i]);
    }

    //建立k个数据的小堆
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(minheap, i, k);
    }

    int x = 0;
    while (fscanf(fout, "%d", &x) != EOF)
    {
        //读取剩余的数据,谁比堆顶大,就替换它进堆
        if (x > minheap[0])
        {
            minheap[0] = x;
            AdjustDown(minheap, 0, k);
        }
    }

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

    fclose(fout);
}

int main()
{
    CreatNdata();
    topk();
    return 0;
}

最小值代码

c 复制代码
//求最小几个数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

// 堆的结构
typedef int HPDataType;
typedef struct Heap
{
    HPDataType* arr;
    int size;    // 有效数据个数
    int capacity; // 空间大小
}HP;

// 交换两个整数的值
void Swap(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

// 大顶堆向下调整
void AdjustDown(HPDataType* arr, int parent, int n)
{
    int child = parent * 2 + 1; // 左孩子
    while (child < n)
    {
        // 保障右孩子存在且右孩子更大
        if (child + 1 < n && arr[child] < arr[child + 1])
        {
            child++;
        }
        if (arr[child] > arr[parent])
        {
            // 调整
            Swap(&arr[child], &arr[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

// 生成随机数据并保存到文件
void CreatNdata()
{
    // 造数据
    int n = 100000;
    srand(time(0));

    const char* file = "data.txt";
    FILE* fin = fopen(file, "w");
    if (fin == NULL)
    {
        perror("fopen fail!");
        return;
    }

    for (int i = 0; i < n; i++)
    {
        int x = (rand() + 1) % n;
        fprintf(fin, "%d\n", x);
    }
    fclose(fin);
}

// 找出最小的k个数
void topk()
{
    // 最小的前多少个数
    printf("请输入k>");
    int k = 0;
    scanf("%d", &k);

    const char* file = "data.txt";
    FILE* fout = fopen(file, "r");
    if (fout == NULL)
    {
        perror("fopen error");
        return;
    }
    int val = 0;
    int* maxheap = (int*)malloc(sizeof(int) * k);
    if (maxheap == NULL)
    {
        perror("malloc fail");
        return;
    }

    // 读取前k个数据
    for (int i = 0; i < k; i++)
    {
        fscanf(fout, "%d", &maxheap[i]);
    }

    // 建立k个数据的大顶堆
    for (int i = (k - 1 - 1) / 2; i >= 0; i--)
    {
        AdjustDown(maxheap, i, k);
    }

    int x = 0;
    //修改后
    while (fscanf(fout, "%d", &x) != EOF)
    {
        // 读取剩余的数据,谁比堆顶小,就替换它进堆
        if (x < maxheap[0])
        {
            maxheap[0] = x;
            AdjustDown(maxheap, 0, k);
        }
    }

    // 输出最小的k个数
    for (int i = 0; i < k; i++)
    {
        printf("%d ", maxheap[i]);
    }
    printf("\n");

    fclose(fout);
    free(maxheap);
}

int main()
{
    CreatNdata();
    topk();
    return 0;
}
相关推荐
Ludicrouers38 分钟前
【Leetcode-Hot100】和为k的子数组
算法·leetcode·职场和发展
巨可爱熊1 小时前
高并发内存池(定长内存池基础)
linux·运维·服务器·c++·算法
爱数模的小驴3 小时前
2025 年“认证杯”数学中国数学建模网络挑战赛 C题 化工厂生产流程的预测和控制
深度学习·算法·计算机视觉
虔城散人4 小时前
C语言 |位域结构体
c语言
Hanson Huang4 小时前
【数据结构】堆排序详细图解
java·数据结构·排序算法·堆排序
Susea&4 小时前
数据结构初阶:队列
c语言·开发语言·数据结构
序属秋秋秋5 小时前
算法基础_数据结构【单链表 + 双链表 + 栈 + 队列 + 单调栈 + 单调队列】
c语言·数据结构·c++·算法
王鑫的博客8865 小时前
本地git操作
c语言·git
apcipot_rain6 小时前
【密码学——基础理论与应用】李子臣编著 第五章 序列密码 课后习题
算法·密码学
不要不开心了6 小时前
sparkcore编程算子
pytorch·分布式·算法·pygame