一 算法描述
归并排序算法(Merge Sort)是一种采用分治法(Divide-And-Conquer)的排序算法。分治策略将原问题划分为n个规模较小而结构与原问题相似的子问题,递归地解决这些子问题,然后在合并其结果,就得到原问题的解。分治模式在每一层递归上都包含三个步骤:分解、解决、合并。
具体到归并算法,三个步骤分别为:
分解:将n个元素分成各含n/2个元素的子序列。
解决:用归并排序法对两个子序列递归的排序。
合并:合并两个已排序的子序列以得到排序结果。
下图是利用归并算法对一个数组进行排序的执行过程(图片截取自《算法导论》):
二 算法实现
1 用于整数数组的升序排序
public static void MergeSort(int[] array)
{
MergeSort(array, 0, array.Length - 1);
}
private static void MergeSort(int[] array, int p, int r)
{
if (p < r)
{
int q = (p + r) / 2;
MergeSort(array, p, q);
MergeSort(array, q + 1, r);
Merge(array, p, q, r);
}
}
private static void Merge(int[] array, int p, int q, int r)
{
int[] L = new int[q - p + 2];
int[] R = new int[r - q + 1];
L[q - p + 1] = int.MaxValue;
R[r - q] = int.MaxValue;
for (int i = 0; i < q - p + 1; i++)
{
L[i] = array[p + i];
}
for (int i = 0; i < r - q; i++)
{
R[i] = array[q + 1 + i];
}
int j = 0;
int k = 0;
for (int i = 0; i < r - p + 1; i++)
{
if (L[j] <= R[k])
{
array[p + i] = L[j];
j++;
}
else
{
array[p + i] = R[k];
k++;
}
}
}2 泛型版本
public static void MergeSort<T>(T[] array, Comparison<T> comparison)
{
MergeSort<T>(array, 0, array.Length - 1, comparison);
}
private static void MergeSort<T>(T[] array, int p, int r, Comparison<T> comparison)
{
if (p < r)
{
int q = (p + r) / 2;
MergeSort<T>(array, p, q, comparison);
MergeSort<T>(array, q + 1, r, comparison);
Merge<T>(array, p, q, r, comparison);
}
}
private static void Merge<T>(T[] array, int p, int q, int r, Comparison<T> comparison)
{
T[] left = new T[q - p + 1];
T[] right = new T[r - q];
for (int i = 0; i < q - p + 1; i++)
{
left[i] = array[p + i];
}
for (int i = 0; i < r - q; i++)
{
right[i] = array[q + 1 + i];
}
int j = 0;
int k = 0;
for (int i = 0; i < r - p + 1; i++)
{
if (j < q - p + 1 && k < r - q)
{
if (comparison(left[j], right[k]) <= 0)
{
array[p + i] = left[j];
j++;
}
else
{
array[p + i] = right[k];
k++;
}
}
else if (j < q - p + 1 && k >= r - q)
{
for (int m = j; m < q - p + 1; m++)
{
array[p + i + m - j] = left[m];
}
return;
}
else if (j >= q - p + 1 && k < r - q)
{
for (int n = k; n < r - q; n++)
{
array[p + i + n - k] = right[n];
}
return;
}
}
}
1 在每一次的Merge过程中,函数会声明两个数组用于临时存放子过程中已排序的数组,这个数组长度为子过程中元素的数量,因此在最坏的情况下,长度为n/2或(n/2+1)(取决于待排序元素总数的奇偶性),即归并排序的空间复杂度为O(n),所以归并排序并不是一种原址排序算法。
2 注意到在Merge过程中,如果左边元素小于或等于右边元素的话,在合并时取左边的元素,因此合并过程并不会造成值相同元素的相对顺序的变化。故而归并排序也是一种稳定的排序算法。
3 归并排序算法中 包含了对自身的递归调用,其运行时间为一个递归式:
T(n) = θ(1) 如果n=1
2T(n/2) + θ(n) 如果n>1
如下图,为递归式构造一颗递归树,在树上的每一层的总时间代价均为cn,当树完全扩展时,一共有lgn + 1层。因此归并排序的时间代价为θ(nlgn)。
此结论也可以直接通过主定理得到。
递归树(图片截取自《算法导论》)
四 运行结果
在归并排序中,耗时的部分主要在每一层已排列子数组的合并上。在进行合并的时候,每一次都会判断左右子数组中排在最前面的谁更小(大),因此,归并算法依然可以通过记录元素的比较次数来估计整个算法的执行次数。
在算法的泛型实现版本中,通过在委托传入的比较方法里加入计数语句,则能很容易的得到比较语句执行的次数。
private static int AscComparison(int x, int y)
{
count++;
if (x > y)
{
return 1;
}
else if (x == y)
{
return 0;
}
else
{
return -1;
}
}
static void Main(string[] args)
{
for (int i = 0; i < 10000; i++)
{
//在1-100内产生10个随机数
int[] randomIntArray = DataCreator.CreateRandomIntArray(1, 100, 10);
Sort.MergeSort(randomIntArray, AscComparison);
PrintAarry(randomIntArray);
}
int averageCount = count / 10000;
Console.WriteLine(averageCount);
} 测试结果:
n = 10, averageCount = 22= 0.67* 10 * lg10;
n = 100, averageCount = 541= 0.81 * 100 * lg100;
n = 1000, averageCount = 8706 = 0.87 * 1000 * lg1000;
由测试结果可知,归并排序算法的平均运算时间复杂度也是θ(nlgn)。
同时,插入排序、选择排序等算法比较,在n较小时,归并排序的优势并不明显,如n=10时,插入排序的平均比较次数是29,归并排序的平均比较次数是22,相差并不大。但是随着n的增加,归并排序的优势显著增大。在n=1000时,归并排序已经比插入排序等快了2个数量级。

本文详细介绍了归并排序的算法描述、实现过程及时间复杂度分析。通过C#代码实现,展示了如何进行整数数组的升序排序。归并排序采用分治法,时间代价为θ(nlgn),在大数据量时表现出优越性能。
191

被折叠的 条评论
为什么被折叠?



