排序算法6:快速排序

數(shù)據(jù)結(jié)構(gòu)與算法

  • 快速排序為應(yīng)用最多的排序算法隘道,因為快速二字而聞名∧ㄖ瘢快速排序和歸并排序一樣怜瞒,采用的都是分治思想父泳。
  • 分治法的基本思想是:將原問題分解為若干個規(guī)模更小但結(jié)構(gòu)與原問題相似的子問題。遞歸地解這些子問題吴汪,然后將這些子問題的解組合為原問題的解惠窄。
  • 我們只需關(guān)注最小問題該如何求解,和如何去遞歸既可以得到正確的算法實現(xiàn)漾橙。
  • 快速排序可以分為:單路快速排序杆融,雙路快速排序,三路快速排序近刘,他們區(qū)別在于選取幾個指針來對數(shù)組進(jìn)行遍歷下面我們依次來講解擒贸。

1. 單路快速算法

1.1 單路快速算法的思想:

image

首先我們選取數(shù)組中的一個數(shù)臀晃,將其放在合適的位置,這個位置左邊的數(shù)全部小于該數(shù)值介劫,這個位置右邊的數(shù)全部大于該數(shù)值 徽惋。

  1. 假設(shè)數(shù)組為 arr[l...r] 假設(shè)指定數(shù)值為數(shù)組第一個元素 int v = arr[l],假設(shè) j 標(biāo)記為比 v 小的最后一個元素座韵, 即 arr[j+1] > v险绘。當(dāng)前考察的元素為 i 則有arr[l + 1 ... j] < v , arr[j+1,i) >= v 如上圖所示誉碴。
  2. 假設(shè)正在考察的元素值為 e 宦棺,e >= v 的時候我們只需交將不動,直接 i++ 去考察下一個元素黔帕,
  3. 當(dāng)e < v 由上述假設(shè)我們需要將 e 放在<v 的部分 代咸,此時我們只需將 arr[j] 和 arr[i] 交換一下位置即可。
  4. 最后一個元素考察完成以后成黄,我們再講 arr[l]和 arr[j]調(diào)換一下位置就可以了呐芥。
  5. 上述遍歷完成以后 arr[l + 1 ... j] < v , arr[j+1,i) >= v 就滿足了奋岁,接下來我們只需要遞歸的去考察 arr[l + 1 ... j] 和 arr[j+1,r] 即可思瘟。

1.2 單路快速排序的 Java 實現(xiàn):


    public static void quickSortOneWay(int[] arr, int l, int r) {
        if (l >= r) {
            return;
        }
        int p = partition(arr, l, r);

        quickSortOneWay(arr, l, p - 1);
        quickSortOneWay(arr, p + 1, r);

    }

    private static int partition(int[] arr, int l, int r) {
        // 為了提高效率,減少造成快速排序的遞歸樹不均勻的概率闻伶,
        // 對于一個數(shù)組滨攻,每次隨機(jī)選擇的數(shù)為當(dāng)前 partition 操作中最小最大元素的可能性為 1/n
        int randomNum = (int) (Math.random() * (r - l + 1) + l);
        swap(arr, l, randomNum);


        //將小于v的數(shù)據(jù)放在索引為j的位置
        int v = arr[l];
        int j = l;
        for (int i = l + 1; i <= r; i++) {
            if (arr[i] < v) {
                swap(arr, j + 1, i);
                j++;
            }
        }
        
        swap(arr, l, j);
        return j;
    }

    //交換位置
    private static void swap(int[] arr, int l, int r) {
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }

對于上述算法中為什么選取了當(dāng)前排序數(shù)組中隨機(jī)一個元素進(jìn)行比較,假設(shè)我們在考察的數(shù)組已經(jīng)為已經(jīng)排序好的數(shù)組蓝翰,那么我們遞歸樹就會向右側(cè)延伸 N 的深度光绕,這種情況使我們不想要看到的,如果我們每次 partition 都隨機(jī)從數(shù)組中取一個數(shù)霎箍,那么這個數(shù)是當(dāng)前排序數(shù)組中最小元素可能性為 1/n 那么每次都取到最小的數(shù)的可能性就很低了奇钞。

2雙路快速排序

2.1 雙路快速排序算法思想:

  1. 跟單路一樣,雙路快速排序漂坏,同樣選擇數(shù)組的第一個元素當(dāng)做標(biāo)志位(經(jīng)過隨機(jī)選擇后的)
  2. 雙路快速排序要求有兩個指針,指針 i j 分別指向 l+1 和 r 的位置然后兩者同時向數(shù)組中間遍歷 在遍歷過程中要保證arr[l+1 ... i) <= v媒至, arr(j....r] >= v 因此我們可以初始化 i = l+1 以保證左側(cè)區(qū)間初始為空顶别,j = r 保證右側(cè)空間為空
  3. 遍歷過程中要 i <= r 且 arr[i] <= v 的時候 i ++ 就可以了
    當(dāng) arr[i] > v 時表示遇到了 i 的值大于 v 數(shù)值 此刻能等待 j 角標(biāo)的值,從右向左遍歷數(shù)組 當(dāng) arr[i] < v 表示遇到了 j 的值小于 v 的元素拒啰,它不該在這個位置呆著驯绎,
  4. 得到了 i j 的角標(biāo)后 先要判斷是否到了循環(huán)結(jié)束的時候了,即 i 是否已經(jīng) 大于 j 了谋旦。
  5. 否則 應(yīng)該講 i 位置的元素和 j 位置的元素交換位置剩失,然后 i++ j-- 繼續(xù)循環(huán)
  6. 遍歷結(jié)束的條件是 i>j 此時 arr[j]為最后一個小于 v 的元素 arr[i] 為第一個大于 v 的元素 因此 j 這個位置 就應(yīng)該是 v 所應(yīng)該在數(shù)組中的位置 因此遍歷結(jié)束后需要交換 arr[l] 與 arr[j]


    image

2.2 雙路快速排序的 Java 實現(xiàn):

    public static void quickSortOneWay(int[] arr, int l, int r) {
        if (l >= r) {
            return;
        }
        int p = partition(arr, l, r);

        quickSortOneWay(arr, l, p - 1);
        quickSortOneWay(arr, p + 1, r);

    }

    private static int partition(int[] arr, int l, int r) {
        // 為了提高效率屈尼,減少造成快速排序的遞歸樹不均勻的概率,
        // 對于一個數(shù)組拴孤,每次隨機(jī)選擇的數(shù)為當(dāng)前 partition 操作中最小最大元素的可能性為 1/n
        int randomNum = (int) (Math.random() * (r - l + 1) + l);
        swap(arr, l, randomNum);


        //將小于v的數(shù)據(jù)放在索引為j的位置
        int v = arr[l];
        int i = l;
        int j = r;

        while (true) {
            while (i < r && arr[i] <= v) i++;
            while (j > l + 1 && arr[j] >= v) j--;

            if (i > j) {
                break;
            }

            swap(arr, i, j);
            i++;
            j--;

        }


        swap(arr, l, j);
        return j;
    }

    //交換位置
    private static void swap(int[] arr, int l, int r) {
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }

3 三路快速排序

3.1 三路快速排序算法思想

上述兩種算法我們發(fā)現(xiàn)對于與標(biāo)志位相同的值得處理總是脾歧,做了多余的交換處理,如果我們能夠?qū)?shù)組分為> = <三部分的話效率可能會有所提高演熟。

  1. 我們將數(shù)組劃分為 arr[l+1...lt] <v arr[lt+1..i) =v arr[gt...r] > v三部分 其中 lt 指向 < v 的最后一個元素前一個元素鞭执,gt 指向>v的第一個元素的前一個元素,i 為當(dāng)前考察元素
  2. 定義初始值得時候依舊可以保證這初始的時候這三部分都為空 int lt = l; int gt = r; int i = l + 1;
  3. 當(dāng) e > v 的時候我們需要將 arr[i] 與 arr[gt-1] 交換位置芒粹,并將 > v 的部分?jǐn)U大一個元素 即 gt-- 但是此時 i 指針并不需要操作兄纺,因為換過過來的數(shù)還沒有被考察。
  4. 當(dāng) e = v 的時候 i ++ 繼續(xù)考察下一個
  5. 當(dāng) e < v 的時候我們需要將 arr[i] 與 arr[lt+1] 交換位置
  6. 當(dāng)循環(huán)結(jié)束的時候 lt 位于小于 v 的最后一個元素位置所以最后我們需要將arr[l] 與 arr[lt] 交換一下位置化漆。


    image
image

3.2 三路快速排序 Java 代碼實現(xiàn):

    @Override
    public void quickSort(int[] arr, int left, int right) {
        if (left >= right) {
            return;
        }

        // 為了提高效率估脆,減少造成快速排序的遞歸樹不均勻的概率,
        // 對于一個數(shù)組座云,每次隨機(jī)選擇的數(shù)為當(dāng)前 partition 操作中最小最大元素的可能性 降低 1/n!

        int randomNum = (int) (Math.random() * (right - left + 1) + left);
        swap(arr, left, randomNum);

        int v = arr[left];
        // 三路快速排序即把數(shù)組劃分為大于 小于 等于 三部分
        //arr[l+1...lt] <v  arr[lt+1..i) =v  arr[gt...r] > v 三部分
        // 定義初始值得時候依舊可以保證這初始的時候這三部分都為空
        int leftEnd = left;
        int rightStart = right;
        int i = left + 1;

        while (i <= rightStart) {
            if (arr[i] < v) {
                swap(arr, i, leftEnd + 1);
                i++;
                leftEnd++;
            } else if (arr[i] == v) {
                i++;
            } else {
                swap(arr, i, rightStart);
                rightStart--;
                //i++ 注意這里 i 不需要加1 因為這次交換后 i 的值仍不等于 v 可能小于 v 也可能等于 v 所以交換完成后 i 的角標(biāo)不變
            }
        }

        swap(arr, left, leftEnd);
        leftEnd--;
        quickSort(arr, left, leftEnd);
        quickSort(arr, rightStart, right);

    }

4 快速排序時間復(fù)雜度空間復(fù)雜度

4.1 時間復(fù)雜度

  • 在最優(yōu)的情況下旁蔼,快速排序算法的時間復(fù)雜度為O(nlogn)。
  • 在最壞的情況下疙教,待排序的序列為正序或者逆序最終其時間復(fù)雜度為O(n2)棺聊。
  • 平均的情況,設(shè)樞軸的關(guān)鍵字應(yīng)該在第k的位置(1≤k≤n)贞谓,那么:由數(shù)學(xué)歸納法可證明限佩,其數(shù)量級為O(nlogn)。

4.2 空間復(fù)雜度

就空間復(fù)雜度來說裸弦,主要是遞歸造成的椝钔空間的使用

  • 最好情況,遞歸樹的深度為log2n理疙,其空間復(fù)雜度也就為O(logn)
  • 最壞情況晕城,需要進(jìn)行n‐1遞歸調(diào)用,其空間復(fù)雜度為O(n)
  • 平均情況窖贤,空間復(fù)雜度也為O(logn)砖顷。

可惜的是,由于關(guān)鍵字的比較和交換是跳躍進(jìn)行的滤蝠,因此,快速排序是一種不穩(wěn)定的排序方法授嘀。

參考

搞懂基本排序算法
快速排序最好,最壞蹄皱,平均復(fù)雜度分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末芯肤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子崖咨,更是在濱河造成了極大的恐慌,老刑警劉巖晴弃,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異上鞠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)芍阎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谴咸,“玉大人,你說我怎么就攤上這事岭佳⊙。” “怎么了珊随?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叶洞。 經(jīng)常有香客問我,道長衩辟,這世上最難降的妖魔是什么螟炫? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮艺晴,結(jié)果婚禮上昼钻,老公的妹妹穿的比我還像新娘。我一直安慰自己财饥,他們只是感情好换吧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钥星,像睡著了一般。 火紅的嫁衣襯著肌膚如雪满着。 梳的紋絲不亂的頭發(fā)上谦炒,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天贯莺,我揣著相機(jī)與錄音,去河邊找鬼宁改。 笑死缕探,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的还蹲。 我是一名探鬼主播爹耗,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谜喊!你這毒婦竟也來了潭兽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤斗遏,失蹤者是張志新(化名)和其女友劉穎山卦,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诵次,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡账蓉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逾一。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铸本。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遵堵,靈堂內(nèi)的尸體忽然破棺而出箱玷,到底是詐尸還是另有隱情,我是刑警寧澤鄙早,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布汪茧,位于F島的核電站,受9級特大地震影響限番,放射性物質(zhì)發(fā)生泄漏舱污。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一弥虐、第九天 我趴在偏房一處隱蔽的房頂上張望扩灯。 院中可真熱鬧,春花似錦霜瘪、人聲如沸珠插。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捻撑。三九已至,卻和暖如春顾患,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背江解。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工犁河, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桨螺。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓彭谁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親则奥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361