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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Quick Sort 快速排序算法

發布時間:2025/5/22 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Quick Sort 快速排序算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Table of Contents

  • 前言
  • 算法步驟
  • 選取樞紐元
  • 分割數組
  • 算法實現
  • 小數組和插入排序
  • 結語
  • 前言

    快速排序算法應該是常見的排序算法中使用的最多的一個,很多語言內置的排序算法都間接或直接的使用了這一算法。

    因此,我們是很有必要學習快速排序算法的。

    算法步驟

    在了解詳細的算法步驟之前可以先來看一下快速排序算法的算法復雜度:

    時間復雜度(平均)時間復雜度(最壞)空間復雜度
    $O(nlog_2n)$$O(n^2)$$O(1)$

    通過快速排序算法的算法復雜度我們可以猜測它可能是一種 分治 算法,而事實也是如此。

    當通過快速排序算法對數組 \(A\) 進行排序時,我們需要遞歸的將其分為不同的部分進行處理,其基本步驟為:

  • 如果數組 \(A\) 中的元素個數為 1 或 0 時,就直接返回
  • 選擇數組 \(A\) 中的任一元素 \(P\) 作為 樞紐元
  • 將數組 \(A\) 中的元素分為兩個部分:所有元素 小于等于 \(P\) 的部分 \(A_1\) 和所有元素 大于等于 \(P\) 的部分 \(A_2\)
  • 返回 [\(QuickSort(A_1)\), \(P\), \(QuickSort(A_2)\)]
  • 可以看到,快速排序算法的基本步驟并不難,實現這個算法主要需要考慮的問題便是 樞紐元 的選取和 怎樣分割 數組。

    選取樞紐元

    常見的樞紐元選擇方式大概就是選擇數組 中間 的那個元素,簡單直接有效。

    在選取樞紐元后,通常需要對兩端和樞紐元處的值進行排序,這樣可以稍稍提高算法的效率并避免一些意外情況1

    public static int selectPivot(int[] arr, int left, int right) { // 包含右邊界int mid = (left + right) / 2;if (arr[left] > arr[mid]) {swap(arr, left, mid);}if (arr[left] > arr[right]) {swap(arr, left, right);}if (arr[mid] > arr[right]) {swap(arr, mid, right);}/* arr[left] <= arr[mid] <= arr[right] */swap(arr, mid, right - 1);return arr[right - 1]; }

    在將兩端和樞紐元處的值排序后,最左端的值必然是小于等于樞紐元處的值的,最右端的值必然是大于等于樞紐元處的值的,這時,我們需要分割的數組便變成了 [left + 1, right - 1].

    然后,交換樞紐元和 right - 1 處的元素,使得樞紐元離開要被分割的數組2,這時,我們需要分割的數組便變成了 [left + 1, right - 2].

    介紹完了常用的做法,這里在介紹一種不常用的做法和一種常見的錯誤做法:

    • 隨機選取樞紐元是一種不常用的做法,因為隨機數的生成成本相較于中值的計算成本要昂貴的多
    • 直接選取第一個元素作為樞紐元是一種常見的錯誤做法,當輸入的數組是預排序的時,會讓所有元素都分配到單個組中,并不斷遞歸,使得時間復雜度變成 \(O(n^2)\)

    分割數組

    分割數組時,需要首先將選取的樞紐元和末端的元素交換位置,然后從左向右遍歷所有小于樞紐元的元素,從右向左遍歷所有大于樞紐元的元素,當兩者遇到大于或小于樞紐元的元素停下時,便交換遇到的元素,然后繼續遍歷,直到交錯:

    初始狀態:8 1 4 9 0 3 5 2 7 6i j p交換前:8 1 4 9 0 3 5 2 7 6i j p交換后:2 1 4 9 0 3 5 8 7 6i j p

    交錯后便將 i 處的元素和末端的樞紐元相交換,此時,樞紐元左邊的元素都小于等于它,右邊的元素都大于等于它:

    交錯后:2 1 4 5 0 3 9 8 7 6j i p交換后:2 1 4 5 0 3 6 8 7 9i p

    遍歷過程中需要考慮的一個問題是:遇到和樞紐元相等的元素怎么處理?是都停止還是都不停止?

    假設輸入的元素都相等,我們來看一下兩種策略最后可能的情況:

    都停止:8 8 8 8 8 8 8 8 8j i p都不停止:8 8 8 8 8 8 8 8 8i j p

    可以看到,采用都停止的策略時,雖然會產生一些不必要的交換,但是都不停止的話,數組的分割便會極為的不均衡,這會使得時間復雜度很高。

    因此,更好的選擇是在遇到和樞紐元相等的元素后都停下來。

    算法實現

    在確定了樞紐元的選取方法和數組的分割策略后,就可以嘗試實現快速排序算法了:

    public static void quickSort(int[] arr) {quickSort(arr, 0, arr.length - 1); }public static void quickSort(int[] arr, int left, int right) { // 包含右邊界if (left >= right) { // 元素小于等于 1 個return;}int i = left, j = right - 1, pivot = selectPivot(arr, left, right);while (i < j) {while (arr[++i] < pivot) {}while (arr[--j] > pivot) {}if (i < j) {swap(arr, i, j) ;}}swap(arr, i, right - 1);quickSort(arr, left, i - 1);quickSort(arr, i + 1, right); }public static int selectPivot(int[] arr, int left, int right) {int mid = (left + right) / 2;if (arr[left] > arr[mid]) {swap(arr, left, mid);}if (arr[left] > arr[right]) {swap(arr, left, right);}if (arr[mid] > arr[right]) {swap(arr, mid, right);}/* arr[left] <= arr[mid] <= arr[right] */swap(arr, mid, right - 1);return arr[right - 1]; }public static void swap(int[] arr, int i, int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp; }

    算法的實現并不是很難,但是需要注意的一段代碼是:

    while (i < j) {while (arr[++i] < pivot) {}while (arr[--j] > pivot) {}if (i < j) {swap(arr, i, j) ;} }

    假如將這段代碼修改為如下形式,會使得在遇到 arr[i] = arr[j] = pivot 的情況后陷入死循環:

    while (i < j) {while (arr[i] < pivot) {i++;}while (arr[j] > pivot) {j--;}if (i < j) {swap(arr, i, j) ;} }

    小數組和插入排序

    快速排序在小數組上的表現還不如插入排序好,因此,在實現快速排序時,常常還會通過插入排序來排序較小的數組,比如說 OpenJDK 中的實現便是這樣的。

    改進后的實現如下:

    public static void insertSort(int[] arr, int left, int right) { // 包含右邊界for (int p = left + 1; p <= right; p++) {int tmp = arr[p];for (int j = p; j > left && arr[j - 1] > tmp; j--) {arr[j] = arr[j - 1];}arr[j] = tmp;} }public static void quickSort(int[] arr) {quickSort(arr, 0, arr.length - 1); }public static void quickSort(int[] arr, int left, int right) {if (left + 20 >= right) { // 小數組insertSort(arr, left, right);return;}int i = left, j = right - 1, pivot = selectPivot(arr, left, right);while (i < j) {while (arr[++i] < pivot) {}while (arr[--j] > pivot) {}if (i < j) {swap(arr, i, j) ;}}swap(arr, i, right - 1);quickSort(arr, left, i - 1);quickSort(arr, i + 1, right); }

    結語

    這篇博客的大部分內容都來源于《數據結構和算法分析 —— C 語言描述》一書的 7.7 節,有興趣的可以看一看 @_@

    Footnotes

    1 意外情況可以參考《數據結構和算法分析 —— C 語言描述》一書中的習題 7.38

    2 我并沒有得到這種做法的原因和解釋,只知道這種做法可以讓數組的分割更加安全(避免出錯或低效)

    轉載于:https://www.cnblogs.com/rgbit/p/11069585.html

    總結

    以上是生活随笔為你收集整理的Quick Sort 快速排序算法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。