Crack the Fxxking QuickSort

做題做到 QuickSelect,結(jié)果感覺已經(jīng)有點(diǎn)記不清 QS 了……在此用力復(fù)習(xí)一下 QS私植。

本文解答所有關(guān)于 QS 的疑難雜癥威根。

首先上代碼,來自 Sedgewick 的 Algorithm:

public class Quick
{
    public static void sort(Comparable[] a)
    {
        StdRandom.shuffle(a);          // Eliminate dependence on input.
        sort(a, 0, a.length - 1);
    }

    private static void sort(Comparable[] a, int lo, int hi)
    {
        if (hi <= lo) return;
        int j = partition(a, lo, hi);    // Partition (see page 291).
        sort(a, lo, j - 1);              // Sort left part a[lo .. j-1].
        sort(a, j + 1, hi);              // Sort right part a[j+1 .. hi].
    }

    private static int partition(Comparable[] a, int lo, int hi)
    {  // Partition into a[lo..i-1], a[i], a[i+1..hi].
        int i = lo, j = hi + 1;          // left and right scan indices
        Comparable v = a[lo];            // partitioning item
        while (true)
        {  // Scan right, scan left, check for scan complete, and exchange.
            while (less(a[++i], v)) if (i == hi) break;
            while (less(v, a[--j])) if (j == lo) break;
            if (i >= j) break;
            exch(a, i, j);
        }
        exch(a, lo, j);    // Put v = a[j] into position
        return j;          // with a[lo..j-1] <= a[j] <= a[j+1..hi].
    }
}

1. QuickSort 的總體思路:

在要排序的序列中選定一個(gè) pivot(這里選最左邊的元素)针炉,將序列進(jìn)行 partition,使得所有位于pivot 左邊的元素都小于 pivot扳抽,位于右邊的元素都大于 pivot篡帕,但此時(shí)左右兩部分被視為無序狀態(tài):

[……(無序的左邊部分)……],[(pivot)]贸呢,[……(無序的右邊部分)……]

這樣還不足以排序, 但我們發(fā)現(xiàn)镰烧,只要分別 sort 一下左邊和右邊部分,整個(gè)序列就有序了:

function sort(a[])
{
  partition(a[]);
  sort(a[]左邊部分);
  sort(a[]右邊部分)楞陷;
}

到此QuickSort 已經(jīng)結(jié)束了怔鳖,EOF

——“然鵝,sort 左邊部分和右邊部分不還是要 sort 嗎固蛾?我們還是沒有實(shí)現(xiàn) sort 敖嶂础度陆!”

不過可能你已經(jīng)發(fā)現(xiàn)了,上面那個(gè) function sort(a[]) 是一個(gè)遞歸函數(shù)献幔!也就是說坚芜,每次我們分成左右兩個(gè)子序列,都要進(jìn)行 parttition斜姥,直到這個(gè)子序列只有一個(gè)元素!這樣僅靠 partition沧竟,我們就完成了排序铸敏,sort()函數(shù)作為遞歸體,不斷調(diào)用 partition()來處理子序列悟泵。

2. partition()

到此杈笔,我們已經(jīng)知道 partition 要達(dá)到什么目的,只需要再實(shí)現(xiàn) partition 的功能:首先先要選取一個(gè) pivot糕非,關(guān)于 pivot 的選取至關(guān)重要蒙具,因?yàn)闀?huì)極大地影響復(fù)雜度,稍后詳細(xì)分析時(shí)間復(fù)雜度朽肥。

public class QuickSort
{
    public static int partition(int[] a, int low, int high)
    {
        int pivot = int[low];
        int i = low, j = high + 1;
        while(true)
        {
            while(a[++ i] < pivot)  // pointer i keeps going if pointed element is less than pivot
            {
                if(i >= high)   break;
            }
            while(a[-- j] > pivot)  // pointer j keeps going if pointed element is larger than pivot
            {
                if(j <= low)    break;
            }
            if(i >= j)  // if two pointer cross
                break;
            swap(a, i , j);
        }
        swap(a, low, j);    // put pivot between two partitions
        return j;           // return the index of pivot
    }

    public static void sort(int[] a, int low, int high)
    {
        int pivotIndex = partition(a, low, high);
        sort(a, low, pivot - 1);
        sort(a, pivot + 1, high);
    }
}

*為什么與 pivot 比較的時(shí)候是“<”禁筏、">"?為什么還要交換兩個(gè)相同的元素衡招?
理想狀態(tài)下我們希望每次切分都得到兩個(gè)規(guī)模相同的子序列篱昔,也就是說 i,j 兩個(gè)指針能停下來的時(shí)候就停下來始腾,從而使最后 Pivot 的位置保持一個(gè)比較靠中間的位置州刽。否則,pivot 最終的 index 過于偏向一邊浪箭,就會(huì)增大遞歸的深度(best case是logN穗椅,而 worst case 則是 N)。

3. 3-way-partition

如果元素大量重復(fù)奶栖,上述辦法則還有可以提高的空間匹表,因?yàn)槲覀兘粨Q了大量重復(fù)的元素,還可以壓榨這部分的復(fù)雜度:

Sedgewick 配圖相當(dāng)好驼抹,注意 lt 桑孩、gt 和 i 三個(gè)指針的位置(相當(dāng)精確);灰色部分是當(dāng)前還沒掃描到的部分

對(duì)于每次切分:從數(shù)組的左邊到右邊遍歷一次框冀,維護(hù)三個(gè)指針流椒,其中l(wèi)t指針使得元素(arr[0]-arr[lt-1])的值均小于切分元素;gt指針使得元素(arr[gt+1]-arr[N-1])的值均大于切分元素明也;i指針使得元素(arr[lt]-arr[i-1])的值均等于切分元素宣虾,(arr[i]-arr[gt])的元素還沒被掃描惯裕,切分算法執(zhí)行到i>gt為止。每次切分之后绣硝,位于gt指針和lt指針之間的元素的位置都已經(jīng)被排定蜻势,不需要再去處理了。之后將(lo,lt-1),(gt+1,hi)分別作為處理左子數(shù)組和右子數(shù)組的遞歸函數(shù)的參數(shù)傳入鹉胖,遞歸結(jié)束握玛,整個(gè)算法也就結(jié)束。

public class Quick3way
{
     private static void sort(Comparable[] a, int lo, int hi)
     {  
        if (hi <= lo) return;
        int lt = lo, i = lo+1, gt = hi;
        Comparable v = a[lo];
        while (i <= gt)
        {
           int cmp = a[i].compareTo(v);
           if      (cmp < 0) exch(a, lt++, i++);
           else if (cmp > 0) exch(a, i, gt--);
           else              i++;
        }  // Now a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].
        sort(a, lo, lt - 1);
        sort(a, gt + 1, hi);
     }
}

這里就沒有一個(gè)單獨(dú)的 partition()甫菠,而是將其整合進(jìn)了 sort() 里面挠铲。

4. pivot 的選取

pivot 的選取至關(guān)重要,理想狀態(tài)是每次都取到位于中間的 pivot寂诱,這樣就能保證遞歸深度為 LogN拂苹。如果對(duì)一個(gè)一定程度上有序的序列使用這種快排,復(fù)雜度則是 O(n)痰洒。

改進(jìn):雖然我們每次都取最左邊的當(dāng) pivot瓢棒,但只要在取之前對(duì) array 進(jìn)行 shuffle,將有序性去除丘喻,就能很好的避免掉進(jìn) O(N)遞歸深度的坑里脯宿。

至于 shuffle 的方式有好幾種,比如 kunth-shuffle 等等泉粉,another story嗅绰。我們也可以直接用 API。

5. 復(fù)雜度分析

(鴿)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搀继,一起剝皮案震驚了整個(gè)濱河市窘面,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叽躯,老刑警劉巖财边,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異点骑,居然都是意外死亡酣难,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門黑滴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憨募,“玉大人,你說我怎么就攤上這事袁辈〔艘ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尾膊。 經(jīng)常有香客問我媳危,道長(zhǎng),這世上最難降的妖魔是什么冈敛? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任待笑,我火速辦了婚禮,結(jié)果婚禮上抓谴,老公的妹妹穿的比我還像新娘暮蹂。我一直安慰自己,他們只是感情好癌压,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布椎侠。 她就那樣靜靜地躺著,像睡著了一般措拇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上慎宾,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天丐吓,我揣著相機(jī)與錄音,去河邊找鬼趟据。 笑死券犁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汹碱。 我是一名探鬼主播粘衬,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咳促!你這毒婦竟也來了稚新?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤跪腹,失蹤者是張志新(化名)和其女友劉穎褂删,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冲茸,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屯阀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轴术。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片难衰。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖逗栽,靈堂內(nèi)的尸體忽然破棺而出盖袭,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布苍凛,位于F島的核電站趣席,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏醇蝴。R本人自食惡果不足惜宣肚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望悠栓。 院中可真熱鬧霉涨,春花似錦、人聲如沸惭适。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽癞志。三九已至往枷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凄杯,已是汗流浹背错洁。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戒突,地道東北人屯碴。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膊存,于是被迫代替她去往敵國(guó)和親导而。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 算法簡(jiǎn)介 是一種分治的排序算法隔崎,特點(diǎn)就是快今艺,而且效率高。 基本思路 通過一趟排序?qū)⒋旁胤指舫瑟?dú)立的兩部分爵卒,其中...
    TinyDolphin閱讀 3,465評(píng)論 0 3
  • 選擇排序 選擇排序是一種簡(jiǎn)單直觀的排序算法洼滚,無論什么數(shù)據(jù)進(jìn)去都是 O(n?) 的時(shí)間復(fù)雜度。所以用到它的時(shí)候技潘,數(shù)據(jù)...
    無灃閱讀 1,324評(píng)論 0 0
  • 代碼雖源自抄襲遥巴,自己重寫時(shí)改了一下變量名,消化更好了_ 冒泡排序(Bubble Sort) 1. 算法步驟 比較相...
    _july77閱讀 195評(píng)論 0 1
  • 昨晚得知老弟脫單享幽,我開心到吼起來铲掐,感慨這個(gè)圍繞在我們家?guī)啄甑脑掝}梗終于破解了。我是家里第一個(gè)知道的值桩,這個(gè)秘密我是守...
    明小神閱讀 580評(píng)論 0 0
  • 相見歡·午后東湖 嘗踱凌波門前摆霉,小舟閑。醉其白云在水水接天。 今風(fēng)過携栋,黃葉落搭盾,煙雨寒。愁若花少長(zhǎng)放月少圓婉支。
    一只有了思想的猴子閱讀 220評(píng)論 4 3