归并排序——C#实现

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


       一 算法描述

       归并排序算法(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;      
    }      
}


       为了测试该算法的平均运行情况,通过对10000个随机数组进行排序取平均:

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个数量级。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值