标签:
二路归并排序算法
一、基本思想:将两个有序表放在同一数组中相邻的位置上,如 R[low...mid] 和 R[mid+1...high],每次从两个段中取一个较小的数据顺序的放入数组 R´中,即将两个有序的子表合并成一个有序的表。
二、C 语言代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 /** 5 * 首先介绍将两个有序表直接归并为一个有序表的算法 Merge(), 假设两个有序表放在同一个数组中相邻的位置上: R[low...mid], R[mid+1...high] 6 先将它们合并到一个局部的暂存数组 R1 中, 待合并完以后将 R1 复制回 R 中. 为了简便, 称 R[low...mid] 为第一段, R[mid+1...high] 为第二段 7 每次从两个段中取出一个记录进行关键字的比较,将较小者放入 R1 中, 最后将各段中余下的部分直接复制到 R1 中. 这样 R1 是一个有序的表, 再将 8 其复制回 R 中. 算法如下: 9 **/ 10 11 void merge(RecType R[], int low, int mid, int high) 12 { 13 //k 是 R1 的下标, i、j 分别为第 1、2 段的下标. 14 RecType *R1; 15 int i = low; 16 int j = mid + 1; 17 int k = 0; 18 19 //动态分配空间 20 R1 = (RecType *) malloc((high - low + 1) * sizeof(RecType)); 21 22 //在第一段和第二段均未扫描完成时循环 23 while (i <= mid && j <= high) { 24 if (R[i].key <= R[j].key) { //将第一段记录放入 R1 中 25 R1[k] = R[i]; 26 i++; 27 k++; 28 } else { //将第二段记录放入 R2 中 29 R1[k] = R[j]; 30 j++; 31 k++; 32 } 33 } 34 35 //将第一段剩余的部分复制到 R1 36 while (i < mid) { 37 R1[k] = R[i]; 38 i++; 39 k++; 40 } 41 42 //将第二段剩余的部分复制到 R2 43 while (j < high) { 44 R1[k] = R[j]; 45 j++; 46 k++; 47 } 48 49 //将 R1 复制回 R 中 50 for (k = 0; i = low; i <= high; K++; i++) { 51 R[i] = R1[k]; 52 } 53 } 54 55 /** 56 * Merge() 算法实现了一次归并, 其中使用的辅助空间正好是要归并的元素个数, 接下来需要利用 Merge() 解决一趟归并问题, 在某趟归并中, 57 设各个子表长度为 length(最后一个子表的长度可能小于 length), 则归并前 R[0..n-1] 中共 [n/length] 个有序的子表: R[0..length-1], 58 R[length..2length-1], ..., R[([n/length]) * length..n-1]. 调用 Merge() 算法将相邻的一对子表进行归并时, 必须对表的个数可能是奇数 59 和最后一个子表的长度小于 length 这两种特殊情况作特殊处理: 若子表个数为奇数, 则最后一个子表无须和其他子表归并(即轮空); 若子表个数 60 为偶数, 则要注意到最后一对子表的区间上届是 n-1, 算法如下: 61 **/ 62 63 //对整个数列进行一趟归并 64 void mergePass(RecType R[], int length, int n) 65 { 66 int i; 67 68 //归并 length 长的两相邻子表 69 for (i = 0; i + 2*length -1 < n; i = i + 2*length) { 70 merge(R, i, i + length-1, i + 2*length-1); 71 } 72 73 //余下的两个子表, 后者的长度小于 length, 归并这两个子表 74 if ( i + length-1 < n ) { 75 merge(R, i, i + length-1, n-1); 76 } 77 } 78 79 80 //归并排序实现方法一: 自底向上 81 /** 82 * 自底向上的基本思想: 第一趟归并排序时, 将待排序的表 R[0..n-1] 看作是 n 个长度为 1 的有序子表, 将这些子表两两归并, 若 n 为偶数, 83 则得到了 [n/2] 个长度为 2 的有序子表; 若 n 为奇数, 则最后一个子表轮空(不参与归并), 故本次归并完成后, 前 [n/2]-1 个有序子表长度 84 为 2, 但最后一个子表的长度仍然为 1; 第二趟归并则是将第一趟归并所得到的 [n/2] 个有序表两两归并, 如此反复, 直到合成最后一个长度为 85 n 的有序表位置. 上述的每次归并操作, 均是将两个有序的子表合并成一个有序的子表, 故称其为"二路归并排序". 算法如下: 86 **/ 87 88 void mergeSort(RecType R[], int n) 89 { 90 int length; 91 for (lenght = 1; length < n; length = 2*length) { 92 mergePass(R, length, n); 93 } 94 } 95 96 //归并排序实现方法二: 自顶向下 97 /** 98 * 上述的自底向上归并算法效率较高, 但可读性较差, 若采用自顶向下的方法设计, 算法会更加简洁, 设归并排序的当前区间为 R[low..high], 则 99 递归归并的两个步骤: 100 1.分解, 将当前区间 R[low..high] 一分为二, 即求 mod = (low + high)/2; 递归地对两个子区间 R[low..mid] 和 R[mid+1..high] 进行继续 101 分解, 其终结条件是子区间长度为 1(因为一个记录的子表一定是有序表). 102 2.归并, 与分解过程相反, 将已排序的两个子区间 R[low..mid] 和 R[mid+1..high] 归并为一个有序的区间 R[low..high]. 算法如下: 103 **/ 104 105 void mergeSortDC(RecType R[], int low, int high) 106 { 107 int mid; 108 109 if (low < high) { 110 mid = (low + high) / 2; 111 mergeSortDC(R, low, mid); 112 mergeSortDC(R, mid+1, high); 113 merge(R, low, mid, high); 114 } 115 } 116 117 void mergeSort(RecType R[], int n) 118 { 119 mergeSortDC(R, 0, n-1); 120 }
三、算法分析
时间复杂度:由算法代码可知,归并排序的算法较为复杂,中间涉及多个过程和算法,可以根据其基本思想来粗略估算时间复杂度,对于 n 个记录而言,二路选择需要移动 log2n+1 次,然后,被选出来的记录再经过 n-1 次比较完成排序,故对于二路归并算法而言:
关键字比较和记录移动的最少次数是 (n-1)log2n+1,算法的时间复杂度为 O(nlog2n)。
关键字比较和记录移动的最多次数为 (n-1)log2n+1,算法的时间复杂度为 O(nlog2n)。
关键字平均比较和记录移动次数为 (n-1)log2n+1,算法的时间复杂度为 O(nlog2n)。
空间复杂度:由算法代码可知,所需的额外空间有 n-1 个 tmp 变量,故二路归并排序算法空间复杂度为 O(n)。
四、思考
归并排序是一种非常有效、非常典型的快速算法,那么,归并排序是如何体现分治思想的呢?
标签:
原文地址:http://www.cnblogs.com/lishiyun19/p/4319633.html