二分查找学习总结

基于减而治之的思想,在有序向量的区间[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、成功查找不能提前终止

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值