减治法
中位数
问题描述
设计一个算法求给定的两个有序序列的中位数
例如:序列a=(11,13,15,17,19),其中位数是15,若序列b=(2,4,6,8,20),其中位数是6
两个等长有序序列的中位数是含他们所有元素的有序序列的中位数,例如a、b两个有序序列的中位数为11
问题求解
对于含有n个元素的有序序列a[s……t]
当n为奇数时,中位数是出现在m=
⌊
(
s
+
t
)
/
2
⌋
\biggl\lfloor(s+t)/2\biggr\rfloor
⌊(s+t)/2⌋处
当n为偶数时,中位数有两个,但出于复杂度考虑,视为中位数m=
⌊
(
s
+
t
)
/
2
⌋
\biggl\lfloor(s+t)/2\biggr\rfloor
⌊(s+t)/2⌋处
设计思路
采用二分法求两个等长有序序列的中位数:
两个序列都只有一个元素
直接返回较小者
两个序列含有一个以上的元素:
- 当两个序列的中位数都相等时,直接返回即可
- 当两个序列的中位数不相等时,合并序列中位数较小的中位数后面所有元素和中位数较大的中位数前面所有元素再次求中位数即可(注:被舍弃的序列长度必须相等)
取元素时的做法:
n为奇数时—要将中位数自己包括进去
n为偶数时—取前一部分时:中位数需要包括进去;取后一部分时:中位数不需要包括进去
计算时间复杂度
T(n)=1
T(n)=2T(n/2)+1
易推出:T(n)=O(
log
2
n
\log_{2}n
log2n)
代码示例
int midnum(int a[], int begin1, int end1, int b[], int begin2, int end2){
int mid1,mid2;
if(begin1==end1&&begin2==end2){
return a[begin1]<b[begin2]?a[begin1]:b[begin2];//取两者较小者
}
else{
mid1=(begin1+end1)/2;//取中位数
mid2=(begin2+end2)/2;
if(a[mid1]==b[mid2]){
return a[mid1];
}
if(a[mid1]<b[mid2]){
getpost(begin1, end1);//取后半部分
getpre(begin2, end2);//取前半部分
return midnum(a, begin1, end1, b, begin2, end2);
}
else{
getpost(begin2, end2);
getpre(begin1, end1);
return midnum(a, begin1, end1, b, begin2, end2);
}
}
}
PS:那个取前半部分后半部分懒得写了,很简单就是按照上面描述的取法返回对应的一个下标即可,即去前半部分返回end,取后半部分返回begin
折半查找
基本思路
设序列a[low……high],首先找到中间元素和目标进行对比,小于时向前查找,大于时向后,等于直接返回,然后重复进行
时间复杂度分析
C(n)=1
C
(
n
)
≤
1
+
C
(
⌊
n
/
2
⌋
)
C(n)\le1+C(\biggl\lfloor n/2\biggr\rfloor)
C(n)≤1+C(⌊n/2⌋)
所以算法的时间复杂度为O(
log
2
n
\log_{2}n
log2n)
代码示例
//折半查找(递归方法)
int Binsearch(int a[], int low, int high, int k){
int mid;
if(low<=high){
mid=(low+high)/2;//mid=low+(high-low)/2 此方法可防止计算时low+high超出类型范围
if(a[mid]==k){
return mid;
}
if(a[mid]>k){
return Binsearch(a, low, mid-1, k);
}
else{
return Binsearch(a, mid+1, high, k);
}
}
return -1;
}
//折半查找(非递归)
int Binsearch(int a[], int low, int high, int k){
int mid;
while(low<=high){
mid=(low+high)/2;
if(a[mid]==k){
return mid;
}
if(a[mid]>k){
high=mid-1;
}
else{
low=mid+1;
}
}
return -1;
}
拓展
- 当有序数组中存在相同元素x时,如何查找最左边x
- 当有序数组中存在相同元素x时,如何查找最右边x
拓展分析1
针对查找,有如下三种情况:
- 数组中不存在目标值,返回-1
- 数组中只有一个目标值,返回下标
- 数组中有多个目标值,返回最左/右边的x的下标
//修改后代码
int Binsearch(int a[], int low, int high, int k){
int mid;
while(low<=high){
mid=(low+high)/2;
if(a[mid]>=k){//求最右边时修改条件,为<=,low=mid
high=mid;
}
else{
low=mid+1;
}
}
if(low>high){
return -1;
}
else if(a[low!=k]){
return -1;
}
else{
return low;
}
}