线性时间选择问题

来源:互联网 发布:关于人工智能英语作文 编辑:程序博客网 时间:2024/06/04 17:57

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)