本文主要描述了归并排序的两种实现方式,递归方式和非递归方式,以及二者的优缺点比较。下面首先介绍一下归并排序的原理。
一、理解归并排序
归并排序的本质:通过两两合并排序再合并,最终获得了一个有序的数组。通过在纸上演示,就会发现它其实是一棵倒置的完全二叉树。
原理:假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列;再两两归并,…,如此重复,直到得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。也是分治法的典型应用。下面来张图,直观的感受一下:
二、递归方式实现
1.实现过程
(1)拆分:先将待排序列拆分成单个元素。
(2)合并:拆分之后,再按照有序的进行合并。
具体代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 归并排序
{
class MergeSortTest
{
private static MergeSortTest _instance;
public static MergeSortTest Instance {
get {
if (_instance == null)
{
_instance = new MergeSortTest();
}
return _instance;
}
}
//递归实现
public void MergeSort(int[] datas)
{
//拆分时递归 调用与合并
MSort(datas,datas,0,datas.Length-1);
}
/// <summary>
/// 将SR[s..t]归并排序为TR1[s..t]
/// </summary>
/// <param name="SR">归并前原始序列</param>
/// <param name="TR1">每次归并后的结果</param>
/// <param name="s">SR归并开始的索引</param>
/// <param name="t">SR归并结束的索引</param>
private void MSort(int[] SR, int[] TR1, int s, int t)
{
int m;
int[] TR2 = new int[SR.Length]; //用于每次递归临时存放待排序的子序列
//递归终止条件
if (s == t)
{
TR1[s] = SR[s];
}
else
{
m = (s + t) / 2 ;
//递归拆分,并把子序列保存在TR2中
MSort(SR,TR2, s,m);//左拆分
MSort(SR,TR2,m+1,t);//右拆分
//递归合并排序
//将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t]
Merge(TR2,TR1,s,m,t); //TR2为有序的,TR1为空
}
}
/// <summary>
/// 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n]
/// </summary>
/// <param name="SR">上述里面的TR2</param>
/// <param name="TR">保存排序的结果</param>
/// <param name="i">TR2序列的起始位置</param>
/// <param name="m">TR2序列的中间位置</param>
/// <param name="n">TR2序列的结束位置</param>
private void Merge(int[] SR, int[] TR, int i, int m, int n)
{
int j, k, l; //i指向SR的[i..m],j指向SR的[m+1..n],k指向TR
for (j = m + 1, k = i; i <= m && j <= n;k++)
{
if (SR[i] < SR[j])
{
TR[k] = SR[i++];
}
else
{
TR[k] = SR[j++];
}
}
if (i <= m)
{
//将剩余的SR[i..m]复制到TR
for (l = 0; l <= m - i; l++)
{
TR[k + l] = SR[i+l];
}
}
if (j <= n)
{
//将剩余的SR[j..m]复制到TR
for (l = 0; l <= n - j; l++)
{
TR[k + l] = SR[j+l];
}
}
}
}
}
2.递归方式的特点
一趟归并需要将SR[0]~SR[n]中相邻的长度为h的有序序列进行两两归并,并将结果放到TR1[0]~TR1[n]中,这需要将待排序列序列中的所有记录扫描一遍,因此耗费O(n)时间,而由完全二叉树的深度可知,这个归并排序的需要进行log2(n)次,因此总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。由于归并排序的过程中需要与原始记录序列同样数量的存储空间存放归并结果以及递归时深度为log2(n)的栈空间,因此空间复杂度为O(n+logn)。由于是挨个比较,因此归并排序是一种稳定的排序算法,比较占用内存,但效率高且稳定的算法。
三、非递归方式实现
1.实现过程
非递归方式就少了拆分过程,直接按照2,4,…,2n方式进行归并,归并的方式采用循环的来实现。具体实现方式如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 归并排序
{
class MergeSortTest
{
private static MergeSortTest _instance;
public static MergeSortTest Instance {
get {
if (_instance == null)
{
_instance = new MergeSortTest();
}
return _instance;
}
}
//非递归实现
public void MergeSort2(int[] datas)
{
int[] TR = new int[datas.Length];
int k = 1; //归并增量
while (k < datas.Length)
{
//datas与TR 来回归并
MergePass(datas, TR, k, datas.Length - 1);
k = 2 * k;
MergePass(TR, datas, k, datas.Length - 1);
k = 2 * k;
}
}
//将SR[]中相邻长度为s的子序列两两归并到TR[],s归并长度增量,n待排序列最后元素的下标
public void MergePass(int[] SR, int[] TR, int s, int n)
{
int i = 0, j;
//两两归并
while (i <= n - 2 * s + 1)
{
Merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;
}
//归并最后两个子序列
if (i < n - s + 1)
{
Merge(SR, TR, i, i + s - 1, n);
}
//若最后只剩单个子序列
else
{
for (j = i; j <= n; j++)
{
TR[j] = SR[j];
}
}
}
/// <summary>
/// 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n]
/// </summary>
/// <param name="SR">上述里面的TR2</param>
/// <param name="TR">保存排序的结果</param>
/// <param name="i">TR2序列的起始位置</param>
/// <param name="m">TR2序列的中间位置</param>
/// <param name="n">TR2序列的结束位置</param>
private void Merge(int[] SR, int[] TR, int i, int m, int n)
{
int j, k, l; //i指向SR的[i..m],j指向SR的[m+1..n],k指向TR
for (j = m + 1, k = i; i <= m && j <= n;k++)
{
if (SR[i] < SR[j])
{
TR[k] = SR[i++];
}
else
{
TR[k] = SR[j++];
}
}
if (i <= m)
{
//将剩余的SR[i..m]复制到TR
for (l = 0; l <= m - i; l++)
{
TR[k + l] = SR[i+l];
}
}
if (j <= n)
{
//将剩余的SR[j..m]复制到TR
for (l = 0; l <= n - j; l++)
{
TR[k + l] = SR[j+l];
}
}
}
}
}
2.非递归方式的特点
非递归的迭代方法,避免了递归时深度为log2(n)的栈空间,空间只是用到申请归并临时用的TR数组,因此空间复杂度为O(n),并且避免递归也在时间性能上有一定的提升,应该说,使用归并排序时,尽量考虑用非递归方式。
若文中有不当或不正确的地方,欢迎指出。虚心求教,谢谢!