排序 - 交換排序 [2 -- 快速排序]


“傻文,如果你也跟我一樣沒有耐性,看我的文章吧,專爲沒有耐性的朋友準備”


研究了幾天這個快速排序的算法,可能我比較笨,斷斷續續加起來估計超過5個小時的時間了。


因爲我很沒有耐性,所以總是看一點忘一點。


從我本身來說 我覺得這個算法的邏輯性還是很強的,閱讀時真的需要保持清醒,因爲我不確定我能說的足夠清楚讓大家一次明白。


好,我準備先講一下快速排序的算法描述。


其實快速排序和冒泡排序屬於同一大類:交換排序中,主要思想都是把一個大數和一個小數交換位置,以這種方法來使數組逐漸有序。


今天我們待排序的數組是 {6,8,4,3,5,9,11,7}


在排序之前我先說下大致思路:

1. 選擇一個數字作爲支點;

2. 把小於支點的放到支點的左面,大於支點的放到右面;

3. 分別對支點的左右兩部分,再對每一部分分別做#1和#2。那什麼時候結束呢?我們試着討論一下。


下面我具體說說到底這個算法是如何執行的。

我還是想打斷一下,因爲第一步是選擇一個“支點”, 這個支點的選擇通常有3種方法:

1. 第一個元素

2. 中間的元素

3. 最後一個元素


我們討論前兩種,算法大同小異,只是執行過程還是有細微的不同。


好吧開始,我們選擇第一個元素作爲支點。

我們會分幾趟來完成整個排序,第一趟的目標是把數組以支點爲中心,左右兩邊分開。是這樣的:


1. 支點選擇爲6;

2. 我們會從左右兩邊同時開始比較;

3. 比較最右面的元素,7,  7 大於 6,故他理所當然應該在右面,沒有問題;

4. 繼續從右面往左走,11 也大於 6,不用理會;

5. 9 大於 6,繼續;

6. 5, 5 大於 6嗎?否,故,停下來,我們需要記住這個右位置的元素 :5;

7. 這時我們再從左面開始,因爲我們選擇了6作爲支點,這個元素我們不考慮,直接看他下一個元素:8;

8. 8 小於 6嗎?(注意,這裏的條件變成了小於,前面是大於!原因很簡單,左面的應該比支點小,右面的應該比支點大)否,故停下來,我們也記住這個左位置的元素: 8!

9. 兩邊都停下來了,這時怎麼辦?交換!

10. 好的,在停下來的地方,我們交換他們,左面的 8  和 右面的 5 進行交換,交換後數組變爲:{6,5,4,3,8,9,11,7};

11. 不要太高興,這一趟還沒完呢,我們要繼續從剛纔右位置開始往左走,這個很簡單了,下一個元素是 3, 3 大於 6嗎?否,故再次停下來;

12. 從左面繼續,左位置下一個元素是4, 4 小於 6嗎?是的,繼續;

13. 下一個元素是3, 3 小於 6 嗎?是的,... 咦?剛纔我們不是已經比較過3跟6的關係了嗎?“嗯,是的,在第#11步的時候,我們比較過,但是因爲 3 大於 6不成立,我們停了下來。”;

14. 對!這就是我們這一趟排序停止的一個條件:從左右兩邊同時往中間走,在某個位置“碰頭”了(左位置已經等於右位置 或者 左位置已經大於右位置,這兩種都算碰頭吧),這時我們就可以停下來這一趟排序了;

15. 但我們需要做一件事情,那就是把支點跟這個停下來的位置做交換,交換後我們得到的數組爲{3,5,4,6,8,9,11,7}。


停下來觀察一下,我們選擇了 6 爲支點,此時左面爲 3, 5, 4; 右面爲 8, 9, 11, 7

很明顯,左面 < 支點 < 右面,這個關係成立了。


我們離目標近了一步!


下面呢,分別對左右兩部分再進行上面的步驟。

先說左面吧:

我們要排序的數組爲: 3,5,4

我們還選擇第一個元素:3 作爲支點。


1. 從右面開始,4 大於 3嗎?是的,繼續;

2, 5 大於 3 嗎?是的,繼續;

3, 3 大於 3 嗎?否,停下來(我們說這種情況的停位置在元素: 3,下面會再提到“停位置”);

4. 從左面開始,5 大於 3 ?。。。 啊哦!左位置(此時指向元素5)已經大於右位置(此時指向元素3)了,這一趟又停了。

5. 所以我們得到了:3,5,4, 支點右面的元素 5, 4 都是大於支點的,而左面沒有元素了,所以我們只需要考慮右面部分的排序就行了;


繼續對右面部分 5, 4 進行排序:

1. 選擇5作爲支點;

2. 從右面開始往左走,4大於5嗎?否,停下來(我們說這個停位置在:4,下面還會提到“停位置”);

3. 從左面往右走,4?噢!又碰頭了,這一趟又結束了;

4. 我們將支點跟停下來的位置進行交換,得到 4, 5


所以整個左面部分就排序完成了:3, 4, 5。


停!

我想你一定不滿意了,你會說:你好像沒有說清楚什麼時候交換什麼時候不交換啊!

是的!

你注意到這一點了,很好!


分兩種情況:

一種情況是:

尚未碰頭的時候,從右往左遍歷時發現了有比支點小的值,而從左往右遍歷時發現了有比支點大的值,而此時還沒有碰頭,是一定要交換的!


另一種情況:

我們在對整個數組做一趟快速排序的時候,我們在最後做了支點和停位置(6 和 3 交換了)元素的交換;

在對左面部分做快速排序時,我們沒有對支點和停位置(3 和 3 沒有交換),呵呵 也不需要交換對吧,因爲他們指向了同一個元素;

在最後一趟快速排序的時候,也做了支點和停位置(5 和 4 交換了)的交換。


嗯?有什麼規律嗎?

其實這個規律不太容易被發現,但稍微悉心就能看到:如果從右位置停下來的時候跟支點重合,那就不做交換,如果停下來的位置還沒到支點位置,那就交換。

再回頭看看,是不是呢?


對右面部分8, 9, 11, 7的排序我就不囉嗦了,想必你一定很清楚了。


我們來說說Java算法實現吧!

public class QuickSort {

  public static void quickSort(int[] sort, int start, int end) {
    // 這個條件很容易理解吧,當只有一個元素的時候 start == end, 
    // 而我們知道 一個元素的數組就是有序的不需要排序
    if (end > start) {
      // 找到支點,一分爲二
      int p = partition(sort, start, end);
      // 快排左半部分
      quickSort(sort, start, p - 1);
      // 快排右半部分
      quickSort(sort, p + 1, end);
    }
  }
  
  /**
   * 找到分割點
   * @return
   */
  public static int partition(int[] sort, int start, int end) {
    int p = start;
    int pValue = sort[p];
    
    int left = start;
    int right = end + 1;
    for (;;) {
      // 從右面往左走,直到某個值小於或等於支點停止
      while (sort [--right] > pValue) {
        if (right <= left) break;
      }
      
      // 從左面往右走,直到某個值大於或等於支點停止
      while (sort[++left] < pValue ) {
        if (left >= right) break;
      }
      
      // 左位置大於等於右位置說明已經碰頭了
      // 如果還沒有碰頭,
      //從右往左遍歷時發現了有比支點小的值,
      // 而從左往右遍歷時發現了有比支點大的值,而此時還沒有碰頭,是一定要交換的!
      if (left >= right) 
        break;
      else 
        // 沒碰頭時的交換
        swap(sort, left, right);
    }
    
    // 如果右面的停位置等於支點位置,則不交換
    if (right == p) {
      return right;
    }
    else {
      // 右面的停位置不等於支點位置,則需要交換
      swap(sort, p, right);
      return right;
    }
  }
  
  // 交換
  private static void swap(int[] sort, int left, int right) {
    int tmp = 0;
    tmp = sort[left];
    sort[left] = sort[right];
    sort[right] = tmp;
  }

  // For test
  public static void main(String[] args) {
    int[] sort = {6,8,4,3,5,9,11,7};
    quickSort(sort, 0, sort.length - 1);
    for (int i : sort) {
      System.out.print(i + ", ");
    }
  }

}


複雜度:

時間複雜度:O(n*logn)
空間複雜度:O(1)


在下一篇中,我們繼續討論下,如果選擇數組的中間元素爲支點,是怎麼樣的情況。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章