排序
排序基本上分为两种:
- 内部排序法(Internal Sorting)
- 外部排序法(External Sorting)
内部排序:是将数据存储在内存中,然后进行排序。
外部排序:因为需要排序的数据太大,无法全部存储在内存时所运行的排序算法,在排序过程中使用外部存储设备。
内部排序
1.插入排序
插入排序(Insert Sort)是一种简单直观的排序方法,其基本思想就是每次将一个待排序的记录按照其关键字大小插入到已经排序好了的子序列当中,直到全部记录都插入完成。
1.1 直接插入排序
直接插入排序(Direct Insertion Sort)也是一种最简单的排序方法,其基本操作是将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增1的有序表。
算法思想
- 将待排序序列分为两部分,一部分有序一部分无序。
- 我们把第一个元素看作有序序列,从第二个元素到最后为无序序列。
- 将无序序列中每一个元素依次插入到有序序列的合适位置------>从小到大(从大到小)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P1uu2J3J-1665916459800)(E:\data\排序算法\img\Snp_10-16_15-2.png)]
动画演示
算法分析
1.时间复杂度:
- 最好情况就是全部有序,此时只需遍历一次,最好的时间复杂度为O(n)
- 最坏情况全部反序,内层每次遍历已排序部分,最坏时间复杂度为O(n^2)
- 综上,因此直接插入排序的平均时间复杂度为O(n^2)
2.空间复杂度:
- 仅仅只使用了常数个辅助单元,因此空间复杂度为O(1)
3.稳定性:
- 由于每次插入元素时总是会从后往前先比较再移动,所以不会出现相同元素相对位置发送变化的情况,即直接插入排序是一种稳定的排序算法。
4.适用性:
- 直接插入排序适用于顺序存储和链式存储的线性表。为链式存储时,可以从前往后查找指定元素的位置。
- 注意:大部分的排序算法只适用于顺序存储的线性表
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
int[] resultArray = directInsertSort(array);
System.out.println(Arrays.toString(resultArray));
}
private static int[] directInsertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int num = array[i];
for (int j = i - 1; j >= 0; --j) {
if (array[j] > num) {
array[j + 1] = array[j];
} else {
break;
}
array[j] = num;
}
}
return array;
}
}
1.2 折半插入排序
折半插入排序(Binary Insertion Sort)又称二分插入排序,是对直接插入排序的一种改进。
改进:
- (直接插入排序)线性查找 —> 折半查找
- 减少比较次数
算法思想
基本思路:
- 每次插入操作,采用折半查找的方式,先查找插入位置
- 再根据要插入的位置插入元素(先挪后插入)。
动画演示
算法分析
1.时间复杂度:
- 折半插入排序的时间复杂度为O(n^2)
2.空间复杂度:
- 空间复杂度为O(1)
3.稳定性:
- 是一种稳定的排序算法。
4.适用性:
- 只适用于顺序存储的线性表
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
int[] resultArray = binaryInsertSort(array);
System.out.println(Arrays.toString(resultArray));
}
private static int[] binaryInsertSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int num = array[i];
int low = 0;
int high = i - 1;
while (high >= low) {
int mid = (high + low) / 2;
if (num < array[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
for (int j = i - 1; j >= high + 1; j--) {
array[j + 1] = array[j];
}
array[high + 1] = num;
}
return array;
}
}
1.3 希尔排序
希尔排序(Shell Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本
算法思想
基本思路:
- 1.先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再取一个比第一增量小的整数作为第二增量,重复上述操作…
- 2.当增量的大小减到1时,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。
动画演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rrvjftli-1665916459802)(E:\data\排序算法\img\20210509190237603.gif)]
算法分析
1.时间复杂度:
- 希尔排序的平均时间复杂度为O(n^1.3)
2.空间复杂度:
- 空间复杂度为O(1)
3.稳定性:
- 希尔排序是一种不稳定的排序算法。
4.适用性:
- 希尔排序算法只适用于顺序存储的线性表
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49, 55, 4};
int[] resultArray = shell(array);
System.out.println(Arrays.toString(resultArray));
}
private static int[] shell(int[] array) {
int gap = array.length;
while (gap > 1) {
shellSort(array, gap);
gap /= 2;
}
shellSort(array, 1);
return array;
}
//每次的小排序都是直接排序
private static void shellSort(int[] array, int gap) {
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
for (int j = i - gap; j >= 0; j -= gap) {
if (array[j] > tmp) {
array[j + gap] = array[j];
} else {
break;
}
array[j] = tmp;
}
}
}
}
2.交换排序
交换排序(Exchange Sort),所谓交换,是指根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置
2.1 冒泡排序
冒泡排序(Bubble Sort)是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。
算法思想
- 每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。
- 而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。
动画演示
算法分析
1.时间复杂度:
- 冒泡排序的时间复杂度为O(n^2)
2.空间复杂度:
- 空间复杂度为O(1)
3.稳定性:
- 是一种稳定的排序算法。
4.适用性:
- 适用于顺序存储或链式存储的线性表
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
// int[] resultArray = bubbleSort(array);
int[] resultArray2 = bubbleSort2(array);
// System.out.println(Arrays.toString(resultArray));
System.out.println(Arrays.toString(resultArray2));
}
//从后往前冒泡:优先选择最小元素
/* private static int[] bubbleSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = array.length - 1; j > i; j--) {
if (array[j] < array[j - 1]) {
int temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
return array;
}*/
//从前往后冒泡:优先选择最大元素
private static int[] bubbleSort2(int[] array) {
for (int i = array.length - 1; i > 0; i--) {
for (int j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
return array;
}
}
2.2 快速排序
快速排序(Quick Sort)的基本思想是基于分治法的,是通过多次比较与交换来完成排序,而这个过程又被分为了多次重复单趟排序
算法思想
- 1、选出一个key,一般是最左边或是最右边的。
- 2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
- 3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
- 4.此时key的左边都是小于key的数,key的右边都是大于key的数
- 5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序
动画演示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VNvhOPQZ-1665916459804)(E:\data\排序算法\img\1d43762b58681c0ace7caa3fa93b33da.gif)]
算法分析
1.时间复杂度:
- 快速排序的时间复杂度为O(n^2)
2.空间复杂度:
- 由于快速排序是递归的,需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量应该与递归调用的最大深度一致。
- 最好情况下为O(log2n)
- 最坏情况下,因为要进行(n-1)次递归调用,所以栈的深度为O(n)
- 平均情况下,栈的深度为O(log2n),所以空间复杂度也为O(log2n)。
3.稳定性:
- 是一种不稳定的排序算法。
4.适用性:
- 只适用于顺序存储结构
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
int[] resultArray = quickSort(array);
System.out.println(Arrays.toString(resultArray));
}
private static int[] quickSort(int[] array) {
quick(array, 0, array.length - 1);
return array;
}
private static void quick(int[] array, int low, int high) {
if (low < high) {
int pivots = partition(array, low, high);
quick(array, low, pivots - 1);
quick(array, pivots + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
int pivot = array[low];
while (low < high) {
while (low < high && array[high] >= pivot) {
high--;
}
array[low] = array[high];
while (low < high && array[low] <= pivot) {
low++;
}
array[high] = array[low];
}
array[low] = pivot;
return low;
}
}
3.选择排序
选择排序(Select Sort)就是每一趟(如第i趟)在后面(n-i+1)个待排序元素中选取关键字最小的元素,所为有序子序列的第i个元素,直到第(n-1)趟做完,待排序元素只剩下一个,就不要再选了。
3.1 简单选择排序
简单选择排序(Simple Selection Sort)是一种简单直观的排序算法。
算法思想
- 1、第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置。
- 2、然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。
- 3、以此类推,直到全部待排序的数据元素的个数为零。
动画演示
算法分析
1.时间复杂度:
- 简单选择排序的时间复杂度为O(n^2)
2.空间复杂度:
- 空间复杂度为O(1)
3.稳定性:
- 是一种不稳定的排序算法。
4.适用性:
- 适用于顺序存储结构或链式存储结构
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
int[] resultArray = simpleSelectionSort(array);
System.out.println(Arrays.toString(resultArray));
}
private static int[] simpleSelectionSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
//min为标记位,记录的是最小元素的下标
int min = i;
for (int j = i + 1; j < array.length; j++) {
if (array[min] > array[j]) {
min = j;
}
}
int temp = array[min];
array[min] = array[i];
array[i] = temp;
}
return array;
}
}
3.2 堆排序
堆排序(Heap Sort)是根据堆的这种数据结构设计的一种排序,堆的结构可以分为大根堆和小根堆,是一个完全二叉树
完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树
大根堆(最大堆):每一个结点的值大于等于左右子树的结点的值,根结点值最大
小根堆(最小堆):每一个结点的值小于等于左右子树的结点的值,根结点值最小
算法思想
- 1.首先将待排序的数组构造成一个大根堆(或者小根堆),此时,整个数组的最大值就是堆结构的顶端
- 2.将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
- 3.将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
动画演示
向大根堆中添加元素:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VtrIuCOl-1665916459805)(E:\data\排序算法\img\20200201212226900.gif)]
向大根堆中取出元素:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PkoVfZdD-1665916459805)(E:\data\排序算法\img\20200201232927732.gif)]
算法分析
1.时间复杂度:
- 建堆的时间复杂度为O(N)
- 向下调整算法的时间复杂度为O(h)=O(log2N);
- 所以堆排序的时间复杂度为O(N*log2N);
2.空间复杂度:
- 空间复杂度为O(1)
3.稳定性:
- 是一种不稳定的排序算法。
4.适用性:
- 适用于顺序存储结构的完全二叉树
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
int[] resultArray = heapSort(array);
System.out.println(Arrays.toString(resultArray));
}
private static void buildMaxHeap(int[] array,int len) {
for (int parent = len / 2; parent > 0; parent--) {
headAdjust(array, parent, len);
}
}
private static void headAdjust(int[] array, int parent, int len) {
int child = parent * 2 - 1;
while (child < len) {
if ((child + 1 < len) && array[child] < array[child + 1]) {
child++;
}
if (array[child] > array[parent - 1]) {
swap(array, child, parent - 1);
}
break;
}
}
private static int[] heapSort(int[] array) {
//初始化建堆
buildMaxHeap(array,array.length);
int end = array.length - 1;
while (end >= 0) {
swap(array, 0, end);
//多次建堆选出最大元素放在末尾
buildMaxHeap(array,end);
end--;
}
return array;
}
private static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
4.归并排序
归并排序(Merge Sort)对的”归并“的含义是将两个或两个以上的有序表组合成一个新的有序表
4.1 二路归并排序
假定待排序表含有n个记录,则可将其视为n个有序的个表,每个子表的长度为1,然后两两归并,得到 ⌈ n/2 ⌉ 个长度为2或1的有序表:继续两两----如此重复,直到合并成一一个长度为 n的有序表为止,这种排序方法称为2路归并排序。
向下取整的运算称为Floor,用数学符号⌊⌋表示;向上取整的运算称为Ceiling,用数学符号⌈⌉表示。例如:⌊59/60⌋=0和⌈59/60⌉=1
算法思想
该算法是采用分治法:
- 设数组a中存放了n个数据元素,初始时把它们看成是n个长度为1的有序子数组,然后从第一个有序子数组开始,把相邻的有序子数组两两合并,得到[n/2]个长度为2的新的有序子数组(当n为奇数时,最后一个新的有序子数组的长度为1)。对这些新的有序子数组再进行两两归并。如此重复,直到得到一个长度为n的有序数组为止
- 一次二路归并排序算法的目标是把若干个长度为k的相邻有序子数组从前、向后进行两两归并,得到个数减半的长度为2k的相邻有序子数组。算法设计中要考虑的一个问题是:若元素个数为2k的整数倍,则两两归并正好完成n个数据元素的一次二路归并;
- 若元素个数不为2k的整数倍,则当归并到最后一组时,剩余的元素个数会不足2k个,这时的处理方法是:
- 若剩余的元素个数大于k而小于2k,则把前k个元素作为一个子数组,把剩余的元素作为最后一个子数组。
- 若剩余的元素个数小于k时,也即剩余的元素个数只够一组时,则不用再进行两两归并排序。
动画演示
算法分析
1.时间复杂度:
- 每趟归并的时间复杂度为O(n)
- 一共进行 ⌈ log2(n) ⌉ 趟归并
- 所以二路归并排序的时间复杂度为O(nlog2(n))
2.空间复杂度:
- 空间复杂度为O(n)
3.稳定性:
- 是一种稳定的排序算法。
4.适用性:
- 适用于顺序存储结构或链式存储结构
代码实现
以下用数组49, 38, 65, 97, 76, 13, 27, 49为例
从小到大排序
public class Demo {
public static void main(String[] args) {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49};
int[] resultArray = mergerSort(array);
System.out.println(Arrays.toString(resultArray));
}
public static int[] mergerSort(int[] array) {
mergeSortFunc(array, 0, array.length - 1);
return array;
}
private static void mergeSortFunc(int[] array, int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
//1、分解左边
mergeSortFunc(array, left, mid);
//2、分解右边
mergeSortFunc(array, mid + 1, right);
//3、进行合并
merge(array, left, right, mid);
}
private static void merge(int[] array, int start, int end, int midIndex) {
//辅助数组
int[] tmpArr = new int[end - start + 1];
int k = 0;//tmpArr数组的下标
int s1 = start;
int s2 = midIndex + 1;
//两个归并段 都有数据
while (s1 <= midIndex && s2 <= end) {
if (array[s1] <= array[s2]) {
tmpArr[k++] = array[s1++];
} else {
tmpArr[k++] = array[s2++];
}
}
//当走到这里的时候 说明 有个归并段 当中 没有了数据 ,拷贝另一半的全部到tmpArr数组当中
while (s1 <= midIndex) {
tmpArr[k++] = array[s1++];
}
while (s2 <= end) {
tmpArr[k++] = array[s2++];
}
//把排好序的数字 拷贝回 原数组
for (int i = 0; i < k; i++) {
array[i + start] = tmpArr[i];
}
}
}
5.基数排序
基数排序(Radix Sort)是桶排序的扩展,也是一种非比较排序
桶排序适用于数据分布比较均匀的情况,如果数据分布不是很均匀,并且数据中存在几个极大值,这会导致桶排序存在很多的空桶,这会造成内存的浪费。桶排序使用的是序列中元素的范围,基数排序使用的是序列中元素的位数,基数排序由于位数的范围是10位或者26位,所以可以避免创建过多的桶。
基数排序将数据划分为一个个的关键字,其对相同数位上的关键字进行计数排序,如果位数不够则前面补0,从低位到高位进行排序,如果字符串比较可以从左到右比较。
算法思想
- 基数排序整体来看是一种“分配和收集”排序,其将序列中的元素按照位数进行划分,不够的补0,
- 将相同位数的元素先分配,分配后相同位数的元素排好序后,进行收集,重复该操作,可以完成排序。
- 也可以这样理解,基数排序使用的是一种分治思想,对序列中相同位数的元素进行计数排序,所有的位数循环完成,则排序完成。
动画演示
算法分析
(k是位数,n是序列的长度,d是桶的个数)
1.时间复杂度:
- 基数排序的时间复杂度是O(k(n+d))
2.空间复杂度:
- 空间复杂度为O(dn)
3.稳定性:
- 是一种稳定的排序算法。
4.适用性:
- 适用于顺序存储结构或链式存储结构
代码实现
public class Demo {
public static void main(String[] args) {
int[] arr = {11, 12, 3, 43, 87, 11, 9, 0, 76, 35,
21, 22, 33, 11, 22, 345, 543, 321, 123,
456, 987, 789, 876, 12, 23, 3, 1, 9, 999};
radixSort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void radixSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
// 获取最大值
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 用二维数组模拟桶放入东西,一共有10个桶
int[][] temp = new int[10][arr.length];
// 元素最大值的长度
String maxString = max + "";
int length = maxString.length();
int temp1 = 1;
for (int i = 0; i < length; i++) {
// 用于记录每个桶中放入元素的位置
int[] count = new int[10];
for (int i1 = 0; i1 < arr.length; i1++) {
int i2 = (arr[i1] / temp1) % 10;
temp[i2][count[i2]] = arr[i1];
count[i2]++;
}
temp1 *= 10;
// 将数据取出放入原数组
int sum = 0;
for (int i1 = 0; i1 < count.length; i1++) {
for (int i2 = 0; i2 < count[i1]; i2++) {
arr[sum++] = temp[i1][i2];
}
}
}
}
}
外部排序
1.基本概念
在内存中进行的排序是内部排序,而在许多应用中,经常需要对大文件进行排序,因为文件中的记录很多、信息量庞大,无法将整个文件复制进内存中进行排序。
因此,需要将待排序的记录存储在外存中,排序时再把数据一部分一部分地调入内存进行排序,在排序过程中需要多次进行内存和外存之间地交换。这种排序方法就称为外部排序。
2.外部排序的方法
文件通常是按块存储在磁盘上的,操作系统也是按块对磁盘上的信息进行读写的。
因为磁盘读 / 写的机械动作所需的时间远远超过内存运算的时间(相对而言可以忽略不记),因此在外部排序过程中的时间代价主要考虑访问磁盘的次数,即I/O次数。
外部排序通常采用归并排序法。它包括两个相对独立的阶段:
- 根据内存缓冲区大小,将外存上的文件分成若干长度为t的子文件,依次读入内存并利用内部排序方法对他们进行排序,并将排序后得到的有序子文件重新写回外存,称这些有序子文件为归并段或顺串。
- 对这些归并段进行逐趟归并,是归并段逐渐由小到大,直至得到整个有序文件位置。
在外部排序中实现两两归并时,由于不可能将两个有序段及归并结果段同时存放在内存中,因此需要不停地将数据读出、写入磁盘,而这会耗费大量的时间。一般情况下:
- 外部排序的总时间 = 内存排序所需的时间 + 外存信息读取的时间 + 内部归并所需的时间
显然,外存信息读取地时间远大于内部排序和内部归并地的时间,因此应着力减少I/O次数。由于外村信息的读/写是以“磁盘块”为单位进行的,以8个归并段为例,可知每一趟归并需进行16次读和16次写,3趟归并并加上内部排序时所需进行的读/写,使得总共需进行128次读写。若改用4路归并排序,则只需2趟排序,外部排序时的总读写次数便减少为96。
}
}
# 外部排序
## 1.基本概念
在内存中进行的排序是内部排序,而在许多应用中,经常需要对大文件进行排序,因为文件中的记录很多、信息量庞大,无法将整个文件复制进内存中进行排序。
因此,需要将待排序的记录存储在外存中,排序时再把数据一部分一部分地调入内存进行排序,在排序过程中需要多次进行内存和外存之间地交换。这种排序方法就称为外部排序。
## 2.外部排序的方法
文件通常是按块存储在磁盘上的,操作系统也是按块对磁盘上的信息进行读写的。
因为磁盘读 / 写的机械动作所需的时间远远超过内存运算的时间(相对而言可以忽略不记),因此在外部排序过程中的时间代价主要考虑访问磁盘的次数,即I/O次数。
外部排序通常采用归并排序法。它包括两个相对独立的阶段:
- 根据内存缓冲区大小,将外存上的文件分成若干长度为t的子文件,依次读入内存并利用内部排序方法对他们进行排序,并将排序后得到的有序子文件重新写回外存,称这些有序子文件为归并段或顺串。
- 对这些归并段进行逐趟归并,是归并段逐渐由小到大,直至得到整个有序文件位置。
在外部排序中实现两两归并时,由于不可能将两个有序段及归并结果段同时存放在内存中,因此需要不停地将数据读出、写入磁盘,而这会耗费大量的时间。一般情况下:
- 外部排序的总时间 = 内存排序所需的时间 + 外存信息读取的时间 + 内部归并所需的时间
显然,外存信息读取地时间远大于内部排序和内部归并地的时间,因此应着力减少I/O次数。由于外村信息的读/写是以“磁盘块”为单位进行的,以8个归并段为例,可知每一趟归并需进行16次读和16次写,3趟归并并加上内部排序时所需进行的读/写,使得总共需进行128次读写。若改用4路归并排序,则只需2趟排序,外部排序时的总读写次数便减少为96。
因此增大归并路数可以减少归并趟数,进而减少总的磁盘I/O次数。