數據結構與算法系列——排序(9)_快速排序 快速排序 圖解排序算法(五)之快速排序——三數取中法

1. 工做原理(定義)

  快速排序(Quicksort)是對冒泡排序的一種改進。(分治法策略)html

  快速排序的基本思想是在待排序的n個記錄中任取一個記錄(一般取第一個記錄)做爲基準,把該記錄放入適當位置後,數據序列被此記錄劃分紅兩部分,分別是比基準小和比基準大的記錄;而後再對基準兩邊的序列用一樣的策略,分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。
   這裏寫圖片描述

2. 算法步驟

  實現方法兩種方法:挖坑法和指針交換法git

  基準的選擇三種方法:1.選擇最左邊記錄  2.隨機選擇   3.選擇平均值github

2.1 挖坑法

  1. 設置兩個變量i、j,排序開始的時候:i=0,j=N-1;
  2. 以第一個數組元素做爲關鍵數據,賦值給key,即key=A[0];
  3. 從j開始向前搜索,即由後開始向前搜索(j--),找到第一個小於key的值A[j],將A[j]和A[i]的值交換;
  4. 從i開始向後搜索,即由前開始向後搜索(i++),找到第一個大於key的A[i],將A[i]和A[j]的值交換;
  5. 重複第三、4步,直到i=j;

  【3,4步中,沒找到符合條件的值,即3中A[j]不小於key,4中A[i]不大於key的時候改變j、i的值,使得j=j-1,i=i+1,直至找到爲止。找到符合條件的值,進行交換的時候i, j指針位置不變。另外,i==j這一過程必定正好是i+或j-完成的時候,此時令循環結束】。 pivot算法

2.2 指針交換法

  

2.3 基準的選擇

  1. 選擇最左邊記錄 : 給定的待排序列是一個隨機,無序的序列,那麼這種選擇策略是能夠接受的。但若是這個序列是一個正序或逆序的,仍是以選擇最左邊記錄做爲基準的策略的話,這將會產生一個很是極端,糟糕的狀況
  2. 隨機選擇 
  3. 選擇平均值  

3. 動畫演示

   

4. 性能分析

  基準的選擇對於快速排序算法來講是很是重要的,雖然選擇任何一個元素做爲基準均可以完成排序,可是好的基準的選擇能最大發揮快速排序算法的性能,而很差的基準選擇則會讓快速排序的性能大打折扣,甚至變成」慢速」排序。所以在選擇基準上,最好的狀況就是儘可能選擇能均勻劃分子序列的基準(即儘可能使子序列相等)。數組

1. 時間複雜度

  快速排序的時間性能取決於快速排序遞歸的深度,遞歸過程當中,子序列越平衡,則性能越好
  (1)最優狀況下時,遞歸過程造成的遞歸樹是一課平衡樹,快速排序算法的時間複雜度爲O(nlogn)。
  (2)最壞狀況下時,遞歸過程造成的遞歸樹是一課斜樹,快速排序算法的時間複雜度爲O(n2)。
  (3)平均狀況下,時間複雜度爲O(nlogn)。數據結構

2. 空間複雜度

  就空間複雜度來講,主要是遞歸形成的棧空間的使用。 
  (1)最好狀況,遞歸樹的深度爲log2n,其空間複雜度也就爲O(logn)。 
  (2)最壞狀況,須要進行n‐1遞歸調用,其空間複雜度爲O(n)。 
  (3)平均狀況,空間複雜度也爲O(logn)。post

3. 算法穩定性 

  快速排序在排序過程當中,因爲基準數的比較和交換是跳躍進行的,所以,快速排序是一種不穩定的排序算法。性能

4. 初始順序狀態

  1. 比較次數:
  2. 移動次數:
  3. 複雜度:    
  4. 排序趟數:

5. 歸位

  能歸位,每一趟排序有一個元素歸位。學習

6. 優勢

6. 改進算法

  在有序或者接近有序的時候上面的方法效率會很是低(摘錄自百度百科)動畫

  1.  三數取中法:在待排序序列的第一個元素,中間元素和最後一個元素中取大小位於中間的那個元素。
  2. 根據分區大小調整算法:在遞歸子問題的時候在區間內的數據比較少的時候咱們能夠再也不劃分區間,直接用直接插入排序或者堆排序等算法效率會更高,由於接着劃分又要建立棧楨,沒有必要。在遞歸排序子分區的時候,老是選擇優先排序那個最小的分區。這個選擇可以更加有效的利用存儲空間從而從總體上加速算法的執行。

  3. 不一樣的分區方案考慮:相同元素不少的狀況下,將分區分爲三塊而不是原來的兩塊:一塊是小於中軸值的全部元素,一塊是等於中軸值的全部元素,另外一塊是大於中軸值的全部元素。另外一種簡單的改進方法是,當分區完成後,若是發現最左和最右兩個元素值相等的話就避免遞歸調用而採用其餘的排序算法來完成。

  4. 並行的快速排序:因爲快速排序算法是採用分治技術來進行實現的,這就使得它很容易可以在多臺處理機上並行處理。
      在大多數狀況下,建立一個線程所須要的時間要遠遠大於兩個元素比較和交換的時間,所以,快速排序的並行算法不可能爲每一個分區都建立一個新的線程。通常來講,會在實現代碼中設定一個閥值,若是分區的元素數目多於該閥值的話,就建立一個新的線程來處理這個分區的排序,不然的話就進行遞歸調用來排序。
      對於這一併行快速排序算法也有其改進。該算法的主要問題在於,分區的這一步驟老是要在子序列並行處理以前完成,這就限制了整個算法的並行程度。解決方法就是將分區這一步驟也並行處理。改進後的並行快速排序算法使用2n個指針來並行處理分區這一步驟,從而增長算法的並行程度。

7. 具體代碼

public class QuickSort {
    /**
     * 快排遞歸
     * @param arr 待排序的數組
     * @param left 數組的左邊界(例如,從起始位置開始排序,則l=0)
     * @param right 數組的右邊界(例如,排序截至到數組末尾,則right=arr.length-1)
     */
    public static void quickSort(int[] arr, int left, int right) {
        if (left< right) {
            int index = getPartitionIndex2(arr, left, right);/* 獲取分區索引 */
            quickSort(arr, left, index-1); /* 遞歸調用 */
            quickSort(arr, index+1, right); /* 遞歸調用 */
        }
    }
   
    //快速排序--挖坑法  獲取分區索引的
    public static int getPartitionIndex1(int[] arr, int left, int right) {
        int low,high,pivot;
        low = left;
        high = right;
        dealPivot(arr, left, right);//三數中值
        pivot = arr[left]; //最左基準
        while (low < high) {
            while(low < high && arr[high] > pivot)
                high--; // 從右向左找第一個小於pivot的數
            if(low < high)
                arr[low++] = arr[high];
            while(low < high && arr[low] < pivot)
                low++; // 從左向右找第一個大於pivot的數
            if(low < high)
                arr[high--] = arr[low];
        }
        arr[low] = pivot;
        return low;
    }

    //快速排序--指針交換法  獲取分區索引的
    public static int getPartitionIndex2(int[] arr, int left, int right) {
        int low,high,pivot;
        low = left;
        high = right;
        dealPivot(arr, left, right);//三數中值
        pivot = arr[left];//最左基準
        while (low < high) {
            while(low < high && arr[high] > pivot)
                high--; // 從右向左找第一個小於pivot的數
            while(low < high && arr[low] <= pivot)
                low++; // 從左向右找第一個大於等於pivot的數
            if(low < high)
                swap(arr, low, high);
        }
         // 此時right和left值是相同的,將基準元素與重合位置元素交換
        arr[left] = arr[low];
        arr[low] = pivot;
        return low;
    }

    //三數取中法,也就是取左端、中間、右端三個數,而後進行排序,將中間數做爲樞紐值。將中間數放在了left位置上
    public static void dealPivot(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[right] < arr[mid]) {//右邊大於中間
            swap(arr, right, mid);
        }
        swap(arr, left, mid);//左邊《中間《右邊   將中間的值換到左邊
    }
    
    // 交換數組中的兩個數
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
    public static void main(String[] args){
        int[] arr = { 49, 38, 65, 97, 76, 13, 27, 50 };
        quickSort(arr,0,arr.length-1);
        System.out.println("排好序的數組:");
        for (int e : arr)
            System.out.print(e+" ");
    }
}

8. 參考網址

  1. 數據結構基礎學習筆記目錄
  2. 76-交換排序——快速排序
  3. 排序算法系列之快速排序
  4. 快速排序
  5. https://visualgo.net/en/sorting
  6. https://www.runoob.com/w3cnote/bubble-sort.html
  7. https://github.com/hustcc/JS-Sorting-Algorithm
  8. 漫畫:什麼是快速排序?(完整版) 
  9. 圖解排序算法(五)之快速排序——三數取中法
相關文章
相關標籤/搜索