概念
二分查找(Binary Search)算法,也叫折半查找算法。
当要从一个序列中查找一个元素的时候,二分查找是一种非常快速的查找算法。
二分查找是针对有序数据集合的查找算法,如果是无序数据集合就遍历查找。
二分查找之所以快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半的元素。因此可能包含目标元素的有效范围就收缩得很快,而不像顺序查找那样,每次仅能排除一个元素。
原理
比如有一个有序表数组[1,3,5,7,9,11,13,15,17,19,21],它是按照从小到大的顺序来进行排列的,现在需要在该有序表中查找元素19,步骤如下:
首先设置两个指针low和high,分别指向数据集合的第一个数据元素1(位序为0)和最后一个数据元素21(位序为10)。
然后把整个数据集合长度分成两半,并用一个指针指向它们的临界点,所以定义指针mid指向了中间元素11(位序5),也就是说mid=(high+low)/2,其中high和low都代表所指向的元素的位序,如下图:
接着,将mid所指向的元素(11)与待查找元素(19)进行比较。
因为19大于11,说明待查找的元素(19)一定位于mid和high之间。所以继续折半,则low = mid+1,而mid = (low+high)/2,结果如下图:
接着,又将mid所指向的元素(17)与待查找元素(19)进行比较,由于19大于17,所以继续折半,则low = mid+1,而mid = (low+high)/2,结果如下图:
最后,又将mid所指向的元素(19)与待查找元素(19)进行比较,结果相等,则查找成功,返回mid指针指向的元素的位序。
如果查找的元素值不是19,而是20,那么在最后一步之前还得继续折半查找,最后出现的情况如下图:
代码实现
public class Test01 {
public static void main(String[] args) {
//二分法查找
int[] arr = {1,2,3,4,5,6,7,8,9,11,11,11,11,11,11};
int index = binarySearch01(arr, 11);
System.out.println("指定元素出现的下标位置:" + index);
List<Integer> indexList = binarySearch02(arr, 11);
System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));
index = recursionbinarySearch01(arr, 0, arr.length-1, 11);
System.out.println("递归方式 - 指定元素出现的下标位置:" + index);
indexList = recursionbinarySearch02(arr, 0, arr.length-1, 11);
System.out.println("递归方式 - 指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));
}
/**
* 有序的数组中查找某个元素出现的下标位置
* 不使用递归的二分查找
* 返回出现的下标位置
*/
public static int binarySearch01(int[] arr,int val){
int low = 0;
int high = arr.length-1;
while(low <= high){
int mid = (low + high)/2;
if(val > arr[mid]){
//目标在右侧
low = mid+1;
}else if(val < arr[mid]){
//目标在左侧
high = mid-1;
}else{
return mid;
}
}
return -1;
}
/**
* 有序的数组中查找某个元素首次出现的下标位置
* 不使用递归的二分查找
* 返回下标集合
*/
public static List<Integer> binarySearch02(int[] arr,int val){
int low = 0;
int high = arr.length-1;
while(low <= high){
int mid = (low + high)/2;
if(val > arr[mid]){
//目标在右侧
low = mid+1;
}else if(val < arr[mid]){
//目标在左侧
high = mid-1;
}else{
// 定义放置索引下标的集合
List<Integer> list = new ArrayList<>();
// 将首次查找的位置放入集合
list.add(mid);
// 判断是否还有重复值
int index = mid + 1;
while(index < arr.length){
if(arr[index] == val){
list.add(index);
}else{
break;
}
index++;
}
index = mid-1;
while(index >= 0){
if(arr[index] == val){
list.add(index);
}else{
break;
}
index--;
}
return list;
}
}
return null;
}
/**
* 有序的数组中查找某个元素出现的下标位置
* 使用递归的二分查找
* 返回出现的下标位置
*/
public static int recursionbinarySearch01(int[] arr,int low,int high,int val){
if(val < arr[low] || val > arr[high] || low > high){
return -1;
}
int mid = (low + high)/2;
if(val > arr[mid]){
//目标在右侧
return recursionbinarySearch01(arr, mid+1, high, val);
}else if(val < arr[mid]){
//目标在左侧
return recursionbinarySearch01(arr, low, mid-1, val);
}else{
return mid;
}
}
/**
* 有序的数组中查找某个元素首次出现的下标位置
* 使用递归的二分查找
* 返回下标集合
*/
public static List<Integer> recursionbinarySearch02(int[] arr,int low,int high,int val){
if(val < arr[low] || val > arr[high] || low > high){
return null;
}
int mid = (low + high)/2;
if(val > arr[mid]){
//目标在右侧
return recursionbinarySearch02(arr, mid+1, high, val);
}else if(val < arr[mid]){
//目标在左侧
return recursionbinarySearch02(arr, low, mid-1, val);
}else{
// 定义放置索引下标的集合
List<Integer> list = new ArrayList<>();
// 将首次查找的位置放入集合
list.add(mid);
// 判断是否还有重复值
int index = mid + 1;
while(index < arr.length){
if(arr[index] == val){
list.add(index);
}else{
break;
}
index++;
}
index = mid-1;
while(index >= 0){
if(arr[index] == val){
list.add(index);
}else{
break;
}
index--;
}
return list;
}
}
}
优缺点
优点:速度快,不占空间,不开辟新空间
缺点:必须是有序的数组,数据量太小没有意义