【题目】
在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; }
【时间复杂度】