【问题】一个长度为 n(n≥1) 的升序序列 S,处在第 n/2 个位置的数称为序列 S 的中位数(median number),例如,序列 S1={11,13,15,17,19} 的中位数是 15。两个序列的中位数是它们所有元素的升序序列的中位数,例如,S2={2,4,6,8,20},则 S1 和 S2 的中位数是11。现有两个等长升序序列 A 和 B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列的中位数。
【想法】算法的基本思想如下。
(1)分别求出两个序列的中位数,记为 a 和 b 。
(2)比较 a 和 b,有下列三种情况:
a=b,则 a 即为两个序列的中位数;
a<b,则中位数只能出现在 a 和 b 之间,在序列 A 中舍弃 a 之前的元素得到序列 A1,在序列 B 中舍弃 b 之后的元素得到序列 B1;
a>b,则中位数只能出现在 b 和 a 之间,在序列 A 中舍弃 a 之后的元素得到序列 A1,在序列 B 中舍弃 b 之前的元素得到序列 B1。
(3)在 A1 和 B1 中分别求出中位数,重复上述过程,直到两个序列中只有一个元素,则较小者即为所求。
例如,对于两个给定的序列 A={11,13,15,17,19},B={2,4,10,15,20},求序列 A 和 B 的中位数的过程如下表所示,在求解过程中注意保持两个序列的长度相等。
【算法】减治法求解两个序列中位数的算法用伪代码描述如下。
算法:两个序列中位数 SearchMid
输入:两个长度为 n 的有序序列 A 和 B
输出:序列 A 和 B 的中位数
1.循环直到序列 A 和序列 B 均只有一个元素
1.1 a=序列 A 的中位数;
1.2 b=序列 B 的中位数;
1.3 比较 a 和 b,执行下面三种情况之一:
1.3.1 若a=b,则返回 a,算法结束;
1.3.2 若a<b,则在序列 A 中舍弃 a 之前的元素,在序列 B 中舍弃 b 之后的元素,转步骤1;
1.3.3 若a>b,则在序列 A 中舍弃 a 之后的元素,在序列 B 中舍弃 b 之前的元素,转步骤1,
2.序列 A 和序列 B 均只有一个元素,返回较小者。
【算法分析】由于每次求两个序列的中位数后,得到的两个子序列的长度都是上一个序列的一半,因此,循环共执行次,时间复杂性为O()。算法除简单变量外没有额外开辟临时空间,因此,空间复杂性为O(1)。
【算法实现】为了记载序列 A 和序列 B 在不断查找过程中的变化,用下标 s1 和 e1 表示序列 A 的上下界,用下标 s2 和 e2 表示序列 B 的上下界,算法用JAVA语言描述如下:
public class lgxldzws {
public static void main(String[] args)
{
int A[]={1,2,3,5,7};
int B[]={2,4,6,8,10};
int mid=SearchMid(A,B,5);
System.out.println("两个序列的中位数是:"+mid);
}
static int SearchMid(int A[], int B[], int n)
{
int s1 = 0, e1 = n - 1, s2 = 0, e2 = n-1; //初始化两个序列的上下界
int mid1, mid2;
while (s1 < e1 && s2 < e2) //循环直到区间只有一个元素
{
mid1= (s1 + e1)/2; //序列A的中位数的下标
mid2 = (s2 + e2)/2; //序列B的中位数的下标
if (A[mid1] == B[mid2]) return A[mid1]; //第①种情况
if (A[mid1] < B[mid2]) //第②种情况
{
if ((s1 + e1) % 2 == 0)
s1 = mid1;
else
s1 = mid1 + 1; //保证两个子序列的长度相等
e2 = mid2;
}
else
{
if ((s2 + e2) % 2 == 0)
s2 = mid2;
else
s2 = mid2 + 1; //保证两个子序列的长度相等
e1 = mid1;
}
}
if (A[s1] < B[s2]) return A[s1]; //较小者为所求
else return B[s2];
}
}
运行结果如下:
from:算法设计与分析(第2版)——王红梅 胡明 编著——清华大学出版社