【分享】求两个有序数组的中位数的算法

pandm 2011-05-23 05:38:58
前几天想到的,贴出来给大家看看。
ps,本人快要毕业了,算法一直不行,希望还有进步的可能,跪求有效的方法。。。。



【问题】给定两个长度都为N的有序数组X和Y,写一个函数来找到X和Y合并后的数组的中位数。


【解答】:

首先明确一点,因为X和Y长度一样,所以中位数肯定是有两个的,左中位数和右中位数。
现在我们的思路是,要不断缩小两个数组的长度,同时保证那两个中位数总是在两个数组中,当两个数组的长度足够小的时候,我们就可以轻易找到答案了。

为了方便叙述,我们假定我们可以对数组的每个元素染色,染成黑色或者白色。
假设左右中位数分别为a,b。那么,我们把X和Y中所有小于等于左中位数的数都染成白色,把所有大于等于右中位数的数染成黑色。

由于X和Y是有序的,所以X和Y它们的颜色都必定是一串白接着一串黑,而且X里的白色数的个数等于Y里的黑色数的个数,这一点画个图就清楚了。


假设N是奇数,那么我们取出X的中位数x,和Y的中位数y,那么,x和y必然其中一个为白色另一个为黑色(画个图就明白了),又因为黑色数必定比白色数大,所以我们只要比较一下x和y的大小,我们就可以分辨出哪个是黑色哪个是白色。然后,我们把白色的数前面的数全部删掉,把黑色那个数后面的数全部删掉,这样子,我们就成功删掉了同样个数的白数和黑数,删掉同样的黑数和白数并不会改变剩下的X和Y的中位数。
这里有个问题,为什么我们不能删掉这一步的x和y,既然它们也是一个白一个黑,因为我们删数一定要保证我们不会连我们要找的那2个中位数也删掉。不妨让x是白色,y是黑色,因为左中位数是所有白色数里最大的那一个,所以比x小的那些白色数都不可能是左中位数,所以我们删掉它们,但是x还是有可能就是左中位数的,所以我们不能删。y的部分同理。

假设N是偶数,那么我们取出X的左中位数x和Y的右中位数y,可以证明,任何情况下,x和y必然其中一个为黑色另一个为白色(画个图就明了了)。所以,我们还是可以通过比较x和y的大小来分辨出它们的颜色,然后和上面的做法一样,我们删掉同样数量的白数和黑数。

我们递归地利用剩余的X和Y来做删数的操作,直到由于删除的数总是开头或者结尾的连续一段,所以其实不需要删,只要变一下首尾下标就可以了。由于每次删除的数为当前数组长度的1/2,所以时间复杂度是O(logN)

递归结束条件:
当X和Y的长度都小于等于2的时候,我们可以直接用一个枚举来得到答案。我对于递归的结束条件总是感觉很不踏实(包括这个算法),有时候因为加一减一的问题会导致死循环,所以一般都是当规模小于一定程度时直接转去调用一个枚举函数来获得最终解。。。。
...全文
1251 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhoujk 2011-05-30
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 pandm 的回复:]
谢谢楼上的提醒,确实只要用归并排序就可以了,但是这样的时间复杂是O(N),我那个方法是O(logN)的
[/Quote]
这个问题需要每个变量的参与,任何一个值发生变化,都可能导致中值的改变,因此它的复杂度应该是O(n)级的啊,当然,如果不需要返回精确答案,就是另外一回事了。
pandm 2011-05-30
  • 打赏
  • 举报
回复
[Quote=引用 20 楼 zhoujk 的回复:]
引用 8 楼 pandm 的回复:
谢谢楼上的提醒,确实只要用归并排序就可以了,但是这样的时间复杂是O(N),我那个方法是O(logN)的

这个问题需要每个变量的参与,任何一个值发生变化,都可能导致中值的改变,因此它的复杂度应该是O(n)级的啊,当然,如果不需要返回精确答案,就是另外一回事了。
[/Quote]

= = 并没有需要每个变量的参与,这是二分法,每次只是比较两个中间值,就好像二分查找,你并不需要每个变量的参与,而且每次迭代都会有一半的变量永久失去参与的资格,所以复杂度是O(logN)
pandm 2011-05-24
  • 打赏
  • 举报
回复
更正一下,刚才复制错误了




如果两个数组长度不一样,原来的思路还是可以用的。
假设长数组为X,短数组为Y。
我们还是把X和Y里的数染色,排名在合并后的数组的50%以内的染成白色,剩余的50%染成黑色。
首先,如果X里的白数比较多,那么Y里就肯定黑色比较多;同理如果X里的黑色比较多,Y里的白数就会比较多。
所以,我们取出X的中位数x和Y的中位数y,x和y肯定是不同颜色的。
我们通过比较x和y的大小关系确定它们的颜色。
假如y是黑色的,那么我们可以把Y数组的一半长度砍掉(y后面的元素都砍掉),同时在X数组里砍掉同样数量的白数(把X首部的若干个元素砍掉)。
同理,如果y是白色,也类似地砍。
砍完以后,长数组还是会比短数组长,所以下次砍的时候,还是会砍掉短数组的一半。


但是最后Y数组剩下2个元素时可能会没法再砍了,应该怎么办,这个还没想清楚。。。

pandm 2011-05-24
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 cnmhx 的回复:]
这种算法好吗?
时间复杂度如何?和将两个数组合并后直接找中位数,效率高多少?
[/Quote]

时间复杂度是O(logN),空间复杂度是O(1)
如果用归并的方法,时间复杂度和空间复杂度都是O(N)
pandm 2011-05-24
  • 打赏
  • 举报
回复
色。
首先,如果X里的白数比较多,那么Y里就肯定黑色比较多;同理如果X里的黑色比较多,Y里的白数就会比较多。
所以,我们取出X的中位数x和Y的中位数y,x和y肯定是不同颜色的。
我们通过比较x和y的大小关系确定它们的颜色。
假如y是黑色的,那么我们可以把Y数组的一半长度砍掉(y后面的元素都砍掉),同时在X数组里砍掉同样数量的白数(把X首部的若干个元素砍掉)。
同理,如果y是白色,也类似地砍。
砍完以后,长数组还是会比短数组长,所以下次砍的时候,还是会砍掉短数组的一半。


但是最后Y数组剩下2个元素时可能会没法再砍了,应该怎么办,这个还没想清楚。。。
cnmhx 2011-05-24
  • 打赏
  • 举报
回复
这种算法好吗?
时间复杂度如何?和将两个数组合并后直接找中位数,效率高多少?
showjim 2011-05-24
  • 打赏
  • 举报
回复
ljsspace 2011-05-23
  • 打赏
  • 举报
回复
我用quicksort查找中位数的一个实现:http://blog.csdn.net/ljsspace/archive/2011/05/10/6409416.aspx
pandm 2011-05-23
  • 打赏
  • 举报
回复
不管是哪个你需要把2*(N-1)个中位数扫描并标记一次,其实复杂度已经很高了

=================
为什么是2*(N-1)个??
pandm 2011-05-23
  • 打赏
  • 举报
回复
不知道你说的“左右中位数分别为a,b”是什么意思,肯定不是要求的两个中位数吧,它就是你要求的
如果N为奇数,是这两个数组中的中位数中较小的和较大的
N为偶数的话难道是4个中位数中的居中的两个?
不管是哪个你需要把2*(N-1)个中位数扫描并标记一次,其实复杂度已经很高了


================================================

当数组a[1..N]的N为偶数的时候,左中位数就是指a[N/2],右中位数就是a[N/2+1]
我这个算法类似于二分查找,每次只是查看数组中间的那个元素,并且每次查看完就可以减少数组长度的一半,所以复杂度只是logN的
其次,这个算法可以写成循环形式,空间复杂度为O(1)
zyren988 2011-05-23
  • 打赏
  • 举报
回复
不知道你说的“左右中位数分别为a,b”是什么意思,肯定不是要求的两个中位数吧,它就是你要求的
如果N为奇数,是这两个数组中的中位数中较小的和较大的
N为偶数的话难道是4个中位数中的居中的两个?
不管是哪个你需要把2*(N-1)个中位数扫描并标记一次,其实复杂度已经很高了


我的简单想法
如果不需要求出合并后的数组,可以只运行为N+1次循环判断
即最基本的归并算法,但是只用归并到“一半”即可
因为原数组都是相同有序的(相反有序稍微麻烦点),所以中位数必然是归并后的第N、N+1个数

int main()
{
int i, j, k, N=5, mid[2];
//假设2数组都是升序
int a[]={1,3,4,8,10}, b[]={2,5,6,7,9};
i = j = k = 0;
while((i<N)&&(j<N))
{
if (a[i]<b[j])
{
i++;
k++;
if ((k==N)||(k==N+1))
{
mid[k-N] = a[i-1];//因为升序
}
}
else
{
j++;
k++;
if ((k==N)||(k==N+1))
{
mid[k-N] = b[j-1];
}
}
if(k==N+1)
{
printf("The middle numbers are %d and %d\n", mid[0], mid[1]);
return 0;
}
}

while(i<N)
{
i++;
k++;
if ((k==N)||(k==N+1))
{
mid[k-N] = a[i-1];
}
if(k==N+1)
{
printf("The middle numbers are %d and %d\n", mid[0], mid[1]);
return 0;
}
}

while(j<N)
{
j++;
k++;
if ((k==N)||(k==N+1))
{
mid[k-N] = b[j-1];
}
if(k==N+1)
{
printf("The middle numbers are %d and %d\n", mid[0], mid[1]);
return 0;
}
}
return 1;
}

如果还要求归并后的数组,则正如3#和7#所说就是完整归并
pandm 2011-05-23
  • 打赏
  • 举报
回复
贴上实现代码:



#include <algorithm>
#include <cstdio>
using namespace std;
struct duo {
int x;
int y;
};

duo make_duo(int x, int y) {
duo d;
d.x = x;
d.y = y;
return d;
}


duo find_mid(int a, int b, int c, int d) {
if (a > b) swap(a, b);
if (b > c) swap(b, c);
if (c > d) swap(c, d);
if (a < b) swap(a, b);
if (b < c) swap(b, c);
return make_duo(a, b);
}


duo find_mid(int a[], int a_beg, int a_end,
int b[], int b_beg, int b_end)
{
int len = a_end - a_beg + 1;

if (len == 1) {
return make_duo(a[a_beg], b[b_beg]);
} else if (len == 2) {
return find_mid(a[a_beg], a[a_end], b[b_beg], b[b_end]);
}

int a_mid, b_mid;
if (len & 1) {
a_mid = a_beg + len / 2;
b_mid = b_beg + len / 2;
}
else {
a_mid = a_beg + len / 2 - 1;
b_mid = b_beg + len / 2;
}

if (a[a_mid] < b[b_mid])
return find_mid(a, a_mid, a_end,
b, b_beg, b_mid);
else
return find_mid(a, a_beg, a_mid,
b, b_mid, b_end);


}


int a[100], b[100];
int c[300];
int main() {
int N;
scanf("%d", &N);
for (int i = 0; i < N; ++i) {
scanf("%d", &a[i]);
c[i] = a[i];
}
int j = N;
for (int i = 0; i < N; ++i) {
scanf("%d", &b[i]);
c[j] = b[i];
++j;
}
sort(a, a+N);
sort(b, b+N);
sort(c, c+ 2*N);

printf("%d %d\n", c[N-1], c[N]);
duo d = find_mid(a, 0, N - 1, b, 0, N - 1);
if (d.x > d.y) swap(d.x, d.y);
printf("%d %d\n", d.x, d.y);
return 0;
}


hengshan 2011-05-23
  • 打赏
  • 举报
回复
浪费点空间,两个搞在一个数组里面,O(m+n)搞定
pandm 2011-05-23
  • 打赏
  • 举报
回复
谢谢楼上的提醒,确实只要用归并排序就可以了,但是这样的时间复杂是O(N),我那个方法是O(logN)的
zhoujk 2011-05-23
  • 打赏
  • 举报
回复
两个数组有序,就先合并数组,然后取其中位即可,其中的合并方法如下:
源数组为 A、B(假设为升序),合并后的数组为C,其下标分别为A1 = 0,B1 = 0,C1 = 0;
用C1来遍历 C,
如果A[A1] < B[B1],则C = A[A1],A1++
否则 C[C1] = B[B1]; B1 ++;
这样合并出来的C就是升序。然后中其中返回中值即可。应该是 C[C1/2]吧。
pandm 2011-05-23
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 superdullwolf 的回复:]
排序
就不是最快的了。

这个问题应该有O(n+m)级别的算法。

取前n/2大,肯定是O(n)算法。可以找到中位数。
O(n+m)找到两个中位数N,M。
新的中位数,肯定是在N和M之间的数组的中位数。
线性扫描即可。
[/Quote]

其实看不太懂。。。
pandm 2011-05-23
  • 打赏
  • 举报
回复
牛逼。。。忘记了取中位数有O(N)的算法。。。但是那个算法好难理解。。。看来我还是太菜了。。
超级大笨狼 2011-05-23
  • 打赏
  • 举报
回复
突然看到题目里,“有序数组”的字样
有序的话,一定可以折半查找了O(lg(n+m))级别就可以了。

超级大笨狼 2011-05-23
  • 打赏
  • 举报
回复
排序
就不是最快的了。

这个问题应该有O(n+m)级别的算法。

取前n/2大,肯定是O(n)算法。可以找到中位数。
O(n+m)找到两个中位数N,M。
新的中位数,肯定是在N和M之间的数组的中位数。
线性扫描即可。



q107770540 2011-05-23
  • 打赏
  • 举报
回复

void Main()
{
var list1=new List<int>{3,5,8,9,11,22,33};
var list2=new List<int>{4,6,18,19,21,12,13};
var list=list1.Concat(list2).OrderBy(l=>l);
var dictory=list.Select((value,index)=>new {Key=index+1,Value=value});
int length=list1.Count();
var result=dictory.Where(l=>new int[]{length,length+1}.Contains(l.Key)).Average(l=>l.Value);
Console.WriteLine(result); //11.5

}
加载更多回复(1)

33,010

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧