求一个无序数组的中位数。
如:{2,5,4,9,3,6,8,7,1}的中位数为5,{2,5,4,9,3,6,8,7,1,0}的中位数为4和5。
要求:不能使用排序,时间复杂度尽可能高。
提示:考虑堆或者快排思想解决。
思路一:利用堆排序的思路
将前n/2的元素放进一个大堆里,然后将后续的元素与堆顶比较,如果比堆顶大,则继续比下一个,如果比堆顶小,则与堆顶的值进行交换。
这样操作完后,堆里的元素都是比堆顶小的,不再堆里的元素都比堆顶大,则堆顶就是中位数。
参考代码如下:
void FindMid(int a[],int size) { if(a == NULL ||size<1) return ; priority_queue<int> q; //创建一个大堆 for (int i = 0;i<size/2+1;++i) //将前size/2的元素放入堆中 q.push(a[i]); int top = q.top(); for(int i = size/2+1;i<size;++i) //将后半部分的元素一一与堆顶比较 { if (a[i]<top) { q.pop(); q.push(a[i]); } top = q.top(); } if(0==size%2) //判断奇偶,从而判断是几个中位数 { q.pop(); int top1 = q.top(); cout<<"中位数是"<<top<<"和"<<top1<<endl; } else { cout<<"中位数是"<<top<<endl; } }
测试代码如下:void test() { int a[] = {2,5,4,9,3,6,8,7,1};//中位数是5 int b[] = {2,5,4,9,3,6,8,7,1,0};//中位数是4和5 int len = sizeof(a)/sizeof(a[0]); int lenb = sizeof(b)/sizeof(b[0]); FindMid(a,len); FindMid(b,lenb); }
结果如下:+
思路二:利用快排的思想
任意挑一个元素,以改元素为支点,划分集合为两部分,如果左侧集合长度恰为 (n-1)/2,那么支点恰为中位数。如果左侧长度<(n-1)/2, 那么中位点在右侧,反之,中位数在左侧。 进入相应的一侧继续寻找中位点。
这种方法很快,但是在最坏的情况下时间复杂度为O(N^2), 不过平均时间复杂度好像是O(N)。
参考代码如下:
int QucikFind(int a[],int k,int l,int r) { int key = a[l]; int i = l; int j = r; while (i<j) { while (i<j&&a[j]>key) j--; if(i<j) a[i++] = a[j]; while (i<j&&a[i]<key) i++; if(i<j) a[j--] = a[i]; } a[i] = key; if (i == k) return i; else if (i>k) return QucikFind(a,k,l,i-1); else return QucikFind(a,k,i+1,r); } void Find1(int a[],int size) { if (a==NULL||size<1) return ; cout<<"中位数是"; if(0==size%2) //判断奇偶 cout<<a[QucikFind(a,size/2,1,size-1)]<<"和 "<<a[QucikFind(a,size/2-1,1,size-1)]<<endl; else cout<<a[QucikFind(a,size/2,1,size-1)]<<endl; }
测试代码如下:
int a[] = {2,5,4,9,3,6,8,7,1};//中位数是5 int b[] = {2,5,4,9,3,6,8,7,1,0};//中位数是4和5 int len = sizeof(a)/sizeof(a[0]); int lenb = sizeof(b)/sizeof(b[0]); Find1(a,len); Find1(b,lenb);
结果如下: