快速排序的改進

快速排序的改進

快速排序最壞情況下,要比較O(n^2)次,但平均性能爲nlogn,基本達到了比較類排序所需時間的的下界。核心代碼爲:

void qSort(int *data, int begin, int end)

{

    int pivot, i, j;

    if(begin>=end)

        return;

    

    i = begin;

    j = end;

    pivot = data[i];

    while(i<j)

    {

        while(i<j && data[j]>=pivot)

            j--;

        data[i] = data[j];

        while(i<j && data[i]<=pivot)

            i++;

        data[j] = data[i];

    }

    data[i] = pivot;

    

    qSort(data, begin, i-1);

    qSort(data, i+1, end);

}

一種改進:對於一個已排好序的序列來說,插入排序應該是最快了,時間複雜度爲O(n)。插入排序代碼爲:

void iSort(int *data, int begin, int end)

{

    int i,j,tmp;

    for(i=begin+1;i<=end;i++)

    {

        j = i-1;

        tmp = data[i];

        while(j>=begin && data[j]>tmp)

        {

            data[j+1] = data[j];

            j--;

        }

        data[j+1] = tmp;        

    }

    return;

}

 

由於快速排序算法在處理小規模數據集時表現的不是很好(可能是因爲存在遞歸調用),因此在數據規模小到一定程度時,改用插入排序。另一方面考慮,對於大規模數據來說,前期採用的快速排序已經將一些元素放到了正確的位置上,因此當規模降低到一定程度時,可以認爲一些元素已經基本有序了。對基本有序的序列進行排序,插入排序是最好的選擇,不光比較次數少,還免去了遞歸調用的過程。因此,第一種改進方法爲:

當數據規模小於一定程度時,改用插入排序。具體小到何種規模時,採用插入排序,這個理論上還不解,一些文章中說是5到25之間。SGI STL中的快速排序採用的值是10.

 

第二個有改進空間的地方是:中軸元素的選取。如果簡單的只是選擇第一個或最後一個作爲中軸元素,這樣當待排序序列基本有序的時候,它就退化爲O(n^2)的時間複雜度。因此,第二中改進方法爲:

可以取最左邊、最右邊、中間這三個位置的元素中的中間值作爲中軸元素,來避免這種情況。

 

對於一個每個元素都完全相同的一個序列來講,快速排序也會退化到O(n^2)。要將這種情況避免到,可以這樣做:

在分區的時候,將序列分爲3堆,一堆小於中軸元素,一堆等於中軸元素,一堆大於中軸元素,下次遞歸調用快速排序的時候,只需對小於和大於中軸元素的兩堆數據進行排序,中間等於中軸元素的一堆已經放好。

 

總結一下,主要有3點有改進空間:

1 問題降到一定規模時,改用插入排序

2 中軸元素的選取

3 分成3堆,一方面避免相同元素這種情況,另一方面也可以降低子問題的規模。這個感覺有點想DFS中尋找剪枝條件來降低搜索規模一樣。


從以上3點出來,完成新的快速排序,代碼如下:

#include<stdio.h>

#include<stdlib.h>

 

#define M 10

 

void exchange(int *data, int p1, int p2)

{

    int tmp;

    tmp = data[p1];

    data[p1] = data[p2];

    data[p2] = tmp;

}

 

void compexch(int *data, int p1, int p2)

{

    if(data[p1]>data[p2])

        exchange(data, p1, p2);

}

 

void iSort(int *data, int begin, int end)

{

    int i,j,tmp;

   

    for(i=begin+1;i<=end;i++)

    {

        j = i-1;

        tmp = data[i];

        while(j>=begin && data[j]>tmp)

        {

            data[j+1] = data[j];

            j--;

        }

        data[j+1] = tmp;        

    }

    return;

}

 

//完成一次分區 

int partition(int *data, int begin, int end)

{

    int i,j,pivot; 

    i = begin;

    j = end;

    pivot = data[i];

    while(i<j)

    {

        while(i<j && data[j]>=pivot)

            j--;

        data[i] = data[j];

        while(i<j && data[i]<=pivot)

            i++;

        data[j] = data[i];

    }

    data[i] = pivot;

    return i;

}

void qSort(int *data, int begin, int end)

{

    int pi, povit; 

    int i, left, right;

    if(begin >= end)

        return;

    if(end-begin <= M) 

        iSort(data, begin, end);

    else

    {

        //1 選擇中軸元素,將其設置到 begin+1 的位置上 

        exchange(data, begin+1, (begin+end)/2);

        compexch(data, begin, begin+1);

        compexch(data, begin, end);

        compexch(data, begin+1, end);

        //2 分區。此時 begin 和 end 兩個位置已經不影響本次分區了

        pi = partition(data, begin+1, end-1);

        povit = data[pi];

        //3 構建等於中軸元素的中間堆

        left = pi;

        right = pi;

        for(i=begin;i<left;i++)

            if(data[i] == povit)

                exchange(data, i, --left);

        for(i=end;i>right;i--)

            if(data[i] == povit)

                exchange(data, i, ++right);

        //4 遞歸調用快速排序

        qSort(data, begin, left-1);

        qSort(data, right+1, end);        

    }

}

 

 

int main()

{

    int data[20];

    int n;

    int i,j;

    scanf("%d",&n);

    for(i=0;i<n;i++)

        scanf("%d",&data[i]);

    qSort(data, 0, n-1);

    for(i=0;i<n;i++)

        printf("%d ",data[i]);

    printf("\n");

    system("pause");

    return 0;

}


學習,永無止境!知道、會用一個算法,遠遠不夠,掌握它,改進它,and 不斷的改進它!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章