线性时间选择问题

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)

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