各种算法的C#实现系列1 - 合并排序的原理及代码分析

本文算法主要整理自《计算机算法设计与分析(第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 }

 

posted @ 2008-12-02 11:14  hystar  阅读(2377)  评论(0编辑  收藏  举报