【数据结构】堆综合的运用

目录

一,运用堆结构进行排序

二,经典的Topk问题


一,运用堆结构进行排序

        当我们用堆结构进行排序时,首先要明确的是升序排列要建大堆,降序排列要建小堆,不是说升序建小堆就不行,降序建大堆就不行,而是如果运用这种建堆方式进行排序的话效率会比较低,下面我会跟大家详细分析。

        首先,我们来回顾一下堆的概念,大堆结构:双亲结点>=孩子结点。小堆结构:双亲结点<=孩子结点。其中,根节点是所有结点的"祖宗",也就是在大堆中,根节点是在堆中最大的数据,在小堆中,根节点是在堆中最小的数据。排升序时,当我们运用建小堆思想,虽然第一个数据已确定序列位置,但后面的数据要想确定序列位置就必须对除去上一次数据外剩下的元素再次建堆,也就是建一次小堆相当于排好一个数据的序列位置。排降序时运用大堆同理。此时的时间效率为O((n*logn)*n)。由此可见,此种方法效率太低。

        当升序用建大堆思想时,大堆中根结点是最大数据,升序序列中最后的数据都是最大的数据,我们只需在大堆中将首元素与尾元素进行交换后即可确定最后一个元素的序列位置,然后再除去大堆中最后一个元素将根结点运用向下调整算法进行调整,这将又得到除去最后一个元素的大堆,以次不断循环,最终将有序。由此可见,大堆排升序是从后到前的排序。

        降序用小堆的思想与之同理,都是先建立堆,然后不断交换确定数据序列位置,都是从后到前的排序。具体代码如下:

运用大堆排升序:

void Swap(int* x, int* y) {
    int t = *x;
    *x = *y;
    *y = t;
}
void AdjustUp(int* a, int child) {
    assert(a);
    int parent = (child - 1) / 2;
    while (parent >= 0) {
        //调整建大堆
        if (a[parent] < a[child]) {
            Swap(a + parent, a + child);
            child = parent;
            parent = (child - 1) / 2;
        }
        if (a[parent] >= a[child]) {
            return;
        }
    }
}
void AdjustDown(int* a, int n, int parent) {
    assert(a);
    int child = parent * 2 + 1;
    while (child < n) {
        //调整建大堆
        if (child + 1 < n && a[child] < a[child + 1]) {
            child++;
        }
        if (a[child] > a[parent]) {
            Swap(a + child, a + parent);
            parent = child;
            child = parent * 2 + 1;
        }
        if (a[child] <= a[parent]) {
            return;
        }
    }
}
void HeadSort(int* a, int n) {
    //运用向上调整算法建大堆
    for (int i = 1; i < n; i++) {
        AdjustUp(a, i);
    }
    //end是堆中最后一个元素的下标
    int end = n - 1;
    while (end > 0) {
        //将堆中最大数据排到最后
        Swap(&a[0], &a[end]);
        //将end个数据建成堆 
        AdjustDown(a, end, 0);//因为根节点的左右子树都可看成大堆,所以只要向下调节根结点就可使end个数据成为大堆
        //后面已排好了一个数据,继续将剩下的数据建堆排序即可

        end--;
    }
}

运用小堆排降序

void Swap(int* x, int* y) {
    int t = *x;
    *x = *y;
    *y = t;
}
void AdjustUp(int* a, int child) {
    assert(a);
    int parent = (child - 1) / 2;
    while (parent >= 0) {
        //调整建小堆
        if (a[parent] > a[child]) {
            Swap(a + parent, a + child);
            child = parent;
            parent = (child - 1) / 2;
        }
        if (a[parent] <= a[child]) {
            return;
        }
    }
}
void AdjustDown(int* a, int n, int parent) {
    assert(a);
    int child = parent * 2 + 1;
    while (child < n) {
        //调整建小堆
        if (child + 1 < n && a[child] > a[child + 1]) {
            child++;
        }
        if (a[child] < a[parent]) {
            Swap(a + child, a + parent);
            parent = child;
            child = parent * 2 + 1;
        }
        if (a[child] >= a[parent]) {
            return;
        }
    }
}
void HeadSort(int* a, int n) {
    //运用向上调整算法建小堆
    for (int i = 1; i < n; i++) {
        AdjustUp(a, i);
    }
    //end是堆中最后一个元素的下标
    int end = n - 1;
    while (end > 0) {
        //将堆中最小数据排到最后
        Swap(&a[0], &a[end]);
        //将end个数据建成堆 
        AdjustDown(a, end, 0);//因为根节点的左右子树都可看成小堆,所以只要向下调节根结点就可使end个数据成为小堆
        //因为后面排好了一个数据,继续将剩下的数据建堆排序即可

        end--;
    }
}

效率分析:

        我们先观察前面的建堆效率:时间复杂度:O(nlogn)     空间复杂度:O(1)

        在后面排序时,每当确定一个数据的序列位置时就要运用调整算法进行调整一次,所以,要对n个数据排序时的时间复杂度:O(nlogn)   空间复杂度:O(1)。因此,运用堆结构排序的整体效率:时间复杂度:O(nlogn)   空间复杂度:O(1)。


二,经典的Topk问题

        Topk问题:假设有100万个数据(也可以是1千万,1亿个数据,只要数据足够大即可),而这些数据内存又存储不下,所以要靠文件来进行存储,在文件中,我们需要找出最大的前k个数据。

        此问题可以用堆结构来完成,具体步骤如下:

        1,读取文件的前k个数据,在内存数组中建立一个大小为k的小堆。

        2,再依次读取剩下的数据与堆顶数据比较,大于堆顶就替换这个数据进堆,然后向下调整,调整后的数据仍为堆,即堆顶数据是堆中最小的数据。

        3,所有数据读完,堆里面数据就是最大的前k个数据。

        首先,我们先造出这么多个数据,将其写入文件中。

//str是文件的路径
void CreatData(char* str) {FILE* file = fopen(str, "w");if (file == 0) {perror("fopen");exit(-1);}//造出1000000个数据,也可以造出更多srand(time(0));for (int i = 0; i < 1000000; i++) {//造出范围在[1, 1000]的数据int n = (rand() + i) % 1000 + 1;fprintf(file, "%d\n", n);}fclose(file);
}

        然后,先建立起小堆结构,再将前k个数据放入小堆中,最后遍历文件数据进行交换。

void CreatHeap(char* str, int k) {//建立堆结构并初始化int* a = (int*)malloc(sizeof(int) * k);if (a == 0) {perror("malloc");exit(-1);}memset(a, 0, sizeof(int) * k);//先将文件中的前k个数据放到堆中,将其调成小堆FILE* file = fopen(str, "r");if (!file) {perror("fopen");exit(-1);}for (int i = 0; i < k; i++) {fscanf(file, "%d", a + i);}for (int i = (k - 2) / 2; i >= 0; i--) {AdjustDown(a, k, i);}//然后遍历文件中剩下数据int x = 0;while (fscanf(file, "%d", &x) != EOF) {if (x > a[0]) {Swap(&x, &a[0]);AdjustDown(a, k, 0);}}//输出for (int i = 0; i < k; i++) {fprintf(stdout, "%d ", a[i]);}puts("");free(a); fclose(file);
}

整套代码如下(我们将k设置为100,即从100万个数据选出100个最大数):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
void Swap(int* x, int* y) {int t = *x;*x = *y;*y = t;
}
void AdjustUp(int* a, int child) {assert(a);int parent = (child - 1) / 2;while (parent >= 0) {if (a[parent] > a[child]) {Swap(a + parent, a + child);child = parent;parent = (child - 1) / 2;}if (a[parent] <= a[child]) {return;}}
}
void AdjustDown(int* a, int n, int parent) {assert(a);int child = parent * 2 + 1;while (child < n) {if (child + 1 < n && a[child] > a[child + 1]) {child++;}if (a[child] < a[parent]) {Swap(a + child, a + parent);parent = child;child = parent * 2 + 1;}if (a[child] >= a[parent]) {return;}}
}
//str是文件的路径
void CreatData(char* str) {//注意:"w"表示以写入方式打开文件并清空文件内容FILE* file = fopen(str, "w");if (file == 0) {perror("fopen");exit(-1);}//造出1000000个数据,也可以造出更多int n = 1000000;srand(time(0));for (int i = 0; i < n; i++) {//造出范围在[1, 1000]的数据int n = (rand() + i) % 1000 + 1;fprintf(file, "%d\n", n);}fclose(file);
}
void CreatHeap(char* str, int k) {//建立堆结构并初始化int* a = (int*)malloc(sizeof(int) * k);if (a == 0) {perror("malloc");exit(-1);}memset(a, 0, sizeof(int) * k);//先将文件中的前k个数据放到堆中,将其调成小堆FILE* file = fopen(str, "r");if (!file) {perror("fopen");exit(-1);}for (int i = 0; i < k; i++) {fscanf(file, "%d", a + i);}for (int i = (k - 2) / 2; i >= 0; i--) {AdjustDown(a, k, i);}//然后遍历文件中剩下数据int x = 0;while (fscanf(file, "%d", &x) != EOF) {if (x > a[0]) {Swap(&x, &a[0]);AdjustDown(a, k, 0);}}//输出for (int i = 0; i < k; i++) {fprintf(stdout, "%d ", a[i]);}puts("");free(a); fclose(file);
}
//删除路径在str中的文件
void DeleteFile(char* str) {if (remove(str) != 0) {fprintf(stdout, "无法删除文件\n");}else {fprintf(stdout, "文件已删除\n");}
}
int main() {//创建巨多数据,将其输出到文件中CreatData("E:\\test.txt");//k为100,建立大小为100的小堆,并将文件中最大的前100个数据放入小堆中CreatHeap("E:\\test.txt", 100);//删除文件DeleteFile("E:\\test.txt");return 0;
}

 效率分析:

        从以上中不难发现:时间复杂度:O(n*logk)。空间复杂度:O(k)


补:从以上两个运用不难发现,堆结构的效率很高,在今后遇到的复杂问题中,我们都可运用高效率的堆结构来进行优化。

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

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

相关文章

【最短路径算法】一文掌握Dijkstra算法,详解与应用示例+代码

目录 1 Dijkstra算法 2 Dijkstra算法的步骤 3 Dijkstra算法python实现 4 Dijkstra算法应用示例详解 1 Dijkstra算法 Dijkstra算法&#xff08;迪杰斯特拉算法&#xff09;是一种用于在加权图中查找从一个起始节点到所有其他节点的最短路径的算法。该算法最初由荷兰计算机科…

offsetof宏计算某变量相对于首地址的偏移量

宏&#xff1a;offsetof的使用 //offsetof (type,member) //type是结构体的类型名&#xff0c;member是结构体中的成员名。struct Student {char name[5]; // 姓名int age; // 年龄float score; // 成绩 };int main() {struct Student s;printf("%zd\n", off…

【转载】 Bytedance火山引擎智能拥塞控制算法 VICC

BytedanceTechBlog : 火山引擎实时、低延时拥塞控制算法的优化实践 火山引擎 网站如何利用播放器节省20%点播成本点击下面的链接进入原文:原创 翟强俊、唐辉 字节跳动技术团队 2023-10-18 11:59 发表于北京 一些专利摘要 火山引擎智能拥塞控制算法 VICC(Volcano Intelligent…

Android微信逆向--实现发朋友圈动态

Android微信逆向--实现发朋友圈动态 0x0 前言# 最近一直在研究Windows逆向的东西&#xff0c;想着快要把Android给遗忘了。所以就想利用工作之余来研究Android相关的技术&#xff0c;来保持对Android热情。调用微信代码来发送朋友圈动态一直是自己想实现的东西&#xff0c;研…

邻接表存储图或者树

大家好&#xff0c;我叫徐锦桐&#xff0c;个人博客地址为www.xujintong.com。平时记录一下学习计算机过程中获取的知识&#xff0c;还有日常折腾的经验&#xff0c;欢迎大家来访。 介绍 每个顶点都作为头节点&#xff0c;并且存在一个一维数组中h[N]。树就是相当于一种有向图…

redis哨兵模式详解

目录 前言&#xff1a; 手动干预主节点挂的情况 哨兵节点操作流程 哨兵重新选取主节点流程 主观下线 客观下线 哨兵节点选leader 挑选从节点作为主节点 前言&#xff1a; redis在主从模式下&#xff0c;主节点服务就显的尤为重要。为了保证redis集群的高可用&#xff0…

论文阅读-FCD-Net: 学习检测多类型同源深度伪造人脸图像

一、论文信息 论文题目&#xff1a;FCD-Net: Learning to Detect Multiple Types of Homologous Deepfake Face Images 作者团队&#xff1a;Ruidong Han , Xiaofeng Wang , Ningning Bai, Qin Wang, Zinian Liu, and Jianru Xue &#xff08;西安理工大学&#xff0c;西安交…

GB28181学习(十)——视音频文件下载

要求 SIP服务器接收到媒体接收者发送的视音频文件下载请求后向媒体流发送者发送媒体文件下载命令&#xff0c;媒体流发送者采用RTP将视频流传输给媒体流接收者&#xff0c;媒体流接收者直接将视频流保存为媒体文件&#xff1b;媒体流接收者或SIP服务器可通过配置查询等方式获取…

【Hello Algorithm】暴力递归到动态规划(五)

死亡概率问题 现在给定你三个参数 N M K 怪兽有 N 滴血 等着英雄来砍自己 英雄每一次打击会让怪兽流失 0 ~ M 滴血 概率相同 请求在K次之后 英雄把怪兽砍死的概率 递归版本 面对这个问题 我们首先来想递归函数怎么写 首先是返回值 我们无法直接返回一个概率给上层 所以我…

gRPC之gRPC转换HTTP

1、gRPC转换HTTP 我们通常把RPC用作内部通信&#xff0c;而使用Restful Api进行外部通信。为了避免写两套应用&#xff0c;我们使用grpc- gateway 把gRPC转成HTTP。服务接收到HTTP请求后&#xff0c;grpc-gateway把它转成gRPC进行处理&#xff0c;然后以JSON 形式返回数据。…

Go 语言的垃圾回收机制:自动化内存管理

在编程的世界中&#xff0c;内存管理一直是一个重要的问题。不正确的内存管理可能导致内存泄漏和程序崩溃。Go 语言以其高效的垃圾回收机制而闻名&#xff0c;使开发者从手动内存管理的烦恼中解脱出来。本文将深入探讨Go语言的垃圾回收机制&#xff0c;介绍它的工作原理以及如何…

消息队列 RocketMQ 消息重复消费问题(原因及解决)

目录 1.出现重复消费的原因 2.解决 2.1 数据库插入法 2.2 使用布隆过滤器 2.2.1 添加hutool的依赖 2.2.2 测试生产者 2.2.2 测试消费者 1.出现重复消费的原因 BROADCASTING(广播) 模式下&#xff0c;所有注册的消费者都会消费&#xff0c;而这些消费者通常是集群部署的…

ubuntu20.04下安装nc

前言 nc在网络渗透测试中非常好用&#xff0c;这里的主要记一下Ubuntu20.04中nc的安装 编译安装 第一种方式是自己编译安装&#xff0c;先下载安装包 nc.zip wget http://sourceforge.net/projects/netcat/files/netcat/0.7.1/netcat-0.7.1.tar.gz/download -O netcat-0.7.…

当你学会这项python数据提取神器时,请做好升职准备!

一、什么是 jsonpath ● JsonPath 是一种信息抽取类库&#xff0c;是从 JSON 文档中抽取指定信息的工具&#xff0c;提供多种语言实现版本&#xff0c;包括&#xff1a;JavaScript、Python、PHP 和 Java。 二、特点 ● 只能提取 JSON 格式的数据 ● 提取后的数据类型与原数据…

2023秋招笔试算法Python3题解

诸神缄默不语-个人CSDN博文目录 签两方了&#xff0c;感觉秋招已经结束了&#xff0c;所以发布一下之前写的笔试编程题题解。 不全。可能有些题我会继续补。 不保证能过。 后续依然有可能继续刷算法题&#xff0c;但是就另外专门写博文来解析了。 打码是因为原则上其实是不让公…

国密https访问

前言 现在的SSL的加密算法实际上主要是国际算法&#xff0c;包括JDK&#xff0c;Go等语言也仅支持国际算法加密&#xff08;毕竟是国外开源项目&#xff09;&#xff0c;hash。随着国密算法的普及&#xff0c;比如openssl就支持国密了&#xff0c;还要新版本的Linux内核也开始…

【Python第三方包】实现自动化(pyautogui包)

文章目录 前言一、如何安装pyautogui二、pyautogui鼠标操作2.1 鼠标移动2.2 鼠标点击2.3 拖动鼠标三、键盘操作3.1 按下和释放按键3.2 键盘输入四、截图和图像识别4.1 截图4.2 图像识别总结前言 自动化是现代计算机编程和软件开发中的一个重要概念。通过自动化,我们可以节省时…

解决因d3dx9_30.dll丢失程序无法运行,电脑缺失d3dx9_30.dll报错解决方案

我们的生活和工作都离不开电脑。然而&#xff0c;电脑作为一种复杂的工具&#xff0c;也会出现各种各样的问题。其中&#xff0c;丢失d3dx9_30.dll文件是一个常见的问题。d3dx9_30.dll是DirectX的动态链接库文件&#xff0c;如果丢失或损坏&#xff0c;可能会导致许多软件和游戏…

[论文笔记]GPT-1

引言 今天带来论文Improving Language Understanding by Generative Pre-Training的笔记,它的中文题目为:通过生成式预训练改进语言理解。其实就是GPT的论文。 自然语言理解可以应用于大量NLP任务上,比如文本蕴含、问答、语义相似和文档分类。虽然无标签文本语料是丰富的,…

Js使用ffmpeg在视频中添加png或gif

Js使用ffmpeg在视频中添加png或gif ffmpeg 使用场景是需要在web端对视频进行编辑 添加图片和gif。 注意: 以下所有的使用案例均基于vue3 setup。 同时由于ffmpeg版本不同会导致使用的api不同&#xff0c;使用案例前需要注意ffmpeg版本问题。 如果使用的是0.12需要使用新的…