先來簡單看一遍一些基本排序的情況:
- 穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
- 不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。
1. 快速排序
對於包含n個數的輸入數組來說,快速排序是一種最壞情況時間複雜度爲的排序算法。雖然最壞情況時間複雜度很高,但是快速排序通常是實際排序應用中最好的選擇,因爲它的平均性能很好;它的期望時間複雜度是,而且中隱含的常數因子非常小。另外,它還能夠進行原址排序。
與歸併排序一樣,快速排序使用了分治思想。下面是對一個典型的子數組A[p…r]進行快速排序的三步分治過程;
分治:數組A[p…r]被劃分爲兩個(可能爲空)子數組A[p…q-1]和A[q+1…r],使得A[p…q-1]中的每一個元素都小於等於A[q],而A[q]也小於等於A[q+1…r]中的每個元素。其中,計算下標q也是劃分過程的一部分。
解決:通過遞歸調用快速排序,對子數組 A[p…q-1]和A[q+1…r]進行排序。
合併:因爲子數組都是原址排序的,所以不需要合併操作:數組A[p…r]已經有序。
下面的程序實現快速排序:
QUICKSORT(A,p,r)
if p < r
q = PARTITION(A, p, r)
QUICKSORT(A, p, q-1)
QUICKSORT(A, q+1, r)
爲了排序一個數組A的全部元素,初始調用是QUICKSORT(A, 1, A.length)
數組的劃分
算法的關鍵部分是PARTITION過程,它實現了對子數組A[p…r]的原址重排。
PARTITION(A, p, r)
x = A[r]
i = p-1
for j = p to r-1
if A[j] <= x
i = i+1
exchange a[i] with A[j]
exchange A[i+1] with A[r]
return i+1
1.1 快速排序的性能
快速排序的運行時間依賴於劃分是否平衡,而平衡與否又依賴於用於劃分的元素。如果劃分是平衡的,那麼快速排序算法性能與歸併排序一樣。如果劃分是不平衡的,那麼快速排序的性能接近於插入排序。
最壞情況劃分-時間複雜度
當劃分產生的兩個子問題分別包含n-1個元素和0個元素時,快速排序的最壞情況發生了。不妨假設算法的每一次遞歸調用中都出現了這種不平衡劃分。劃分操作的時間複雜度是。由於對一個大小爲0的數值進行遞歸調用會直接返回,因此T(0)=,於是算法運行時間的遞歸式可以表示爲:通過這個遞歸公式可以知道最壞情況下的時間複雜度爲。
因此,如果在算法的每一層遞歸上,劃分都是最大程度不平衡的,那麼算法的時間複雜度就是。也就是說,在最壞情況下,快速排序算法的運行時間並不比插入排序更好。此外,當輸入數組已經完全有序時,快速排序的時間複雜度仍然爲。而在同樣情況下,插入排序的時間複雜度爲。
最好情況劃分-時間複雜度
在可能的最平衡的劃分中,PARTITION得到的兩個子問題的規模都不大於n/2,這是因爲其中一個子問題的規模爲,而另一個子問題的規模爲。在這種情況下,快速排序的性能非常好。此時,算法運行時間的遞歸式爲:在上式中,我們忽略了一些餘項以及減1操作的影響。上述遞歸式的解爲。快速排序的平均運行時間更接近於其最好情況,而非最壞情況。
空間複雜度
快速排序的空間複雜度爲O(lgn),因爲快速排序是在原址上進行排序的,所以主要是遞歸是產生的空間消耗。n個元素進行二分需要lgn次才能劃分完畢,因此需要存儲迭代產生的消耗是2lgn,因此空間複雜度爲O(lgn)。
1.2 快速排序的隨機化版本
在快速排序中採用一種稱爲隨機抽樣的隨機化技術,與始終採用A[r]作爲主元的方法不同,隨機抽樣是從子數組A[p…r]中隨機選擇一個元素作爲主元。爲達到這一目的,首先將A[r]與從A[p…r]中隨機選出的一個元素交換。通過對序列p,…,r的隨機抽樣,我們可以保證主元元素x=A[r]是等概率地從子數組的r-p+1個元素中選取的。
對PARTITION和QUICKSORT的代碼的改動非常小。在新的劃分程序中,我們只是在真正進行劃分前進行一次交換:
RANDOMIZED-PARTITION(A, p, r)
i = RANDOM(p, r)
exchange A[r] with A[i]
return PARTITION(A, p, r)
新的快速排序不再調用PARTITION,而是調用RANDOMIZED-PARTITION:
RANDOMIZED-QUICKSORT(A, p, r)
if p < r
q = RANDOMIZED-PARTITION(A, p, r)
RANDOMIZED-QUICKSORT(A, p, q-1)
RANDOMIZED-QUICKSORT(A, q+1, r)
1.3 快速排序算法實現
- python版本
def quick_sort(collection):
length = len(collection)
if length <= 1:
return collection
else:
pivot = collection.pop() # 將collection中的最後一個數字彈出
greater, lesser = [], []
for element in collection: # 進行二分
if element > pivot:
greater.append(element)
else:
lesser.append(element)
return quick_sort(lesser) + [pivot] + quick_sort(greater)
if __name__ == "__main__":
user_input = input("Enter numbers separated by a comma:\n").strip() # strip()的作用是移除字符串頭尾指定的字符
unsorted = [int(item) for item in user_input.split(",")]
print(quick_sort(unsorted))
- python版本
def quick_sort_3partition(sorting, left, right):
if right <= left:
return
a = i = left
b = right
pivot = sorting[left]
while i <= b:
if sorting[i] < pivot:
sorting[a], sorting[i] = sorting[i], sorting[a]
a += 1
i += 1
elif sorting[i] > pivot:
sorting[b], sorting[i] = sorting[i], sorting[b]
b -= 1
else:
i += 1
quick_sort_3partition(sorting, left, a - 1)
quick_sort_3partition(sorting, b + 1, right)
if __name__ == "__main__":
user_input = input("Enter numbers separated by a comma:\n").strip()
unsorted = [int(item) for item in user_input.split(",")]
quick_sort_3partition(unsorted, 0, len(unsorted) - 1)
print(unsorted)
- C++版本
#include <iostream>
#include<vector>
using namespace std;
int PARTITION(vector<int>& arr,int begin, int end)
{
int temp = arr[end];
int i = begin - 1;
for(int j=begin;j<end;++j)
{
if(arr[j] <= temp)
{
i++;
swap(arr[i],arr[j]);
}
}
swap(arr[i+1],arr[end]);
return i+1;
}
void QUICK_SORT(vector<int>& arr, int begin, int end )
{
if(begin < end) {
int temp = PARTITION(arr, begin, end);
QUICK_SORT(arr, begin, temp - 1);
QUICK_SORT(arr, temp + 1, end);
}
}
int main() {
vector<int> arr;
int num;
int n;
while(cin>>num ) //輸入eof結束
{
arr.push_back(num);
n++;
}
QUICK_SORT(arr,0,n-1);
for (int i = 0; i < arr.size(); i++) {
cout << arr[i] << ",";
}
return 0;
}