線性時間選擇問題

1、問題描述

給定的線性集中n個元素和一個正整數k(1≤k≤n),要求在線性時間內(即時間複雜度爲O(n))找出這n個元素中第k小的元素。

2、算法設計思想

將n個元素劃分成n/5組,每組5個元素,只可能有一組不是5個元素。再用冒泡排序法,將每組內的五個元素排好序,取出其中位數,共n/5個。
然後遞歸調用Select方法找出這n/5個數中的中位數。若n/5是偶數,就找其最大的數。以這個元素作爲劃分標準。判斷k與n的位置,再進行下一步的劃分。

3、算法過程描述

以[8,31,60,33,17,4,51,57,49,35,11,43,37,3,13,52,6,19,25,32,54,
16,5,41,7,23,22,46,29]爲例查找這29個元素中的第18個元素:
(1)把這29個元素分成6組:
(8,31,60,33,17),(4,51,57,49,35),(11,43,37,3,13),(52,6,19,25,32),(54,16,5,41,7),(23,22,46,29)
(2)取出每一組的中位數,爲31,49,13,25,16
(3)遞歸出的中值爲25
(4)根據25將29個元素劃分爲3個子數組:
P = {8,4,11,17,3,13,6,19,16,5,7,23,22}
Q = {25}
L = {31,60,33,51,57,49,35,43,37,52,32,54,41,46,29}
(5)因爲|p| = 13,|Q| = 1,18>13 +1,所以第18個元素在L區域,找第18-13-1=4個元素,對L遞歸;
(6)將L劃分成3組:
{31,60,33,51,57}{49,35,43,37,52}{32,54,41,46,29}
(7)取出每一組的中位數,爲51,43,41遞歸出的中值爲43
(8)根據43將L組劃分爲3個子數組:
{31,33,35,37,32,41,29}
{43}
{60,51,57,49,52,54,41,46}
(9)因爲第一個子數組中的元素的個數大於4,所以第18個元素在第一個子數組中,對第一個子數組遞歸;
(10)將第一個子數組分成了1組:
{31,33,35,37,32}
(11)取出中位數爲33;
(12)根據33將第一個子數組分成3個子數組:
{31,32,29}
{33}
{35,3,41}
(13)因爲第一個,第二個子數組的元素的個數之和爲4,所以33即爲所求的第18個元素。

4、算法實現及運行結果

(一)代碼實現:

#include <stdio.h>
#define SIZE (29) 

//主函數 
int main (void);  
//遞歸分組 
int Select(int array[],int left,int right,int ith); 
//尋找中位數 
int Findmiddata(int array[],int left,int right);
//排序取中位數下標 
int InsertSort(int array[],int a,int b);
//分組,以中位數爲界,將比中位數小的放在左邊,比中位數大的放在右邊
int Partition(int array[],int left,int right,int mid);
//交換 
void swap (int array[],int a,int b);

int main(void){
    //數組 
    int array[SIZE] = {8,31,60,33,17,4,51,57,49,35,11,43,37,3,13,52,6,19,25,32,54,16,5,41,7,23,22,46,29} ; 
    int size = SIZE ;   
    int ith = 18 ; 
for(int i = 0;i < size;i++){
        printf("%d ",array[i]); 
    }
    printf("\n");
    printf("該數組的第%d位的元素是:%d\n",ith,Select(array,0,size - 1,ith)); 
}

//遞歸分組 
int Select(int array[],int left,int right,int ith){
    int findMiddateMid = Findmiddata(array,left,right);

    int PartitionMid = Partition(array,left,right,findMiddateMid);
    if(PartitionMid == ith - 1){
        return array[PartitionMid];
    }
    if(PartitionMid < ith - 1){
        Select(array,PartitionMid + 1,right,ith);
    }else{
        Select(array,left,PartitionMid - 1,ith);
    }
}

//尋找中位數 
int Findmiddata(int array[],int left,int right){
    int i,mid;
    for(i = 0;i <= (right - left)/5;i++){
        if((left + i * 5 + 4) < right){
            mid = InsertSort(array,left + i * 5, left + i * 5 + 4);
        }
        swap(array,i,mid);
    }
    mid = InsertSort(array,0,(right - left) / 5);
    return mid;
}

//排序取中位數下標 
int InsertSort(int array[],int a,int b){
    int i,j;
    //冒泡排序 
    for(i = a; i < b - 1; i++){
        for(j = i + 1; j < b;j++){
            if(array[i] > array[j]){
                swap(array,i,j);
            }
        }
    }
    //取中位數的下標 
    if((b - a + 1) % 2 == 0){
        return (b + a) / 2 + 1; 
    }else{
        return (b + a) / 2;
    }
}

//分組,以中位數爲界,將比中位數小的放在左邊,比中位數大的放在右邊 
int Partition(int array[],int left,int right,int mid){
    int value = array[mid];
    int i = left;
    int j = right;
    int size = SIZE ; 
    while(1){
        while (array[i] < value){
            i++;
        }
        while (array[j] > value){
            j--;
        }
        if (i < j)  
            swap(array,i,j) ;  
        else  
            break ;  
    }
    for(int k = 0; k < size;k++){
        if(array[k] == value){
            mid = k;
        } 
    }

    return mid;
}

//交換 
void swap (int array[],int a,int b){  
    int temp ;  
    temp = array[a];  
    array[a] = array[b] ;  
    array[b] = temp ;  
}  

(二)運行結果:
這裏寫圖片描述
**

5、算法複雜度分析

**
(1)時間複雜度
上述算法中,設n = r - p + 1,即n爲輸入數組的長度,算法的遞歸調用只有在n>=75時執行。因此,當n<75時,算法Select所用的計算時間不超過一個常數,找到中位數的中位數x後,算法Select以x爲劃分基準調用函數Partition對數組進行劃分,這需要O(n)時間。算法Select的循環共執行n/5次,每一次要O(1)時間,因此,共需O(n)時間。
設對n個元素的數組調用Select需要T(n)時間,那麼找中位數的中位數x至少要T(n/5)時間。先以證明,按照算法所選的基準x進行劃分所得的兩個子數組分別至多有3n/4個元素,所以無論對哪個子數組調用Select都至多用T
(3n/4)時間。
所以算法的時間複雜度爲
T(n) ≤ 這裏寫圖片描述
由此可得T(n)=O( n ).
(2)空間複雜度
算法在歸併過程中,共需要n個輔助存儲空間來臨時保存合併的結果。所以空間複雜度S(n)= O(n)

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