线性时间选择

Posted 王勋广

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性时间选择相关的知识,希望对你有一定的参考价值。

题目

在n个元素的无序数组中选择第k(1<=k<=n)小元素。
当k=1时,相当于找最小值。
当k=n时,相当于找最大值。
当k=n/2时,称中值。
【要求】
线性时间内完成,即O(n)。

算法解析

通过执行下列步骤,算法SELECT可以确定一个有n>1个不同元素的输入数组中第i小的元素。(如果n=1,则SELECT只返回它的唯一输入数值作为第i小的元素。)
1、将输入数组的n个元素划分为⌈n/5⌉组,每组5个元素,且至多有一个组由剩下的n mod 5个元素组成。
2、寻找这⌈n/5⌉组中每一组的中位数:首先对每组元素进行插入排序(我这里用的是改进版的冒泡排序),然后确定有序元素的中位数。
3、对第2步中找出的⌈n/5⌉个中位数,递归调用SELECT找出其中位数x。(如果有偶数个中位数,为了方便,约定x是较小的中位数)。
4、利用修改过的PARTITION过程,按中位数的中位数x对输入数组进行划分。让k比划分低区的元素多1,因此x是第k小的元素,并且有n-k个元素在划分的高区。
5、如果i=k,则返回x,否则,如果i<k,则在低区递归调用SELECT以找出第i小的元素,如果i>k,则在高区递归查找第(i-k)小的元素。

核心代码

template<class Type>
Type select (Type a[],int p, int r, int k){
    if (r-p<75) {
    //对数组a[p:r]排序;
    bubbleSort(a,p,r);
    return a[p+k-1];
    }
    
    //找中位数的中位数,r-p-4即上面所说的n-5
    for ( int i = 0; i<=(r-p-4)/5; i++ ){
        int s=p+5*i,t=s+4;
        //冒泡操作保证第三个元素为中位数 
        for (int j=0;j<3;j++) bubble(a,s,t-j);
        //将a[p+5*i]至a[p+5*i+4]的第3小元素(即中位数)与a[p+i]交换位置;
        swap(a, p+i, s+2);
    }
    
    //获取中位数的中位数 
    Type x = select(a,p, p+(r-p-4)/5, (r-p-4)/10);
    //根据x划分序列 ,i中位数的中位数所在下标 
    int i=partition(a,p,r,x);
    //j是中位数的中位数的序数 
    int j=i-p+1;
    
    if (k<=j) return select(a,p,i,k);
    else return select(a,i+1,r,k-j);
}

 

完整代码

#include <iostream>

using namespace std;


//交换函数 
template<class Type>
void swap(Type a[],int p,int r){
    Type temp=a[p];
    a[p]=a[r];
    a[r]=temp;
}

//改进版冒泡排序 
template<class Type>
void bubbleSort(Type a[],int p,int r){
    int lastSwapPos = 0,lastSwapPosTemp = 0;
    for (int i = p; i < r; i++)  {  
        lastSwapPos = lastSwapPosTemp;
        for (int j = r; j >lastSwapPos; j--)  {
            if (a[j - 1] > a[j]){
                swap(a,j-1,j);
  
                lastSwapPosTemp = j;  
            }  
        }
        
        if (lastSwapPos == lastSwapPosTemp)  
            break;  
    }
}

//冒泡操作 
template <class T>
void bubble(T a[],int p, int r){
    for(int i=p;i<r;i++){
        if(a[i]>a[r])
        swap(a,i,r);
    }
}

//划分函数 
template <class T>
int partition (T a[], const int p, const int r,T x) {
    int pivotpos = p;
    for (int i = p; i <= r; i++)
    if (a[i] < x) {
        pivotpos++;
        if (pivotpos != i){
            swap(a,pivotpos,i);
        }
    }
    return pivotpos;
}



template<class Type>
Type select (Type a[],int p, int r, int k){
    if (r-p<75) {
    //对数组a[p:r]排序;
    bubbleSort(a,p,r);
    return a[p+k-1];
    }
    
    //找中位数的中位数,r-p-4即上面所说的n-5
    for ( int i = 0; i<=(r-p-4)/5; i++ ){
        int s=p+5*i,t=s+4;
        //冒泡操作保证第三个元素为中位数 
        for (int j=0;j<3;j++) bubble(a,s,t-j);
        //将a[p+5*i]至a[p+5*i+4]的第3小元素(即中位数)与a[p+i]交换位置;
        swap(a, p+i, s+2);
    }
    
    //获取中位数的中位数 
    Type x = select(a,p, p+(r-p-4)/5, (r-p-4)/10);
    //根据x划分序列 ,i中位数的中位数所在下标 
    int i=partition(a,p,r,x);
    //j是中位数的中位数的序数 
    int j=i-p+1;
    
    if (k<=j) return select(a,p,i,k);
    else return select(a,i+1,r,k-j);
}


int main() {
    
    //int a[]={0,6,5,1};
    //int a[]={1,2,3,4,5,6,7,8,9,10};
    int a[]={1,29,3,4,5,9,7,855,97,106};
    cout<<select(a,0,9,8);

    return 0;
}

 

时间复杂度

 

以上是关于线性时间选择的主要内容,如果未能解决你的问题,请参考以下文章

垂直线性布局中的多个片段

在android中的类内的对话框片段的线性布局中添加textview

VSCode自定义代码片段——CSS选择器

VSCode自定义代码片段6——CSS选择器

Android 底部工作表布局边距

如何从片段中调用 getSupportFragmentManager()?