归并排序

归并排序

所谓归并,就是将两个或两个以上的有序表合并成一个新的有序表。如下图所示,有两个已经排好序的有序表A[1]~A[n]和B[1]~B[m](在图中只给出了它们的关键字),通过归并把它们合成一个有序表C[1]~C[m+n]。

基本思想:归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

归并排序
归并排序

两路归并算法的C++描述

归并排序
归并排序

在这个算法中,两个待归并的有序表首尾相接存放在数组sourceTable.Arr[]中,其中第一个表的下标范围是从 left 到 mid,另一个表的下标范围从 mid+1 到 right。归并后得到的新有序表存放在另一个辅助数组中 mergedTable.Arr[] 中,其下标范围从 left 到 right。

template <class Type>
void merge ( sortlist<Type> & sourceTable, sortlist <Type> & mergedTable,
 const int left, const int mid,  const int right ) {
    int i = left,  j = mid+1,  k =left;//指针初始化
    while ( i <= mid && j <= right ){
        if ( sourceTable.Arr[i].getKey() <= sourceTable.Arr[j].getKey()){ 
            mergedTable.Arr[k] = sourceTable.Arr[i];
            i++;  
            k++; 
        } else { 
            mergedTable.Arr[k] = sourceTable.Arr[j]; 
            j++;  
            k++; 
        }
    }
    if ( i <= mid ){
        for ( int p = k, q = i;  q <= mid;  p++, q++ ){
            mergedTable.Arr[p] = sourceTable.Arr[q]; 
        }
    } else {
        for ( int p = k, q = j; q <= right; p++, q++){
            mergedTable.Arr[q] = sourceTable.Arr[p];
        }
    }
}

两路归并排序

两路归并排序就是利用两路归并算法进行排序。

其算法基本思想是:假设初始排序表有n个数据元素,首先把它看成是长度为1的首尾相接的n个有序子表(以后称它们为归并项),先做两两归并,得 ⌈n/2⌉ 个长度为2的归并项(如果n为奇数,则最后一个归并项的长度为1);再做两两归并,……,如此重复,最后得到一个长度为n的有序序列。

归并排序
归并排序

一趟归并的算法描述如下

template <class Type> 
void MergePass ( sortlist<Type> & sourceTable,
               sortlist<Type> & mergedTable,  const int len ) {
    int i =0;
    while ( i+2*len <= CurrentSize-1 ){
        merge ( sourceTable, mergedTable, i, i+len-1, i+2*len-1);
        i += 2 * len; 
    }
    if ( i+len <= CurrentSize-1 ){
        merge ( sourceTable, mergedTable,i, i+len-1, CurrentSize-1 );
    }else{
        for ( int j=i; j <= CurrentSize-1; j++ ){
            mergedTable.Arr[j] = sorceTable.Arr[j];
        }
    }        
}

两路归并排序算法描述如下

template <class Type> 
void MergeSort ( sortlist<Type> & table ) {
    sortlist<Type> & tempTable;
    int len = 1;
    while ( len < table.CurrentSize ) {
         MergePass (table , tempTable, len );   len *= 2;
         MergePass (tempTable , list , len );   len *= 2;
    }
    delete []tempTable;
}

在两路归并排序算法中,函数MergePass( )做一趟归并,要调用merge( )函数 ⌈n/(2*len)⌉ ≈ O(n/len)次,而每次merge( )要执行比较次数不超过2*len-1,为O(len),函数MergeSort( )调用MergePass( )正好 ⌈log2n⌉ 次,所以两路归并排序算法总的时间复杂度为O(nlog₂n)。

两路归并排序占用附加存储较多,需要另外一个与原待排序数据元素数组同样大小的辅助数组,所以其空间复杂度为O(n)。 两路归并排序是一个稳定的排序方法。

递归的归并排序

在递归的归并排序方法中,首先要把整个排序表划分为长度大致相等的左右两个部分,分别称之为左子表和右子表,对这两个子表分别进行归并排序(递归),然后再把已排好序的这两个子表进行两路归并,得到一个有序表。

递归的归并排序示例

归并排序
归并排序

在递归的归并排序过程中,如果使用前面给出的两路归并算法,需要进行数组元素的传递,这非常影响归并的效率。如果排序表采用链表的存储表示,可以得到一种有效的归并排序算法。

在此排序表以静态链表存储,设待排序的数据元素存放在类型为的静态链表table中。table.Arr[0]用于表示结果链表的头结点。函数linkListMerge()是将两个有序的单链表归并成一个有序单链表的算法。

静态链表上的递归归并排序示例

归并排序
归并排序

在静态链表上实现两路归并的算法描述如下

template <class Type> 
int linkListMerge (sortlinklist<Type> &table, const int p, const int q ) {
   int k = 0, i = p, j = q;
   //初始化指针,其中k为结果链表的尾结点指针,i、j为搜索指针
   while ( i !=-1 && j !=-1 ){
        if ( table.Arr[i].getKey()<=table.Arr[j].getKey() ){ 
            table.Arr[k].setLink(i);
            k = i; 
            i = table.Arr[i].getLink( ); 
        }else  { 
            table.Arr[k].setLink(j);
            k = j; 
            j = table.Arr[j].getLink( ); 
        }
   }      
   if (!i){
        table.Arr[k].setlink(j); 
   }else{
        table.Arr[k].setLink(i);
   }
   return table.Arr[0].getLink();
}

递归归并排序算法

template <class Type>
int linkListMergeSort (sortlinklist<Type> &table, const int low, const int high )
{
    if ( low >= high ) return low;
    int mid = ( low + high ) / 2;
    return linkListMerge (table, linkListMergeSort ( table, low, mid ),
                          linkListMergeSort ( table,mid+1, right ) );
}

在静态链表上实现的递归归并排序算法,通过链接指针的修改以实现数据元素接点的逻辑有序链接,因此不需要数据元素移动。计算时间可以用数据元素关键字的比较次数来测量。

算法的递归深度为O(log₂n),所以总的时间复杂度为0(nlog₂n)。它也是一种稳定的排序方法。

归并排序的C++实现

设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low...m],R[m+1...high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low...high]中。

合并过程:

  1. 设置i,j和p三个指针,其初值分别指向这三个记录区的起始位置;
  2. 合并时依次比较R[i]和R[j]的关键字,取关键字较小的记录复制到R1[p]中;
  3. 然后将被复制记录的指针i或j加1,以及指向复制位置的指针p加1。
  4. 重复这一过程直至两个输入的子文件有一个已全部复制完毕(不妨称其为空),此时将另一非空的子文件中剩余记录依次复制到R1中即可。
归并排序
归并排序
归并排序
归并排序
void Merge(int src[], int des[], int low, int mid, int high)
{
    int i = low;
    int j = mid + 1;
    int k = low;

    while( (i <= mid) && (j <= high) ) //将小的放到目的地中
    {
        if( src[i] < src[j] )
        {
            des[k++] = src[i++];
        }
        else
        {
            des[k++] = src[j++];
        }
    }

    while( i <= mid )  //若还剩几个尾部元素
    {
        des[k++] = src[i++];
    }

    while( j <= high ) //若还剩几个尾部元素
    {
        des[k++] = src[j++];
    }
}

//每次分为两路 当只剩下一个元素时,就不需要在划分。先划分再归并。
void MSort(int src[], int des[], int low, int high, int max)
{
    if( low == high ) //只有一个元素,不需要归并,结果赋给des[low]
    {
        des[low] = src[low]; 
    }
    else //如果多个元素,进行两路划分
    {
        int mid = (low + high) / 2;
        int* space = (int*)malloc(sizeof(int) * max);

        //递归进行两路,两路的划分 
        //当剩下一个元素的时,递归划分结束,然后开始merge归并操作
        if( space != NULL )
        {
            MSort(src, space, low, mid, max); 
            MSort(src, space, mid+1, high, max);
            Merge(space, des, low, mid, high); //调用归并函数进行归并
        }

        free(space);
    }
}

void MergeSort(int array[], int len)
{
    MSort(array, array, 0, len-1, len);
}

归并排序的Java实现

/*
 * 归并操作(merge),也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。   
 * 如设有数列{6,202,100,301,38,8,1}   
 * 初始状态: [6] [202] [100] [301] [38] [8] [1] 比较次数   
 * i=1 [6 202 ] [ 100 301] [ 8 38] [ 1 ] 3   
 * i=2 [ 6 100 202 301 ] [ 1 8 38 ] 4   
 * i=3 [ 1 6 8 38 100 202 301 ] 4 
 */
public class MergeSort {
    public static void sort(int[] data) {
        int[] temp = new int[data.length];
        mergeSort(data, temp, 0, data.length - 1);
    }

    private static void mergeSort(int[] data, int[] temp, int l, int r) {
        int mid = (l + r) / 2;
        if (l == r)
            return;
        mergeSort(data, temp, l, mid);
        mergeSort(data, temp, mid + 1, r);

        for (int i = l; i <= r; i++) {
            temp[i] = data[i];
        }
        int i1 = l;
        int i2 = mid + 1;
        for (int cur = l; cur <= r; cur++) {
            if (i1 == mid + 1)
                data[cur] = temp[i2++];
            else if (i2 > r)
                data[cur] = temp[i1++];
            else if (temp[i1] < temp[i2])
                data[cur] = temp[i1++];
            else
                data[cur] = temp[i2++];
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,245评论 0 2
  • 数据结构与算法--归并排序 归并排序 归并排序基于一种称为“归并”的简单操作。比如考试可能会分年级排名和班级排名,...
    sunhaiyu阅读 873评论 0 6
  • 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非...
    NEXTFIND阅读 962评论 0 0
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,170评论 0 52
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,729评论 0 15