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

news2025/4/16 2:37:47

前言

上一篇文章讲解了堆的概念和堆排序,本文是对堆的内容补充
主要包括:堆排序的时间复杂度、TOP

这里写目录标题

    • 前言
    • 正文
      • 堆排序的时间复杂度
      • TOP-K

正文

堆排序的时间复杂度

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

#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.先把前面学过的代码拿过来,这里刚好可以复习一下如何用向下调整法建堆

//求最大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个随机数

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)关闭文件

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;
}

最小值代码

//求最小几个数
#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

atypica.AI:用「语言模型」为「主观世界」建模

人们不是在处理概率&#xff0c;而是在处理故事。 —— 丹尼尔卡尼曼 People dont choose between things, they choose between descriptions of things. —— Daniel Kahneman 商业研究是一门理解人类决策的学问。人并不只是根据纯粹理性做决策&#xff0c;而是受到叙事、情…

LLaMA-Factory双卡4090微调DeepSeek-R1-Distill-Qwen-14B医学领域

unsloth单卡4090微调DeepSeek-R1-Distill-Qwen-14B医学领域后&#xff0c;跑通一下多卡微调。 1&#xff0c;准备2卡RTX 4090 2&#xff0c;准备数据集 医学领域 pip install -U huggingface_hub export HF_ENDPOINThttps://hf-mirror.com huggingface-cli download --resum…

【WPF】自定义控件:ShellEditControl-同列单元格编辑支持文本框、下拉框和弹窗

需要实现表格同一列&#xff0c;单元格可以使用文本框直接输入编辑、下拉框选择和弹窗&#xff0c;文本框只能输入数字&#xff0c;弹窗中的数据是若干位的二进制值。 本文提供了两种实现单元格编辑状态下&#xff0c;不同编辑控件的方法&#xff1a; 1、DataTrigger控制控件的…

Seq2Seq - GRU补充讲解

nn.GRU 是 PyTorch 中实现门控循环单元&#xff08;Gated Recurrent Unit, GRU&#xff09;的模块。GRU 是一种循环神经网络&#xff08;RNN&#xff09;的变体&#xff0c;用于处理序列数据&#xff0c;能够更好地捕捉长距离依赖关系。 ⭐重点掌握输入输出部分输入张量&#…

从零开始学Python游戏编程19-游戏循环模式1

在《从零开始学Python游戏编程18-函数3》中提到&#xff0c;可以对游戏代码进行重构&#xff0c;把某些代码写入函数中&#xff0c;主程序再调用这些函数&#xff0c;这样使得代码程序更容易理解和维护。游戏循环模式实际上也是把代码写入到若干个函数中&#xff0c;通过循环的…

Java获取终端设备信息工具类

在很多场景中需要获取到终端设备的一些硬件信息等&#xff0c;获取的字段如下&#xff1a; 返回参数 参数含义备注systemName系统名称remoteIp公网iplocalIp本地ip取IPV4macmac地址去掉地址中的"-“或”:"进行记录cpuSerialcpu序列号hardSerial硬盘序列号drive盘符…

【Linux网络与网络编程】08.传输层协议 UDP

传输层协议负责将数据从发送端传输到接收端。 一、再谈端口号 端口号标识了一个主机上进行通信的不同的应用程序。在 TCP/IP 协议中&#xff0c;用 "源IP"&#xff0c;"源端口号"&#xff0c;"目的 IP"&#xff0c;"目的端口号"&…

没音响没耳机,把台式电脑声音播放到手机上

第一步&#xff0c;电脑端下载安装e2eSoft VSC虚拟声卡&#xff08;安装完成后关闭&#xff0c;不要点击和设置&#xff09; 第二步&#xff0c;电脑端下载安装&#xff08;SoundWire Server&#xff09;&#xff08;安装完成后不要关闭&#xff0c;保持默认配置&#xff09; 第…

XDocument和XmlDocument的区别及用法

因为这几天用到了不熟悉的xml统计数据&#xff0c;啃了网上的资料解决了问题&#xff0c;故总结下xml知识。 1.什么是XML?2.XDocument和XmlDocument的区别3.XDocument示例1示例2&#xff1a;示例3&#xff1a; 4.XmlDocument5.LINQ to XML6.XML序列化(Serialize)与反序列化(De…

Blender安装基础使用教程

本博客记录安装Blender和基础使用&#xff0c;可以按如下操作来绘制标靶场景、道路标识牌等。 目录 1.安装Blender 2.创建面板资源 步骤 1: 设置 Blender 场景 步骤 2: 创建一个平面 步骤 3: 将 PDF 转换为图像 步骤 4-方法1: 添加材质并贴图 步骤4-方法2&#xff1a;创…

【Git】从零开始使用git --- git 的基本使用

哪怕是野火焚烧&#xff0c;哪怕是冰霜覆盖&#xff0c; 依然是志向不改&#xff0c;依然是信念不衰。 --- 《悟空传》--- 从零开始使用git 了解 Gitgit创建本地仓库初步理解git结构版本回退 了解 Git 开发场景中&#xff0c;文档可能会经历若干版本的迭代。假如我们不进行…

Android 中支持旧版 API 的方法(API 30)

Android 中最新依赖库的版本支持 API 31 及以上版本&#xff0c;若要支持 API30&#xff0c;则对应的依赖库的版本就需要使用旧版本。 可通过修改模块级 build.gradle 文件来进行适配。 1、android 标签的 targetSdk 和 compileSdk 版本号 根据实际目标设备的 android 版本来…

[特殊字符] Hyperlane:Rust 高性能 HTTP 服务器库,开启 Web 服务新纪元!

&#x1f680; Hyperlane&#xff1a;Rust 高性能 HTTP 服务器库&#xff0c;开启 Web 服务新纪元&#xff01; &#x1f31f; 什么是 Hyperlane&#xff1f; Hyperlane 是一个基于 Rust 语言开发的轻量级、高性能 HTTP 服务器库&#xff0c;专为简化网络服务开发而设计。它支…

RIP V2路由协议配置实验CISCO

1.RIP V2简介&#xff1a; RIP V2&#xff08;Routing Information Protocol Version 2&#xff09;是 RIP 路由协议的第二版&#xff0c;属于距离矢量路由协议&#xff0c;主要用于中小型网络环境。相较于 RIP V1&#xff0c;RIP V2 在功能和性能上进行了多项改进&#xff0c…

《LNMP架构+Nextcloud私有云超维部署:量子级安全与跨域穿透实战》

项目实战-使用LNMP搭建私有云存储 准备工作 恢复快照&#xff0c;关闭安全软件 [rootserver ~]# setenforce 0[rootserver ~]# systemctl stop firewalld搭建LNMP环境 [rootserver ~]# yum install nginx mariadb-server php* -y# 并开启nginx服务并设置开机自启 [r…

3DMAX笔记-UV知识点和烘焙步骤

1. 在展UV时&#xff0c;如何点击模型&#xff0c;就能选中所有这个模型的uv 2. 分多张UV时&#xff0c;不同的UV的可以设置为不同的颜色&#xff0c;然后可以通过颜色进行筛选。 3. 烘焙步骤 摆放完UV后&#xff0c;要另存为一份文件&#xff0c;留作备份 将模型部件全部分成…

【新人系列】Golang 入门(十三):结构体 - 下

✍ 个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4dd; 专栏地址&#xff1a;https://blog.csdn.net/newin2020/category_12898955.html &#x1f4e3; 专栏定位&#xff1a;为 0 基础刚入门 Golang 的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们…

Spring Boot 自定义商标(Logo)的完整示例及配置说明( banner.txt 文件和配置文件属性信息)

Spring Boot 自定义商标&#xff08;Logo&#xff09;的完整示例及配置说明 1. Spring Boot 商标&#xff08;Banner&#xff09;功能概述 Spring Boot 在启动时会显示一个 ASCII 艺术的商标 LOGO&#xff08;默认为 Spring 的标志&#xff09;。开发者可通过以下方式自定义&a…

Ubuntu虚拟机Linux系统入门

目录 一、安装 Ubuntu Linux 20.04系统 1.1 安装前准备工作 1.1.1 镜像下载 1.1.2 创建新的虚拟机 二、编译内核源码 2.1 下载源码 2.2 指定编译工具 2.3 将根文件系统放到源码根目录 2.4 配置生成.config 2.5 编译 三、安装aarch64交叉编译工具 四、安装QEMU 五、…

【蓝桥杯】2025省赛PythonB组复盘

前言 昨天蓝桥杯python省赛B组比完&#xff0c;今天在洛谷上估了下分&#xff0c;省一没有意外的话应该是稳了。这篇博文是对省赛试题的复盘&#xff0c;所给代码是省赛提交的代码。PB省赛洛谷题单 试题 A: 攻击次数 思路 这题目前有歧义&#xff0c;一个回合到底是只有一个…