數據結構——快速排序

先來簡單看一遍一些基本排序的情況:

在這裏插入圖片描述

  • 穩定:如果a原本在b前面,而a=b,排序之後a仍然在b的前面。
  • 不穩定:如果a原本在b的前面,而a=b,排序之後 a 可能會出現在 b 的後面。

1. 快速排序

對於包含n個數的輸入數組來說,快速排序是一種最壞情況時間複雜度爲O(n2)O(n^2)的排序算法。雖然最壞情況時間複雜度很高,但是快速排序通常是實際排序應用中最好的選擇,因爲它的平均性能很好;它的期望時間複雜度是O(nlgn)O(nlgn),而且O(nlgn)O(nlgn)中隱含的常數因子非常小。另外,它還能夠進行原址排序。

與歸併排序一樣,快速排序使用了分治思想。下面是對一個典型的子數組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個元素時,快速排序的最壞情況發生了。不妨假設算法的每一次遞歸調用中都出現了這種不平衡劃分。劃分操作的時間複雜度是θ(n)\theta(n)。由於對一個大小爲0的數值進行遞歸調用會直接返回,因此T(0)=θ(1)\theta(1),於是算法運行時間的遞歸式可以表示爲:T(n)=T(n1)+T(0)+θ(n)=T(n1)+θ(n)T(n)=T(n-1)+T(0)+\theta(n)=T(n-1)+\theta(n)通過這個遞歸公式可以知道最壞情況下的時間複雜度爲θ(n2)\theta(n^2)

因此,如果在算法的每一層遞歸上,劃分都是最大程度不平衡的,那麼算法的時間複雜度就是θ(n2)\theta(n^2)。也就是說,在最壞情況下,快速排序算法的運行時間並不比插入排序更好。此外,當輸入數組已經完全有序時,快速排序的時間複雜度仍然爲θ(n2)\theta(n^2)。而在同樣情況下,插入排序的時間複雜度爲O(n)O(n)

最好情況劃分-時間複雜度
在可能的最平衡的劃分中,PARTITION得到的兩個子問題的規模都不大於n/2,這是因爲其中一個子問題的規模爲n/2\left \lfloor n/2 \right \rfloor,而另一個子問題的規模爲n/21\left \lceil n/2 \right \rceil-1。在這種情況下,快速排序的性能非常好。此時,算法運行時間的遞歸式爲:T(n)=2T(n/2)+θ(n)T(n) = 2T(n/2) + \theta(n)在上式中,我們忽略了一些餘項以及減1操作的影響。上述遞歸式的解爲T(n)=θ(nlgn)T(n)=\theta(nlgn)。快速排序的平均運行時間更接近於其最好情況,而非最壞情況。

空間複雜度
快速排序的空間複雜度爲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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章