归并排序

一.两个有序数组的排顺

如果有两个有序的数组合并为一个有序数组,我们可以用下面的代码实现:

public void merge(int[] a, int[] b, int[] c){
    int i=0,j=0,k=0;
    while (i<a.length && j<b.length){
        if (a[i]<=b[j]){
            c[k++]=a[i++];
        } else{
            c[k++]=b[j++];
        }
    }
    while (i<a.length){
        c[k++]=a[i++];
    }
    while (j<b.length){
        c[k++]=b[j++];
}

其中数组a,b为我们已经排好序的两个升序数组,c为我们新建的一个长度为a,b两数组相加的一个空数组。
  我们用一组测试用例来测试上述算法,并加上log使之更加清晰:

public class TwoListSort {
    public static void main(String args[]){
        int[] a = {2,6,11 };        //声明数组
        int[] b = {8,9,9,15};
        int[] c = new int[7];
        merge(a,b,c);
    }

    public static void merge(int[] a, int[] b, int[] c){
        int i=0,j=0,k=0;
        while (i<a.length && j<b.length){
            if (a[i]<=b[j]){
                c[k++]=a[i++];
                test(c,i,j,k);
            } else{
                c[k++]=b[j++];
                test(c,i,j,k);
            }
        }
        while (i<a.length){
            c[k++]=a[i++];
            test(c,i,j,k);
        }
        while (j<b.length){
            c[k++]=b[j++];
            test(c,i,j,k);
        }
    }

    public static void test(int[] c, int i, int j, int k){
        for(int h = 0; h < c.length ; h++) {
            System.out.print( c[h] + "," );
          }
         System.out.print( "        "+ " i = "+i +", "+" j = "+j +", "+" k = "+k );
        System.out.println("");
    }
}

log输出为:

2,0,0,0,0,0,0,         i = 1,  j = 0,  k = 1
2,6,0,0,0,0,0,         i = 2,  j = 0,  k = 2
2,6,8,0,0,0,0,         i = 2,  j = 1,  k = 3
2,6,8,9,0,0,0,         i = 2,  j = 2,  k = 4
2,6,8,9,9,0,0,         i = 2,  j = 3,  k = 5
2,6,8,9,9,11,0,         i = 3,  j = 3,  k = 6
2,6,8,9,9,11,15,         i = 3,  j = 4,  k = 7

我们对照log来分析一下上述过程:

初始:i=0,j=0,k=0;
a[0]<b[0]
    c[0] = a[0] = 2;
c = 2,0,0,0,0,0,0,         i = 1,  j = 0,  k = 1
a[1]<b[0]
    c[1] = a[1] = 6;
c = 2,6,0,0,0,0,0,         i = 2,  j = 0,  k = 2
a[2]>b[0]
    c[2] = b[0] = 8;
c = 2,6,8,0,0,0,0,         i = 2,  j = 1,  k = 3
a[2]>b[1]
    c[3] = b[1] = 9;
c = 2,6,8,9,0,0,0,         i = 2,  j = 2,  k = 4
a[2]>b[2]
    c[4] = b[2] = 9;
c = 2,6,8,9,9,0,0,         i = 2,  j = 3,  k = 5
a[2]<b[3]
    c[5] = a[2] = 11;
c = 2,6,8,9,9,11,0,         i = 3,  j = 3,  k = 6

此时,不满足while (i<a.length && j<b.length){这个条件,跳出最上面的循环,进入下面的循环中:
        while (j<b.length){
            c[k++]=b[j++];
            test(c,i,j,k);
        }
c[6] = b[3] = 15
c = 2,6,8,9,9,11,15,         i = 3,  j = 4,  k = 7

上面算法设置的非常巧妙,大家可以多测试几组用例,针对不同情况做出分析。

二.无序数组的排序

现在假设有一个无序数组需要排序,但是可以人为的通过一定操作划分为两个有序的数组A和B,那么我们借助上述算法可以很快的将A和B两个有序的子数组进行归并排序。
  现在有一个无序数组需要排序,并且他是完全无序的,不能划分为任何有序数组,这个时候需要怎么排序呢?这个问题可以转换为,上一种情况中,A和B两个子数组也是无序的,那么我们可以继续将A和B两个数组拆分下去,拆成更小的子数组:a1,a2和b1,b2...如此下去,我们一直将子数组拆分到最小元素,即一个子数组只有一个元素(也就是拆分成一个个元素,每个元素视为长度为1的数组),那么这一个个长度为1的数组,就可以视为有序数组了(毕竟他只有一个元素)。
  那么此时我们可以两个元素,视为“一.两个有序数组的排顺”情况中的A.B两个数组,然后两两合并,知道合并为最初长度的数组,可以看到,这实际上是一个递归的过程。

归并排序.gif

看到这里不理解的话没关系,我们会在代码中带大家一步步分析:

public class MergeSort {
    public static void main(String args[]){
        int[] nums = {27, 8, 57, 9, 23, 41, 65, 19, 0, 1, 2 };      //声明数组
        mergeSort(nums);
    }
    //归并排序
    public static void mergeSort(int[] arr){
        int[] temp =new int[arr.length] ;
        internalMergeSort(arr, temp, 0, arr.length-1);
    }
    private static void internalMergeSort(int[] a, int[] b, int left, int right){
        if (left<right){    //当left==right的时,已经不需要再划分了
            int middle = (left+right)/2;
            internalMergeSort(a, b, left, middle);          //左子数组
            internalMergeSort(a, b, middle+1, right);       //右子数组
            mergeSortedArray(a, b, left, middle, right);    //合并两个子数组
        }
    }
    // 合并两个有序子序列 arr[left, ..., middle] 和 arr[middle+1, ..., right]。temp是辅助数组。
    private static void mergeSortedArray(int arr[], int temp[], int left, int middle, int right){
        int i=left;
        int j=middle+1;
        int k=0;

        while ( i<=middle && j<=right){     // 逐个归并
            if (arr[i] <=arr[j]){
                temp[k++] = arr[i++];
            } else{
                temp[k++] = arr[j++];
            }
        }
        while (i <=middle){      // 将左边剩余的归并
            temp[k++] = arr[i++];
        }
        while ( j<=right){           // 将右边剩余的归并
            temp[k++] = arr[j++];
        }
        for (i=0; i<k; ++i){         //把数据复制回原数组
            arr[left+i] = temp[i];
        }
    }
}

上面展示的就是一个归并排序的过程,internalMergeSort(int[] a, int[] b, int left, int right)该方法就是拆分数组为一个个最小子元素的方法,可以看到该方法中动用了递归调用;mergeSortedArray(int arr[], int temp[], int left, int middle, int right)则是“一.两个有序数组的排顺”情况中将两个有序数组排序的算法,也就是归并方法:

我们给上述两个方法加上必要的log:

   private static void internalMergeSort(int[] a, int[] b, int left, int right){
        //当left==right的时,已经不需要再划分了
        if (left<right){
            int middle = (left+right)/2;
            
            System.out.println("");
            System.out.println(  "(left左 , middle左,right左) = " + " (" + left + "," + middle + "," + right + ") "  );
            for(int i = left; i < right +1;  i++) {
                System.out.print(a[i] + ",");
             }
            internalMergeSort(a, b, left, middle);          //左子数组
            
            System.out.println("");
            
            System.out.println(  "(left右 , middle右,right右) = " + " (" + left + "," + (middle+1) + "," + right + ") "  );
            for(int i = middle+1; i < right +1;  i++) {
                System.out.print(a[i] + ",");
             }
            internalMergeSort(a, b, middle+1, right);       //右子数组
           
            mergeSortedArray(a, b, left, middle, right);    //合并两个子数组
        }
    }
 private static void mergeSortedArray(int arr[], int temp[], int left, int middle, int right){      
         System.out.println("");
         System.out.println(  "(left合并 , middle合并,right合并) = " + " (" + left + "," + middle + "," + right + ") "  );
         System.out.print("排序前:");
         for(int h = left; h < right +1; h++) {
            System.out.print( arr[h] + ",");
          }
         
        int i=left;     
        int j=middle+1;
        int k=0;
        
        while ( i<=middle && j<=right){     // 逐个归并
            if (arr[i] <=arr[j]){
                temp[k++] = arr[i++];
            } else{
                temp[k++] = arr[j++];
            }
        }
        
        while (i <=middle){      // 将左边剩余的归并
            temp[k++] = arr[i++];
        }
        
        while ( j<=right){           // 将右边剩余的归并
            temp[k++] = arr[j++];
        }
       
        for (i=0; i<k; ++i){         //把数据复制回原数组
            arr[left+i] = temp[i];
        }
        
        System.out.println("");
        System.out.print("排序后:");
        for(int h = left; h < right +1; h++) {
            System.out.print( arr[h] + ",");
          }
    }

由于运行的结果比较长,我们将在下面的分析中逐步分开讲解:
下面我们来分析一遍上面的结果——首先是第一部分:

(left左 , middle左,right左) =  (0,5,10) 
27,8,57,9,23,41,65,19,0,1,2,
(left左 , middle左,right左) =  (0,2,5) 
27,8,57,9,23,41,
(left左 , middle左,right左) =  (0,1,2) 
27,8,57,
(left左 , middle左,right左) =  (0,0,1) 
27,8,
(left右 , middle右,right右) =  (0,1,1) 
8,
(left合并 , middle合并,right合并) =  (0,0,1) 
排序前:27,8,
排序后:8,27,

这个部分的log代表的过程图示如下,即第一次循环遍历到左子树的最底层,开始向上归并一层,归并的过程伴随着排序:

归并排序测试用例图解(第一部分).png

归并后的结果为8,27,然后向上递归调用,再归并一层:

(left右 , middle右,right右) =  (0,2,2) 
57,
(left合并 , middle合并,right合并) =  (0,1,2) 
排序前:8,27,57,
排序后:8,27,57,
归并排序测试用例图解(第二步).png

接着归并第三层:

(left右 , middle右,right右) =  (0,3,5) 
9,23,41,
(left左 , middle左,right左) =  (3,4,5) 
9,23,41,
(left左 , middle左,right左) =  (3,3,4) 
9,23,
(left右 , middle右,right右) =  (3,4,4) 
23,
(left合并 , middle合并,right合并) =  (3,3,4) 
排序前:9,23,
排序后:9,23,
(left右 , middle右,right右) =  (3,5,5) 
41,
(left合并 , middle合并,right合并) =  (3,4,5) 
排序前:9,23,41,
排序后:9,23,41,
![Uploading 归并排序_977410.gif . . .]
(left合并 , middle合并,right合并) =  (0,2,5) 
排序前:8,27,57,9,23,41,
排序后:8,9,23,27,41,57,

这一步就是合并8,27,579,23,41这两个有序子数组,毕竟这两个子数组的底层已经递归调用完了,然后合并的过程中伴随着排序,最终得到第二层有序的左子数组:8,9,23,27,41,57,

接着是同样的递归合并第二层右子数组:

(left右 , middle右,right右) =  (0,6,10) 
65,19,0,1,2,
(left左 , middle左,right左) =  (6,8,10) 
65,19,0,1,2,
(left左 , middle左,right左) =  (6,7,8) 
65,19,0,
(left左 , middle左,right左) =  (6,6,7) 
65,19,
(left右 , middle右,right右) =  (6,7,7) 
19,
(left合并 , middle合并,right合并) =  (6,6,7) 
排序前:65,19,
排序后:19,65,
(left右 , middle右,right右) =  (6,8,8) 
0,
(left合并 , middle合并,right合并) =  (6,7,8) 
排序前:19,65,0,
排序后:0,19,65,
(left右 , middle右,right右) =  (6,9,10) 
1,2,
(left左 , middle左,right左) =  (9,9,10) 
1,2,
(left右 , middle右,right右) =  (9,10,10) 
2,
(left合并 , middle合并,right合并) =  (9,9,10) 
排序前:1,2,
排序后:1,2,
(left合并 , middle合并,right合并) =  (6,8,10) 
排序前:0,19,65,1,2,
排序后:0,1,2,19,65,

最后是归并第二层已经拍好队的两个有序子数组:8,9,23,27,41,57,0,1,2,19,65,,最终得到有序的原数组:

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

推荐阅读更多精彩内容

  • 数据结构与算法--归并排序 归并排序 归并排序基于一种称为“归并”的简单操作。比如考试可能会分年级排名和班级排名,...
    sunhaiyu阅读 871评论 0 6
  • 归并排序 百度上的解释:归并排序:是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide ...
    半月迎风阅读 629评论 0 4
  • 序言 上一篇文章我们已经讲完了插入排序,也就是说我的On^2 的算法基本就写完了,当然还有别的On^2 的算法,但...
    再见远洋阅读 1,648评论 0 3
  • 归并排序 所谓归并,就是将两个或两个以上的有序表合并成一个新的有序表。如下图所示,有两个已经排好序的有序表A[1]...
    JackChen1024阅读 2,356评论 0 4
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到已排序好...
    依依玖玥阅读 1,239评论 0 2