无处不在的二分搜索

        二分搜索想必学计算机的人都很熟了,它是很多算法里的都会用到的,编程珠玑里有专门的介绍。二分搜索的框架大家都很熟悉,但要完完整整写对也不是那么容易的事情。这很考验一个人的基本功的,这也是面试官常考的一个问题。下面就对其进行一下总结。

       1. 常规写法

int BinarySearch(int *a, int l, int r, int z){
	if(l > r)
		return -1;
	else if(l == r)
		if(a[l] == z)
			return l;
		else 
			return -1;
	else{
		int mid;
		while(l <= r){
			mid = (l + r)/2;
			if(a[mid] < z)
				l = mid +1;
			else if(a[mid] > z)
				r = mid - 1;
			else
				return mid;
		}
		return -1;
	}
}

        这里需要注意几点:

        (1)mid的取法,是取上整(mid = (l + r +1)/2)还是取下整(mid = (l + r )/2)。这里是取下整,这意味着mid总是倾向于偏小的索引;否则,mid
倾向于偏大的索引 。

        (2)l 或 r 的更新。当mid倾向于偏小值时(取下整),当查找值比中间值还大时( a[mid] < z ),l 要倾向于偏大值,所以 l = mid + 1,不然就可能出现mid索引值不变导致死循环;相反,当mid倾向于偏大值时(取上整),当查找值比中间值还小时( a[mid] > z ),r 要倾向于偏小值,所以 r = mid - 1。

       现在再说说二分搜索算法的改进。使内层循环更紧凑。这里内层用的是三路比较,现在改进为二路比较。

    

int BinarySearch(int *a, int l, int r, int z){
	if(l > r)
		return -1;
	else if(l == r)
		if(a[l] == z)
			return l;
		else 
			return -1;
	else{
		int mid;
		while(l < r){
			mid = (l + r)/2;
			if(a[mid] < z)
				l = mid +1;
			else
				r = mid;
		}
		if(a[l] == z)
			return l;
		return -1;
	}
}


        同样,这里还是要注意前面提到的那两点。不同的是,(1)如果查找值不比中间值大,即 if(a[mid] < z) 不成立的话,r = mid ,包含了<=两个情况;(2)while循环的条件变成了 l < r ,l == r 变成了终止条件,放到外面;(3)while循环外面的 if 判断,当 l == r  时,a[l] 和 a[r] 是否都指向要查找的值。

 

      二分查找改进版

     当数组中有重复元素时,返回满足条件的最右元素或最左元素。我自己写了个java实现版

class BinarySearch {
	//在大小为n的数组a中查找值为n的元素,返回满足条件的最右元素
	public static int binarySearchR(int[] a, int x, int n) {
	    if(n > 0 && x >= a[0]) {
	        int left = 0;
	        int right = n-1; 
	        while(left < right) {
	            int middle = (left + right + 1)/2;
	            if(x < a[middle])
	                right = middle - 1;
	            else 
	                left = middle;
	        }
	        if(x == a[left])
	            return left;
	    }
	    return -1;
	}
	
	//在大小为n的数组a中查找值为n的元素,返回满足条件的最左元素
	public static int binarySearchL(int[] a, int x, int n) {
	    if(n > 0 && x >= a[0]) {
	        int left = 0;
	        int right = n-1; 
	        while(left < right) {
	            int middle = (left + right)/2;
	            if(x > a[middle])
	                left = middle + 1;
	            else 
	                right = middle;
	            
	        }
	        if(x == a[left])
	            return left;
	    }
	    return -1; 
	}
}
public class BinarySearchTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		int[] a  = {1,1,6,6,6,6,6,8,8};
		
		System.out.println(BinarySearch.binarySearchL(a, 1, 9));
		System.out.println(BinarySearch.binarySearchR(a, 1, 9));
		System.out.println(BinarySearch.binarySearchL(a, 6, 9));
		System.out.println(BinarySearch.binarySearchR(a, 6, 9));
		System.out.println(BinarySearch.binarySearchL(a, 8, 9));
		System.out.println(BinarySearch.binarySearchR(a, 8, 9));
	}

}


         近日看过一篇博问,里面讲到二分查找中容易忽略的一个错误——数组下标上溢。当给middle赋值时,middle = (left + right)/2 或 middle = (left + right + 1)/2,如果数组很大的话,left + right可能上溢。

所以建议在给给middle赋值时,采用 middle = left + ( right - left )/2 或 middle = left + ( right - left  + 1)/2

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值