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

    合并排序算法是用分治策略实现对n个元素进行排序的算法。

基本思想是:将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。

合并排序算法递归实现的程序:

class  MergeRecursion < T >   where  T:  new () 

public   void  MergeSort(List < T >  li,  int  left,  int  right) 

// 临时存储数据区 
List < T >  li_b  =   new  List < T > ( 100 ); 
for  ( int  i  =   1 ; i  <=   100 ; i ++

T item 
=   new  T(); 
li_b.Add(item); 


// 判断,保证至少有个元素 
if  (left  <  right) 

// 取中点 
int  m  =  (left  +  right)  /   2
MergeSort(li, left, m); 
MergeSort(li, m 
+   1 , right); 

// 合并到数组b 
MergeCommon < T > .Merge(li, li_b, left, m, right); 

// 复制回数组a 
Copy(li, li_b, left, right); 

else  

return

}

private   static   void  Copy(List < T >  li, List < T >  li_b,  int  left,  int  right) 

for  ( int  i  =  left; i  <=  right; i ++

li[i] 
=  li_b[i]; 


}

 

Merge方法将两个排好序的数组段合并到一个新的数组b中,然后由Copy将合并后的数组段再复制回a中。合并算法Merge的实现如下:

class  MergeCommon < T >  

public   static   void  Merge(List < T >  li, List < T >  li_b,  int  left,  int  m,  int  right) 

// 比较用
BComparer < T >  bc  =  BComparer < T > .Instance; 

// 合并li[left:m]和li[m+1:right]到d[left:right] 
int  i  =  left; 
int  j  =  m  +   1
int  k  =  left; 

while  ((i  <=  m)  &&  (j  <=  right)) 

if (bc.Compare(li[i],li[j])  <=   0

li_b[k
++ =  li[i ++ ]; 

else  

li_b[k
++ =  li[j ++ ]; 


if  (i  >  m) 

for  ( int  q  =  j; q  <=  right; q ++

li_b[k
++ =  li[q]; 


else  

for  ( int  q  =  i; q  <=  m; q ++

li_b[k
++ =  li[q]; 



合并排序的非递归实现

    由于递归操作是一种较消耗资源的操作,可以考虑实现无递归的合并排序

    思想:首先将数组a中相邻的元素两两配对。用合并算法将他们排序,构成n/2组长度为2排好序的子数组段,然后再将它们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排序好。

此思想的算法实现:

class  MergeNRecursion < T >   where  T:  new () 

public   void  MergeSort(List < T >  li,  int  n) 

// 临时存储数据区 
List < T >  li_b  =   new  List < T > ( 100 ); 

for  ( int  i  =   1 ; i  <=   100 ; i ++

T item 
=   new  T(); 
li_b.Add(item); 

int  s  =   1

while  (s  <  n) 

MergePass(li,li_b,s,n); 
+=  s; 
MergePass(li_b,li,s,n); 
+=  s; 


private   static   void  MergePass(List < T >  li, List < T >  li_b,  int  s,  int  n) 

… … 


其中MergePass用于合并排好序的相邻数组段。而具体的合并算法同样由Merge来实现。  

MergePass算法实现: 

private   static   void  MergePass(List < T >  li, List < T >  li_b,  int  s,  int  n) 

// 合并大小为s的相邻子数组 
int  i  =   0

while  (i  <=  n  -   2   *  s) 

// 合并大小为s的相邻2段子数组 
MergeCommon < T > .Merge(li,li_b,i,i + s - 1 ,i + 2 * s - 1 ); 
=  i  +   2   *  s; 


// 剩下的元素个数少于2s 
if  (i  +  s  <  n) 

MergeCommon
< T > .Merge(li, li_b, i, i  +  s  -   1 , n  -   1 ); 

else  

for  ( int  j  =  i; j  <=  n  -   1 ; j ++

li_b[j] 
=  li[j]; 


 

解释:剩余元素个数少于2s后的处理程序中,i + s < n 这句判断可以得出个数不足2s的那段数组是否已排过序,如果if条件成立,说明剩余元素还未排过序,需要调用Merge方法排序,否则说明在前一次MergePass执行过程中就已经对相同的数组段进行过排序,不用重复进行,只需要直接复制到另一个数组即可(else实现)。

这种非递归的合并排序拥有自然合并排序的基本思想

算法中一个很重要的代码是对泛型对象的大小比较,代码来自光辉的晨星Blog原文见此

把代码粘贴于此:

public   class  BComparer < T >  
{
// 比较委托 
Comparison < T >  _comparison; 

static   readonly  Dictionary < Type, Delegate >  _map  =   new  Dictionary < Type, Delegate > (); 

// 实现单例(代替构造函数的功能) 
static   readonly  BComparer < T >  _instance  =   new  BComparer < T > (); 

// 构造函数
private  BComparer() { } 

public   static  BComparer < T >  Instance 

get  

System.Diagnostics.Debug.Assert(_map.ContainsKey(
typeof (T))); 

// 强转为具体的比较委托 
_instance._comparison  =  (Comparison < T > )_map[ typeof (T)]; 
return  _instance; 



// 情态构造,初始化 
static  BComparer() 

// 基础类型比较器 
Type t  =   typeof (T); 

if  (t  ==   typeof ( bool )) 

Comparison
< bool >  cmp_bool  =   delegate ( bool  t1,  bool  t2) 
return  t1  ==  t2  ?   0  : (t1  ?   1  :  - 1 ); }; 

_map.Add(
typeof ( bool ), (Delegate)cmp_bool); 


if  (t  ==   typeof ( int )) 

Comparison
< int >  cmp_int  =   delegate ( int  t1,  int  t2) 
return  t1  >  t2  ?   1  : (t1  ==  t2  ?   0  :  - 1 ); }; 

_map.Add(
typeof ( int ), (Delegate)cmp_int); 

// .其他


// 注册自定义比较 
public   static   void  Register < NT > (Comparison < NT >  comparison) 

System.Diagnostics.Debug.Assert(_map.ContainsKey(
typeof (NT))  ==   false ); 

_map.Add(
typeof (NT), (Delegate)comparison); 


// 比较函数,以后用来实现IComparer用 
public   int  Compare(T t1, T t2) 

System.Diagnostics.Debug.Assert(_comparison 
!=   null ); 
return  _comparison(t1, t2); 

 

    原书文章中提到了自然合并排序,自然合并排序是上述非递归合并排序算法的一个变形。其基本思想是初始序列中有自然排好序的子数组,用1次对数组线性扫描就足以找出所有这些排好序的子数组段。将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组。这样继续下去直至真个数组排好序。这样就比从1开始排相邻的元素效率要高很多。

下面是个人实现的一种自然排序,请指点:

class MergeNature<T> where T : new() 

//扫描遍子数组
public static List<int> GetChildArray(List<T> li) 

//保存子数组长度(最后一个元素位置)
List<int> arr = new List<int>(100); 
 
int i = 0
arr.Add(
-1); 
 
BComparer
<T> bc = BComparer<T>.Instance; 
 
while (i < li.Count - 1

if (bc.Compare(li[i], li[i + 1]) <= 0

i
++
continue

else 

arr.Add(i); 
i
++


if (arr[arr.Count - 1!= 29

arr.Add(
29); 

return arr; 

 
/// <summary> 
/// 合并子数组(即列表) 
/// </summary> 
/// <param name="li"></param> 
public static void MergeSort(List<T> li) 

//调用GetChildArray获取arr 
List<int> arr = GetChildArray(li); 
 
//临时存储数据区 
List<T> li_b = new List<T>(100); 
for (int i = 1; i <= 100; i++

T item 
= new T(); 
li_b.Add(item); 

int s = 1
while (s < arr.Count - 1

MergePass(li, li_b, s, arr); 
+= s; 
MergePass(li_b, li, s, arr); 
+= s; 


 
private static void MergePass(List<T> li, List<T> li_b, int s, List<int> arr) 

int i = 0
int length = arr.Count - 1
 
while (i <= length - 2 * s ) 

MergeCommon
<T>.Merge(li, li_b, arr[i] + 1, arr[i + s], arr[i + 2 * s]); 
= i + 2 * s; 

 
//arr数组中剩余元素个数少于s 
if (i + s < length) 

MergeCommon
<T>.Merge(li, li_b, arr[i] + 1, arr[i + s], arr[length]); 

else 

for (int j = arr[i] + 1; j <= arr[length]; j++

li_b[j] 
= li[j]; 



 

本程序为原创转载请注明作者:hsytar

本文算法主要整理自《计算机算法设计与分析(第3版)》电子工业出版社,出于学习目的写下此文。 

你可能感兴趣的:(C#)