討厭算法的程序員 5 - 合并算法

討厭算法的程序員系列入口

本篇介紹的“合并”算法,是為后面學(xué)習(xí)“歸并排序”的一個(gè)準(zhǔn)備倦淀。合并算法是歸并排序中的一個(gè)子算法,請(qǐng)注意兩者之間的關(guān)系和差異声畏。

之所以把它獨(dú)立成一篇撞叽,一方面是一旦了解了它再理解歸并排序就會(huì)簡(jiǎn)單很多,另一方面是其本身就具有獨(dú)立性砰识,可以解決很多常見問題能扒,并不非得寄宿在歸并排序里面。

合并算法辫狼,就是將兩個(gè)已經(jīng)各自排好序的序列初斑,合并成一個(gè)排好序的大序列的方法

經(jīng)典應(yīng)用

兩摞撲克牌

《算法導(dǎo)論》里面給出的例子就很好理解膨处。還是拿撲克牌來說事:桌上有兩摞牌见秤,面朝上,每摞都已經(jīng)按照從小到大排好序了真椿。那么如何把它們合并成一摞并排好序呢鹃答?

日常生活中其實(shí)還有很多類似的應(yīng)用。比如校園里學(xué)生按身高由低到高排隊(duì)突硝,偶爾會(huì)遇到兩隊(duì)合一隊(duì)的情況测摔,要求合并后仍然按照由低到高的順序。

合并算法就是解決此類問題的最佳方法解恰。以撲克牌為例锋八,其基本步驟是:

  • 1 比較兩堆牌最頂上的兩張牌,選最小的一張护盈;
  • 2 將其拿出來(此時(shí)該堆頂上將露出一張新牌)挟纱,面朝下放到輸出堆(就是最終的那一大摞);
  • 3 重復(fù)上面兩步腐宋,直到原來兩堆其中一個(gè)為空紊服,此時(shí)將另一堆中的所有剩余的牌,直接面朝下放到輸出堆中胸竞。

假設(shè)最壞情況是兩摞牌要比到各自最后一張欺嗤,此時(shí)算法時(shí)間復(fù)雜度是T(n) = Θ(n),這是因?yàn)檎麄€(gè)算法最多只要遍歷一遍卫枝。

偽碼

接下來剂府,用偽碼實(shí)現(xiàn)上面的思想,但有兩個(gè)額外的變化:

  • 撲克應(yīng)用中的兩摞牌已經(jīng)排好序換一種表達(dá)方式:A是一個(gè)數(shù)組剃盾,p腺占、q和r是數(shù)組下標(biāo)淤袜,滿足p≤q<r,假設(shè)A[p ‥ q]和A[q+1 ‥ r]都已排好序衰伯。期望的輸出是:A的子數(shù)組A[p ‥ r]是通過合并原A[p ‥ q]和A[q+1 ‥ r]形成的且已排好序的子數(shù)組铡羡。
  • 為了避免每次執(zhí)行基本步驟都要檢查是否有堆為空,在每個(gè)堆的底部放置一張“哨兵”牌(哨兵通常包含一個(gè)特殊值意鲸,用于簡(jiǎn)化代碼)烦周,值為∞。它可以保證直到兩堆牌都露出∞時(shí)怎顾,其他牌都已經(jīng)放置到輸出堆读慎。因?yàn)槲覀兪孪戎绖偤胷 - p + 1張牌將被放置到輸出堆,所以一旦已執(zhí)行r - p + 1個(gè)基本步驟槐雾,算法就可以停止了夭委。

定義算法的名字為MERGE,偽碼如下:

MERGE(A, p, q, r)
1  n1 = q - p + 1
2  n2 = r - q
3  let L[1 ‥ n1+1] and R[1 ‥ n2+1] be new arrays
4  for i = 1 to n1
5    L[i] = A[p+i-1]
6  for j = 1 to n2
7    R[j] = A[q+j]
8  L[n1+1] = ∞
9  R[n2+1] = ∞
10 i = 1
11 j = 1
12 for k = p to r
13   if L[i] ≤ R[j]
14     A[k] = L[i]
15     i = i + 1
16   else A[k] = R[j]
17     j = j + 1 

正確性證明

證明算法的正確性中提到:只要證明在初始募强、保持株灸、和終止階段循環(huán)不變式都成立,從而可以通過終止時(shí)的不變式推斷出算法是正確的擎值。

代碼中的12~17行是唯一的循環(huán)慌烧,循環(huán)不變式是什么呢?這里我們令輸出A[p ‥ k-1]作為循環(huán)不變式鸠儿,迭代的任何過程中隨k的增加該數(shù)組總是按從小到大的順序包含原A[p ‥ r]中最小的元素屹蚊,有如下證明:

  • 初始化:循環(huán)第一次迭代之前,k = p进每,所以子數(shù)組A[p ‥ k-1]為空淑翼;
  • 保持:即要證明某次迭代之前不變式為真,下次迭代之前不變式仍為真品追;
    • 假設(shè)某次迭代前,L[i] ≤ R[j]冯丙,此時(shí)L[i]是未被復(fù)制回?cái)?shù)組A的最小元素肉瓦;
    • 與此同時(shí),數(shù)組A[p ‥ k-1]包含k - p個(gè)最小元素胃惜,即迭代前不變式為真泞莉;
    • 第14行代碼將L[i]復(fù)制到A[k]之后,子數(shù)組A[p ‥ k]將包含k - p + 1個(gè)最小元素船殉。增加k的值(for循環(huán))和i的值(第15行代碼)后鲫趁,即為下次迭代前重新建立了該循環(huán)不變式;
    • 反之利虫,若L[i] > R[j]挨厚,則第16~17代碼執(zhí)行適當(dāng)?shù)牟僮鱽砭S持該循環(huán)不變式堡僻。
  • 終止:終止時(shí)k = r + 1。子數(shù)組A[p ‥ k-1]就是A[p ‥ r]且按從小到大的順序包含了L[1 ‥ n1+1]和R[1 ‥ n2+1]中的k - p = r - p + 1個(gè)最小元素疫剃。數(shù)組L和R一共包含n1 + n2 + 2 = r - p + 3個(gè)元素钉疫,多出的2個(gè)就是哨兵,其他所有元素都已經(jīng)被復(fù)制回?cái)?shù)組A巢价。

時(shí)間復(fù)雜度

前面提到過MERGE的時(shí)間復(fù)雜度是Θ(n)牲阁,其中n = r - p + 1。再快速算下:

  • 代碼13行和811行中的每行需要常量時(shí)間壤躲;
  • 代碼4~7行的for循環(huán)需要Θ(n1+n2) = Θ(n)的時(shí)間城菊;
  • 代碼12~17行for循環(huán)有n次迭代,每次迭代需要常量時(shí)間碉克。

Java實(shí)現(xiàn)

public class MergeSort {
public static void mergeInASC(int[] numbers, int p, int q, int r) throws Exception {
    if(numbers.length < 2 || p > q || q >= r)
        throw new Exception("Para error.");

    int n1 = q - p + 1;
    int n2 = r - q;

    int[] L = new int[n1 + 1];
    int[] R = new int[n2 + 1];

    for(int i  = 0; i < n1; i++){
        L[i] = numbers[p + i];
    }
    for(int j = 0; j < n2; j++){
        R[j] = numbers[q + 1 + j];
    }

    L[n1] = Integer.MAX_VALUE;
    R[n2] = Integer.MAX_VALUE;

    int i = 0;
    int j = 0;
    for(int k = p; k <= r; k++){
        if(L[i] > R[j]){
            numbers[k] = R[j];
            j++;
        }
        else{
            numbers[k] = L[i];
            i++;
        }
    }
}
}

MergeSort.java下載

上一篇 4 時(shí)間復(fù)雜度

下一篇 6 歸并排序


共享協(xié)議:署名-非商業(yè)性使用-禁止演繹(CC BY-NC-ND 3.0 CN)
轉(zhuǎn)載請(qǐng)注明:作者黑猿大叔(簡(jiǎn)書)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凌唬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棉胀,更是在濱河造成了極大的恐慌法瑟,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唁奢,死亡現(xiàn)場(chǎng)離奇詭異霎挟,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)麻掸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門酥夭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來译断,“玉大人镀迂,你說我怎么就攤上這事牵寺±计龋” “怎么了赘淮?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵厨埋,是天一觀的道長抖拦。 經(jīng)常有香客問我雀鹃,道長久又,這世上最難降的妖魔是什么巫延? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮地消,結(jié)果婚禮上炉峰,老公的妹妹穿的比我還像新娘。我一直安慰自己脉执,他們只是感情好疼阔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般婆廊。 火紅的嫁衣襯著肌膚如雪迅细。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天否彩,我揣著相機(jī)與錄音疯攒,去河邊找鬼。 笑死列荔,一個(gè)胖子當(dāng)著我的面吹牛敬尺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贴浙,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼砂吞,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了崎溃?” 一聲冷哼從身側(cè)響起蜻直,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袁串,沒想到半個(gè)月后概而,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡囱修,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年赎瑰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片破镰。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡餐曼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲜漩,到底是詐尸還是另有隱情源譬,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布孕似,位于F島的核電站踩娘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏喉祭。R本人自食惡果不足惜养渴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望臂拓。 院中可真熱鬧,春花似錦习寸、人聲如沸胶惰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孵滞。三九已至中捆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坊饶,已是汗流浹背泄伪。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匿级,地道東北人蟋滴。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像痘绎,于是被迫代替她去往敵國和親津函。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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

  • 版本記錄 前言 將數(shù)據(jù)結(jié)構(gòu)和算法比作計(jì)算機(jī)的基石毫不為過孤页,追求程序的高效是每一個(gè)軟件工程師的夢(mèng)想尔苦。下面就是我對(duì)算法...
    刀客傳奇閱讀 2,885評(píng)論 0 0
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序行施,而外部排序是因排序的數(shù)據(jù)很大允坚,一次不能容納全部...
    蟻前閱讀 5,170評(píng)論 0 52
  • 概述:排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序蛾号,而外部排序是因排序的數(shù)據(jù)很大稠项,一次不能容納全部...
    每天刷兩次牙閱讀 3,729評(píng)論 0 15
  • 這是我們魅力講師課程的第四天。 上課之前張志剛老師會(huì)帶著我們一起練習(xí)腹式呼吸须教,一起練習(xí)發(fā)音皿渗。 這兩天他一直強(qiáng)調(diào)在演...
    語馨_f389閱讀 201評(píng)論 0 0
  • 古箏十級(jí)已經(jīng)考完,我卻還是只有四五級(jí)的水平轻腺,甚至這還算高估了自己的乐疆。一切似乎又從頭來過一樣。一個(gè)指頭一個(gè)指頭的練贬养,...
    維C牛肉粒閱讀 146評(píng)論 0 0