数据结构:Heap堆应用(堆排序,TOP-K问题)手把手带你入门数据结构~

微软技术分享
优质创作者: 编程框架技术领域
领域专家: 操作系统技术领域
2026-03-29 11:33:17

在学习了堆之后,我们一起来看一看堆的应用~

一、堆排序

1. 数据入堆

在这里插入图片描述


想要让一组数据进行堆排序,我们来想一想,像下面这一组数据要怎么建堆?

在这里插入图片描述


这是一个乱序的数组,将每一个数组的元素取出来向上调整建堆。

在这里插入图片描述


排成如下图所示的样子,就完成数据入堆了~

在这里插入图片描述

在这里插入图片描述

for (int i = 0; i < n; i++)
{
    AdjustUp(arr, i);
}

2. 堆排序

现在我们已经有了一个小根堆,那么我们就可以将堆排成降序结构。

小根堆排降序 大根堆排升序

在这里插入图片描述

//循环将堆顶数据跟最后位置(会变化,每次减少一个数据)的数据进行交换
int end = n - 1;
while (end > 0)
{
    Swap(&arr[0], &arr[end]);
    AdjustDown(arr, 0, end);
    end--;
}
void AdjustDown(HPDataType* arr, int parent, int n)
{
    int child = parent * 2 + 1;//左孩子
    //while (parent < n)
    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 AdjustUp(HPDataType* arr, int child)
{
    int parent = (child - 1) / 2;

    while (child > 0)//不需要等于,child只要走到根节点的位置,根节点没有父节点不需要交换
    {
        //建大堆,>
        //建小堆,<
        if (arr[child] < arr[parent])
        {
            Swap(&arr[parent], &arr[child]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }

}

3. 时间复杂度

(1)冒泡排序时间复杂度

时间复杂度为O(n^2)

//冒泡排序
//时间复杂度:0(N^2)
void BubbleSort(int* arr, int n)
{
    for (int i = 0; i < n; i++)
    {
        int exchange = 0;
        for (int j = 0; j < n - i - 1; j++)
        {
            //升序
            if (arr[j] > arr[j + 1])
            {
                exchange = 1;
                Swap(&arr[j], &arr[j + 1]);
            }
        }
        if (exchange == 0)
        {
            break;
        }
    }
}

(2)向上调整建堆的时间复杂度

在这里插入图片描述


从这张图可以简单分析得知,向上调整的时间复杂度是log n,而循环有n次,因此时间复杂度为log n。

for (int i = 0; i < n; i++)
{
    AdjustUp(arr, i);
}

那么堆排序和冒泡排序的差距有多大呢?我们看下面这张图。

在这里插入图片描述


因此堆排序的效率高。

下面我们严谨证明一下向下调整算法建堆的时间复杂度:

在这里插入图片描述

(3)向下调整建堆的时间复杂度

那么向下调整怎么建堆呢?

在这里插入图片描述

向下调整算法有⼀个前提:左右⼦树必须是⼀个堆,才能调整。

向下调整算法 • 将堆顶元素与堆中最后⼀个元素进⾏交换 • 删除堆中最后⼀个元素 • 将堆顶元素向下调整到满⾜堆特性为⽌

在这里插入图片描述

//向下调整算法建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
    AdjustDown(arr, i, n);
}

接下来我们来推理一下向下调整建堆的时间复杂度。

在这里插入图片描述


所以用向下调整建堆的方法更优

在这里插入图片描述

因此堆排序的时间复杂度为 O(n* log n)~

4. 堆排序总结

//堆排序
//空间复杂度为0(1)
//时间复杂度为O(n*logn)
void HeapSort(int* arr, int n)
{
    //建堆
    //升序---大堆
    //降序----小堆


    //for (int i = 0; i < n; i++)
    //{
    //    AdjustUp(arr, i);
    //}

    //向下调整算法建堆
    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--;
    }
}

二、TOP-K问题

1. TOP-K问题背景

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

⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了(可能数据都不能⼀下⼦全部加载到内存中)。

2. TOP-K问题解决方法

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

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

2)⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素 将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素.

在这里插入图片描述

3. 创建庞大的数据

将数据写到文件中,为了方便观察,我们可以手动改几个数据

void CreateNDate()
{
    // 造数据
    int n = 100000;
    srand(time(0));
    const char* file = "data.txt";
    FILE* fin = fopen(file, "w");
    if (fin == NULL)
    {
        perror("fopen error");
        return;
    }
    for (int i = 0; i < n; ++i)
    {
        int x = (rand() + i) % 1000000;
        fprintf(fin, "%d\n", x);
    }
    fclose(fin);
}

4. TOP-K问题的解决

void TOPk()
{
    int k = 0;
    printf("请输入k:");
    scanf("%d", &k);

    const char* file = "data.txt";
    FILE* fout = fopen(file, "r");
    if (fout == NULL)
    {
        perror("fopen fail!");
        exit(1);
    }
    int* minHeap = (int*)malloc(k * sizeof(int));
    if (minHeap == NULL)
    {
        perror("malloc fail!");
        exit(2);
    }

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

    //建堆---小堆
    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]);
    }

    fclose(fout);
}

总结

通过上述讲解,相信大家对堆的经典问题已经有了大概的了解,观众老爷们下去要多多练习,感谢支持!

真相永远只有一个!


文章来源: https://jiyufan-allforlove.blog.csdn.net/article/details/142447079
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
98 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

6,680

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧