日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【算法】快速排序算法的编码和优化

發(fā)布時(shí)間:2025/5/22 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【算法】快速排序算法的编码和优化 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?

參考資料

《算法(第4版)》????????? — — Robert Sedgewick, Kevin Wayne 《啊哈! 算法》????????????? — — 啊哈磊 《數(shù)據(jù)結(jié)構(gòu)(教材)》???? — — 嚴(yán)蔚敏,吳偉民 ? ?

快速排序算法的編碼描述

?

快排的基本思路

?

?

? 快速排序的基本思路是:
  • 先通過第一趟排序,將數(shù)組原地劃分為兩部分其中一部分的所有數(shù)據(jù)都小于另一部分的所有數(shù)據(jù)。原數(shù)組被劃分為2
  • 通過遞歸的處理, 再對(duì)原數(shù)組分割的兩部分分別劃分為兩部分,同樣是使得其中一部分的所有數(shù)據(jù)都小于另一部分的所有數(shù)據(jù)。 這個(gè)時(shí)候原數(shù)組被劃分為了4
  • 就1,2被劃分后的最小單元子數(shù)組來看,它們?nèi)匀皇菬o序的,但是! 它們所組成的原數(shù)組卻逐漸向有序的方向前進(jìn)。
  • 到最后, 數(shù)組被劃分為多個(gè)由一個(gè)元素或多個(gè)相同元素組成的單元, 這時(shí)候整個(gè)數(shù)組就有序了
  • ? 總結(jié): 通過第一趟排序,將原數(shù)組A分為B和C兩部分, 整體上B<C, 第二躺排序時(shí)候?qū)劃分為B1,B2兩部分, 使得B1<B2, 同理C1<C2。那么通過兩趟排序, 從B1/B2/C1/C2的長度的單元看待整個(gè)數(shù)組, 從左至右 B1<B2<C1<C2, 數(shù)組是“有序”的, 并且隨著排序的深入,原數(shù)組有序性越來越強(qiáng) ? 整體的排序過程如下圖所示(暫且不管實(shí)現(xiàn)的具體細(xì)節(jié))

    ?

    如上圖所示, 數(shù)組 3 1 4 1 5 9 2 6 5 3

    ?

    通過第一趟排序被分成了2 1 1 和4 5 9 3 6 5 3兩個(gè)子數(shù)組,且對(duì)任意元素,左子數(shù)組總小于右子數(shù)組 通過不斷遞歸處理,最終得到 1 1 2 3 3 4 5 5 6

    ?

    這個(gè)有序的數(shù)組 ?

    快排的實(shí)現(xiàn)步驟

    ? 快排具體的實(shí)現(xiàn)步驟如下圖所示: ?

    ?

    圖中的步驟3,4不難理解,這里就不多贅述,因?yàn)椴襟E3中的遞歸思想是大家比較熟悉的, 步驟4中的“組合”其實(shí)就只是個(gè)概念上的詞,因?yàn)樗械淖訑?shù)組本來就連接在一起,只要所有的遞歸結(jié)束了,整個(gè)數(shù)組就是有序的。 ?

    下面我就只講解1和2步驟, 而在1,2中,關(guān)鍵在于如何實(shí)現(xiàn)“劃分”

    切分的關(guān)鍵點(diǎn):?基準(zhǔn)元素,?左游標(biāo)和右游標(biāo)

    ?

    劃分的過程有三個(gè)關(guān)鍵點(diǎn):“基準(zhǔn)元素”, “左游標(biāo)” 和“右游標(biāo)”。

    ?
    • 基準(zhǔn)元素:它是將數(shù)組劃分為兩個(gè)子數(shù)組的過程中, 用于界定大小的值, 以它為判斷標(biāo)準(zhǔn), 將小于它的數(shù)組元素“劃分”到一個(gè)“小數(shù)值數(shù)組”里, 而將大于它的數(shù)組元素“劃分”到一個(gè)“大數(shù)值數(shù)組”里面。這樣,我們就將數(shù)組分割為兩個(gè)子數(shù)組, 而其中一個(gè)子數(shù)組里的元素恒小于另一個(gè)子數(shù)組里的元素
    • 左游標(biāo) 它一開始指向待分割數(shù)組最左側(cè)的數(shù)組元素。在排序過程中,它將向右移動(dòng)
    • 右游標(biāo): 它一開始指向待分割數(shù)組最右側(cè)的數(shù)組元素。在排序過程中,它將向左移動(dòng)
    ? 【注意】 1.上面描述的基準(zhǔn)元素/右游標(biāo)/左游標(biāo)都是針對(duì)單趟排序過程的, 也就是說,在整體排序過程的多趟排序中,各趟排序取得的基準(zhǔn)元素/右游標(biāo)/左游標(biāo)一般都是不同的 2. 在不同的教材里,基準(zhǔn)元素也叫“樞軸”,“關(guān)鍵字”, “劃分”也叫“切分” ? 那這基準(zhǔn)元素-右游標(biāo)-左游標(biāo)三個(gè)關(guān)鍵點(diǎn)是如何融會(huì)貫通,搞定一趟切分(劃分)的呢?

    ?一趟切分的具體過程

    ?

    ? 切分的具體過程如圖所示。在下圖中,基準(zhǔn)元素是v,?? 左游標(biāo)是i, 右游標(biāo)是j i一開始指向數(shù)組頭部元素的位置lo, 切分時(shí)向右移動(dòng), j一開始指向數(shù)組末端元素hi,隨后向左移動(dòng), 當(dāng)左右游標(biāo)相遇的時(shí)候,一趟切分就完成了。 ? 當(dāng)然, 看到這里你可能很懵懂,你可能會(huì)問:
    • “基準(zhǔn)元素v是怎么選的?”
    • 游標(biāo)i,j的移動(dòng)的過程中發(fā)生了什么事情(比如元素交換)?,
    • 為什么左右游標(biāo)相遇時(shí)一趟切分就完成了?
    讓我們繼續(xù)往下看: ? 基準(zhǔn)元素的選取 ? 首先,在原則上,基準(zhǔn)元素的選取是任意的 但我們一般選取數(shù)組的第一個(gè)元素為基準(zhǔn)元素(假設(shè)數(shù)組是隨機(jī)分布的 ? 下面以啊哈磊老師的圖示為例: ? 假設(shè)下面的是我們的待排序的數(shù)組的話, 根據(jù)我們的頭元素作為基準(zhǔn)元素的原則,士兵i下面的數(shù)組元素 “6” 就是我們選定的第一趟排序的基準(zhǔn)元素 ?

    ?

    ? (作為入門,啊哈磊老師的《啊哈,算法》里的圖示還是很有趣的! 這里向大家安利一下) ? 【注意】下面在優(yōu)化中會(huì)講關(guān)于基準(zhǔn)元素的選取的訣竅, 但在快排的基礎(chǔ)編碼里,我們只要記住把頭部元素當(dāng)作基準(zhǔn)元素就夠了(假設(shè)數(shù)組元素是隨機(jī)分布的) ? 左右游標(biāo)掃描和元素交換 ? 在選取了基準(zhǔn)元素之后, 切分就正式開始了。這時(shí)候,左右游標(biāo)開始分別向右/左移動(dòng),它們遵循的規(guī)則分別是: ?
    • 左游標(biāo)掃描, 跨過所有小于基準(zhǔn)元素的數(shù)組元素, 直到遇到一個(gè)大于或等于基準(zhǔn)元素的數(shù)組元素, 在那個(gè)位置停下
    • 右游標(biāo)掃描, 跨過所有大于基準(zhǔn)元素的數(shù)組元素, 直到遇到一個(gè)大于或等于基準(zhǔn)元素的數(shù)組元素,在那個(gè)位置停下
    ? 當(dāng)左右游標(biāo)掃描分兩種情況(或者說是兩個(gè)先后階段...)
  • 左右游標(biāo)沒有相遇
  • 左右游標(biāo)相遇了
  • ? 在下圖中, 左游標(biāo)就是士兵i, 而右游標(biāo)是士兵j啦。 ?

    ?

    ? 1.首先,右游標(biāo)j會(huì)向左跨過所有大于基準(zhǔn)元素的元素, 所以士兵j向左跨過了板磚8和10, 然后當(dāng)他遇到了“小于等于”基準(zhǔn)元素6的元素5時(shí)候, “哎呀, 不能再前進(jìn)了,在這里打住吧!”, 于是右游標(biāo)就在5處停了下來, 2.然后, 士兵i(左游標(biāo))跨過了小于基準(zhǔn)元素6的1和2,然后遇到了“大于等于”6的7,在7處停了下來。 3.? 停下來之后, 左右游標(biāo)所指的數(shù)組元素交換了它們的值(兩個(gè)士兵交換了他們腳下的板磚) ? 下圖同上:

    ?

    游標(biāo)掃描和元素交換的意義 ? 很明顯, 兩個(gè)游標(biāo)士兵的“工作” 就是不斷靠近,并檢查有沒有小于(大于)規(guī)定要求(即基準(zhǔn)元素6)的板磚(元素),一旦發(fā)現(xiàn), 就“丟”到對(duì)面去, 而當(dāng)他們相遇的時(shí)候, 大小關(guān)系嚴(yán)格的兩塊子數(shù)組也就分割出來了 ? 【注意】 1.要注意一點(diǎn): 我們選取的基準(zhǔn)元素和左游標(biāo)最初指定的元素是相同的! 那么就我們就會(huì)發(fā)現(xiàn)一個(gè)問題: 當(dāng)左游標(biāo)向右掃描的時(shí)候,第一個(gè)遇到的“大于或等于”的元素就是它本身, 那么問題來了: 需不需要停下來呢? 當(dāng)然根據(jù)邏輯思考可以得出這是不必要的,所以下面我會(huì)結(jié)合算法指出這一細(xì)節(jié): 左游標(biāo)向右掃描的時(shí)候其實(shí)忽略了它最初所指的位置——頭元素的比較 2. 必須等一個(gè)“士兵”(游標(biāo))先走完, 另一個(gè)“士兵”(游標(biāo))才能走不能每人輪流走一步... ? 左右游標(biāo)相遇 ? 承接上文, 這次眼看士兵i和士兵j就要相遇了! 首先士兵j先走,當(dāng)它遇到3的位置的時(shí)候,因?yàn)?“小于等于”6,所以士兵j就停下來了。再然后士兵i向右走,但因?yàn)樗褪勘鴍“碰頭”了,所以士兵i只能無奈地“提前”在3停住了(如果沒和j碰面士兵i是能走到9的!) ? 所以這就是左右游標(biāo)掃描相遇時(shí)候遵循的原則: 只相遇, 不交叉

    ?

    【注意】這里你可能會(huì)問: 在我們制定的規(guī)則里, 左游標(biāo)先掃描和右游標(biāo)先掃描有區(qū)別嗎?? (如果你這樣想的話就和我想到一塊去了...嘿嘿),因?yàn)榫蜕蠄D而言,兩種情況下一趟排序中兩個(gè)游標(biāo)相遇的位置是不同的(一般而言,除非相遇位置的下方的元素剛好和基準(zhǔn)元素相同):

    • 如果右游標(biāo)先掃描,左右游標(biāo)相遇的位置應(yīng)該是3上方(圖示)
    • 但如果左游標(biāo)先掃描, 左右游標(biāo)相遇的位置卻是9上方
    通過編碼驗(yàn)證和翻閱書籍,我得出的結(jié)論是:對(duì)排序的劃分過程有影響,但對(duì)最終結(jié)果是沒有具體的影響的。特別的,在《數(shù)據(jù)結(jié)構(gòu)》這本書中采取的是右游標(biāo)先掃描,而在《算法(第四版)》書中,則采取左游標(biāo)先掃描的策略 ? 基準(zhǔn)元素歸位 ? 當(dāng)?shù)竭_(dá)了我上面所說的“左右游標(biāo)相遇”這個(gè)階段后, 我們發(fā)現(xiàn), 左右兩個(gè)子數(shù)組已經(jīng)基本有序了,即分成了 1 2 5 4 3和9 7 10 8 這兩段元素,其中前一段元素都小于后一段元素 ? 等等! 好像有兩個(gè)數(shù)字違和感很強(qiáng)地打破了這個(gè)大小關(guān)系, 那就是6! (基準(zhǔn)元素) 如下所示: 6 1 2 5 4 3 9 7 10 8

    ?

    這時(shí)候我們發(fā)現(xiàn)整個(gè)數(shù)組的組成是這樣的: 大小居中的基準(zhǔn)元素 + 小數(shù)值數(shù)組 + 大數(shù)值數(shù)組 所以我們只要把基準(zhǔn)元素6和游標(biāo)相遇元素3換一下, 不就可以變成: 小數(shù)值數(shù)組 + 大小居中的基準(zhǔn)元素 +?? 大數(shù)值數(shù)組 了嗎? 1 2 5 4 3 6 9 7 10 8

    ?

    ? 如圖所示 ? ?

    ?

    至此, 一趟排序結(jié)束, 回到中間的6已經(jīng)處于有序狀態(tài),只要再對(duì)左右兩邊的元素進(jìn)行遞歸處理就可以了

    ?總結(jié)一趟排序的過程

    ? OK,這里讓我們總結(jié)下一趟快速排序的四個(gè)過程: ?

    ?

    ? 一趟排序全過程圖示 ? (A - Z 字母排序, A最小, Z最大)

    ?

    ?

    快速排序代碼展示

    ?

    具體的代碼

    這是我們的輔助函數(shù)exchange: 用于交換任意兩個(gè)數(shù)組元素的位置: // 交換兩個(gè)數(shù)組元素 private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp; }

    ?

    ? 這是切分函數(shù)partition, 它完成了一輪排序的主要工作,使得待分割數(shù)組以基準(zhǔn)元素為界,分成了一個(gè)小數(shù)值數(shù)組和一個(gè)大數(shù)值數(shù)組 private static int partition (int[] a, int low, int high) {int i = low, j = high+1;?// i, j為左右掃描指針 PS: 思考下為什么j比i 多加一個(gè)1呢?int pivotkey = a[low];? // pivotkey 為選取的基準(zhǔn)元素(頭元素)while(true) {?while (a[--j]>pivotkey) {?? if(j == low) break; }? // 右游標(biāo)左移while(a[++i]<pivotkey) {?? if(i == high) break;? }? // 左游標(biāo)右移if(i>=j) break;??? // 左右游標(biāo)相遇時(shí)候停止, 所以跳出外部while循環(huán)else exchange(a,i, j) ;? // 左右游標(biāo)未相遇時(shí)停止, 交換各自所指元素,循環(huán)繼續(xù)? ??}exchange(a, low, j); // 基準(zhǔn)元素和游標(biāo)相遇時(shí)所指元素交換,為最后一次交換return j;? // 一趟排序完成, 返回基準(zhǔn)元素位置 }

    ?

    ? 這是主體函數(shù)sort, 將partition遞歸處理 private static void sort (int [] a,? int low, int high) {if(high<= low) { return; } // 終止遞歸int j = partition(a, low, high);? // 調(diào)用partition進(jìn)行切分sort(a,? low,? j-1);?? // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素左邊的子數(shù)組進(jìn)行遞歸sort(a,? j+1,? high); // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素右邊的子數(shù)組進(jìn)行遞歸 }

    ?

    ?

    對(duì)切分函數(shù)partition的解讀

    ? 1. 直觀上看, partition由兩部分組成: 外部while循環(huán)和兩個(gè)并列的內(nèi)部while循環(huán)。 ? 2. 內(nèi)部While循環(huán)的作用是使得左右游標(biāo)相互靠近 例如對(duì): while (a[--j]>pivotkey) {? ...?? }

    先將右游標(biāo)左移一位,然后判斷指向的數(shù)組元素和基準(zhǔn)元素pivotkey比較大小, 如果該元素大于基準(zhǔn)元素, 那么循環(huán)繼續(xù),j再次減1,右游標(biāo)再次左移一位...... (循環(huán)體可以看作是空的)

    ? 3.外部While循環(huán)的作用是不斷通過exchange使得“逆序”元素的互相交換, 不斷向左子數(shù)組小于右子數(shù)組的趨勢靠近,? 對(duì) if(i>=j) break;?

    從i < j到 i == j 代表了“游標(biāo)未相遇”到“游標(biāo)相遇”的過度過程,此時(shí)跳出外部循環(huán), 切分已接近完成,緊接著通過 exchange(a, low, j) 交換基準(zhǔn)元素和相遇游標(biāo)所指元素的位置, low是基準(zhǔn)元素的位置(頭部元素), j是當(dāng)前兩個(gè)游標(biāo)相遇的位置

    ? 4. 第一個(gè)內(nèi)部while循環(huán)體里面的的? if(j == low) break;判斷其實(shí)是多余的,可以去除。 因?yàn)樵?/span> while (a[--j]>pivotkey) {?? if(j == low) break; }? // 右游標(biāo)左移

    中,當(dāng)隨著右游標(biāo)左移,到j(luò) = low + 1的時(shí)候,有 a[--j] == pivotkey為true(兩者都是基準(zhǔn)元素),自動(dòng)跳出了while循環(huán),所以就不需要在循環(huán)體里再判斷 j == low 了

    ? 5. 注意一個(gè)細(xì)節(jié): j 比 i 多加了一個(gè)1,為什么? 如下 int i = low, j = high+1

    結(jié)合下面兩個(gè)While循環(huán)中的判斷條件:

    while (a[--j]>pivotkey) {? ...?? } while (a[++i]<pivotkey) {? ...? }?

    可知道, 左游標(biāo) i 第一次自增的時(shí)候, 跳過了對(duì)基準(zhǔn)元素 a[low] 所執(zhí)行的 a[low] < pivotkey判斷, 這是因?yàn)?span style="text-decoration:underline;">在我們當(dāng)前的算法方案里,基準(zhǔn)元素和左游標(biāo)初始所指的元素是同一個(gè),所以沒有執(zhí)行a[low]>pivotke這個(gè)判斷的必要。所以跳過( 一開始a[low] == pivotkey,如果執(zhí)行判斷那么一開始就會(huì)跳出內(nèi)While循環(huán),這顯然不是我們希望看到的)

    ? 而相比之下,右游標(biāo)卻必須要對(duì)它初始位置所指的元素執(zhí)行a[++i]<pivotkey , 所以 j 比 i 多加了一個(gè) ? ? ?

    對(duì)主體函數(shù)sort的解讀

    ? 1. high<= low是判斷遞歸結(jié)束的條件 2. int j = partition(a, low, high);? 有兩種作用: 一是進(jìn)行一輪切分二是取得上一輪的基準(zhǔn)元素的最終位置j, 傳遞給另外兩個(gè)sort函數(shù),通過另外兩個(gè)sort函數(shù)的調(diào)用 sort(a,? low,? j-1);?? sort(a,? j+1,? high);

    進(jìn)行下一輪遞歸,設(shè)置j -1 和j + 1 是因?yàn)樯弦惠喕鶞?zhǔn)元素的位置已經(jīng)是有序的了,不要再納入下一輪遞歸里

    ? 快速排序QuickSort類的全部代碼: public class QuickSort {// 交換兩個(gè)數(shù)組元素private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}private static int partition (int[] a, int low, int high) {int i = low, j = high+1;????? // i, j為左右掃描指針 PS: 思考下為什么j比i 多加一個(gè)1呢?int pivotkey = a[low];? // pivotkey 為選取的基準(zhǔn)元素(頭元素)while(true) {?while (a[--j]>pivotkey) {?? if(j == low) break; }? // 右游標(biāo)左移while(a[++i]<pivotkey) {?? if(i == high) break;? }? // 左游標(biāo)右移if(i>=j) break;??? // 左右游標(biāo)相遇時(shí)候停止, 所以跳出外部while循環(huán)else exchange(a,i, j) ;? // 左右游標(biāo)未相遇時(shí)停止, 交換各自所指元素,循環(huán)繼續(xù)? ??? }exchange(a, low, j); // 基準(zhǔn)元素和游標(biāo)相遇時(shí)所指元素交換,為最后一次交換return j;? // 一趟排序完成, 返回基準(zhǔn)元素位置 ? }private static void sort (int [] a,? int low, int high) {if(high<= low) { return; } // 當(dāng)high == low, 此時(shí)已是單元素子數(shù)組,自然有序, 故終止遞歸int j = partition(a, low, high);? // 調(diào)用partition進(jìn)行切分sort(a,? low,? j-1);?? // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素左邊的子數(shù)組進(jìn)行遞歸sort(a,? j+1,? high); // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素右邊的子數(shù)組進(jìn)行遞歸 ? }public static void sort (int [] a){ //sort函數(shù)重載, 只向外暴露一個(gè)數(shù)組參數(shù)sort(a, 0, a.length - 1);} }

    ?

    ? 測試代碼 public class Test {public static void main (String [] args) {int [] array = {4,1,5,9,2,6,5,6,1,8,0,7 };QuickSort.sort(array);for (int i = 0; i < array.length; i++) {System.out.print(array[i]);}} }

    ?

    結(jié)果: 01124556789

    ?

    ?

    優(yōu)化點(diǎn)一 —— 切換到插入排序

    ? 對(duì)于小數(shù)組而言, 快速排序比插入排序要慢, 所以在排序小數(shù)組時(shí)應(yīng)該切換到插入排序。 只要把sort函數(shù)中的 if(high<= low) { return; }

    ?

    改成: if(high<= low + M) {? Insertion.sort(a,low, high) return; } // Insertion表示一個(gè)插入排序類

    ?

    就可以了,這樣的話,這條語句就具有了兩個(gè)功能: 1. 在適當(dāng)時(shí)候終止遞歸 2. 當(dāng)數(shù)組長度小于M的時(shí)候(high-low <= M), 不進(jìn)行快排,而進(jìn)行插排 ? 轉(zhuǎn)換參數(shù)M的最佳值和系統(tǒng)是相關(guān)的,一般來說, 5到15間的任意值在多數(shù)情況下都能令人滿意 ? 例如, 將sort函數(shù)改成: ? private static void sort (int [] a,? int low, int high) {if(high<= low + 10) {? Insertion.sort(a,low, high) return; } // Insertion表示一個(gè)插入排序類int j = partition(a, low, high);? // 調(diào)用partition進(jìn)行切分sort(a,? low,? j-1);?? // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素左邊的子數(shù)組進(jìn)行遞歸sort(a,? j+1,? high); // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素右邊的子數(shù)組進(jìn)行遞歸}

    ?

    ?

    優(yōu)化點(diǎn)二 —— 基準(zhǔn)元素選取的隨機(jī)化

    ? 上面說過,基準(zhǔn)元素的選取是任意的,但是不同的選取方式對(duì)排序性能的影響很大。 ? 在上面所有的快速排序的例子中,我們都是固定選取基準(zhǔn)元素,這種操作做了一個(gè)假設(shè)性的前提:數(shù)組元素的分布是隨機(jī)的而如果數(shù)組不是隨機(jī)的,而是有一定順序的,甚至在最壞的情況下:完全正序或完全逆序, 這個(gè)時(shí)候麻煩就來了:?快排所消耗的時(shí)間大大延長,完全達(dá)不到快排應(yīng)有的效果。 ? 所以為了保證快排算法的隨機(jī)化,我們必須進(jìn)行一些優(yōu)化。 ? 下面介紹的方法有三種:
  • 排序前打亂數(shù)組的順序
  • 通過隨機(jī)數(shù)保證取得的基準(zhǔn)元素的隨機(jī)性
  • 三數(shù)取中法取得基準(zhǔn)元素(推薦)
  • ? 1. 排序前打亂數(shù)組的順序 public static void sort (int [] a){StdRandom.shuffle(a)? // 外部導(dǎo)入的亂序算法,打亂數(shù)組的分布sort(a, 0, a.length - 1); }

    ?

    當(dāng)然來了,因?yàn)閬y序函數(shù)的運(yùn)行,這會(huì)增加一部分耗時(shí),但這可能是值得的 2.通過隨機(jī)數(shù)保證取得的基準(zhǔn)元素的隨機(jī)性 ? private static int getRandom (int []a, int low, int high) {int RdIndex = (int) (low + Math.random()* (high - low)); // 隨機(jī)取出其中一個(gè)數(shù)組元素的下標(biāo)exchange(a, RdIndex, low);? // 將其和最左邊的元素互換return a[low]; ? }private static int partition (int[] a, int low, int high) {int i = low, j = high+1;????? // i, j為左右掃描指針 PS: 思考下為什么j比i 多加一個(gè)1呢?int pivotkey = getRandom (a, low, high); // 基準(zhǔn)元素隨機(jī)化while(true) {?while (a[--j]>pivotkey) {?? if(j == low) break; }? // 右游標(biāo)左移while(a[++i]<pivotkey) {?? if(i == high) break;? }? // 左游標(biāo)右移if(i>=j) break;??? // 左右游標(biāo)相遇時(shí)候停止, 所以跳出外部while循環(huán)else exchange(a,i, j) ;? // 左右游標(biāo)未相遇時(shí)停止, 交換各自所指元素,循環(huán)繼續(xù)? ??? }exchange(a, low, j); // 基準(zhǔn)元素和游標(biāo)相遇時(shí)所指元素交換,為最后一次交換return j;? // 一趟排序完成, 返回基準(zhǔn)元素位置}

    ?

    ? 3.? 三數(shù)取中法(推薦) 一般認(rèn)為, 當(dāng)取得的基準(zhǔn)元素是數(shù)組元素的中位數(shù)的時(shí)候,排序效果是最好的,但是要篩選出待排序數(shù)組的中位數(shù)的成本太高, 所以只能從待排序數(shù)組中選取一部分元素出來再取中位數(shù), 經(jīng)大量實(shí)驗(yàn)顯示: 當(dāng)篩選數(shù)組的長度為3時(shí)候,排序效果是比較好的, 所以由此發(fā)展出了三數(shù)取中法: ? 三數(shù)取中法 分別取出數(shù)組的最左端元素,最右端元素和中間元素, 在這三個(gè)數(shù)中取出中位數(shù),作為基準(zhǔn)元素 ? package mypackage;public class QuickSort {// 交換兩個(gè)數(shù)組元素private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}// 選取左中右三個(gè)元素,求出中位數(shù), 放入數(shù)組最左邊的a[low]中private static int selectMiddleOfThree(int[] a, int low, int high) {int middle = low + (high -? low)/2;? // 取得位于數(shù)組中間的元素middle
    if(a[low]>a[high])??? {? exchange(a, low, high);? //此時(shí)有 a[low] < a[high] ??? }if(a[middle]>a[high]){ exchange(a, middle, high); //此時(shí)有 a[low], a[middle] < a[high] ??? }if(a[middle]>a[low]) {exchange(a, middle, low); //此時(shí)有a[middle]< a[low] < a[high] ??? }return a[low];? // a[low]的值已經(jīng)被換成三數(shù)中的中位數(shù), 將其返回 ? }private static int partition (int[] a, int low, int high) {int i = low, j = high+1;????? // i, j為左右掃描指針 PS: 思考下為什么j比i 多加一個(gè)1呢?int pivotkey = selectMiddleOfThree( a, low, high);while(true) {?while (a[--j]>pivotkey) {?? if(j == low) break; }? // 右游標(biāo)左移while(a[++i]<pivotkey) {?? if(i == high) break;? }? // 左游標(biāo)右移if(i>=j) break;??? // 左右游標(biāo)相遇時(shí)候停止, 所以跳出外部while循環(huán)else exchange(a,i, j) ;? // 左右游標(biāo)未相遇時(shí)停止, 交換各自所指元素,循環(huán)繼續(xù)? ??? }exchange(a, low, j); // 基準(zhǔn)元素和游標(biāo)相遇時(shí)所指元素交換,為最后一次交換return j;? // 一趟排序完成, 返回基準(zhǔn)元素位置 ? }private static void sort (int [] a,? int low, int high) {if(high<= low) { return; } // 當(dāng)high == low, 此時(shí)已是單元素子數(shù)組,自然有序, 故終止遞歸int j = partition(a, low, high);? // 調(diào)用partition進(jìn)行切分sort(a,? low,? j-1);?? // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素左邊的子數(shù)組進(jìn)行遞歸sort(a,? j+1,? high); // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素右邊的子數(shù)組進(jìn)行遞歸 ? }public static void sort (int [] a){ //sort函數(shù)重載, 只向外暴露一個(gè)數(shù)組參數(shù)sort(a, 0, a.length - 1);} }

    ?

    ? ?

    優(yōu)化點(diǎn)三 —— 去除不必要的邊界檢查

    ? 我在上面說過:“ 第一個(gè)內(nèi)部while循環(huán)體里面的的? if(j == low) break;判斷其實(shí)是多余的,可以去除”? (請(qǐng)把文章往上翻到標(biāo)題—“對(duì)切分函數(shù)partition的解讀”中的第4點(diǎn)) ? 那么, 能不能把另外一個(gè)邊界檢查? if(i == high) break; 也去除呢? 當(dāng)然是不能直接去除的,但是我們可以通過一些技巧使得我們能夠去除它 ? 首先要理解的是 if(i == high) break;的作用: 防止 i 增加到超過數(shù)組的上界, 造成數(shù)組越界的錯(cuò)誤。 那么按照同樣的思考方式,對(duì)于 while(a[++i]<pivotkey) {?? if(i == high) break;? }

    我們只要嘗試把這一作用交給a[++i]<pivotkey去完成, 不就可以把 if(i == high) break; 給去掉了嗎?

    ? 這里的技巧就是: 在排序前先把整個(gè)數(shù)組中最大的元素移到數(shù)組的最右邊,這樣的話, 就算左游標(biāo)i增加(右移)到數(shù)組的最右端,a[++i]<pivotkey也會(huì)判定為false(數(shù)組最大值當(dāng)然是大于或等于基準(zhǔn)元素的), 從而無法越界。 ? 代碼: public class QuickSort {// 交換兩個(gè)數(shù)組元素private static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}//將原數(shù)組里最大的元素移到最右邊, 構(gòu)造“哨兵”private static void Max(int [] a) {int max = 0;?for(int i = 1; i<a.length;i++) {if(a[i]>a[max]) {max = i;}}exchange(a, max, a.length -1);}private static int partition (int[] a, int low, int high) {int i = low, j = high+1;????? // i, j為左右掃描指針 PS: 思考下為什么j比i 多加一個(gè)1呢?int pivotkey = a[low];? // pivotkey 為選取的基準(zhǔn)元素(頭元素)while(true) {?while (a[--j]>pivotkey) {?? }? // 空的循環(huán)體while(a[++i]<pivotkey) {?? }? // 空的循環(huán)體if(i>=j) break;??? // 左右游標(biāo)相遇時(shí)候停止, 所以跳出外部while循環(huán)else exchange(a,i, j) ;? // 左右游標(biāo)未相遇時(shí)停止, 交換各自所指元素,循環(huán)繼續(xù)?}exchange(a, low, j); // 基準(zhǔn)元素和游標(biāo)相遇時(shí)所指元素交換,為最后一次交換return j;? // 一趟排序完成, 返回基準(zhǔn)元素位置 ? }private static void sort (int [] a,? int low, int high) {if(high<= low) { return; } // 當(dāng)high == low, 此時(shí)已是單元素子數(shù)組,自然有序, 故終止遞歸int j = partition(a, low, high);? // 調(diào)用partition進(jìn)行切分sort(a,? low,? j-1);?? // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素左邊的子數(shù)組進(jìn)行遞歸sort(a,? j+1,? high); // 對(duì)上一輪排序(切分)時(shí),基準(zhǔn)元素右邊的子數(shù)組進(jìn)行遞歸 ? }public static void sort (int [] a){ //sort函數(shù)重載, 只向外暴露一個(gè)數(shù)組參數(shù)Max(a); // 將原數(shù)組里最大元素移到最右邊, 構(gòu)造“哨兵”sort(a, 0, a.length - 1);} }

    ?

    ? 如果看到這里對(duì)“哨兵”這個(gè)概念還不是很清楚的話,看看下面這張圖示: 《三種哨兵》 ?

    ?

    關(guān)于哨兵三再說幾句: 在處理內(nèi)部子數(shù)組的時(shí)候,右子數(shù)組中最左側(cè)的元素可以作為左子數(shù)組右邊界的哨兵(可能有點(diǎn)繞) ?

    優(yōu)化點(diǎn)四 —— 三切分快排(針對(duì)大量重復(fù)元素)

    ? 普通的快速排序還有一個(gè)缺點(diǎn), 那就是會(huì)交換一些相同的元素 ? 回憶一下我在前面提到的快排中對(duì)左右游標(biāo)指定的規(guī)則:
    • 左游標(biāo)向右掃描, 跨過所有小于基準(zhǔn)元素的數(shù)組元素, 直到遇到一個(gè)大于或等于基準(zhǔn)元素的數(shù)組元素, 在那個(gè)位置停下。
    • 右游標(biāo)向左掃描, 跨過所有大于基準(zhǔn)元素的數(shù)組元素,直到遇到一個(gè)大于或等于基準(zhǔn)元素的數(shù)組元素,在那個(gè)位置挺停下
    ? 特別的, 當(dāng)左右游標(biāo)都指向和基準(zhǔn)元素相同的元素時(shí)候, 不必要的交換就發(fā)生了 如圖: (下圖中基準(zhǔn)元素是6)

    ?

    ? 所以由此人們研究出了三切分快排(三路劃分) , 在左右游標(biāo)的基礎(chǔ)上,再增加了一個(gè)游標(biāo),用于處理和基準(zhǔn)元素相同的元素 ?

    ?

    ? 代碼如下: package mypackage;public class Quick3way {public static void exchange(int [] a , int i, int j) {int temp = a[i];a[i] = a[j];a[j] = temp;}public static void sort (int [] a, int low, int high) {if(low>=high)? { return; }int lt = low, gt = high, i =low+1;int v = a[low];while(i<=gt) {int aValue = a[i];if(aValue>v) { exchange(a, i, gt--);? }else if(aValue<v) { exchange(a, i++, lt++); }else{ i++; }}sort(a, low, lt-1);sort(a, gt+1, high);}public static void sort (int [] a) {sort(a, 0, a.length - 1);} }

    ?

    ? ? 切分軌跡: ? (A - Z 字母排序, A最小, Z最大)

    ?

    (不好意思,pdf書里的截圖實(shí)在太模糊了,所以我自己用手機(jī)照了一張)

    ?

    ?

    其實(shí)啊,我只是把你們喝咖啡的時(shí)間,都用來喝啤酒而已

    總結(jié)

    以上是生活随笔為你收集整理的【算法】快速排序算法的编码和优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。