风言枫语  

归并排序(Merge Sort)是利用"归并"技术来进行排序。归并是指将若干个已排序的子文件合并成一个有序的文件。


归并排序

基本思想:两个有序的子序列(相当于输入序列)放在同一序列中相邻的位置上:array[low..m],array[m + 1..high],先将它们合并到一个局部的暂存序列 temp (相当于输出序列)中,待合并完成后将 temp 复制回 array[low..high]中,从而完成排序。


在具体的合并过程中,设置 i,j 和 p 三个指针(其实只是数组下标而已),其初值分别指向这三个记录区的起始位置。合并时依次比较 array[i] 和 array[j] 的关键字,取关键字较小(或较大)的记录复制到 temp[p] 中,然后将被复制记录的指针 i 或 j 加 1,以及指向复制位置的指针 p 加 1。重复这一过程直至两个输入的子序列有一个已全部复制完毕(不妨称其为空),此时将另一非空的子序列中剩余记录依次复制到 array 中即可。


合并过程的c++实现:

 

void merge(int* array, int low, int mid, int high)
{
    //申请一个暂存数组,用于存储合并过程中的结果
    int* temp = new int [high-low+1];
    if (!temp)
    {
        cout<<"Error:out of memory!"<<endl;
        return;
    }

    int i = low;
    int j = mid + 1;
    int index = 0;

    while (i <= mid && j <= high)
    {
        if (array[i] <= array[j])
        {
            temp[index++] = array[i++];
        }
        else
        {
            temp[index++] = array[j++];
        }
    }

    while (i <= mid)
    {
        temp[index++] = array[i++];
    }

    while (j <= high)
    {
        temp[index++] = array[j++];
    }
    
    //将结果数组拷贝到原来的数组中
    memcpy((void*)(array + low), (void*)temp, (high - low + 1) * sizeof(int)) ;

    delete [] temp;
}


 

 

 归并排序有两种实现方法:自底向上自顶向下


一、自顶向下

自底向上方法,也就是常说的二路归并排序

其基本思想是:第 1 趟排序将长度为 n 的待排序记录看作 n 个长度为 1 的有序子序列,然后将这些子序列两两合并。完成第 1 趟排序之后,将得到 lgn 个长度为 2 的有序子序列(如果 n 为奇数,则最后还有一个长度为 1 的子序列)。第 2 趟排序是在第 1 趟的排序的基础上,将这 lgn 个长度为 2 的子序列两两合并。如此反复,直到最后得到一个长度为n的有序文件为止。从这个排序过程来看,二路归并排序是从将长度为 1 的子序列排序变化到长度为 n 的有序序列,因而是自底向上的。


下面给出两个排序过程的示意图:


(1)待排序元素个数为偶数:

(2)待排序元素个数为奇数:



下面看一下自底向上的归并排序的c++实现:


 

// 对 [0, length - 1] 做一趟归并
//归并的两个元素长度为 n  
void merge_pass(int* array, int length, int n)
{
    int i;
    int sortedLength = 2 * n;

    // 归并长度为 n 的两个相邻子序列
    for(i = 0; i <= length-1; i = i + sortedLength)
    {

        int low = i;
        int mid = i+n-1;
        int high = i+sortedLength-1;
        
        //注意下标不要越界
        if(mid > (length-1)) mid  = length-1;
        if(high > (length-1)) high = length-1;
        
        //输出debug信息
        cout<<"basic length = "<<n<<"  low = "<<low<<"  mid = "<<mid<<"  high = "<<high<<endl;
        merge(array, low, mid, high);
    }
}

void merge_sort_buttomUp(int* array, int length)
{
    for(int n = 1; n < length; n = (n *2))
    {
        merge_pass(array, length, n);
    }
}


 

下面通过两个实例来稍稍解说一下:


1、待排序元素个数为偶数:


 

int a[8] = {2,1,6,10,4,8,3,5};
    merge_sort_buttomUp(a,8);
    cout<<"排序结果:"<<endl;
    for(int i = 0; i<=7; i++)cout<<a[i]<<" ";


 


注意到我在每一趟合并之前都打印输出了合并的参数,先说明一下输出结果中,basic length 表示此时合并时,两个合并数组的长度。那么结合上面的合并示意图应该可以理解了的。


2、待排序元素个数为偶数:


 

int a[9] = {2,1,6,10,4,8,3,5,0};
    merge_sort_buttomUp(a,9);
    cout<<"排序结果:"<<endl;
    for(int i = 0; i<=8; i++)cout<<a[i]<<" ";


 


结合上面的示意图,注意到输出的debug信息中有 low = mid = high = 8 的情况,这就是在奇数个待排序元素的时候,对最后一个数组本身进行了一次合并,结合示意图和debug信息综合理解。

 

 二、自上而下

 

 自底向上的二路归并排序算法虽然效率较高,但可读性较差(循环实现比递归实现一般效率都要高些)。下面来看看自上而下的递归实现,其可读性要好得多。自上而下的方法是采用分治法思想,具体排序过程分成三个过程:

(1)分解:将当前区间一分为二,即求分裂点 mid = (low + high)/2

(2)求解:递归地对两个子区间 array[low..mid] 和 array[mid + 1..high] 进行归并排序;递归的终结条件:子区间长度为 1(一个记录自然有序)。

(3)合并:将已排序的两个子区间R[low..mid]和R[mid + 1..high]归并为一个有序的区间 array[low..high]。


下面给出一张示意图说明一下其过程:




  

下面看一下自上而下的归并排序的c++实现:


 

void merge_sort_pass(int* array, int low, int high)
{
    int mid;
    if (low < high)
    {
        mid = (low + high)/2;
        
        //递归过程分割
        merge_sort_pass(array, low, mid);
        merge_sort_pass(array, mid + 1, high);
        
        //合并
        merge(array, low, mid, high);
    }
}

void merge_sort_topDown(int* array, int length)
{
    merge_sort_pass(array, 0, length - 1);
}


下面结合上图的示例,给出测试代码:

 

 

int a[6] = {14,12,15,13,11,16};
    merge_sort_topDown(a,6);
    cout<<"排序结果:"<<endl;
    for(int i = 0; i<=5; i++)cout<<a[i]<<" ";

 





观察可以知道:

其中:自底向上的实现过程采用迭代的实现;而自上而下的实现过程采用递归的实现。



好了,上面关于归并排序的两种思路都做了介绍,下面分析一下这个算法:


 

时间复杂度分析:
   对长度为 n 的序列进行 lgn 趟二路归并,而每一趟归并的时间复杂度为 O(n),因此归并排序的平均时间复杂度为 nlgn。


空间复杂度分析:

需要与待排记录等大的空间来存储中间变量,因为其空间复杂度为 O(n)。因此,归并排序肯定就不是就地排序了。


归并排序是稳定排序。


参考文章:http://www.cppblog.com/kesalin/archive/2011/03/13/merge_sort.html

 

posted on 2013-10-14 09:08  风言枫语  阅读(779)  评论(0编辑  收藏  举报