您的位置:首页 > 编程语言 > C#

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

2008-12-02 11:14 1366 查看

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


合并排序算法是用分治策略实现对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 += s;
MergePass(li_b,li,s,n);
s += 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 = 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 += s;
MergePass(li_b, li, s, arr);
s += 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 = 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];
}
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: