演算法介紹(2) 快速排序演算法
阿新 • • 發佈:2019-01-01
本篇介紹快速排序演算法,以及相應的時間空間複雜度求解。
首先介紹一下氣泡排序的時間複雜度: 對於n位的數列則有比較次數為 (n-1) + (n-2) + ... + 1 = n * (n - 1) / 2,這就得到了最大的比較次數而O(N^2)表示的是複雜度的數量級。舉個例子來說,如果n = 10000,那麼 n(n-1)/2 = (n^2 - n) / 2 = (100000000 - 10000) / 2,相對10^8來說,10000小的可以忽略不計了,所以總計算次數約為0.5 * N^2。用O(N^2)就表示了其數量級(忽略前面係數0.5)。在沒有改進的情況下,氣泡排序的時間複雜度都是相同的,為O(N^2)。以下改進的方法可以使最佳情況時為O(n)。
快速排序的時間複雜度為:
T(n) <= cn + 2T(n/2) c是一個常數
<= cn + 2(cn/2+2T(n/4)) = 2cn+ 4T(n/4)
<= 2cn + 4(cn/4+ 2T(n/8)) = 3cn + 8T(n/8)
…… ……
<= cnlogn + nT(1) = O(nlogn) 其中cn 是一次劃分所用的時間,c是一個常數
最壞的情況,每次劃分都得到一個子序列,時間複雜度為:
T(n) = cn + T(n-1)
= cn + c(n-1) + T(n - 2) = 2cn -c + T(n-2)
= 2cn -c + c(n - 2) + T(n-3) = 3cn -3c + T(n-3)
……
= c[n(n+1)/2-1] + T(1) = O(n2)
快速排序的時間複雜度在平均情況下介於最佳與最差情況之間,假設每一次分割時,基準值處於最終排序好的位置的概率是一樣的,基準值將陣列分成長度為0 和 n-1,1 和 n-2,……的概率都是 1/n。在這種假設下,快速排序的平均時間複雜性為:
T(n) = cn + 1/n(T(k)+ T(n-k-1)) T(0) = c, T(1) = c
這是一個遞推公式,T(k)和T(n-k-1)是指處理長度為 k 和 n-k-1 陣列是快速排序演算法所花費的時間, 根據公式所推算出來的時間為 O(nlogn)。因此快速排序的平均時間複雜性為O(nlogn)。
空間複雜度為:
快速排序需要棧空間來實現遞迴,如果陣列按局等方式被分割時,則最大的遞迴深度為 log n,需要的棧空間為 O(log n)。最壞的情況下在遞迴的每一級上,陣列分割成長度為0的左子陣列和長度為 n - 1 的右陣列。這種情況下,遞迴的深度就成為 n,需要的棧空間為 O(n)。
以下貼出快速排序的程式碼:
首先介紹一下氣泡排序的時間複雜度: 對於n位的數列則有比較次數為 (n-1) + (n-2) + ... + 1 = n * (n - 1) / 2,這就得到了最大的比較次數而O(N^2)表示的是複雜度的數量級。舉個例子來說,如果n = 10000,那麼 n(n-1)/2 = (n^2 - n) / 2 = (100000000 - 10000) / 2,相對10^8來說,10000小的可以忽略不計了,所以總計算次數約為0.5 * N^2。用O(N^2)就表示了其數量級(忽略前面係數0.5)。在沒有改進的情況下,氣泡排序的時間複雜度都是相同的,為O(N^2)。以下改進的方法可以使最佳情況時為O(n)。
快速排序的基本思想如下:首先在要排序的序列 a 中選取一箇中軸值,而後將序列分成兩個部分,其中左邊的部分 b 中的元素均小於或者等於 中軸值,右邊的部分 c 的元素 均大於或者等於中軸值,而後通過遞迴呼叫快速排序的過程分別對兩個部分進行排序,最後將兩部分產生的結果合併即可得到最後的排序序列。 為了實現一次劃分,我們可以從陣列(假定資料是存在陣列中)的兩端移動下標,必要時交換記錄,直到陣列兩端的下標相遇為止。為此,我們附設兩個指標(下角標)i 和 j, 通過 j 從當前序列的有段向左掃描,越過不小於基準值的記錄。當遇到小於基準值的記錄時,掃描停止。通過 i 從當前序列的左端向右掃描,越過小於基準值的記錄。當遇到不小於基準值的記錄時,掃描停止。交換兩個方向掃描停止的記錄 a[j] 與 a[i]。 然後,繼續掃描,直至 i 與 j 相遇為止。掃描和交換的過程結束。這是 i 左邊的記錄的關鍵字值都小於基準值,右邊的記錄的關鍵字值都不小於基準值。public void bubbleSort(int arr[]) { boolean didSwap; for(int i = 0, len = arr.length; i < len - 1; i++) { didSwap = false; for(int j = 0; j < len - i - 1; j++) { if(arr[j + 1] < arr[j]) { swap(arr, j, j + 1); didSwap = true; } } if(didSwap == false) return; } }
快速排序的時間複雜度為:
T(n) <= cn + 2T(n/2) c是一個常數
<= cn + 2(cn/2+2T(n/4)) = 2cn+ 4T(n/4)
<= 2cn + 4(cn/4+ 2T(n/8)) = 3cn + 8T(n/8)
…… ……
<= cnlogn + nT(1) = O(nlogn) 其中cn 是一次劃分所用的時間,c是一個常數
最壞的情況,每次劃分都得到一個子序列,時間複雜度為:
T(n) = cn + T(n-1)
= cn + c(n-1) + T(n - 2) = 2cn -c + T(n-2)
= 2cn -c + c(n - 2) + T(n-3) = 3cn -3c + T(n-3)
……
= c[n(n+1)/2-1] + T(1) = O(n2)
快速排序的時間複雜度在平均情況下介於最佳與最差情況之間,假設每一次分割時,基準值處於最終排序好的位置的概率是一樣的,基準值將陣列分成長度為0 和 n-1,1 和 n-2,……的概率都是 1/n。在這種假設下,快速排序的平均時間複雜性為:
T(n) = cn + 1/n(T(k)+ T(n-k-1)) T(0) = c, T(1) = c
這是一個遞推公式,T(k)和T(n-k-1)是指處理長度為 k 和 n-k-1 陣列是快速排序演算法所花費的時間, 根據公式所推算出來的時間為 O(nlogn)。因此快速排序的平均時間複雜性為O(nlogn)。
空間複雜度為:
快速排序需要棧空間來實現遞迴,如果陣列按局等方式被分割時,則最大的遞迴深度為 log n,需要的棧空間為 O(log n)。最壞的情況下在遞迴的每一級上,陣列分割成長度為0的左子陣列和長度為 n - 1 的右陣列。這種情況下,遞迴的深度就成為 n,需要的棧空間為 O(n)。
以下貼出快速排序的程式碼:
#include <iostream> using namespace std; int a[101], n; void quickSort(int left, int right) { int i, j, t, temp; if (left > right) return; temp = a[left]; i = left; j = right; while (i != j) { while (a[j] >= temp && i < j) j--; while (a[i] <= temp && i < j) i++; if (i < j) { t = a[i]; a[i] = a[j]; a[j] = t; } } a[left] = a[i]; a[i] = temp; quickSort(left, i - 1); quickSort(i + 1, right); } int main() { int i; cin >> n; for (i = 0; i < n; i++) { cin >> a[i]; } quickSort(0, n - 1); for (i = 0; i < n; i++) { cout << a[i] << " "; } system("pause"); return 0; }