本文算法主要整理自《计算机算法设计与分析(第3版)》电子工业出版社,出于学习目的写下此文。
合并排序算法是用分治策略实现对n个元素进行排序的算法。
其基本思想是:将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。
合并排序算法递归实现的程序:
1 class MergeRecursion<T> where T: new() 2 { 3 public void MergeSort(List<T> li, int left, int right) 4 { 5 //临时存储数据区 6 List<T> li_b = new List<T>(100); 7 for (int i = 1; i <= 100; i++) 8 { 9 T item = new T(); 10 li_b.Add(item); 11 } 12 13 //判断,保证至少有个元素 14 if (left < right) 15 { 16 //取中点 17 int m = (left + right) / 2; 18 19 MergeSort(li, left, m); 20 MergeSort(li, m + 1, right); 21 //合并到数组b 22 MergeCommon<T>.Merge(li, li_b, left, m, right); 23 //复制回数组a 24 Copy(li, li_b, left, right); 25 } 26 else 27 { 28 return; 29 } 30 } 31 32 private static void Copy(List<T> li, List<T> li_b, int left, int right) 33 { 34 for (int i = left; i <= right; i++) 35 { 36 li[i] = li_b[i]; 37 } 38 } 39 }
Merge方法将两个排好序的数组段合并到一个新的数组b中,然后由Copy将合并后的数组段再复制回a中。合并算法Merge的实现如下:
1 class MergeCommon<T> 2 { 3 public static void Merge(List<T> li, List<T> li_b, int left, int m, int right) 4 { 5 //比较用 6 BComparer<T> bc = BComparer<T>.Instance; 7 //合并li[left:m]和li[m+1:right]到d[left:right] 8 int i = left; 9 int j = m + 1; 10 int k = left; 11 while ((i <= m) && (j <= right)) 12 { 13 if(bc.Compare(li[i],li[j]) <= 0) 14 { 15 li_b[k++] = li[i++]; 16 } 17 else 18 { 19 li_b[k++] = li[j++]; 20 } 21 } 22 if (i > m) 23 { 24 for (int q = j; q <= right; q++) 25 { 26 li_b[k++] = li[q]; 27 } 28 } 29 else 30 { 31 for (int q = i; q <= m; q++) 32 { 33 li_b[k++] = li[q]; 34 } 35 } 36 } 37 }
合并排序的非递归实现
由于递归操作是一种较消耗资源的操作,可以考虑实现无递归的合并排序
思想:首先将数组a中相邻的元素两两配对。用合并算法将他们排序,构成n/2组长度为2排好序的子数组段,然后再将它们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排序好。
此思想的算法实现:
1 class MergeNRecursion<T> where T: new() 2 { 3 public void MergeSort(List<T> li, int n) 4 { 5 //临时存储数据区 6 List<T> li_b = new List<T>(100); 7 for (int i = 1; i <= 100; i++) 8 { 9 T item = new T(); 10 li_b.Add(item); 11 } 12 13 int s = 1; 14 while (s < n) 15 { 16 MergePass(li,li_b,s,n); 17 s += s; 18 MergePass(li_b,li,s,n); 19 s += s; 20 } 21 } 22 23 private static void MergePass(List<T> li, List<T> li_b, int s, int n) 24 { 25 … … 26 } 27 }
其中MergePass用于合并排好序的相邻数组段。而具体的合并算法同样由Merge来实现。
MergePass算法实现:
1 private static void MergePass(List<T> li, List<T> li_b, int s, int n) 2 { 3 //合并大小为s的相邻子数组 4 int i = 0; 5 while (i <= n - 2 * s) 6 { 7 //合并大小为s的相邻2段子数组 8 MergeCommon<T>.Merge(li,li_b,i,i+s-1,i+2*s-1); 9 i = i + 2 * s; 10 } 11 12 //剩下的元素个数少于2s 13 if (i + s < n) 14 { 15 MergeCommon<T>.Merge(li, li_b, i, i + s - 1, n - 1); 16 } 17 else 18 { 19 for (int j = i; j <= n - 1; j++) 20 { 21 li_b[j] = li[j]; 22 } 23 } 24 }
解释:剩余元素个数少于2s后的处理程序中,i + s < n 这句判断可以得出个数不足2s的那段数组是否已排过序,如果if条件成立,说明剩余元素还未排过序,需要调用Merge方法排序,否则说明在前一次MergePass执行过程中就已经对相同的数组段进行过排序,不用重复进行,只需要直接复制到另一个数组即可(else实现)。
这种非递归的合并排序拥有自然合并排序的基本思想
算法中一个很重要的代码是对泛型对象的大小比较,代码来自光辉的晨星的Blog原文见此。
把代码粘贴于此:
1 public class BComparer<T> 2 { 3 //比较委托 4 Comparison<T> _comparison; 5 6 static readonly Dictionary<Type, Delegate> _map = new Dictionary<Type, Delegate>(); 7 8 //实现单例(代替构造函数的功能) 9 static readonly BComparer<T> _instance = new BComparer<T>(); 10 //构造函数 11 private BComparer() { } 12 13 public static BComparer<T> Instance 14 { 15 get 16 { 17 System.Diagnostics.Debug.Assert(_map.ContainsKey(typeof(T))); 18 //强转为具体的比较委托 19 _instance._comparison = (Comparison<T>)_map[typeof(T)]; 20 return _instance; 21 } 22 } 23 //情态构造,初始化 24 static BComparer() 25 { 26 //基础类型比较器 27 Type t = typeof(T); 28 if (t == typeof(bool)) 29 { 30 Comparison<bool> cmp_bool = delegate(bool t1, bool t2) 31 { return t1 == t2 ? 0 : (t1 ? 1 : -1); }; 32 _map.Add(typeof(bool), (Delegate)cmp_bool); 33 } 34 if (t == typeof(int)) 35 { 36 Comparison<int> cmp_int = delegate(int t1, int t2) 37 { return t1 > t2 ? 1 : (t1 == t2 ? 0 : -1); }; 38 _map.Add(typeof(int), (Delegate)cmp_int); 39 } 40 //....其他 41 } 42 //注册自定义比较 43 public static void Register<NT>(Comparison<NT> comparison) 44 { 45 System.Diagnostics.Debug.Assert(_map.ContainsKey(typeof(NT)) == false); 46 47 _map.Add(typeof(NT), (Delegate)comparison); 48 } 49 //比较函数,以后用来实现IComparer用 50 public int Compare(T t1, T t2) 51 { 52 System.Diagnostics.Debug.Assert(_comparison != null); 53 54 return _comparison(t1, t2); 55 } 56 }
原书文章中提到了自然合并排序,自然合并排序是上述非递归合并排序算法的一个变形。其基本思想是初始序列中有自然排好序的子数组,用1次对数组线性扫描就足以找出所有这些排好序的子数组段。将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组。这样继续下去直至真个数组排好序。这样就比从1开始排相邻的元素效率要高很多。
下面是个人实现的一种自然排序,请指点:
1 class MergeNature<T> where T : new() 2 { 3 public static List<int> GetChildArray(List<T> li) 4 { 5 //保存子数组长度(最后一个元素位置) 6 List<int> arr = new List<int>(100); 7 8 int i = 0; 9 arr.Add(-1); 10 11 BComparer<T> bc = BComparer<T>.Instance; 12 13 while (i < li.Count - 1) 14 { 15 if (bc.Compare(li[i], li[i + 1]) <= 0) 16 { 17 i++; 18 continue; 19 } 20 else 21 { 22 arr.Add(i); 23 i++; 24 } 25 } 26 if (arr[arr.Count - 1] != 29) 27 { 28 arr.Add(29); 29 } 30 return arr; 31 } 32 33 /// <summary> 34 /// 合并子数组(即列表) 35 /// </summary> 36 /// <param name="li"></param> 37 public static void MergeSort(List<T> li) 38 { 39 //调用GetChildArray获取arr 40 List<int> arr = GetChildArray(li); 41 42 //临时存储数据区 43 List<T> li_b = new List<T>(100); 44 for (int i = 1; i <= 100; i++) 45 { 46 T item = new T(); 47 li_b.Add(item); 48 } 49 50 int s = 1; 51 while (s < arr.Count - 1) 52 { 53 MergePass(li, li_b, s, arr); 54 s += s; 55 MergePass(li_b, li, s, arr); 56 s += s; 57 } 58 } 59 60 private static void MergePass(List<T> li, List<T> li_b, int s, List<int> arr) 61 { 62 int i = 0; 63 int length = arr.Count - 1; 64 65 while (i <= length - 2 * s ) 66 { 67 MergeCommon<T>.Merge(li, li_b, arr[i] + 1, arr[i + s], arr[i + 2 * s]); 68 i = i + 2 * s; 69 } 70 71 //arr数组中剩余元素个数少于s 72 if (i + s < length) 73 { 74 MergeCommon<T>.Merge(li, li_b, arr[i] + 1, arr[i + s], arr[length]); 75 } 76 else 77 { 78 for (int j = arr[i] + 1; j <= arr[length]; j++) 79 { 80 li_b[j] = li[j]; 81 } 82 } 83 } 84 }