22 外部排序

news2024/9/21 12:26:37

外部排序

外部排序的基本内容

前面介绍过的排序方法都是在内存中进行的(称为内部排序)。外部排序是一种无法全部装入内存的大规模数据集的排序算法。与在内存中处理数据的内部排序相比,外部排序要复杂的多,主要因为是其需要解决的问题更多。主要的挑战之一就是I/O操作,因为硬盘或其他外部存储设备的数据访问速度通常比内存要慢得多。

外部排序的一般方法

文件在磁盘上的存储和操作系统的读写操作,一般来说,确实是按块进行的。块的大小可以根据系统的配置和需求进行设定。而由于磁盘读/写操作的机械动作所需的时间远远超过内存中数据处理的时间,因此外部排序中最大的时间消耗通常来自于对磁盘的读写操作,也就是I/O操作。

外部排序的常用方法包括归并排序和置换-选择排序。其中,归并排序是最常用的一种,它包括两个阶段:

  1. 分阶段:根据内存缓冲区的大小,将存储在外部存储设备上的大文件分成若干长度为l的子文件。每个子文件被单独读入内存,并使用内部排序方法进行排序。排序后,每个子文件变成一个有序的子文件,也被称为归并段或顺串。这些有序的子文件随后被写回到外部存储设备中。
  2. 合并阶段:在此阶段,多个归并段被逐个合并,每次合并产生一个更大的有序归并段,这个过程被称为归并。通过反复的归并操作,最终所有的归并段都会被合并成一个完整的有序文件。

在这个过程中,内存的大小会影响到子文件的长度,进而影响到整个排序过程需要的归并次数。而归并次数则直接关联到了需要进行的I/O操作的次数。因此,内存的大小是影响外部排序效率的重要因素。

举例

我们首先考虑内存和磁盘块的配置。假设我们有2000个记录要排序,并且每个磁盘块可以容纳125个记录。由于这个文件太大无法全部加载到内存中,我们必须采取一种分块处理的策略。我们的内存可以容纳两个输入缓冲区和一个输出缓冲区,每个都可以容纳一个磁盘块。

在开始排序前,我们首先需要对大文件进行预处理,生成一些初级的有序段,这个过程通常被称为"分阶段"。在这个阶段,我们一次处理两个磁盘块(250条记录),并使用内部排序算法(如快速排序,堆排序等)将其排序,形成一个有序的子文件或称之为归并段。这样我们将得到8个归并段(R1到R8),每个段都含有250个记录。

然后我们进入"合并阶段",在这个阶段,我们会逐步将小的归并段合并成大的归并段,直到得到一个完全有序的大文件。这个阶段的操作是,首先从两个输入归并段(如R1和R2)中分别读入一个块,放在输入缓冲区1和输入缓冲区2中。然后,执行两路归并操作,将最小的记录依次取出并放入输出缓冲区。当输出缓冲区满时,我们把它写回到磁盘中,形成一个新的归并段(如R1’)。如果某个输入缓冲区的记录已经取完,那么就从相应的归并段中读入下一块。这样进行下去,直到所有的记录都被处理完。通过这样的方式,我们首先将R1和R2归并成R1’,然后依次归并R3和R4成R2’,R5和R6成R3’,R7和R8成R4’。

在完成一趟归并后,我们再次进行归并操作,将上一趟归并得到的结果进一步合并。首先,我们将R1’和R2’归并成R1",然后将R3’和R4’归并成R2"。在第三趟归并中,我们将R1"和R2"归并,最终得到一个完全有序的大文件。

在整个过程中,我们通过内存缓冲区的巧妙使用,实现了对磁盘I/O操作的最小化,大大提高了外部排序的效率。这就是使用多路归并进行外部排序的一个典型例子,它体现了如何利用有限的内存资源处理大文件排序的问题。

image-20230520135536454

在外部排序实现两两归并时,由于不可能将两个有序段及归并结果同时存放在内存中,因此需要不停地将数据读出、写入磁盘、而这会消耗大量的时间。一般情况下:外部排序的总时间 = 内部排序所需时间 + 外部排序读写时间 + 内部归并所需的时间。

显然,外存信息读写的时间远大于内部排序和内部归并的时间,因此应着力减少I/O次数。由于外存信息的读/写是以“磁盘块”为单位的,可知每一趟归并需进行16次“读”和16次“写”,3趟归并加上内存排序时所需进行的读/写,使得总共需要进行32*3+32=128次读写。

若改用4路归并排序,则只需两趟归并,外部排序时的总读/写次数便减少至32*2+32=96.因此,增加归并路数,可减少归并趟数,进而减少总的磁盘的I/O次数。image-20230520141607338

路数越大,每趟归并处理的数据越多,总的归并趟数就越少,从而减少了I/O操作次数。然而,增大归并路数也会使得内存的负载增加,因为每一趟归并都需要将多个数据块加载到内存中。

多路平衡归并与败者树

多路平衡归并的核心思想在于增加归并路数k,以减少归并趟数S,从而降低I/O次数。这在处理大数据时极为重要。然而,增大归并路数k的同时,普通的内部归并排序算法的时间复杂度也会随之增大,因为每趟归并需要在k个元素中选择关键字最小的记录,需要做(k-1)次比较。

为了解决这个问题,我们引入了败者树,它是树形选择排序的一种变种,可以看作是一棵完全二叉树。在败者树中,k个叶节点分别存放k个归并段的当前比较记录,内部节点用来存储“失败者”,胜者继续上升进行比较,直到根节点。这样,根节点就指向了关键字最小的记录。

通过使用败者树,我们可以在log2k的深度下完成一次关键字最小记录的选择,大大降低了比较的次数。这样,我们就可以在内存允许的情况下,通过增大归并路数k来降低归并树的高度,减少I/O次数,提高外部排序的速度。

然而,归并路数k并不是越大越好。因为增大k意味着需要增加输入缓冲区的数量,这会占用更多的内存空间。如果内存空间固定,那么每个输入缓冲区的容量就需要减小,导致内存和外存之间的数据交换次数增加。当k值过大时,尽管归并趟数减少,但是读写外存的次数可能会增加,这反而可能降低排序的效率。

置换-选择排序

置换-选择排序是一种生成初始归并段的算法,它在内存不足以对所有数据进行排序时特别有用。它的核心思想是:通过维护一个内存工作区,从中选择关键字最小的记录输出到初始归并段,同时不断从输入文件中读入新的记录到工作区,以此方式生成尽可能长的初始归并段。这种方式能有效减少后续归并操作的次数,提升外部排序的效率。

以下是置换-选择排序的详细步骤:

  1. 初始化:首先,从待排序文件(FI)中读取w个记录到内存工作区(WA)。w是由内存大小决定的一个值,也可以理解为内存工作区的容量。此时,初始归并段输出文件(FO)为空。
  2. 选择最小记录:在内存工作区(WA)中选择关键字最小的记录,我们称其为MINIMAX记录。
  3. 输出最小记录:将选中的MINIMAX记录输出到初始归并段输出文件(FO)中。
  4. 读入新记录:如果待排序文件(FI)还有未读取的记录,那么从FI中读取下一个记录到内存工作区(WA)中。
  5. 选择新的最小记录:在WA中,选出关键字大于刚才输出到FO中的MINIMAX记录关键字的所有记录中,关键字最小的那个记录,作为新的MINIMAX记录。
  6. 重复输出和读入:然后重复步骤3至5,直到无法在WA中选出新的MINIMAX记录,这就形成了一个初始归并段。
  7. 生成所有初始归并段:接着,重新读入w个新记录到WA,然后重复步骤2至6,直到处理完FI中的所有记录,得到所有的初始归并段。

这里要注意的是,置换-选择排序允许新加入的记录的关键字小于当前归并段的最大关键字,只要这个记录的关键字大于刚才输出的MINIMAX记录的关键字。这种方法的灵活性使得置换-选择排序能够生成长度更长的初始归并段,比简单的内部排序生成的初始归并段长度要大。

最佳归并树

在处理大量数据时,我们常常需要进行排序操作。但是,如果数据量超过了内存的容量,我们就不能使用一般的内部排序算法了,此时,我们需要使用外部排序算法。其中,归并排序是一种非常常见的外部排序方法。归并排序的基本思想是将多个已排序的子序列合并为一个有序的总序列。

然而,如何优化归并排序的效率是一个重要的问题。我们知道,归并排序的主要时间消耗在于读取和写入数据,即I/O操作。为了减少I/O操作的次数,我们引入了最佳归并树的概念。

在最佳归并树中,每个节点代表一个归并段,归并段的长度就是节点的权值。根据哈夫曼树的原理,我们知道树的带权路径长度最小的二叉树是最优二叉树,即哈夫曼树。所以,我们可以应用哈夫曼树的原理到k叉树中,使得每个归并段的长度(权值)乘以其在树中的路径长度最小,这样可以最小化总的I/O操作次数。

这就是最佳归并树的基本概念。然而,实际上,我们可能会遇到一些问题。比如说,我们有的时候无法正好构成一个严格的k叉树。这个时候,我们就需要引入“虚段”的概念。虚段是一个长度为0的归并段,可以作为额外的叶节点添加到归并树中,以保持其严格的k叉形态。我们可以通过一个简单的公式来判断需要添加多少个虚段。

设度为0的节点有n0个,度为k的节点有nk个。对于一个严格的k叉树,有n0 = (k-1) * nk + 1。这个公式告诉我们,如果叶节点的数量减1后不能被(k-1)整除,那么就说明我们有一些叶节点是多余的,不能被包含在k叉归并树中。为了让所有的叶节点都能被包含在k叉归并树中,我们需要再增加一个内节点,并添加k-u-1个虚段,其中u是(n0-1)%(k-1)的余数。

举个例子,假设我们有8个初始归并段,想要构成一个3叉树。我们计算(8-1)%(3-1) = 1,说明7个归并段刚好可以构成一棵严格的3叉树。但是我们还有一个多余的归并段,为了包含这个归并段,我们将一个叶节点变成内节点,然后添加3-1-1=1个虚段,就可以构成一棵严格的3叉树。

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

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

相关文章

【c语言】二进制文件的读写操作

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

1.1 IAR新建工作空间 及 新建工程

目录 新建工作空间 新建工程 新建工作空间 &#xff08;1&#xff09;创建一个名字为Workspace的文件夹&#xff0c;如图所示。 &#xff08;2&#xff09;运行IAR EW for 8051 10.10.1&#xff0c;如图所示。 &#xff08;3&#xff09;依次选择File和New Workspace&#xf…

【IDEA使用指南】如何将 IDEA 开发工具作为可视化工具来操作数据库?

如何将 IDEA 开发工具作为可视化工具来操作数据库&#xff1f; 步骤1&#xff1a;找到数据源配置的工具栏。 如下图所示&#xff0c;找到 “View -> Tool Windows -> Database”&#xff0c;点击“Database”。 步骤2&#xff1a;选择数据源类型。 在开发工具右侧会…

CSS背景色渐变

从上到下渐变&#xff1a;background: linear-gradient(red, pink); 从左到右渐变&#xff1a;background: linear-gradient(to right, red , pink); 对角&#xff08;从左上角到右下角&#xff09;渐变&#xff1a;background: linear-gradient(to bottom right, red , pink);…

【Linux】进程信号详解(三)

文章目录 一、可重入函数二、volatile三、SIGCHLD信号 一、可重入函数 假设有一个不带头的单链表&#xff0c;要进行头插操作&#xff0c;在我们数据结构阶段都已经学习过&#xff0c;我们可以有以下的步骤&#xff1a; 要将node1头插到单链表中&#xff0c;调用insert函数&…

什么是数据仓库

数据仓库的概念可以追溯到20世纪80年代&#xff0c;当时IBM的研究人员开发出了“商业数据仓库”。本质上&#xff0c;数据仓库试图提供一种从操作型系统到决策支持环境的数据流架构模型。数据仓库概念的提出&#xff0c;是为了解决与这个数据流相关的各种问题&#xff0c;主要是…

flutter 在动图上添加文字

前言 有这样一个场景&#xff0c;在一个展示很多文字的App中背景图片可以自定义&#xff0c;当然也可以是动态的&#xff0c;但是这个主页是可以分享出去的&#xff0c;也就是我需要在一个动态的背景上写上文字并保存为一张新的图片并分享出去。 实现 前置准备 需要导入一个…

【计算机视觉 | 目标检测】Objects365 :最新大规模高质量目标检测数据集

文章目录 一、前言二、数据集的规模三、数据集的质量四、泛化能力五、结语 一、前言 2019 年 4 月&#xff0c;在北京举行的智源学者计划启动暨联合实验室发布会上&#xff0c;北京旷视科技有限公司与北京智源人工智能研究院共同发布了全球最大的目标检测数据集 &#xff1a; …

ChatGPT国内免费访问

背景 ChatGPT作为一种基于人工智能技术的自然语言处理工具&#xff0c;近期的热度直接沸腾&#x1f30b;。 作为一个程序员&#xff0c;我也忍不住做了一个基于ChatGPT的网站&#xff0c;免费&#xff01;免梯子&#xff01;&#xff01;国内可直接对话ChatGPT&#xff0c;也…

使用Python和Scrapy实现抓取网站数据

Scrapy是一个功能强大的网络爬虫框架&#xff0c;允许开发者轻松地抓取和解析网站内容&#xff0c;这篇文章主要为大家介绍了如何使用Python的Scrapy库进行网站数据抓取&#xff0c;需要的可以参考一下 在本文中&#xff0c;我们将介绍如何使用Python的Scrapy库进行网站数据抓…

00后才是内卷界的扛把子,被卷的头皮发麻....

人们都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前一周时间我们公司来了个00年的&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪20K&#xff0c;都快接近我了。后来听同事说才知道人家是个卷王&#xff0c;从早干到晚就差搬张床…

哈希表应用——布隆过滤器

注&#xff1a;布隆过滤是用来处理海量数据且允许存在误判 目录 布隆过滤器提出 布隆过滤器概念 布隆过滤器的理论知识 布隆过滤器的实现 布隆过滤器的删除 布隆过滤器优点 布隆过滤器缺陷 布隆过滤器的应用场景 哈希切分 布隆过滤器/哈希切分面试题 布隆过滤器提出 …

免交互Here Document

文章目录 免交互Here Document1 定义2 语法格式2.1 免交互方式实现对行数的统计2.2 通过 read 命令接收输入并打印2.3 通过 passwd 给用户设置密码2.4 支持变量替换2.5 多行注释 3 expect4 实例4.1 su切换用户4.2 嵌入执行模式4.3 实现ssh自动登录 免交互Here Document 1 定义…

Linux CentOS7中yum的使用更新yum源

我们在windows中我们是经常需要下载一些我们需要的软件&#xff0c;那么我们在Linux中理所当然也是需要的&#xff0c;那么我们如何安装软件呢&#xff1f;&#xff1f; Linux中软件安装的方法 1.源代码安装&#xff1a;源代码安装就是直接自己安装源代码&#xff0c;并且是自…

MyBatis动态推理参数类型

前言 思考一个问题&#xff0c;前面的#{}和${}的区别中&#xff0c;我们知道了#{}&#xff0c;MyBatis底层调用的是preparestatement这种预编译的方式&#xff0c;这种方式sql语句会预先编程 select * from t_user where id ?这种形式&#xff0c;随后调用setInt(),setString…

【Java数据结构】——第十节(上).直接插入排序、希尔排序

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;Java初阶数据结构 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目…

iptables 防火墙(一)

目录 一&#xff1a;iptables概述 二&#xff1a;netfilter/iptables关系 三&#xff1a;四表五链 1.规则表和规则链的作用 2. 四表 3.五链 ​4.规则链之间的匹配顺序 &#xff08;1&#xff09;主机型防火墙 &#xff08;2&#xff09;网络型防火墙 5.规则链内的匹配…

考研复试刷题第十四天: 表达式树 【二叉树,表达式运算】

1.概念解释: 表达式树其实就是叶节点装树&#xff0c;其他节点装符号的二叉树。 2.题目部分 这道题一开始没理解它的意思&#xff0c;以后写题一定要理解题意之后再动手。尤其是看清楚注意事项。 我一开始拿到题目&#xff0c;以为会有这种情况就是说一个节点之下会有一遍没…

面了一个测试工程师要求月薪26K,总感觉他背了很多面试题...

最近有朋友去华为面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

OpenLayers多图层切换显示隐藏,支持多个Layer同时显示和隐藏,以百度地图和高德地图为例实现vue的Layer图层管理组件

前言 OpenLayers默认并没有提供图层管理组件,实现起来也很简单,评论区里有同学提到了这个,必须立刻满足,这就着手区实现一个简单又强大的地图图层管理组件。 那么本章就专门讲一下在vue中如何使用ElementUI的下拉框做一个简单的图层管理组件。 话不多说,让我们直接开始吧…