引言
一般来说,对一个数组的排序,我们常用冒泡排序、快速排序、堆排序等算法进行排序。这样的数组能够一次性加载到内存中,使用上述的排序算法就能轻而易举进行排序,所以上述的排序算法可以称之为堆内排序。
然而有些场景下,一个待排序的文件可能超过甚至远远大于应用程序的运行内存。这种情况,一次性将文件加载到内存中,明显是不可行的,需要使用其他的排序手段进行排序。外部排序就是一种可行的方案。
基本思想
- 1、将原文件拆分成一个个能一次性加载到内存的小文件
- 2、依次将小文件排序
- 3、将排好序的小文件归并成一个有序的大文件
归并的方法
二路归并
各片段均已采用内排序算法进行排序,每次读入2路有序片段的前m个元素进行归并;若输出缓冲区已满,则将已归并好的元素写入文件;若其中一路m个元素归并完成,读入该路剩下的前m个元素。重复交替执行,直到所有元素都归并完成为止
缺点:元素需要反复比较,比较次数过多,导致归并的效率很低。
k路归并
基于二路归并多次比较的缺点,有人提出了改进算法,采用多路归并来提高效率。
k路归并可以使用堆进行排序,利用完全二叉树的性质,可以很快更新,保持堆的性质。但是,操作次数不够精简。
胜者树
归并过程:
- 1、在拆分成k个文件并排序后,取每个文件的首个元素作为叶子节点,构建一颗胜者树
- 2、输出一个最值到缓冲区;如果缓冲区已满,则将数据写入文件
- 3、从输出的值对应的来源文件取元素,加入到胜者树中,同时调整树
- 4、重复第2、3步骤,直到所有数据都读取并归并,最终得到排序的文件
胜者树父节点记录的的值为兄弟节点比较胜利的元素。所以在新值添加到树中,需要与父节点和子节点比较。
败者树
归并过程:
- 1、在拆分成k个文件并排序后,取每个文件的首个元素作为叶子节点,构建一颗败者树
- 2、输出一个最值到缓冲区;如果缓冲区已满,则将数据写入文件
- 3、从输出的值对应的来源文件取元素,加入到胜者树中,同时调整树
- 4、重复第2、3步骤,直到所有数据都读取并归并,最终得到排序的文件
败者树将败者存放在父结点中,而胜者再与上一级的父结点比较。败者树的更新只需将子节点与父节点比较。
总结
1、大文件过大,不能一次加载,所以需要拆分成k个小文件
2、k个小文件各自排序,为下一步文件归并打好铺垫
3、二路归并元素比较次数过多,效率低
4、k路归并中,堆排序可以很快更新,但操作数不够精简;胜者树父节点记录胜利的一方,更新时需要比较父节点和兄弟节点;败者树是胜者树的一种变体,父节点记录失败的一方,同时胜利一方与上一级的父节点比较,更新只需要比较父节点。因此在实际应用中采用败者树更好。