数据结构 二分查找

  二分查找,即折半查找,仅适用于有序的顺序表。基本思想:将给定值key与表中中间位置元素比较,如果相等则查找成功并返回该元素的存储位置,否则key只能在前半部分或后半部分中(例如,顺序表升序排列时,如果key大于中间元素,那么key只能在后半部分)。在缩小的范围内继续上述过程,直到找到或者确定表中没有key。

  在数组{2,4,5,7,8,9,13,23,34,45}中查找元素34,过程见下图:

  数据结构 二分查找

 1 public class BinarySearch {
 2     public static int binarySearch(int[] arr, int key) {
 3         if (arr == null || arr.length == 0) {
 4             return -1;
 5         }
 6         
 7         int low = 0, high = arr.length - 1;
 8         // 二分查找
 9         while (low <= high) {
10             int mid = (low + high) >> 1;
11             // 1 如果找到
12             // 2 如果在前半部分
13             // 3 如果在后半部分
14             if (arr[mid] == key) {
15                 return mid;
16             } else if (arr[mid] > key) {
17                 high = mid - 1;
18             } else {
19                 low = mid + 1;
20             }
21         }
22         
23         // 没有找到
24         return -1;
25     }
26 }

  测试用例:

 1 import static org.junit.Assert.assertEquals;
 2 
 3 import org.junit.Test;
 4 
 5 public class BinarySearchTest {
 6 
 7     @Test
 8     public void test() {
 9         int[] arr = {2, 4, 5, 7, 8, 9, 13, 23, 34, 45};
10         assertEquals(8, BinarySearch.binarySearch(arr, 34));
11     }
12 
13 }

  测试结果:

  数据结构 二分查找

  二分查找的过程可以用判定树来描述,树中每个圆形结点表示一个元素,结点中的值是元素值。树中最下面的叶子结点是方形的,表示查找不成功的情况。查找成功时的查找长度为从根结点到目的结点的路径上的结点数,而查找不成功时的查找长度为从根结点到对应失败结点的父结点的路径上的结点数。每个结点值大于左孩子结点值,小于或等于右孩子结点值。如果有序序列有n个元素,那么对应的判定树有n个圆形的非叶子结点和n+1个方形的叶子结点。

  数据结构 二分查找

  二分查找给定值的比较次数不会超过树的高度。平均情况下二分查找的时间复杂度为O(log2n),效率高于顺序查找。因为二分查找需要方便地定位查找区域,所以二分查找适合于线性表的顺序存储结构,不适合链式存储结构,要求元素有序排列。

  

  lowerBound

  返回有序序列中第一个大于或等于给定值的元素位置,如果不存在则返回序列右边界+1。

 1 public static int lowerBound(int[] arr, int key) {
 2     if (arr == null || arr.length == 0) {
 3         return -1;
 4     }
 5     
 6     int low = 0, high = arr.length;
 7     while (low < high) {
 8         int mid = (low + high) >> 1;
 9         if (arr[mid] >= key) {
10             high = mid;
11         } else {
12             low = mid + 1;
13         }
14     }
15     
16     return high;
17 }

  测试用例:

1 @Test
2 public void test1() {
3     int[] arr = {2, 4, 5, 7, 8, 9, 13, 23, 34, 34};
4     assertEquals(8, BinarySearch.lowerBound(arr, 34));
5 }

  测试结果:

  数据结构 二分查找

 

  upperBound

  返回有序序列中第一个大于给定值的元素位置,如果不存在则返回序列右边界+1。

 1 public static int upperBound(int[] arr, int key) {
 2     if (arr == null || arr.length == 0) {
 3         return -1;
 4     }
 5     
 6     int low = 0, high = arr.length;
 7     while (low < high) {
 8         int mid = (low + high) >> 1;
 9         if (arr[mid] > key) {
10             high = mid;
11         } else {
12             low = mid + 1;
13         }
14     }
15     
16     return high;
17 }

  测试用例:

1 @Test
2 public void test2() {
3     int[] arr = {2, 4, 5, 7, 8, 9, 13, 23, 34, 37};
4     assertEquals(9, BinarySearch.upperBound(arr, 34));
5 }

  测试结果:

  数据结构 二分查找

 

  参考资料

  《2017年数据结构联考复习指导》 P233-234

  数据结构图文解析之:二分查找及与其相关的几个问题解析

  判定树

  二分查找求上、下界