排序算法(3)

你真的完全理解了快速排序嗎?如果沒有請仔細閱讀本文褥琐,讓我們一起成長。廢話不哆嗦炭臭,開始今天的學習——快速排序永脓,單獨說明快速排序是因為其重要,首先快速排序是基礎排序算法中表現比較亮眼的鞋仍,其次是因為快排還有很多的改進方式常摧,具體介紹如下。

快速排序基礎

快速排序的思想是采用分治的算法思想威创,具體算法是選擇一個隨機元素落午,通常是待排數組的起點,然后設置前后指針肚豺,對比確定該元素的位置溃斋,并且在確定位置的過程中進行數據交換,讓確定元素位置的之前的元素都小于選擇的元素吸申,讓后面的元素都大于該元素梗劫,那么這個元素的位置在整個數組中就是確定;然后以此類推截碴,在數組前半部分循環(huán)上面步驟梳侨,在數組后半部分循環(huán)使其有序,直到分到一個元素時日丹,即得到一個序列有序走哺。具體代碼如下:

public static void sort(int[] nums, int low, int high){
    Array.nonEmpty(nums);
    if(low < high){
        int mid = partition(nums, low, high);
        sort(nums, low, mid-1);
        sort(nums, mid+1, high);
    }
}

public static int partition(int[] nums, int low, int high){
    int temp = nums[low];
    while(low < high){
        while (low < high && nums[high] >= temp) high--;
            nums[low] = nums[high];
        while (low < high && nums[low] < temp) low++;
            nums[high] = nums[low];
    }
    nums[low] = temp;
    return low;
}

快速排序是不穩(wěn)定的排序,不穩(wěn)定的原因就是元素的跨節(jié)點的交換哲虾,如果是相鄰交換丙躏,則是穩(wěn)定排序,快速排序的平均時間復雜度為O(nlgn)束凑,最壞時間復雜度為O(n^2)彼哼,但由于其出現最壞的情況較少,所以其排序性能在排序算法中算好的湘今。

改進一

考慮一下這種情況敢朱,數組有序,然后應用快速排序對數組排序摩瞎,按照上面的實現拴签,選擇最小的元素為隨機元素,那么這時的快排就變成了O(n^2)旗们,所以改進思路就是讓隨機元素變“隨機”蚓哩,改進的第一種思路產生了。下面只給出修改的代碼:

public static int partition(int[] nums, int low, int high){
    // 讓隨機元素變的隨機
    swap(arr[low], arr[rand() % (high - low + 1) + low]);
    int temp = nums[low];
    while(low < high){
        while (low < high && nums[high] >= temp) high--;
            nums[low] = nums[high];
        while (low < high && nums[low] < temp) low++;
            nums[high] = nums[low];
    }
    nums[low] = temp;
    return low;
}

這種改進方式對已經有序的數組是有效的上渴,能夠降低其時間復雜度岸梨。

改進二

試想這樣一種情況喜颁,排序數組中存在大量的重復元素,那么在分治的過程中曹阔,相同元素會分布在同一個分區(qū)中半开,如果重復元素比較多,會造成分區(qū)的長度極不平衡赃份,增加排序的復雜度寂拆。基于此我們可以提出兩種改進方式抓韩,其中一種是將重復的元素均勻的分部在兩個分區(qū)中纠永,這樣解決了大于、小于兩個分區(qū)長度不平衡的問題谒拴,這種改進方式為雙路快排尝江,代碼實現如下:

private static int partition(int[] nums, int start, int end) {  // 排序方法和普通快排相同
    int i = start + 1;
    int j = end;
    int v = nums[start];
    while (true) {
        while (i <= end && nums[i] < v) i++;
        while (j >= start + 1 && nums[j] > v) j++; // 沒有等號,否則從這就直接走了
        if (i > j) break;
        swap(nums, i, j);
        i++;
        j--;
    }
    swap(nums, start, j);
    return j;
}

大量重復元素進行排序時英上,普通快排由于遞歸層數較大炭序,很快就是棧溢出,但是在百萬級重復元素的情況下善延,雙路快排還是能夠快速將數據排好少态,很強悍城侧!但是易遣,這種方式雖然讓分區(qū)平衡,但是其沒有解決相同元素比較的問題嫌佑,所以這就是產生了下一種優(yōu)化的方式豆茫,three-way QS(三路快排),其改進思想是屋摇,在原來大于揩魂、小于兩個分區(qū)的基礎上增加一個等于的分區(qū),將原來的兩個分區(qū)炮温,擴充至三個分區(qū)火脉,這樣等于的分區(qū)中元素就可以不參與排序,而直接排好大于和小于兩個分區(qū)的元素即可柒啤,具體代碼實現如下:

private static void sort(int[] array, int start, int end) {
    Arrays.nonEmpty(array);    
    if (start > end) return;
    //找到一個隨機key
    int value = array[start];
    int lt = start; int gt = end + 1; int i = start + 1;
    while (i < gt) {
        if (array[i] < value) {
            swap(array, i, lt + 1);
            lt++; i++;
        } else if (array[i] > value) {
            swap(array, i, gt - 1);
            gt--;
        } else i++;
    }
    swap(array, lt, start);
    //直接跳過相等元素的比較
    sort(array, start, lt - 1);
    sort(array, gt, end);

三路快排很好的解決了數據重復的問題倦挂,能夠在數據大量重復的情況下,提升排序性能(這里就不給出性能對比了)担巩;

改進三

在快排算法中還有一種改進方式方援,即內省式快排,在STL中的快排使用的就是這種算法涛癌。首先看一下這種改進方式針對的問題以及改進的思路犯戏。

1.為什么需要這種算法

快排在排序小數組(比如大小為10的數組)且基本有序的情況下送火,它的表現還沒插入排序要好(很多改進是在分區(qū)起止位置小于一定長度后改用插入排序)。因為數組是基本有序先匪,使得插入排序不用很多次的執(zhí)行元素的移動种吸,并且可以避免遞歸。 在SGI STL中的函數sort使用的排序算法就是內省式的排序算法胚鸯。內省的排序算法是基于快排實現的衣屏。假設待排序的數組大小為n洞拨,去一個k值,使得k為滿足2^k <= n的最大值。k為最大的遞歸層次哈扮、 為什么要設置最大遞歸層次呢? 因為快排的遞歸層次過深的時候岁钓,很可能會退化成O(n^2)狸捅。內省式排序使用k來控制快排的遞歸深度,當快排的遞歸深度到達k的時候選擇使用heap排序拟糕。

2. 為什么不一開始就使用heap排序

heap排序在平均時間復雜度是O(nlgn)判呕,最壞情況也是O(nlgn),看起來要比快排要快送滞。但是實際上侠草,快排是要比heap排序要快,第一個原因是:heap排序雖然和快排在平均情況下的時間復雜度是O(nlgn)犁嗅,但是heap排序的時間常數要比快排的時間常數要大边涕。第二個原因是:據統(tǒng)計,快排的最壞情況在是很少發(fā)生的褂微。第三個原因是:快排能夠比較好的吻合程序的空間局部性原理功蜓,因為它操作的基本都是相鄰的元素(虛擬存儲器的設計理論基礎中就有程序的時間局部性和空間局部性),能夠減少內存缺頁中斷的發(fā)生次數宠蚂。

3.為什么要使用heap排序呢

因為在遞歸層次太深的時候式撼,就意味著發(fā)生最壞情況的概率大大的提升了,這時候因為heap排序的最壞情況下的時間復雜度是O(nlgn)比快排的O(n^2)要好求厕,因此使用heap排序能更好優(yōu)化排序效率著隆。

基于以上三點,誕生了一種新的改機算法呀癣,即內省排序美浦,名字很符合算法思想,自我省查十艾,發(fā)現苗頭不對抵代,立即采用候補排序算法,不是一條道走到黑忘嫉,算法領域的思想和人生是多么相似荤牍,只有這樣才能獲得很好的性能表現案腺;這個算法就不給出具體實現了,主要是有點復雜康吵,但是這種改進算法的思路值得我們思考借鑒劈榨,

總結

本文主要是介紹了快速排序及其幾種改進思路,要改進一種算法必須是建立在對其十分了解并且充分思考過后的晦嵌,學習算法思想是一方面同辣,但對于大多數人來說,算法高效實現才是比較考驗人的惭载,好多人都是知道其思想旱函,但是實現不了,讀萬卷書描滔,還要行萬里路棒妨,在編程領域,后者更為重要含长,完畢券腔。

紙上得來終覺淺,絕知此事要躬行拘泞。 ——陸游

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末纷纫,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子陪腌,更是在濱河造成了極大的恐慌辱魁,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偷厦,死亡現場離奇詭異商叹,居然都是意外死亡燕刻,警方通過查閱死者的電腦和手機只泼,發(fā)現死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卵洗,“玉大人请唱,你說我怎么就攤上這事」澹” “怎么了十绑?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酷勺。 經常有香客問我本橙,道長,這世上最難降的妖魔是什么脆诉? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任甚亭,我火速辦了婚禮贷币,結果婚禮上,老公的妹妹穿的比我還像新娘亏狰。我一直安慰自己役纹,他們只是感情好,可當我...
    茶點故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布暇唾。 她就那樣靜靜地躺著促脉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪策州。 梳的紋絲不亂的頭發(fā)上瘸味,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機與錄音够挂,去河邊找鬼硫戈。 笑死,一個胖子當著我的面吹牛下硕,可吹牛的內容都是我干的丁逝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼梭姓,長吁一口氣:“原來是場噩夢啊……” “哼霜幼!你這毒婦竟也來了?” 一聲冷哼從身側響起誉尖,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤罪既,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后铡恕,有當地人在樹林里發(fā)現了一具尸體琢感,經...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年探熔,在試婚紗的時候發(fā)現自己被綠了驹针。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡诀艰,死狀恐怖柬甥,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情其垄,我是刑警寧澤苛蒲,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站绿满,受9級特大地震影響臂外,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一漏健、第九天 我趴在偏房一處隱蔽的房頂上張望辜膝。 院中可真熱鬧,春花似錦漾肮、人聲如沸厂抖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忱辅。三九已至,卻和暖如春谭溉,著一層夾襖步出監(jiān)牢的瞬間墙懂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工扮念, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留损搬,地道東北人。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓柜与,卻偏偏與公主長得像巧勤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子弄匕,可洞房花燭夜當晚...
    茶點故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內容