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)输入控制
如果数组为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!");
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代表集合中元素的个数)
空间复杂度