中位数(第k大数)快速求法
来源:互联网 发布:unity3d 上海 外包 编辑:程序博客网 时间:2024/06/07 03:12
本文为twenz根据个人经验整理,转载请注明来源,谢谢!
中位数即为一系列数中的大小在中间位置的数,快速找中位数的有效方法有:
1.排序法:先对数组进行排序,时间复杂度为O(nlogn),然后选择中间的数
2.快排的筛选法(类似于找第k大的数):思想是选定一个数,找比它大的数和小的数,然后根据数量再在大的部分或者小的部分循环递归找,时间复杂度应该为O(n)
如果给定两个有序的系列,需要查找他们共同的中位数,时间复杂度又会是多少呢?
1. 直接对两个有序系列进行逐个元素比较,时间复杂度为O(m+n)
2. 采用二分查找(类似于找第k大点),先以一个序列的基准点(二分来确定)来在另外一个系列中二分查找,再不断更新两系列的上下界限,时间复杂度应该在O(logm*logn). 下面是一个程序的基本框架,不一定正确,只是提供架构思想,待以后碰到类似问题我将更新该代码:
double findMid(int pm,int m)
{
if(pm == 0 || pm == m){
if(m%2)return a[m/2]*1.0;
else return (a[m/2]+a[m/2-1])/2.0;
}
int low1=0,up1=pm-1,low2=pm,up2=m-1,mid1,mid2,num;
while(low2<=up2){
mid2=(low2+up2)/2;
while(low1<=up1){
mid1=(low1+up1)/2;
if(a[mid1]<=a[mid2])low1=mid1+1;
else up1=mid1-1;
}
num = mid1+(mid2-pm);
if(a[mid1]<=a[mid2]){
num +=1;
if(num == (m-1)/2)break;
else if(num < (m-1)/2){
low2=mid2+1;
up2=m-1;
low1=mid1+1;
up1=pm-1;
}
else{
low2=pm;
up2=mid2-1;
low1=0;
up1=mid1-1;
}
}
else{
if(num == (m-1)/2)break;
else if(num < (m-1)/2){
low2=mid2+1;
up2=m-1;
low1=mid1;
up1=pm-1;
}
else{
low2=pm;
up2=mid2-1;
low1=0;
up1=mid1-1;
}
}
}
int ans,x;
if(num != (m-1)/2){
if(num > (m-1)/2)mid2--;
ans = (m-1)/2-(mid2-pm+1);
}
else ans = mid2;
if(m%2)return a[ans]*1.0;
else {
if(ans<pm){
if(mid2<m-1&&ans<pm-1)x=a[ans+1]>a[mid2+1]?a[mid2+1]:a[ans+1];
else if(mid2<m-1)x=a[mid2=1];
else if(ans<pm-1)x=a[ans+1];
return (x+a[ans])/2.0;
}
else{
if(ans<m-1&&a[mid1]>a[ans])x=a[ans+1]>a[mid1]?a[mid1]:a[ans+1];
else if(ans<m-1&&mid1<pm-1&&a[mid1]<=a[ans])x=a[ans+1]>a[mid1+1]?a[mid1+1]:a[ans+1];
else if(ans<m-1)x=a[ans+1];
else if(a[mid1]>a[ans])x=a[mid1];
else if(mid1<pm-1&&a[mid1]<=a[ans])x=a[mid1+1];
return (x+a[ans])/2.0;
}
}
}
但是如果给定的是一个动态的不断增加的系列,期间需要不断地查询中位数,该怎么办呢?
1. 采用插入排序,这样每次查询的都是有序的系列。时间复杂度O(n*n)
2. 利用上面的两个有序序列的思想,以一区间段M作为标准将系列分成两个有序系列,其中第二个系列采用插入排序(M*M*n/M),如果达到M就和1系列归并(n*n/M),查找中位数则采用上面的方法(n*logn*logM)。总时间复杂度为O(nM+n*n/M+nlogn*logM),则M取n的平方根可能较好
3. 还有一种思路就是仍然采用插入排序,但是使用双向链表插入排序,此外以M作为区间记录M倍数的位置的节点,这样插入过程只需找区间(n/M)以及在区间中找(M),则每次的时间复杂度为(n/M+M),而一个中位数指针维护指向中间节点,插入大的考虑右移小的左移O(1)。这样总的时间复杂度为O(n*(n/M+M)+1)及为O(n^1.5)
4. 二叉排序树方法(http://baike.baidu.com/view/647462.htm):排序O(nlogn),构建二叉排序树插入O(logn),查找O(logn)
5. 红黑树(http://hi.baidu.com/yellobd/blog/item/b7972850bf03b13242a75b83.html): 插入及查找均为O(logn),总复杂度O(nlogn)
6. 如果查询的第k大数的k呈现非递减关系,则可以采用维护最大堆和最小堆。最小堆k个数。时间复杂度O(nlogn)
下面这道题即是:http://acm.scs.bupt.cn/onlinejudge/newoj/Statistic/Statistic.php?problem_id=67 采用方法3的代码:
#include<iostream>
#include<stdio.h>
using namespace std;
#define M 200
#define N 100001
struct Node{
int v;
Node *prev,*next;
}node[N];
Node *inde[N/M+2],*mid,*last,*start;
void insert(Node *add,int lm,int m)
{
if(last == NULL){
start = last = mid = add;
return;
}
if(last->v <= add->v){
last->next = add;
add->prev = last;
last = add;
}
else if(start->v > add->v){
add->next = start;
start->prev = add;
start = add;
for(int i = lm-1;i >= 0;i --)inde[i] = inde[i]->prev;
}
else{
int i = lm-1;
while(i>=0 && inde[i]->v>add->v){
inde[i] = inde[i]->prev;
i --;
}
Node *p;
if(i == lm-1)p=last->prev;
else{
p=inde[i+1];
if(p->v<=add->v)inde[i+1]=add;
}
while(p->v>add->v)p=p->prev;
add->prev = p;
add->next = p->next;
p->next->prev = add;
p->next = add;
}
if(mid->v<=add->v){
if(m%2)mid=mid->next;
}
else{
if(m%2==0)mid=mid->prev;
}
}
int main()
{
int n,m,lm;
while(scanf("%d",&n)!=EOF)
{
int i,x,tt;
m = 0;
lm = 0;
start=mid=last=NULL;
for(i=0;i<n;i++)
{
scanf("%d",&x);
if(x == 1){
scanf("%d",&tt);
Node *add = &node[m++];
add->v = tt;
add->next=add->prev=NULL;
insert(add,lm,m);
if(m%M == 0)
inde[lm++]=last;
}
else if(x == 2){
double ans = mid->v;
if(m%2==0)ans=(ans+mid->next->v)/2.0;
printf("%.1lf\n",ans);
}
}
}
return 0;
}
---------------------------------方法4------------------------
#include <algorithm>
#include <iostream>
using namespace std;
#define N 100001
int a[N],b[N],tag[N],lm;
struct Node{
int v;
int count;
bool tag;
Node *l,*r;
}node[N];
Node * makeTree(int low,int up){
int mid = (low+up)/2;
while(mid>low&&a[mid]==a[mid-1])mid--;
Node *root = &node[lm++];
root->count = 0;
root->tag = false;
root->v=a[mid];
if(low<=mid-1)root->l=makeTree(low,mid-1);
else root->l=NULL;
if(mid+1<=up)root->r=makeTree(mid+1,up);
else root->r=NULL;
return root;
}
void insert(Node *root,int xx){
if(root->v>xx){
root->count++;
insert(root->l,xx);
}
else if(root->v==xx&&root->tag==false)
root->tag=true;
else
insert(root->r,xx);
}
int look(Node *root,int middle)
{
if(root->count >= middle)return look(root->l,middle);
else if(root->count == middle-1 && root->tag)return root->v;
if(root->tag)return look(root->r,middle-1-root->count);
return look(root->r,middle-root->count);
}
int main()
{
int i,j,x,n,m,resn;
while(scanf("%d",&n)!=EOF)
{
m = 0;
resn = 0;
lm = 0;
for(i=0;i<n;i++)
{
scanf("%d",&x);
if(x == 1){
scanf("%d",&a[m]);
b[m]=a[m];
m ++;
}
else if(x == 2){
tag[resn++]=m;
}
}
sort(a,a+m);
Node *root = makeTree(0,m-1);
j = 0;
for(i=0;i<m;i++){
insert(root,b[i]);
while(j < resn && tag[j] == i+1){
double ans = look(root,i/2+1);
if(i%2)ans = (ans+look(root,i/2+2))/2;
printf("%.1lf\n",ans);
j ++;
}
}
}
return 0;
}
- 中位数(第k大数)快速求法
- 算法: 快速求中位数(第k大数)
- 中位数与二分->以至于第k大数与二分
- 快速排序 and 第K大数
- 快速排序与求第k大数
- 快速排序 求第k大数
- bfprt算法,中位数的中位数算法,O(n)时间复杂度求解第k大数
- 第k短路的求法
- 多数组中位数,k大数 -- 二分思路
- 求序列第K大数的部分快速排序法
- 两种快速排序和找第k大数
- 求第k大数
- 第K大数问题
- 寻找第k大数
- 区间第K大数
- zcmu1540 第k大数
- 区间第K大数
- 求第k大数
- HIT LTP的使用及利用句法关系简单抽取示例
- Java中Object的使用:重载equals、hashCode及实现compareTo
- 文件流fstream处理多个文件
- 带负号大数加减法简单程序
- Word问题集锦
- 中位数(第k大数)快速求法
- 系列前m大数算法总结
- 情感对象抽取【opinion mining】
- 后缀数组
- Ubuntu 12.04安装openGL与opencv
- oracle数据库的使用及安装
- 一些简单的Linux命令
- JUnit中标注Annotation介绍
- 利用Firebug和XPath寻找locator