1.问题描述
输入一个规模为n的数组,求数组中的第k小元素,其中1<=k<=n。
2.BFPTR算法描述
之所以介绍BFPTR算法,是因为这个算法求第k小元素的时间复杂度为O(n),时间复杂度分析见文末。
(1)文字描述
第一步:对数组中元素连续5个为一组进行分组,每一组都进行排序。
第二步:选取每组中的中间元素进入数组MID。
第三步:利用BFPTR算法求出MID数组中的中间元素mid。
第四步:遍历整个原始数组,比mid小的数放入数组small,比mid大的数放入数组big。
第五步:如果k=| small |+1,则返回mid;如果k<| small |+1,则递归处理small数组,求第k小元素;否则,递归处理big元素,求第k-1-| small |元素。
(2)伪码描述
BFPTR(a[],k)
if size of a[] is less than 5,sort a[] using any sorting algorithm;
Divide a[] into M=n/2 groups,each of which has size 5;
Find the median of each group and denote the set of medians by M;
mid=BFPTR(M,(|M|+1)/2);
Compare the elements of a[] with x;
Let small be the set of the elements less than x;
Let big be the set of the elements larger than x;
if k=|small|+1 than return mid;
else if k<|small|+1 than BFPTR(small,k);
else return BFPTR(big,k-1-|small|;
3.代码实现
template<class T>
int split(vector<T>& a,int start,int end)
{
T temp=a[start];
int low=start,high=end;
for(;;)
{
while(low<high&&a[high]>=temp)
high--;
if(low==high)
break;
a[low++]=a[high];
while(low<high&&a[low]<=temp)
low++;
if(low==high)
break;
a[high--]=a[low];
}
a[high]=temp;
return high;
}
template<class T>
void QuickSort(vector<T>& a,int start,int end)
{
int mid;
if(start>=end)
return ;
mid=split(a,start,end);
QuickSort(a,start,mid-1);
QuickSort(a,mid+1,end);
}
template<class T>
T BFPTR(vector<int>& a,int k,int n)
{
if(n<=5)
{
QuickSort(a,0,n-1);
return a[k-1];
}
vector<T> MID;//存放各小组中的中位数
vector<T>small;//存放比中位数小的数
vector<T>big;//存放比中位数大的数
int len_MID;
int len_s;
int len_b;
T mid;//中位数
int i;//循环计数
bool flag=false;
for(i=0;i+4<n;i+=5)
QuickSort(a,i,i+4)//5个一组使用快速排序进行排序
if(n%5==0)//输入规模刚好是5的倍数
;
else//最后一个小组元素个数不够5个
{
int more=n%5;
QuickSort(a,n-more,n-1);//对最后一个小组进行快速排序
}
for(i=2;i<n;i+=5)
MID.push_back(a[i])//将每个小组中的中间元素加入MID
len_MID=MID.size();
mid=BFPTR(MID,(len_MID+1)/2,len_MID);//使用同样的算法找出中位数
for(i=0;i<n;i++)//遍历所有元素
{
if(a[i]<=mid)
{
//以下if-else语句是使类似于1 2 3 3 4 5,k=4这种情况正常输出3
//即len_s+len_b=n-1
if(a[i]==mid&&flag==false)//flag=false表示第一次遇到和中位数元素相同的元素,应该跳过该元素
//置flag=true,表示下次遇到时不需要再次跳过
flag==true;
else
small.push_back(a[i]);
}
else
big.push_back(a[i]);
}
len_s=small.size();
len_b=big.size();
if(k==len_s+1)
return mid;
else if(k<len_s+1)
return BFPTR(small,k,len_s);
else
return BFPTR(big,k-len_s-1,len_b);
}
4.时间复杂度分析
将各个组的中位数由大到小排好序后,黄色部分元素一定小于等于m,红色部分元素一定大于m,而紫色部分不确定。考虑最坏情况,即紫色部分元素全部小于m,则需要递归处理small数组求第k小。
设一共有2r+1列,最坏情况下 len_s =7r+2,总元素数n=10r+4,r=(n-4)/10,代入7r+2可知 len_s <7n/10,因此子问题规模最大为7n/10。
设算法时间复杂度为T(n)
(1)子问题规模最大7n/10,因此使用BFPTR处理子问题的时间复杂度为 T(7n/10)
(2)5个元素一组进行排序时,时间复杂度为常数级
(3)选取每组的中位数进入MID的时间复杂度为O(n/5)
(4)使用BFPTR算法从MID中找到中位数的时间复杂度为T(n/5)
(5)遍历数组的时间复杂度为O(n)
因此T(n)=T(7n/10)+T(n/5)+O(n)
可以解出,BFPTR算法的时间复杂度为O(n)。