線性時間選擇問題
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)