排序之快排

  • 排序是將一串數據按照其某個或者某些關鍵字的大小進行遞增或遞減排列的操做我,一般指的排序是升序,排序方式是原地排序
  • 下面介紹下快速排序,簡稱快排

快排

  • 原理:
    • 從待排序區間選擇一個數,做爲基準值(pivot)
    • Partition: 遍歷整個待排序區間,將比基準值小的(能夠包含相等的)放到基準值的左邊,將比基準值大的(能夠包含相等的)放到基準值的右邊
    • 採用分治思想,對左右兩個小區間按照一樣的方式處理,直到小區間的長度等於1,表明已經有序,或者小區間的長度等於0,表明沒有數據。
  • 快排是一個不穩定的排序

實現方式

  1. 快排的邏輯其實很簡單,數組

    • 遞歸分治
    • 代碼以下:ide

      public static void quickSort(int[] array) {
              //待排序區間爲[0, array.length - 1]
              quickSortIternal(array, 0, array.length - 1);
      }
      
      private static void quickSortIternal(int[] array, int left, int right) {
              if(left >= right)
                      return;
      
              //這裏的就選擇最左邊的元素做爲基準值來操做
              //index表示基準值停留的下標
              int index = partition(array, left, right);
      
              quickSortIternal(array, left, index - 1);
              quickSortIternal(array, index + 1, right);
      }
    • 非遞歸分治
    • 經過使用棧實現,將數組的左右下標放入棧中
    • 每次取出判斷left和right的關係,若是left >= right 表示該小區間排序完畢
    • 存入每一個區間的左右兩下標,依次循環,當棧爲空時,表示排序結束
    • 代碼以下:性能

      public static void quickSort2(int[] array) {
                  Stack<Integer> stack = new Stack<>();
                  stack.push(array.length - 1);
                  stack.push(0);
      
                  while(!stack.isEmpty()) {
                          int left = stack.pop();
                          int right = stack.pop();
      
                          if(left >= right) {
                                  continue;
                          }
      
                          int index = partition(array, left, right);
      
                          stack.push(right);
                          stack.push(index + 1);
      
                          stack.push(index - 1);
                          stack.push(left);
                  }
          }
  2. 重點是partition的實現ui

    • 實現方法一: Hoare法
    • 使用雙引用的的方式,一個指向區間的最左邊,一個指向區間的最右邊,兩個引用向中間遍歷的靠攏
    • 若是左引用指向的值小於等於基準值就移動
    • 若是右引用指向的值大於等於基準值就移動
    • 當左引用遇到大於基準值的數據且右引用遇到小於基準值的數據時,交換這兩個引用處的數據
    • 當兩個引用相遇時說明本次partition結束,返回基準值的下標
    • 代碼以下:code

      public int partition(int[] array, int left, int right) {
                  int pivot = array[left];
                  int l = left;
                  int r = right;
      
                  while(l < r) {
                          while(l < r && array[r] >= pivot) {
                                  r--;
                          }
                          while(l < r && array[l] <= pivot) {
                                  l++;
                          }
                          swap(array, l, r);
                  }
                  swap(array, left, l);
                  return l;
          }
      
          private static void swap(int[] array, int i, int j) {
                  int tmp = array[i];
                  array[i] = array[j];
                  array[j] = tmp;
          }
    • 實現方法二:填坑法
    • 一樣須要雙引用,指定一個變量保存基準值,假象基準值的位置是一個「坑」
    • 右引用遇到大於等於基準值的數值就向左移動,直到遇到第一個小於基準值的數值,將該數值填到「坑」中,此處的位置是一個新的「坑」
    • 開始移動左引用,只有遇到一個大於基準值的數值時,填到「坑」中
    • 直到左引用和右引用相遇時說明只剩最後一個坑了,將基準值填到「坑」中,返回基準值的下標,本次partition結束
    • 代碼以下:排序

      public int partition(int[] array, int left, int right) {
          int pivot = array[left];
          int l = left;
          int r = right;
      
          while(l < r) {
                  while(l < r && array[r] >= pivot) {
                          r--;
                  }
                  array[l] = array[r];
                  while(l < r && array[l] <= pivot) {
                          l++;
                  }
                  array[r] = array[l];
          }
          array[l] = pivot;
          return l;
      }
    • 實現方法三:先後遍歷法
    • 一樣是使用雙引用slow和fast,起初同時指向基準值的後一個元素left + 1
    • 引用fast向後遍歷,若是遍歷到的數值大於基準值就向後移動
    • 遇到小於基準值的數值時,交換slow引用和fast引用指向的值,slow向後移動一位
    • 始終保證slow以前的值都是小於基準值的數值,slow和fast之間的是大於等於基準值的數值,當fast移動到區間最右端right時,遍歷結束
    • 此時show - 1處的數值爲最遍歷結束後最後一個小於基準值的數值
    • 交換left和slow - 1兩位置處的數值,slow - 1表示下標便是基準值的下標,返回slow - 1
    • 代碼以下:遞歸

      private int partition(int[] array, int left, int right) {
                  int pivot = array[left];
                  int slow = left + 1;
                  int fast = left + 1;
      
                  while(fast <= right) {
                          if(array[fast] < pivot) {
                                  swap(array, slow, fast);
                                  slow++;
                          }
                          fast++;
                  }
                  swap(array, left, slow - 1);
                  return slow - 1;
          }
  3. 基準值的選擇
    • 兩邊取其一(左或者右)
    • 隨機選擇
    • 幾數取中(例三數取中:array[left], array[mid], array[right] 大小是中間的爲基準值 )

性能分析

  • 時間複雜度:
    • 最好的狀況:O(N*logN)
    • 平均狀況:O(N*logN)
    • 最壞的狀況:O(N^2)
  • 空間複雜度:O(1)
    • 最好的狀況:O(logN)
    • 平均狀況:O(logN)
    • 最壞的狀況:O(N)
  • 穩定性:不穩定
相關文章
相關標籤/搜索