隨機快速排序

轉載於:https://blog.csdn.net/Daniel960601/article/details/63259638

快速排序算法
基本思想: 
1. 在數組a中選擇一個元素,作爲主元,例如選擇a[0],保存在temp變量中 
2. 用兩個指針left和right,分別指向數組的左右兩端 
3. 當a[right] > temp時right左移,直到a[right] <= temp,然後將a[right]放到a[left]的位置 
4. 當a[left] <= temp時left右移,直到a[left] > temp,然後將a[left]放到a[right]的位置 
5. 然後重複3和4,直到left==right,將temp放到left位置 
6. 對left左邊和右邊的元素進行上面步驟

以上就是快速排序,很容易看出有遞歸的思想在,並且已經證明,綜合(代碼的實現、時間空間複雜度)來看,遞歸實現比非遞歸要更優,所以一般選擇用遞歸的方式實現快速排序,下面是遞歸排序的遞歸實現。

//對區間[left,right]以a[0]爲界進行劃分 ,故初始值left=0,right=n-1,下標從0開始 
int partition(int a[], int left, int right){
    int temp = a[left];  //將a[left]存放至臨時變量temp 
    while(left < right){ //只要left和right不相遇 
        while(left < right && a[right] > temp) right--; //反覆左移right 
        a[left] = a[right];  //將a[right]挪到a[left] 
        while(left < right && a[left] <= temp) left++; //反覆右移Left 
        a[right] = a[left]; //將a[left]挪到a[right] 
    }
    a[left] = temp; //將temp放到left與right相遇的地方 
    return left; //返回相遇下標 

//快速排序,初值left=0,right=n-1,下標從0開始 
void quickSort(int a[], int left, int right){
    if(left < right) { //當前區間長度大於1 
        int pos = partition(a,left,right); //將[left,right]按a[left]一分爲二 
        quickSort(a,left,pos-1);    //對左子區間遞歸進行快速排序 
        quickSort(a,pos+1,right);   //將右子區間遞歸進行快速排序 
    }


快速排序的平均時間複雜度是O(nlogn),但是最壞情況的時間複雜度可以到達O(n^2),這是因爲待排序列已經基本有序,選擇a[left]作爲主元無法將序列分成長度相近的兩部分,所以時間複雜度就退化到了O(n^2)。

要解決這個問題的一種辦法就是隨機選擇主元,而不是總是用a[left]作爲主元,這樣雖然算法的最壞時間複雜度還是O(n^2),但對於任意輸入數據的期望時間複雜度都能達到O(nlogn),也就是說,不存在一組特定的數據使得這個算法出現最壞情況。(詳細證明可以看《算法導論》)

下面就看一下如何生成隨機數。

隨機數的生成
在C語言中有可以產生隨機數的函數,需要添加stdlib.h和time.h頭文件。首先在main函數開頭加上“srand((unsigned)time(NULL));”,這個語句將生成隨機數的種子。然後,在需要生成隨機數的地方使用rand()函數。

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

using namespace std;

int main(){

    srand((unsigned)time(NULL));

    for(int i = 0; i < 10; i++){
        printf("%d ",rand());
    }

    return 0;
}


rand()函數只能生成[0,RAND_MAX]範圍內的整數(RAND_MAX是stdlib.h中的一個常數,在不同的系統中會有所不同),因此,如果想要輸出給定範圍[a,b]內的隨機數,需要使用rand()%(b-a+1)+a,其中rand()%(b-a+1)可以輸出[0,b-a]內的隨機數,再+a則可以輸出[a,b]內的隨機數。

但是,以上的方法僅僅對左右端點相差不超過RAND_MAX的區間的隨機數有效,如果需要生成更大的數就不行了。想要生成大範圍的隨機數有很多種方法,這裏記錄一種: 
1. 先用rand()生成一個[0,RAND_MAX]範圍內的隨機數 
2. 然後用這個隨機數除以RAND_MAX,這樣就會得到一個[0,1]範圍內的浮點數 
3. 再用這個浮點數乘以範圍長度(b-a+1),在加上a即可 
即(int)((double)rand()/RAND_MAX*(b-a+a)+a)

下面的例子是生成10個[10000,60000]範圍內的隨機數的程序:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

using namespace std;

int main(){

    srand((unsigned)time(NULL));

    for(int i = 0; i < 10; i++){
        printf("%d ",(int)(1.0*rand()/RAND_MAX*50000+10000));
    }

    return 0;
}


隨機快排
上面討論了快速排序和生成隨機數的寫法,在此基礎上討論隨機快排的寫法。

由於需要在a[left……right]中隨機選擇一個主元,因此不妨生成一個數p,作爲主元下標,即a[p]作爲主元。然後用a[p]和a[left]交換,按照之前partition()函數的寫法即可,代碼如下:

int randPartition(int a[], int left, int right){
    //生成left~right之間的隨機數p
    int p = round(1.0*rand()/RAND_MAX*(right-left)+left);
    swap(a[p],a[left]); //交換a[p]和a[left] 

    int temp = a[left];  //將a[left]存放至臨時變量temp 
    while(left < right){ //只要left和right不相遇 
        while(left < right && a[right] > temp) right--; //反覆左移right 
        a[left] = a[right];  //將a[right]挪到a[left] 
        while(left < right && a[left] <= temp) left++; //反覆右移Left 
        a[right] = a[left]; //將a[left]挪到a[right] 
    }
    a[left] = temp; //將temp放到left與right相遇的地方 
    return left; //返回相遇下標 

--------------------- 
 

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