外部排序的方法
外部排序的基本问题是如何在内存容量有限的情况下,对存储在外存中的大量数据进行排序。解决这个问题的关键在于将排序过程分解为多个阶段,每个阶段只需要处理能够装入内存的数据量,通过多次外存读写和内存排序的配合,最终完成整个文件的排序。外部排序最常用的方法是多路归并排序,它充分利用了归并排序的思想,将其扩展到外存环境。
1. 外部排序的两个阶段
外部排序的完整过程可以划分为两个明确的阶段,这两个阶段的配合是实现外部排序的基础。
第一个阶段是生成初始归并段阶段。在这个阶段,需要将外存文件分批读入内存,每次读入的数据量不超过内存容量,然后利用内部排序算法对这批数据进行排序,排序后将结果写回外存,形成一个有序的初始归并段。重复这个过程,直到将整个文件分解为若干个初始归并段。这些初始归并段虽然各自内部有序,但彼此之间并无顺序关系。假设待排序文件包含10000个记录,内存一次只能容纳1000个记录,那么需要读入10次,每次排序后写回外存,最终得到10个初始归并段。
第二个阶段是多路归并阶段。在这个阶段,需要对生成的初始归并段进行多次归并,逐步减少归并段的数量,增加每个归并段的长度,直到最终形成一个完整的有序文件。每次归并操作从若干个有序的归并段中各取出一部分记录到内存,通过比较选出最小值输出,形成新的更长的归并段。这个过程类似于内部排序中的归并排序,但需要在外存和内存之间频繁进行数据交换。
2. 二路归并的基本过程
为了更清楚地理解外部排序的工作原理,首先从最简单的二路归并情况入手进行分析。二路归并是指每次归并操作从两个有序归并段中选择记录进行合并。
假设有一个包含16个记录的文件需要排序,而内存一次只能容纳4个记录。外部排序的完整过程可以用下图表示。
上图完整展示了外部排序的全过程。初始文件包含16个无序记录,第一趟处理时,每次读入4个记录到内存,进行内部排序后写回外存,生成4个初始归并段,每个归并段内部有序。第二趟进行二路归并,将相邻的两个归并段合并,前两个归并段合并成一个包含8个记录的有序段,后两个归并段也合并成一个包含8个记录的有序段,此时归并段数量减少为2个。第三趟继续二路归并,将剩余的两个归并段合并成一个包含全部16个记录的最终有序文件,排序完成。
在二路归并的每一趟中,需要为两个输入归并段各分配一个输入缓冲区,为输出归并段分配一个输出缓冲区。归并时,从两个输入缓冲区中各取出一个记录进行比较,将较小的记录输出到输出缓冲区。当某个输入缓冲区为空时,从对应的归并段继续读入一批记录;当输出缓冲区满时,将其内容写回外存。这个过程一直持续到两个输入归并段都处理完毕。
3. 多路归并的扩展
二路归并虽然思路清晰,但归并趟数较多,导致磁盘读写次数较多。为了减少归并趟数,可以采用多路归并的方法,即每次归并时同时处理kkk个归并段,而不仅仅是两个。
多路归并的基本思想是,在每趟归并时,同时从kkk个有序归并段中选择记录。具体做法是为kkk个输入归并段各分配一个输入缓冲区,再分配一个输出缓冲区。每次从kkk个输入缓冲区中选出关键字最小的记录输出。这个选择过程需要在kkk个记录中进行比较,可以通过建立一个大小为kkk的最小堆来优化,使得每次选择的时间复杂度为O(logk)O(\log k)O(logk)。
假设有8个初始归并段,采用4路归并,只需要两趟就能完成排序。第一趟将8个初始归并段两两一组,每组4个进行归并,生成2个更长的归并段。第二趟将这2个归并段进行最后的归并,得到最终的有序文件。相比二路归并需要3趟,4路归并减少了一趟归并操作。
上图展示了4路归并的过程。8个初始归并段在第一趟中分为两组,每组4个段进行4路归并,生成2个更长的归并段。第二趟将这2个归并段进行归并,得到最终结果。整个过程只需要2趟归并,大大减少了磁盘访问次数。
多路归并的路数kkk受到内存大小的限制。假设内存可用空间为MMM,每个缓冲区大小为BBB,那么最多可以进行kkk路归并,其中k+1k+1k+1个缓冲区(kkk个输入缓冲区和1个输出缓冲区)的总大小不能超过MMM,即(k+1)×B≤M(k+1) \times B \leq M(k+1)×B≤M,可得k≤M/B−1k \leq M/B - 1k≤M/B−1。增大路数kkk可以减少归并趟数,但也需要更多的内存空间用于缓冲区。
4. 归并趟数与磁盘访问次数
外部排序的效率主要取决于磁盘访问次数,而磁盘访问次数与归并趟数密切相关。假设初始归并段数量为rrr,采用kkk路归并,那么归并趟数sss可以用公式s=⌈logkr⌉s = \lceil \log_k r \rceils=⌈logkr⌉计算。例如,若有16个初始归并段,采用4路归并,则s=⌈log416⌉=2s = \lceil \log_4 16 \rceil = 2s=⌈log416⌉=2趟。
每趟归并都需要读入所有记录一次,输出所有记录一次,因此每趟需要2次完整的磁盘访问。加上生成初始归并段时的1次读和1次写,总的磁盘访问次数为2(s+1)2(s+1)2(s+1)次。继续上面的例子,2趟归并加上初始段生成,总共需要2×(2+1)=62 \times (2+1) = 62×(2+1)=6次磁盘访问。
减少归并趟数是提高外部排序效率的关键。可以通过两种方式实现:一是增加归并路数kkk,但受到内存容量的限制;二是减少初始归并段数量rrr,这需要增加内存容量或采用更高效的生成初始归并段的方法。在内存容量固定的情况下,需要在归并路数和初始归并段大小之间进行权衡,以达到最优的排序效率。这些优化方法将在后续内容中详细讨论。
1万+

被折叠的 条评论
为什么被折叠?



