[老實李] 算法初探:歸并排序

歸并排序.gif

1、歸并排序的基本思想

以下面的這個數(shù)組為例肋坚,首先我們將數(shù)組對半分割,直到分割成單個元素,此時對于每一個單個元素來說都是有序的智厌,因為就他一個元素嘛诲泌,然后開始向上逐漸歸并的過程,直到上升到最后一個層級铣鹏,此時排序結(jié)束敷扫。

未命名文件.png

那我們?yōu)槭裁促M那么大勁,要將數(shù)組先分割在歸并呢诚卸?大家可以看到我們是對一個長度為“8”的數(shù)組進(jìn)行的排序葵第,一步步通過二分法到單個元素總共是經(jīng)歷了“3”個層級,這個“3”是怎么來的呢合溺?數(shù)組長度8/2/2/2=1卒密,8到1總共是經(jīng)歷了3次除以2的操作,也就是log2(8)=3棠赛,如果有N個元素哮奇,那么就有l(wèi)og2(N)的層級,由此我們可以推斷歸并排序的時間復(fù)雜度是Nlog(N)級別的睛约。

2鼎俘、歸并的過程

可以發(fā)現(xiàn)我們要使用遞歸的方式來逐漸歸并,這里以倒數(shù)第二層歸并到最后一層的過程為例來說明歸并的過程辩涝。
1贸伐、開辟出一個相同的數(shù)組空間;
2怔揩、設(shè)立三個索引捉邢,第一個藍(lán)色箭頭放在原數(shù)組的第一位,第二個紅色箭頭放在要比較的第一個數(shù)組的第一位商膊,第三個紅色箭頭放在要比較的第二個數(shù)組的第一位伏伐;
3、比較兩個紅色箭頭索引處元素的大小翘狱,將較小的那個元素放到藍(lán)色箭頭索引位置處秘案,藍(lán)色箭頭索引+1砰苍,移動的紅色索引+1潦匈,然后繼續(xù)下一輪的比較。


G2IF.gif

三個索引分別對應(yīng):左邊的紅色箭頭定義為i赚导,右邊的紅色箭頭定義為j茬缩,上面的藍(lán)色箭頭定義為k.
具體代碼實現(xiàn)如下:

public class MergeSort {
     //第一步:我們對傳遞的數(shù)組從位置0到n-1進(jìn)行歸并排序,因為我們的定義是前閉后閉吼旧,當(dāng)然你也可以使用前閉后開的定義凰锡。
     public static void mergeSort(int arr[],int n){
           mergeSortT(arr, 0, n-1);
     }
     //第二步:遞歸歸并
     public static void mergeSortT(int arr[],int l,int r){
           if (l >= r) {
                return;
           }
           int mid = (l+r)/2;
           mergeSortT(arr,l,mid);
           mergeSortT(arr, mid+1, r);
           merge(arr, l, mid,r);
     }
     //第三步:歸并操作
     // 將arr[l...mid]和arr[mid+1...r]兩部分進(jìn)行歸并
     private static void merge(int[] arr, int l, int mid, int r) {
           //1、開辟臨時數(shù)組(因為是閉區(qū)間所以這里臨時數(shù)組的大小為:r-l+1)
           int temp[] = new int[r-l+1];
           for (int i = l; i <= r; i++) { //注意是L,不是1
                temp[i-l] = arr[i];
           }
           //2、定義索引:i=L,j,k
           int i = l,j = mid +1;
           for (int k = l; k <= r; k++) { //K=L...
                //判斷索引越界
                if (i>mid) {
                     arr[k] = temp[j-l];
                     j++;
                }
                else if (j>r) {
                     arr[k] = temp[i-l];
                     i++;
                }
                //比較臨時數(shù)組兩邊的大小
                else if (temp[i-l] <temp[j-l]) {
                     arr[k] = temp[i-l];
                     i++;
                }
                else {
                     arr[k] = temp[j-l];
                     j++;
                }
           }

     }
     
}

測試用例:

public static void main(String[] args) {
           Random rand = new Random();
           int arr[] = new int[50000];
           for (int i = 0; i < 50000; i++) {
                int randNum = rand.nextInt() + 1;
                arr[i] = randNum;
           }
           long currentTimeMillis = System.currentTimeMillis();
           mergeSort(arr,50000);
           long currentTimeMillis2 = System.currentTimeMillis();
           System.out.println("基礎(chǔ)歸并排序所用時間:"+(currentTimeMillis2-currentTimeMillis));

     }

輸出結(jié)果:

基礎(chǔ)歸并排序所用時間:24

3掂为、歸并排序的優(yōu)化

第一步優(yōu)化:

當(dāng)我們使用一個近乎有序的數(shù)組作為測試用例時候裕膀,會發(fā)現(xiàn)插入排序的效率比歸并排序的效率要高很多!

public static void main(String[] args) {
           int[] arr = SortTestHelper.generateNearlyOrderdArray(50000, 10);
           int[] copyIntArray = SortTestHelper.copyIntArray(arr, 50000);


           long currentTimeMillis = System.currentTimeMillis();
           mergeSort(arr,50000);
           long currentTimeMillis2 = System.currentTimeMillis();
           System.out.println("基礎(chǔ)歸并排序所用時間:"+(currentTimeMillis2-currentTimeMillis));

           long currentTimeMillis3 = System.currentTimeMillis();
           InsertSort.insertSort(copyIntArray, 0, 50000);
           long currentTimeMillis4 = System.currentTimeMillis();
           System.out.println("插入排序所用時間:"+(currentTimeMillis4-currentTimeMillis3));
     }

輸出結(jié)果:

基礎(chǔ)歸并排序所用時間:33
插入排序所用時間:4

現(xiàn)在只是對五萬個數(shù)進(jìn)行排序勇哗,如果測試的數(shù)據(jù)量更大的話昼扛,差距可想而知。這是因為插入排序?qū)τ谝粋€近乎有序的數(shù)組會降低到O(N)的級別欲诺,那么歸并排序是否有什么方案對這種特殊的情況進(jìn)行優(yōu)化呢抄谐?答案是肯定的!
大家來看這一段代碼:

     public static void mergeSortT(int arr[],int l,int r){
           if (l >= r) {
                return;
           }
           int mid = (l+r)/2;
           mergeSortT(arr,l,mid);
           mergeSortT(arr, mid+1, r);
           merge(arr, l, mid,r);
     }

在這里我們mergeSortT(arr,l,mid);mergeSortT(arr, mid+1, r);對兩個部分進(jìn)行排序之后不管他們之間的順序如何扰法,都會進(jìn)行一次merge操作蛹含,是不是有時候可以省略掉這一次merge操作呢?答案是沒問題的塞颁,當(dāng)arr[mid] > arr[mid +1]我們才需要merge操作浦箱。下面我們將這句話加入到原來的代碼中,運行一下:

基礎(chǔ)歸并排序所用時間:8
插入排序所用時間:4

可見這一步優(yōu)化對于一個基本有序的數(shù)組來說是非常有效的殴边!但是歸并排序還是比插入排序要慢一些憎茂,這是因為歸并排序無法退化成一個O(N)級別的算法。

第二步優(yōu)化

現(xiàn)在我們的遞歸是遞歸到只有一個元素的時候然后return锤岸,但是事實上當(dāng)我們遞歸到元素量非常小的時候竖幔,我們可以轉(zhuǎn)而使用插入排序來提高性能。這是基于兩個方面是偷,一方面當(dāng)我們的元素數(shù)量比較小的時候拳氢,整個數(shù)組近乎有序的概率就會比較大,此時插入排序有優(yōu)勢蛋铆,另外一方面雖然插入排序的最差事件復(fù)雜度是N2 級別的馋评,但是要注意不管是N2還是NlogN都是
有一個N常數(shù)的,換句話說當(dāng)N小到一定程度的時候刺啦,插入排序要比歸并排序要快一些留特。為此對于上面的代碼我們就可以這樣修改:

public static void mergeSortT(int arr[],int l,int r){
           /*if (l >= r) {
                return;
           }*/

           if (r-l <=15) {
                InsertSort.insertSort(arr, l, r);
                return;
           }
           int mid = (l+r)/2;
           mergeSortT(arr,l,mid);
           mergeSortT(arr, mid+1, r);
           if (arr[mid] > arr[mid+1])
           merge(arr, l, mid,r);
     }

輸出結(jié)果:

基礎(chǔ)歸并排序所用時間:7
插入排序所用時間:3

至此我們的兩個優(yōu)化方案也就完成了~
當(dāng)然優(yōu)化是沒有止境的,這里就簡單介紹這么多玛瘸,剩下的還需要自己去探索了蜕青。

本文用到的一個工具類:

public class SortTestHelper {
    public static int[] generateRandomArray(int n, int range_l, int range_r) {
        int arr[] = new int[n];
        Random rand = new Random();

        for (int i = 0; i < n; i++) {
            int randNum = rand.nextInt(range_r - range_l + 1) + range_l;
            arr[i] = randNum;
        }
        return arr;
    }
    public static int[] generateRandomArray(int n) {
        int arr[] = new int[n];
        Random rand = new Random();

        for (int i = 0; i < n; i++) {
            int randNum = rand.nextInt();
            arr[i] = randNum;
        }
        return arr;
    }
    public static int[] generateNearlyOrderdArray(int n, int swapTimes) {
        int arr[] = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = i;
        }
        Random random = new Random();
        for (int i = 0; i < swapTimes; i++) {
            int posx = random.nextInt(n);
            int posy = random.nextInt(n);
            int temp = posx;
            posx = posy;
            posy = temp;
        }
        return arr;
    }

    public static int[] copyIntArray(int a[], int n) {
        int arr[] = new int[n];
        for (int i = 0; i < a.length; i++) {
            arr[i] = a[i];
        }
        return arr;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市糊渊,隨后出現(xiàn)的幾起案子右核,更是在濱河造成了極大的恐慌,老刑警劉巖渺绒,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贺喝,死亡現(xiàn)場離奇詭異菱鸥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躏鱼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門氮采,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人染苛,你說我怎么就攤上這事扳抽。” “怎么了殖侵?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵贸呢,是天一觀的道長。 經(jīng)常有香客問我拢军,道長楞陷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任茉唉,我火速辦了婚禮固蛾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘度陆。我一直安慰自己艾凯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布懂傀。 她就那樣靜靜地躺著趾诗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹬蚁。 梳的紋絲不亂的頭發(fā)上恃泪,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音犀斋,去河邊找鬼贝乎。 笑死,一個胖子當(dāng)著我的面吹牛叽粹,可吹牛的內(nèi)容都是我干的览效。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虫几,長吁一口氣:“原來是場噩夢啊……” “哼锤灿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起持钉,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤衡招,失蹤者是張志新(化名)和其女友劉穎篱昔,沒想到半個月后每强,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體始腾,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年空执,在試婚紗的時候發(fā)現(xiàn)自己被綠了浪箭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辨绊,死狀恐怖奶栖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情门坷,我是刑警寧澤宣鄙,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站默蚌,受9級特大地震影響冻晤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绸吸,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一鼻弧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锦茁,春花似錦攘轩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稿存,卻和暖如春够傍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挠铲。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工冕屯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拂苹。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓安聘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓢棒。 傳聞我的和親對象是個殘疾皇子浴韭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Ba la la la ~ 讀者朋友們,你們好啊脯宿,又到了冷鋒時間念颈,話不多說,發(fā)車连霉! 1.冒泡排序(Bub...
    王飽飽閱讀 1,797評論 0 7
  • 總結(jié)一下常見的排序算法榴芳。 排序分內(nèi)排序和外排序嗡靡。內(nèi)排序:指在排序期間數(shù)據(jù)對象全部存放在內(nèi)存的排序。外排序:指在排序...
    jiangliang閱讀 1,346評論 0 1
  • 一. 寫在前面 要學(xué)習(xí)算法窟感,“排序”是一個回避不了的重要話題讨彼,在分析完并查集算法和常用數(shù)據(jù)結(jié)構(gòu)之后,今天我們終于可...
    Leesper閱讀 2,531評論 0 40
  • 我有一樹繁花似錦柿祈, 在春日里溫暖的午后哈误, 迎風(fēng)飛舞。 雪白的花瓣飄落在林間的小徑躏嚎, 你拂手仰望蜜自, 像攜一縷輕云,漫...
    寒雨心亭閱讀 417評論 0 0
  • 2017年8月8日卢佣,如是家人黃愈惠袁辈,第8天種種子修行日志 發(fā)心:我今不是為了我個人而聞思修,而是為了六道輪回一切如...
    愈惠閱讀 198評論 0 3