用O(lgn)时间求出两个已排序数组的中位数
用O(lgn)时间求出两个已排序数组的中位数
相关问题:
设 x[1..n]和Y[1..n]为两个数组,每个都包含n个已排序的数。给出一个求数组X和Y中所有2n个元素的中位数的O(lgn)时间的算法。
思考过程:
开始我想把两个数组X与Y放入到一个数组Z中,对Z进行排序,这样Z的中位数易求。但是有2点原因使得这种做法不可行,1.是将2数组放入到1个数组中的过程会产生O(n)的时间,所以不满足题意。2如果对新数组Z排序,那么最少需要线性O(n)时间进行排序,也不满足O(lgn)这个时间的需求。然后我又试图用最坏时间线性选择子程序,但是马上想到,这样就是O(n),不是O(lgn)的时间了,所以用不上书中9.3节的内容。
后来我想到如果要想达到O(lgn)这个数量级的查找,那么可以选择二分法进行查找,首先判断数组X(Y)是否所有数都小于数组Y(X),如果假设成立,那么直接返回数组X最后一个数即可。如果集合X与Y之中的数存在集合X的某元素<集合Y的某元素,同时集合X的某元素>集合Y的某元素,那么进入递归。具体递归过程请见代码注释。(PS:其实我认为这个问题就是考察对二分法的灵活运用。)
具体步骤:
开始随机化数组,然后对数组进行排序。因为原题的意思是要想进行O(lgn)时间的选择,那么前提是两数组已排序,所以不能将排序过程算在总的选择时间里面。当然你也可以选择自己设置数组元素值,使其初始化时便已有序。
Two_groups_array_Median函数具体步骤是:
step 1:判断数组X(Y)所有数是否均小于数组Y(X)所有数。如果小于,则直接返回中位数。程序立即结束。
step 2:判断数组X与Y,begin项≥end项?如果成立,则进入最后的微调与返回中位数阶段。
step 3:如果step 1,2都不成立,那么进入二分法的递归过程。具体过程见代码注释。
#include <iostream> #include <time.h> using namespace std; const n=25; void INSERTION_SORT(int A[],int r)//利用任意一个排序算法设置数组为题目条件“有序”。 { int key; for (int j=1;j<=r;j++) { key=A[j-1]; int i=j-1; while (i>0&&A[i-1]>key)//a)插入排序时间复杂度O(n^2)对于长度为k的子列表都有O(k^2),则n/k个为(k^2)*n/k=O(nk) { A[i]=A[i-1]; i=i-1; } A[i]=key; } } void Initialization(int A[],int r,int a,int b)//初始化数组 { srand( (unsigned)time( NULL ) ); for (int i=0;i<n;i++) { A[i]=rand()%(a-b+1)+b; } } void Print(int A[],int r) { for (int i=0;i<r;i++) { cout<<A[i]<<" "; } cout<<endl; } int Two_groups_array_Median(int A[],int B[],int beginA,int endA,int beginB,int endB) { int p=(beginA+endA)/2;int r=(beginB+endB)/2; int t=p+r+2,flag=0;//经过大量的实验证明(我试图用数学归纳法证明,惭愧的是我没有完整证明出来,如果有大牛觉的我的这个结论是错误的,那么可以举反例证明我的错误以待我去改正,小弟在此先感谢纠正我错误给我留言的人):t≈n 最多相差常数个误差。误差产生的原因是p和r定义时除以2后,全部向下取整了,而精确的中位数是需要有时向上取整的。 //t表示当前中位数p和r前面有多少个元素,由于数组下标是从0开始的,所以需要+上A[0]和B[0]。 if (A[n-1]<B[0])//如果集合A与B之中的某一集合所有的数均小于另外一集合所有数,则直接返回较小数组的最后一位即为中位数 { return A[n-1]; } else if (A[0]>B[n-1]) { return B[n-1]; } if (beginA>=endA||beginB>=endB)//如果A与B哪个先递归到begin项≥end项,那么就进行下面的判断。 { if (n-t>0) { for (int i=1;i<=n-t;i++)//对于上面注释所说的误差所差常数的具体数值,根据这个常数数值再求精确的中位数,否则如果没有这个循环,那么所求中位数与真实中位数要相差常数个位置 { ++p;++r; if (A[p]<B[r]) { --r; flag=1; } else { --p; flag=0; } } if (flag)//这个if-else语句是要返回A与B中较小的那个数,较小的数就是中位数。 { return A[p]; } else { return B[r]; } } else//如果n=t,无需进行微调,由于p+r+2正好等于这2n个数的中间值下标n,那么就返回较大值,较大值正好是第n个数。 { if (A[p]<B[r]) { return B[r]; } else { return A[p]; } } } if (A[p]>B[r])//如果数组A的中位数大于数组B的中位数,那么两数组的中位数必在数组A当前中位数的左边,数组B当前中位数的右边 { return Two_groups_array_Median(A,B,beginA,p,r,endB);//对数组A的左半部分数组B的右半部分进行递归。 } else //小于情况类似,这里不做累述。 { return Two_groups_array_Median(A,B,p,endA,beginB,r); } } void main() { int A[n]={0},B[n]={0}; cout<<"随机数组A初始化。。。"<<endl; Initialization(A,n,10000,9); INSERTION_SORT(A,n);//利用插入排序设置数组使其变为题目所说的“已排序”的数组。 Print(A,n); cout<<"随机数组B初始化。。。"<<endl; Initialization(B,n,15000,1000); INSERTION_SORT(B,n); Print(B,n); cout<<"两数组中位数="<<Two_groups_array_Median(A,B,0,n-1,0,n-1)<<endl; }
评论暂时关闭