資料結構與演算法 - 快速排序
快速排序
快速排序的核心思想也是分治法,分而治之。它的實現方式是每次從序列中選出一個基準值,其他數依次和基準值做比較,比基準值大的放右邊,比基準值小的放左邊,然後再對左邊和右邊的兩組數分別選出一個基準值,進行同樣的比較移動,重複步驟,直到最後都變成單個元素,整個陣列就成了有序的序列。
我們以[ 8,2,5,0,7,4,6,1 ]
這組數字來進行演示
首先,我們隨機選擇一個基準值:
與其他元素依次比較,大的放右邊,小的放左邊:
然後我們以同樣的方式排左邊的資料:
繼續排 0 和 1 :
由於只剩下一個數,所以就不用排了,現在的陣列序列是下圖這個樣子:
單邊掃描
快速排序的關鍵之處在於切分,切分的同時要進行比較和移動,這裡介紹一種叫做單邊掃描的做法。
我們隨意抽取一個數作為基準值,同時設定一個標記 mark 代表左邊序列最右側的下標位置,當然初始為 0 ,接下來遍歷陣列,如果元素大於基準值,無操作,繼續遍歷,如果元素小於基準值,則把 mark + 1 ,再將 mark 所在位置的元素和遍歷到的元素交換位置,mark 這個位置儲存的是比基準值小的資料,當遍歷結束後,將基準值與 mark 所在元素交換位置即可。
程式碼實現
void sort(vector<int> &arr) { sort(arr, 0, arr.size()-1) } void sort(vector<int> &arr, int start, int end) { if(end <= start) return; //切分 int pivotIndex = partition(arr, start, end); sort(arr, start, pivotIndex-1); sort(arr, pivotIndex+1, end); } int partition(vector<int>& arr, int start, int end) { int pivot = arr[start]; int mark = start; for(int i = start+1; i <= end; i++) { if(arr[i] < pivot) { //小於基準值 則mark+1,並交換位置。 mark++; int p = arr[mark]; arr[mark] = arr[i]; arr[i] = p; } } //基準值與mark對應元素調換位置 arr[start] = arr[mark]; arr[mark] = pivot; return mark; }
雙邊掃描
另外還有一種雙邊掃描的做法,看起來比較直觀:我們隨意抽取一個數作為基準值,然後從陣列左右兩邊進行掃描,先從左往右找到一個大於基準值的元素,將下標指標記錄下來,然後轉到從右往左掃描,找到一個小於基準值的元素,交換這兩個元素的位置,重複步驟,直到左右兩個指標相遇,再將基準值與左側最右邊的元素交換。
我們來看一下實現程式碼,不同之處只有 partition 方法:
int partition(vector<int>& arr, int start, int end) { int left = start; int right = end; int pivot = arr[start]; // 第一個元素作為基準值 while(true) { //從左往右掃描 while(arr[left] <= pivot) { left++; if(left == right) break; } //從右往左掃描 while(pivot < arr[right]) { right--; if(left == right) break; } // 左右指標相遇 if(left >= right) { break; } int temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; } int temp = arr[start]; arr[start] = arr[right]; arr[right] = temp; return right; }
極端情況
快速排序的時間複雜度和歸併排序一樣,\(O(n \log n)\),但這是建立在每次切分都能把陣列一刀切兩半差不多大的前提下,如果出現極端情況,比如排一個有序的序列,如[ 9,8,7,6,5,4,3,2,1 ]
,選取基準值 9 ,那麼需要切分 n - 1 次才能完成整個快速排序的過程,這種情況下,時間複雜度就退化成了 \(O(n^2)\),當然極端情況出現的概率也是比較低的。
所以說,快速排序的時間複雜度是 \(O(n \log n)\),極端情況下會退化成 \(O(n^2)\),為了避免極端情況的發生,選取基準值應該做到隨機選取,或者是打亂一下陣列再選取。
另外,快速排序的空間複雜度為 \(O(1)\)。