快速排序 Quick Sort


快速排序 Quick Sort

  快速排序的基本思想是,通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。

  一趟快速排序(或一次划分)的過程如下:首先任意選取一個記錄(通常可選第一個記錄)作為樞軸(或支點)(pivot),然后按下列原則重新排列其余記錄:將所有關鍵字比它小的記錄都安置在它的位置之前,將所有關鍵字比它大的記錄都安置在它的位置之后。

  經過一趟快速排序之后,以該樞軸記錄最后所落的位置i作分界線,將序列分割成兩個子序列,之后再分別對分割所得的兩個子序列進行快速排序。

  可以看出這個算法可以遞歸實現,可以用一個函數來實現划分,並返回分界位置。然后不斷地這么分下去直到排序完成,可以看出函數的輸入參數需要提供序列的首尾位置。

快速排序的實現

划分實現1 (樞軸跳來跳去法)

  一趟快速排序的實現:設兩個指針low和high,設樞軸記錄的關鍵字為pivotkey,則首先從high所指位置起向前搜索找到第一個關鍵字小於pivotkey的記錄和樞軸記錄互相交換,然后從low所指位置起向后搜索,找到第一個關鍵字大於pivotkey的記錄和樞軸記錄互相交換,重復這兩步直至low==high為止。

  下面的代碼例子元素類型為int,並且關鍵字就是其本身。

Partition 實現1
typedef int ElemType;

int Patition(ElemType A[], int low, int high)
{
        
    ElemType pivotkey=A[low];
    ElemType temp;


    while(low<high)
    {

        while(low <high && A[high]>=pivotkey)
        {
            --high;
        }
        temp=A[high];
        A[high]=A[low];
        A[low]=temp;
        while(low<high && A[low]<=pivotkey)
        {
            ++low;
        }
        temp=A[high];
        A[high]=A[low];
        A[low]=temp;
    }
    return low;

}

 

划分實現2 (樞軸一次到位法)

  從上面的實現可以看出,樞軸元素(即最開始選的“中間”元素(其實往往是拿第一個元素作為“中間”元素))在上面的實現方法中需要不斷地和其他元素交換位置,而每交換一次位置實際上需要三次賦值操作。

  實際上,只有最后low=high的位置才是樞軸元素的最終位置,所以可以先將樞軸元素保存起來,排序過程中只作元素的單向移動,直至一趟排序結束后再將樞軸元素移至正確的位置上。

  代碼如下:

Partition 實現方法2
int Patition(ElemType A[], int low, int high)
{

    ElemType pivotkey=A[low];
    ElemType temp = A[low];


    while(low<high)
    {

        while(low <high && A[high]>=pivotkey)
        {
            --high;
        }
        A[low]=A[high];
        while(low<high && A[low]<=pivotkey)
        {
            ++low;
        }
        A[high]=A[low];
    }
    A[low] = temp;
    return low;

}

 

  可以看到減少了每次交換元素都要進行的三個賦值操作,變成了一個賦值操作。

  細節就是每次覆蓋掉的元素都已經在上次保存過了,所以不必擔心,而第一次覆蓋掉的元素就是樞軸元素,最后覆蓋在了它應該處於的位置。

 

遞歸形式的快速排序算法

Quick Sort
void QuickSort(ElemType A[], int low, int high)
{
    if(low<high)
    {
        int pivotloc=Patition(A,low, high);
        QuickSort(A, low, pivotloc-1);
        QuickSort(A, pivotloc+1, high);
    }
}

  不管划分是上面哪一種實現,都可以用這個遞歸形式進行快速排序。

  需要注意的是這個if語句不能少,不然沒法停止,會導致堆棧溢出的異常。

 

快速排序的性能分析

時間復雜度

  快速排序的平均時間為Tavg(n)=knln(n),其中n為待排序列中記錄的個數,k為某個常數,在所有同數量級的先進的排序算法中,快速排序的常數因子k最小。

  因此,就平均性能而言,快速排序是目前被認為是最好的一種內部排序方法。通常認為快速排序在平均情況下的時間復雜度為O(nlogn)。

  但是,快速排序也不是完美的。

  若初始記錄序列按關鍵字有序或基本有序,快速排序將蛻化為冒泡排序,其時間復雜度為O(n2)。

  原因:因為每次的樞軸都選擇第一個元素,在有序的情況下,性能就蛻化了。

  如下圖:

 

 

                       

快速排序的空間利用情況

  從空間上看,快速排序需要一個棧空間來實現遞歸。

  若每一趟排序都將記錄序列分割成長度相接近的兩個子序列,則棧的最大深度為log2n+1(包括最外層參量進棧);但是,若每趟排序之后,樞軸位置均偏向子序列的一端,則為最壞情況,棧的最大深度為n。

  如果在一趟划分之后比較分割所得兩部分的長度,且先對長度短的子序列中的記錄進行快速排序,則棧的最大深度可降為O(logn)。

性能改善

  為改進快速排序算法,隨機選取界點或最左、最右、中間三個元素中的值處於中間的作為界點,通常可以避免原始序列有序的最壞情況。

  然而,即使如此,也不能使快速排序在待排記錄序列已按關鍵字有序的情況下達到O(n)的時間復雜度(冒泡排序可以達到)。

  為此,可以如下修改划分算法:在指針high減去1和low增加1的同時進行“起泡”操作,即在相鄰兩個記錄處於“逆序”時進行互換,同時在算法中附設兩個布爾型變量分別指示指針low和high在從兩端向中間移動的過程中是否進行過交換記錄的操作,若沒有,則不需要對低端或高端子表進行排序,這將進一步改善快速排序的平均性能。

  另外,將遞歸算法改為非遞歸算法,也將加快速度,因為避免了進出棧和恢復斷點等工作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM