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 复杂度分析
时间复杂度折半查找每次把搜索区域砍掉一半,很明显时间复杂度为。(n代表集合中元素的个数)
空间复杂度