现在的位置: 首页 > 综合 > 正文

二分查找算法

2018年03月23日 ⁄ 综合 ⁄ 共 4479字 ⁄ 字号 评论关闭

1 定义

       折半查找算法也称二分查找算法或折半搜索算法,是一种在有序数组(即前提必须是数组是已经排好序的)中查找某一特定元素的搜索算法。搜素过程是

1)计算中间元素mid

从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;

2)比较左边元素left, 比较右边元素right

如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。

       这种搜索算法每一次比较都使搜索范围缩小一半。如下图,lowerBound代表查找起始范围,upperBound代表查找终止位置,mid是中间元素。我们需要考虑的是算法何时结束?

2 java代码实现

我们定义如下的一个函数来实现二分查找

	
      / * @param a 待查找的数组
	 * @param fromIndex  查找的起始位置
	 * @param toIndex    查找的终止位置是(toIndex-1)
	 * @param key  要查找的数据
	 * @return  查找数据在数组中的位置
	 * 如果查找的数据不存在,则返回 - 1
	 * insertion point是查找数据如果插入数组时它插入的位置
	 */
	 public static int search(int[] a,int fromIndex,int toIndex,int key){
    //add  your code here
}

1) 代码第一次实现如下


观察上述代码,你能发现几个问题呢?

1)输入控制

如果数组为null,那么a[i]势必会抛出异常;

如果输入的fromIndex>toIndex,函数本身就没有意义;而且fromIndex和toIndex如果不在数组长度范围(0~length-1)内怎么办?

上述可能出现的异常,都源于没有对输入参数做控制处理。

		    //input control
		    if(a==null)  //if a is null
		    	return -1;
		    if(fromIndex<0 || toIndex>a.length-1 || fromIndex>toIndex)
		    	throw new Exception("illegal input!");

2)注意到while循环里面mid没有?在key<a[mid]和key>a[mid]之前,a[mid]已和key比较过,能进行key<a[mid]或key>a[mid],说明a[mid]肯定是和key不相等,所以后面没有必要再比较mid了,修改如下


3)注意到while循环没有循环结束的条件,这是致命的。那么什么时候二分查找结束呢?

注意到进入下一次二分查找改变的地方是upperBound=mid-1和lowerBound=mid+1,所以在这里我们考虑临界条件发生的情况。如果二分后还有2个元素时,我们记为i,i+1,那么mid=(2i+1)/2=i,那么分为2半(i,upperBound=mid-1=i-1)(lowerBound=mid+1=i+1,i+1)

如果是(i,upperBound=mid-1=i-1),那么再做一次划分就会出现lowerBound>upperBound的异常;

如果是(lowerBound=mid+1=i+1,i+1),那么再做一次划分也会出现lowerBound>upperBound;

所以循环结束的条件是数组中只剩2个元素时,直接折半查找每次把查找范围砍掉一半,最后当只有一个元素时(此时lowerBound=upperBound),经过语句upperBound=mid-1或lowerBound=mid+1后,lowerBound>upperBound,此时循环结束。所以临界条件(base
case)是lowerBound>upperBound,此时停止查找。

package zyang.binaryInsertSort;

/** 
 * @version 1.0
 * @date    2012-11-14 上午10:03:59 
 * @fuction  binary search
 * binary search
 * 折半搜索,也称二分查找算法、二分搜索,是一种在有序数组中查找某一特定元素的搜索算法。
 * 搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;
 * 如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
 * 如果在某一步骤查找的数组的下界大于上届,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
 * 时间复杂度:二分搜索每次把搜索区域砍掉一半,很明显时间复杂度为O(logn)。(n代表集合中元素的个数)
 * 空间复杂度:O(1)
 */

public class BinarySearch 
{
	private BinarySearch()
	{}
		
	/**
	 * 根据给定数组下标的范围,用折半查找算法查找指定数据
	 * 数组必须是已排好序的,否则查找结果没有意义。如果数组中有相等的元素,则会找到其中之一,至于是哪一个,是不确定的
	 * @param a 待查找的数组
	 * @param fromIndex  查找的起始位置
	 * @param toIndex    查找的终止位置
	 * @param key  要查找的数据
	 * @return  查找数据在数组中的位置
	 * 如果查找的数据不存在,则返回(- 1)
	 * insertion point是查找数据如果插入数组时它插入的位置
	 * @throws Exception 
	 */
	 //implement by loop
	 public static int search(int[] a,int fromIndex,int toIndex,int key) throws Exception
	    {  
		     /*
		      * input control:specail case
		      * 1)a is null
		      * 2)[fromIndex,toIndex]not in range [0, a.length-1]
		      * 3) fromIndex>toIndex
		      * step:
		      * 1) get mid
		      * 2) compare key with mid
		      * just equal:key==mid,return mid
		      * in left:key<mid, toIndex=mid-1
		      * in right:key>mid, fromIndex=mid+1
		      * 3) do the loop of step2
		      * base case: fromIndex > toIndex
		      */
		     
		 
		    //input control
		    if(a==null)  //if a is null
		    	return -1;
		    if(fromIndex>toIndex || fromIndex<0 || toIndex>a.length-1)
		    	throw new Exception("illegal input!");
		    
	    	int mid;  //mid
	    	while(fromIndex <= toIndex){  //base case:lowerBound>upperBound
	    		mid =(fromIndex+toIndex)/2;
	    		//found it
	    		if(key==a[mid])
	    			return mid;
	    		//left
	    		if(key<a[mid])
	    			toIndex=mid-1;
	    		//right
	    		else
	    			fromIndex=mid+1;
	    	}//end while
	    	return -1; //not found
	    }//end search()
	 
	 //implement by recursion
	 public static int searchRecursion(int[] a,int fromIndex, int toIndex, int key) throws Exception{
		    //input control
		    if(a==null)  //if a is null
		    	return -1;
		    if(fromIndex>toIndex || fromIndex<0 || toIndex>a.length-1)
		    	throw new Exception("illegal input!");
		    
		    int mid; //mid

		    	//base case
			    if(fromIndex>toIndex)
			    	return -1; //not found
			    else{
			    	mid=(fromIndex+toIndex)/2;
			    	if(key==a[mid])
			    		return mid;
			    	if(key<a[mid]) //left
			    		searchRecursion(a, fromIndex, mid-1, key);
			    	else
			    		searchRecursion(a, mid+1, toIndex, key);
			    }//end else
	 }//end searchRecursion()
	    	
	 public static int search(int[] a,int key) throws Exception
	 {
		return  search(a, 0, a.length-1, key);
	 }
	
	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		//test case
		  int[] a={1,2,3,4,5,6};  //test array
		  //specail input 
		  //a==null
		  System.out.println("a==null");
		  System.out.println(BinarySearch.search(null,2,5,4));
		  //fromIndex>toIndex
		  System.out.println("fromIndex>toIndex");
		  System.out.println(BinarySearch.search(a,5,2,4));
		  // out of array bound:fromIndex<0 toIndex<0 toIndex>a.length-1
		  System.out.println("out of array bound");
		  System.out.println(BinarySearch.search(a,-1,2,4));
		  System.out.println(BinarySearch.search(a,1,-2,4));
		  System.out.println(BinarySearch.search(a,1,7,4));
		  //logic test    
		  System.out.println("key in the arrary:"+BinarySearch.search(a, 4));
	      System.out.println("key not in the array:"+BinarySearch.search(a, 4));
	      char[] test={'2','3'};
	}
}

折半查找的边界条件

      折半查找每次把查找范围砍掉一半,最后当只有一个元素时(此时lowerBound=upperBound),经过语句upperBound=mid-1或lowerBound=mid+1后,lowerBound>upperBound,此时循环结束。所以临界条件(base case)是lowerBound>upperBound,此时停止查找。

3 复杂度分析

时间复杂度

折半查找每次把搜索区域砍掉一半,很明显时间复杂度为O\left( \log n  \right)。(n代表集合中元素的个数)

空间复杂度

O\left(  1  \right)

 

抱歉!评论已关闭.