浅谈二分查找

对于二分查找,大多数人都是模模糊糊对它有个印象,但具体写起代码来,又是各种问题。

首先,来看二分查找这个算法的基本方法:

1.在一个有序的连续数组当中(可以是升序,也可以是降序,本文均以升序为例)查找一个数,将这个数组的中间元素和给定数比较,若刚好相等则就找到了这个数,若大于给定数,则向这个数组的较低序列查找,反之则向这个数组的较高序列查找。

2.再拿着这个数组的较低或较高序列进行上一步的查找,如此循环,可供查找的序列范围会越来越小,直到数组边界,若还没有找到,则就证明这个数组当中不存在这个给定数。

以上大致是二分查找这个算法的基本思想,不断地拿中间数与目标数进行比较,从而进行查找,但是这里要明确的一点(数组必须是有序的)。

下面来看两个正确的代码实现:

代码1:

int BinarySearch(int* arr,int size,int data)
{
	assert(arr);
	int left=0;
	int right=size;
	int mid=0;

	while(left<right)
	{
		mid=(left&right)+((left^right)>>1);
		if(arr[mid]==data)
		{
			return mid;
		}
		else if(arr[mid]>data)
		{
			right=mid;
		}
		else
		{
			left=mid+1;
		}
	}

	return -1;
	
}
代码二:

int BinarySearch(int* arr,int size,int data)
{
	assert(arr);

	int left=0;
	int right=size-1;
	int mid=0;

	while(left<=right)
	{
		mid=(left&right)+((left^right)>>1);
		if(arr[mid]==data)
		{
			return mid;
		}
		else if(arr[mid]>data)
		{
			right=mid-1;
		}
		else
		{
			left=mid+1;
		}
	}
	return -1;
}
对比上面两份代码,最主要的区别在于right的初始值,while的条件以及循环体内对于边界left和right的移动。

这里要给出一个规律:

二分查找算法的边界,一般来说分两种情况,一种是左闭右开区间,类似于[left, right),一种是左闭右闭区间,类似于[left, right].需要注意的是, 循环体外的初始化条件,与循环体内的迭代步骤, 都必须遵守一致的区间规则,也就是说,如果循环体初始化时,是以左闭右开区间为边界的,那么循环体内部的迭代也应该如此.如果两者不一致,会造成程序的错误。

上述代码中的size表示的是数组的长度,right=size显然不能被包括在这个算法的控制区间内,所以上述代码1中的循环条件必须是left<right;而上述代码二由于right=size-1,而size-1则是必须被包含在我们算法的控制区间内的,所以当中的循环条件就得改为left<=right。

接下来,就是对于left和right的移动问题,

对于代码1,由于算法控制区间是左闭右开的,也就是[left,right),所以当arr[mid]>data的时候,则要将区间变为较低的左半区,而这里你如果将right=mid-1的话,很明显这里的mid-1对应的数据将不会在你算法的操作区间内,这里显然不合理,如果mid-1对应的数据刚好是我们要找的那个数呢?所以这里right=mid;而对于left而言,由于区间左半边是闭区间,而你之所以会走到这步,就是因为你之前mid对应的数据不对,所以mid就不应该在我们的控制区间内,所以left=mid+1;

而对于代码2,大致分析同代码1的一样,只是代码2是左闭右闭区间[left,right]。

另外,对于二分查找不得不提的一点就是,mid的求法,网上有许多博客上面贴的代码均是mid=(left+right)/2;这样的代码我们下来进行测试的时候,很多情况下并不能发现错误,但是这里是细节:当你给的left和right的数值非常大的情况下,这里就很有可能溢出了,所以这里我们可以改成mid=(right-left)/2+left,或者我们可以将它改成mid=(left&right)+((left^right)>>1),通过位运算符提高运算速率。



拓展:

上面的都是二分查找对于一个数据互不相同且有序的数组进行查找,当一个数组中有多个相同的数呢?

int Bi_Search(int a[],int n,int b)//  
{//返回同为b的第一个  
    if(n==0)  
        return -1;  
    int low = 0;  
    int high = n-1;  
    int last = -1;//用last记录上一次满足条件的下标  
    while (low<=high)  
    {  
        int mid = low +(high-low)/2;  
        if (a[mid]==b)  
        {  
            last = mid;  
            high = mid -1;  
        }  
        else if(a[mid]>b)  
            high = mid -1;  
        else  
            low = mid +1;  
    }  
  
    return last;  
  
}
  
int Bi_Search1(int a[],int n,int b)//大于b的第一个数  
{  
    if(n<=0)  
        return -1;  
    int last = -1;  
    int low = 0;  
    int high = n-1;  
    while (low<=high)  
    {  
        int mid = low +(high - low)/2;  
        if(a[mid]>b)  
        {  
            last = mid;  
            high = mid -1;  
        }  
        else if (a[mid]<=b)  
        {  
            low =mid +1;  
        }  
    }  
  
    return last;  
}

本文参考:http://www.cppblog.com/converse/archive/2009/10/05/97905.html


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值