基于减而治之的思想,在有序向量的区间[lo,hi)内查找元素e
二分查找版本A
int binsearch(int* A,int e,inr lo,int hi){
while(lo<hi){
int mid=(lo+hi)>>1;
if(e<A[mid]) hi=mid; //深入前半段[lo,mid)继续查找
else if(A[mid]>e) lo=mid+1; //深入后半段(mid,hi)继续查找
//上面两行,之所以一个需要mid+1而另一个不用+1是因为我们是在左闭右开区间[lo,hi)里查找
//而上面两行对应的两种情况,均可以把A[mid]这个元素排除,无需再算
else return mid; //在mid处命中
}
return -1;//查找失败
}
注:
1、正因为定义在[lo,hi)区间内查找,因此while循环条件是lo<hi,即hi比lo至少要大1, 保证区间内至少有一个元素
2、版本A的优点:成功查找可以提前终止
3、版本A的缺点:a、有多元素命中时,不能保证返回秩最大者 b、查找失败时简单的返回-1,而不能指示失败的位置
4、版本A的渐进时间复杂度为o(logn),但因为深入前半段只需一次比较,深入后半段和命中需要两次比较,因此导致向左向右的比较次数不同,平均查找长度达到o(1.5*logn),常系数1.5较大。
解决第四点缺陷的方法有二:
1、调整前后区域的宽度,适当加长前子向量,适当缩短后子向量。
2、统一沿两个方向深入所需执行的比较次数
fibonacci查找正是基于第1种思想改进而来
fibonacci数列:1、1、2、3、5、8、13、21、34、55、89…
fibonacci查找,把原数组扩展成略大于数组长的F[n](例如数组长7,可以通过重复最后一个元素把数组扩展成长度为8的数组),又F[n]=F[n-1]+F[n-2],就可以把数组分成前后两部分继续查找,如此反复。
下面代码转自链接
int FibonacciSearch(int *data, int length, int searchValue)
{
int low, high, mid, k, i, fib[FIB_MAXSIZE];
low = 0;
high = length - 1;
//产生斐波那契数列
ProduceFib(fib, FIB_MAXSIZE);
k = 0;
// 找到有序表元素个数在斐波那契数列中最接近的最大数列值
while (high > fib[k] - 1)
{
k++;
}
// 补齐有序表
for (i = length; i <= fib[k] - 1; i++)
{
data[i] = data[high];
}
while (low <= high)
{
mid = low + fib[k - 1] - 1; // 根据斐波那契数列进行黄金分割
if (data[mid] == searchValue)
{
if (mid <= length - 1)
{
return mid;
}
else
{
// 说明查找得到的数据元素是补全值
return length - 1;
}
}
if (data[mid] > searchValue)
{
high = mid - 1;
k = k - 1;
}
if (data[mid] < searchValue)
{
low = mid + 1;
k = k - 2;
}
}
return -1;
}
fibonacci查找时间复杂度o(1.44logn),常系数略有提升
二分查找版本B
版本B是基于“统一沿两个方向深入所需执行的比较次数”这种思想改进的。
从三分支改到两分支,也就是说,无论朝哪个方向深入,都只需要做1次元素的大小比较。
int binsearch(int* A,int e,int lo,int hi){
while(1<hi-lo){
int mid=(lo+hi)>>1;
(e<A[mid])?hi=mid:lo=mid;
}
return (e==A[lo])?lo:-1;
}
注:
1、用1次比较,确定深入[lo,mid)或者[mid,hi)。e>=A[mid]时,无法具体确定是>还是=,因此要继续保留A[mid]这个元素
2、退出while循环时,1==hi-lo,查找区间只包含A[lo]这个元素,但尚不清楚是查找成功还是失败,因此需要在返回前做一次对比。
3、优点:a、每步迭代仅需做一次比较判断
4、缺点:a、有多元素命中时,不能保证返回秩最大者 b、查找失败时简单的返回-1,而不能指示失败的位置 c、成功查找不能提前终止
二分查找版本C
int binsearch(int* A,int e,int lo,int hi){
while(lo<hi){
int mid=(lo+hi)>>1;
(e<A[mid])?hi=mid:lo=mid+1;
}
return --lo;
}
注:
1、经e<A[mid]比较后,确定深入[lo,mi)或(mi,hi)。这样操作之后,[0,lo)中的元素皆不大于e,[hi,n)中的元素皆大于e,直至循环,把原数组分成了两个部分。
2、while循环结束时,lo为大于e的元素的最小秩,故lo-1即不大于e的元素的最大秩
3、优点:a、每步迭代仅需做一次比较判断 b、有多元素命中时,保证返回秩最大者 c、查找失败时返回失败的位置
4、缺点:a、成功查找不能提前终止