【外排序】--- 文件归并排序的实现

news2024/9/24 1:27:23

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     数据结构  


我们之前学习的八大排序:冒泡,快排,插入,堆排等都是内排序,这些排序算法处理的都是在内存中的数据,如果我们要处理的数据量过大,不能一次性装进内存中呢?此时我们需要了解我们的新知识 --- 外排序


🏠 什么是外排序

📒 外排序介绍

外排序(External sorting)是指能够处理极⼤量数据的排序算法。

外排序通常采⽤的是⼀种“排序-归并”的策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到⼀个临时文件,依此进行,将待排序数据组织为多个有序的临时文件。然后在归并阶段将这些临时文件组合为⼀个大的有序文件,也即排序结果。

注意:

1. 通常来说,外排序处理的数据不能一次性装入内存,只能放在读写较慢的外存储器(通常是硬盘上)。

2. 我们之前常见的排序是内排,它们排序思想适应的是数据在内存中支持随机访问。归并排序的思想不需要随机访问数据,只需要依次序列读取数据,所以归并排序既是一个内排序,也是一个外排序

📒 文件归并排序思路分析

   也许有朋友看到文件归并排序会想到,将一个大文件先细分成n个小文件,再各自各自地进行归并,如下图:

这种做法是可行的是但是比较麻烦的是一开始划分小文件的个数以及每个小文件的大小,实现较为复杂,因此我们选择如下的归并思路:

  1.  读取n个值排序后写到file1,再读取n个值排序后写到file2
  2. file1和file2利⽤归并排序的思想,依次读取⽐较,取⼩的尾插到mfile,mfile归并为⼀个有序⽂件
  3. 将file1和file2删掉,mfile重命名为file1
  4. 再次读取n个数据排序后写到file2
  5. 继续⾛file1和file2归并,重复步骤2,直到⽂件中⽆法读出数据。最后归并出的有序数据放到了file1中
动图演示:
这种做法就不需要我们自己去确定划分文件的个数,同时文件大小读取数据固定,而且归并过程中操作文件只有file1 file2这两个文件。

🏠 文件归并排序代码实现

📌 生成大文件

这里我们需要复习C语言库里面的几个关于文件的接口:

fopen  : 打开文件,返回一个FILE*的指针。(const char * filename, const char * mode)

fclose : 关闭文件。( FILE * stream)

fprintf : 将格式化数据写进文件。(FILE * stream, const char * format, ...)

fscanf : 从文件中读取格式化数据。(FILE * stream, const char * format, ...)

具体用法可自行查文档哦 ~

参考代码:

void CreateData()
{
    FILE* fin = fopen("data.txt", "w");
    if (fin == NULL)
    {
        perror("fopen fail");
        return;
    }
    const int N = 1000000;
    srand(time(0));
    //读入N个数 随机数
    for (int i = 0; i < N; i++)
    {
        int num = rand() + i;
        fprintf(fin, "%d\n", num);
    }
    fclose(fin);
}

📌 小文件输入数据并排序

    我们可以利用一个容器利用fscanf从bigfile里读取数据,再用fprintf将读取的数据排序后输入到目标小文件内

//分别读入数据进file1和file2并且排序
void RDatatoSortFile(const char* file1,FILE* bigfile,int n)
{
    vector<int> v;
    v.resize(n);
    FILE* file = fopen(file1, "w");
    if (file == NULL)
    {
        perror("fopen fail");
        return;
    }
    //从bigfile读取n个数据进数组
    for (int i = 0; i < n; i++)
    {
        int num = 0;
        fscanf(bigfile, "%d", &num);
        v.push_back(num);
    }
//排序
    sort(v.begin(), v.end());
    //将数据读入file
    for (int i = 0; i < n; i++)
    {
        fprintf(file, "%d\n", v[i]);
    }
    fclose(file);
    fclose(bigfile);

}

注意:

  • 当bigFile里数据要读取完时,读取数据个数可能不足n个数据,这时我们可以利用fscanf的返回值(读到文件末尾->EOF)和一个变量j来判断是否读完了。
  • 同时我们可以修改返回类型为int,当返回0说明文件都读完了。

修改代码:

//分别读入数据进file1和file2并且排序
int RDatatoSortFile(const char* file1,FILE* bigfile,int n)
{
    vector<int> v;
    v.resize(n);
    FILE* file = fopen(file1, "w");
    if (file == NULL)
    {
        perror("fopen fail");
        return;
    }
    //从bigfile读取n个数据进数组
    int j = 0;
    for (int i = 0; i < n; i++)
    {
        int num = 0;
        num = fscanf(bigfile, "%d", &num);
        if (num == EOF)
            break;
        v.push_back(num);
        j++;
    }
    //数据读完提前返回
    if (j == 0)
        return 0;
    sort(v.begin(), v.end());
    //将数据读入file
    for (int i = 0; i < j; i++)
    {
        fprintf(file, "%d\n", v[i]);
    }
    fclose(file);
  
    return j;
}

注意:输入完数据后不能先fclose(bigfile),这样会导致bigfile文件指针读到了文件末尾,后续输入不进数据因为一直在文件末尾。

📌 合并file1和file2为mfile

  读取file1和file2内的数据,小的先进mfile继续读取这个文件;最后一定有个文件先读完,将剩下的文件继续读完。

参考代码:

void mergeFile(const char* file1,const char* file2,const char* mfile)
{
    FILE* File1 = fopen(file1, "r");
    if (file1 == NULL)
    {
        perror("fopen fail");
        return;
    }

    FILE* File2 = fopen(file2, "r");
    if (file2 == NULL)
    {
        perror("fopen fail");
        return;
    }

    FILE* Mfile = fopen(mfile, "w");
    if (mfile == NULL)
    {
        perror("fopen fail");
        return;
    }
    //fscanf读取数据成功都返回的是1 错误返回的是-1 所以要另外开两个变量接收
    int x1 = 0;
    int ret1 = 0;
    ret1 = fscanf(File1, "%d", &x1);
    int ret2 = 0;
    int x2 = 0;
    ret2 = fscanf(File2, "%d", &x2);
    while (ret1 != EOF && ret2 != EOF)
    {
        if (x1 <x2)
        {
            //读进mfile
            fprintf(Mfile, "%d\n", x1);
            ret1 = fscanf(File1, "%d", &x1);
        }
        else
        {
            fprintf(Mfile, "%d\n", x2);
            ret2 = fscanf(File2, "%d", &x2);
        }
    }
    //没有读完的继续
    while (ret1 != EOF)
    {
        fprintf(Mfile, "%d\n", x1);
        ret1 = fscanf(File1, "%d", &x1);
    }
    
    while (ret2 != EOF)
    {
        fprintf(Mfile, "%d\n", x2);
        ret2 = fscanf(File2, "%d", &x2);
    }
    //关闭文件
    fclose(File1);
    fclose(File2);
    fclose(Mfile);
}

注意:在从大文件读取数据时,要注意的是,fscanf读取数据成功返回的都是1,因此我们需增设两个变量接收读取到的值。

📌总体逻辑

  • 造大文件
  • 分别从大文件读取数据排序好后输入进file1和file2
  • 将file1和file2合并为mfile
  • 删除file1 file2 ,重命名mfile为file1,继续输入数据进file2
  • 不断重复上述过程直到bigfile数据读取完毕

总体参考代码:

//创建大文件
void CreateData()
{
    FILE* fin = fopen("data.txt", "w");
    if (fin == NULL)
    {
        perror("fopen fail");
        return;
    }
    const int N = 100;
    srand(time(0));
    //读入N个数 随机数
    for (int i = 0; i < N; i++)
    {
        int num = rand() + i;
        fprintf(fin, "%d\n", num);
    }
    fclose(fin);
}

//分别读入数据进file1和file2并且排序
int RDatatoSortFile(const char* file1,FILE* bigfile,int n)
{
    int num = 0;
    vector<int> v;
    //注意先提前开空间 因为后面push_back是继续开空间!
    FILE* file = fopen(file1, "w");
    if (file == NULL)
    {
        perror("fopen fail");
        return 0;
    }
    //从bigfile读取n个数据进数组
    int j = 0;
    for (int i = 0; i < n; i++)
    {
        if (fscanf(bigfile, "%d", &num) == EOF)
            break;
        v.push_back(num);
        j++;
    }
    //数据读完提前返回
    if (j == 0)
        return 0;
    sort(v.begin(), v.end());
    //将数据读入file
    for (int i = 0; i < j; i++)
    {
        fprintf(file, "%d\n", v[i]);
    }
    fclose(file);
    //fclose(bigfile);
    return j;
}

void mergeFile(const char* file1,const char* file2,const char* mfile)
{
    FILE* File1 = fopen(file1, "r");
    if (file1 == NULL)
    {
        perror("fopen fail");
        return;
    }

    FILE* File2 = fopen(file2, "r");
    if (file2 == NULL)
    {
        perror("fopen fail");
        return;
    }

    FILE* Mfile = fopen(mfile, "w");
    if (mfile == NULL)
    {
        perror("fopen fail");
        return;
    }
    //fscanf读取数据成功都返回的是1 错误返回的是-1 所以要另外开两个变量接收
    int x1 = 0;
    int ret1 = 0;
    ret1 = fscanf(File1, "%d", &x1);
    int ret2 = 0;
    int x2 = 0;
    ret2 = fscanf(File2, "%d", &x2);
    while (ret1 != EOF && ret2 != EOF)
    {
        if (x1 <x2)
        {
            //读进mfile
            fprintf(Mfile, "%d\n", x1);
            ret1 = fscanf(File1, "%d", &x1);
        }
        else
        {
            fprintf(Mfile, "%d\n", x2);
            ret2 = fscanf(File2, "%d", &x2);
        }
    }
    //没有读完的继续
    while (ret1 != EOF)
    {
        fprintf(Mfile, "%d\n", x1);
        ret1 = fscanf(File1, "%d", &x1);
    }
    
    while (ret2 != EOF)
    {
        fprintf(Mfile, "%d\n", x2);
        ret2 = fscanf(File2, "%d", &x2);
    }
    //关闭文件
    fclose(File1);
    fclose(File2);
    fclose(Mfile);
}



int main()
{
  //CreateData();
    FILE* bigfile = fopen("data.txt", "r");
    if (bigfile == NULL)
    {
        perror("fopen faile");
    }
    const char* file1 = "file1.txt";
    const char* file2 = "file2.txt";
    const char* mfile = "mfile.txt";
    int n = 0;
    cin >> n;
    //1.先分别将数据读入file1和file2
    RDatatoSortFile(file1,bigfile,n);
    RDatatoSortFile(file2, bigfile, n);
    //2.将file1和file2内容合并进mfile 
    //3.将file1和file2删除 mfile改为file1
    while (1)
    {
        mergeFile(file1, file2, mfile);
        // 删除file1和file2                                                                                                                                                                                                                    
        remove(file1);
        remove(file2);
        //重命名mfile为file1
        rename(mfile, file1);
        //归并mfile(file1)和fle2 判断是否读完
        int num = 0;
        num = RDatatoSortFile(file2, bigfile, n);
        if (num == 0)
            break;
    }
    fclose(bigfile);
    return 0;
}

总结:本节我们学习了用于排序内存不能一次性处理的数据量的排序 --- 外排序,同时根据外排序的原理设计了文件归并排序。

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

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

相关文章

java对接kimi详细说明,附完整项目

需求&#xff1a; 使用java封装kimi接口为http接口&#xff0c;并把调用kimi时的传参和返回数据&#xff0c;保存到mysql数据库中 自己记录一下&#xff0c;以做备忘。 具体步骤如下&#xff1a; 1.申请apiKey 访问&#xff1a;Moonshot AI - 开放平台使用手机号手机号验证…

SuccBI+低代码文档中心 — 低代码应用(SuccAP)(概论)

概述&#xff1a; 低代码是什么&#xff1f; 低代码就是通过易用的、可视化的操作、加上少量的代码或脚本的方式快速的搭建业务应用。 低代码的优势&#xff1f; 低代码可以提升开发人员的效率&#xff0c;也可以让非开发人员也能进行应用开发。 低代码的分类&#xff1a;…

基于SpringBoot的大学生信息兼职服务网站系统,源码、部署+讲解

目 录 摘 要 Abstract 目 录 绪 论 1 系统分析 1.1可行性分析 1.1.1经济可行性分析 1.1.2技术可行性分析 1.1.3操作可行性分析 1.2需求分析 1.2.1从学生的角度 1.2.2从企业的角度 1.2.3从管理员的角度 1.3用例建模 1.3.1识别参与者用例 1.3.2用…

3.5 菜单资源

菜单分类 窗口的顶层菜单弹出式菜单&#xff08;鼠标右键的那些选项&#xff0c;记事本窗口左上角点击“文件”弹出的这些&#xff09;系统菜单&#xff08;记事本左上角的图标&#xff09; HMENU类型表示菜单&#xff0c;ID表示菜单项 资源相关 资源脚本文件:*.rc文件编译器…

python入门基础篇(一)

基础篇 Python基础安装与配置Python环境理解Python解释器第一个Python程序&#xff1a;"Hello, World!" 基础语法注释与文档字符串变量与数据类型数字类型&#xff1a;整数、浮点数、复数字符串布尔值None值 运算符算术运算符比较运算符逻辑运算符赋值运算符位运算符…

WEB渗透Web突破篇-SSRF

定义 服务端请求伪造 构造一个由服务器发出请求的漏洞 服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制成因 file_get_contents()、fsockopen()、curl_exec()、fopen()、readfile()等函数使用不当会造成SSRF漏洞挖掘 转码服务 在线翻译 获取超链接…

JavaScript入门day6

目录 1.Web API 基本认知 1.1 变量声明 1.2 作用和分类 1.3 什么是DOM 1.4 DOM树 1.5 DOM对象&#xff08;重要&#xff09; 2.获取DOM对象 2.1 获取DOM元素 2.2 操作元素内容 2.3 操作元素属性 2.3.1 操作元素常用属性 2.3.2 操作元素样式属性 2.3.3 操作表单元素…

C Primer Plus 第5章——第一篇

你该逆袭了 第5章:重点摘录 零、章节介绍一、基本运算符1、赋值运算符(1)、数据对象(2)、左值(3)、右值(4)、运算符 2、加法运算符3、减法运算符4、符号运算符&#xff1a;- 和 5、乘法运算符&#xff1a;*1、指数增长 6、除法运算符&#xff1a;/7、运算符优先级8、优先级 和 …

Python实战:wxauto与百度千帆大模型结合快速实现微信智能回复机器人

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

VGA接口驱动设计验证

前言 VGA接口是一个很有历史的接口&#xff0c;全称为Video Graphics Array&#xff08;VGA&#xff09;视频图形阵列&#xff0c;是IBM公司在1987年随着PS/2一起推出的使用模拟信号的一种视频传输标准。时至今日&#xff0c;这个接口依然还在大量使用&#xff0c;因为这个接口…

普乐蛙VR航天科普展厅VR虚拟现实项目激发青少年对太空探索

普乐蛙品牌VR沉浸体验式业态&#xff0c;定位文旅科教领域&#xff0c;助力中国航天发展。普乐蛙VR航天航空主题拥有华夏神舟、天宫一号、华夏月球车、太空飞船、华夏方舟、地震平台、暴风空间等众多科普体验设备和原创优质的航天航空内容&#xff0c;通过沉浸互动式体验&#…

USB 2.0 规范摘录

文章目录 1、USB 体系简介2、USB 数据流模型四种传输类型 3、USB 物理规范和电气规范4、USB 协议层规范事务传输&#xff08;Transaction&#xff09;的流程 5、USB 框架6、USB 主机&#xff1a;硬件和软件7、USB HUB 规范数据的转发唤醒信号的转发USB HUB 的帧同步HUB Repeate…

11087 统计逆序对(优先做)

这个问题可以通过使用分治策略来解决&#xff0c;这种策略是在归并排序的基础上进行的。我们可以将数组分为两部分&#xff0c;然后分别计算两部分的逆序对数量&#xff0c;最后计算跨越两部分的逆序对数量。 以下是使用C的代码实现&#xff1a; #include <iostream> #…

C++初学者指南-5.标准库(第二部分)--更改元素算法

C初学者指南-5.标准库(第二部分)–更改元素算法 文章目录 C初学者指南-5.标准库(第二部分)--更改元素算法填充/覆盖范围fill / fill_ngenerate / generate_n 更改/替换值transformreplace / replace_ifreplace_copy / replace_copy_if 相关内容 不熟悉 C 的标准库算法&…

【数据泄露】最新 FBI 官员数据库泄露事件

前言 近日&#xff0c;一名化名为 “rpk” 的威胁行为者在 breachforums 论坛上声称泄露了包含 22,175 名 FBI 官员数据的数据库。此事件迅速引起了广泛关注&#xff0c;主要因为 FBI 作为美国联邦调查局&#xff0c;不仅是美国司法部的主要调查机构&#xff0c;还是美国情报界…

太离谱!曝 GPT-4o mini 没做安全测试就开庆功会!OpenAI 严重违背政府协议,被立法者犀利追问 12 条

OpenAI又被曝违约了!这次是违反了对白宫的安全承诺。 去年夏天,OpenAI向美国政府承诺,将严格对其前沿的突破性技术进行安全测试,以确保AI不会造成损害,比如教用户制造生化武器或帮助黑客开发新型网络攻击。 然而,为了应对OpenAI领导层设定的GPT-4o mni 5月份的“deadli…

RabbitMQ高级特性 - 消息分发(限流、负载均衡)

文章目录 RabbitMQ 消息分发概述如何实现消费分发机制&#xff08;限制每个队列消息数量&#xff09;使用场景限流背景实现 demo 非公平发送&#xff08;负载均衡&#xff09;背景实现 demo RabbitMQ 消息分发 概述 RabbitMQ 的队列在有多个消费者订阅时&#xff0c;默认会通过…

MySQL —— 库,数据类型 与 表

库与基础操作 1.1 查看数据库 使用 show databases; 可以查看当前 MySQL 目前有多少个数据库 5 rows 表示有 5 行&#xff0c;这里是表示的是有效的数据&#xff0c;不包括 第一行的指引 set 表示结果集合 0.01 sec 表示这个 sql 语句一共运行了0.01 秒&#xff0c;一般情况…

【多线程】线程的五种创建方法

文章目录 线程在 Java 代码中编写多线程程序Thread 标准库 创建线程的写法1 . 继承 Thread 类代码回调函数休眠操作&#xff1a;sleep()抢占式执行观察线程jconsoleIDEA 内置调试器 2 . 实现 Runnable 接口代码 3. 匿名内部类创建 Thread ⼦类对象代码匿名内部类 4.匿名内部类创…

Python数据分析案例57——信贷风控模型预测评估及其可解释性(shap, scorecardpy包应用)

案例背景 在信贷风控场景下&#xff0c;其实模型的可解释性就变得很重要。在平时做一些普通的机器学习的案例的时候&#xff0c;我们根本不关心这些变量是怎么究竟影响到模型最后的决策的&#xff0c;随便直接把数据丢进去&#xff0c;再把要预测的数据丢进去就能出结果。但是…