前言
二分查找,重在二分,一分两半,就好像用剪刀剪绳子一样将序列一剪两半,当然剪序列和剪绳子是不一样的,绳子可以按缝来剪,而剪序列呢,咱们需要按中间元素来剪,(什么叫中间元素,其实就是在一个按大小顺序排列好的序列中,处于中间下标的元素,所以二分查找是适用于有序序列的),剪完之后的中间元素呢?难道这个元素要被抛弃了吗?当然不行,咱们不能随意放弃任何一个元素,而且这个元素其实在二分查找中是具有非常大的作用和意义的;它分割了序列,将序列分割成两个子序列,左子序列中所有元素都小于等于中间元素,右子序列中所有元素都大于等于中间元素,这样可以更加准确的找到查找元素在序列中的位置。它相对于顺序查找提高了效率,顺序查找需要一个一个元素的进行比对,而二分查找只需要同一个个子序列的中间值进行比对就可以。
基本思想
1、二分查找是需要一个已排好序的序列,并且该序列是可以通过下标取值,这是前提。
2、之后将该序列依照中间元素将其一分为二,拿中间元素和咱们需要查找的元素做比较。
3、若待查找元素若比中间元素小则去左边区间找,比中间元素大则去右边区间找,等于则返回中间元素下标。
这个比较的目的在于锁定查找元素所在区间,这样也可以大大减少比较次数。
4、当锁定查找元素所在区间之后,我们仍然需要不断分割该区间(对半分),这样不断的分割区间的目的仍然是为了减少比较次数和缩小查找范围(我们每次只需要和中间元素进行比较就可以了)。
5、一直缩小区间直到只有一个元素的时候(区间左边界和右边界重合),若此时仍然没有找到该元素,说明该元素不在本序列中。
图示
1、原始序列(已排好序的):
2、折半(找出中间元素):
3、判断我们要找的元素在中间值(mid)的左边还是右边亦或者是等于中间值
4、若我们要查找的值不等于mid,那我们就需要再次分割,在分割之前,我们需要移动left或者right,若比mid小则left不动,right=mid-1
,若比mid大则right不动,left=mid+1
,现在为了方便演示,我们假设要查找的值大于mid
若此时我们要找的元素小于第二次分割时的中间值,left和right重合(指向了同一个元素),就算我们再取中间值也是它本身,此时比较元素和它的大小关系,一旦不等于,那么left就会大于right了,这也就意味这要查找的元素不在本序列中。
实现细节
所需变量
array:查找的数组
left:区间的最左边元素的下标
right:区间最右边元素的下标
mid:区间的中点(因为下标的int类型的,所以运算结果是向下取整的)
分割前提(查找前提)
left <= right
只有当left小于等于right时才有分割和比对的意义(说明区间里还有至少一个元素)
分割细节
若查找元素的值小于中间值,则获取中间值的左边的区间
if (target < array[mid]) {
right = mid - 1;
}
若查找元素的值大于中间值,则获取中间值的右边的区间
if (target > array[mid]) {
left = mid + 1;
}
代码实现
package com.etime.enhance.search;
public class Search {
public static void main(String[] args) {
//有序序列
int[] array = {1, 2, 3, 4, 4, 5, 6, 7, 8, 9};
int search = search(array, 5);
System.out.println(search);
}
public static int search(int[] array, int target) {
//对array进行判空
if (array != null && array.length > 0) {
//初始化左右边界
int left = 0;
int right = array.length - 1;
//中间值到循环中去赋值,因为每次分割后的区间不一样,它的中间值也不一样
int mid;
//用循环来进行区间的分割,当left>right时,说明本序列中没有你要查的元素
while (left <= right) {
//求取中值
mid = (left + right) / 2;
//中值元素和要查询元素的比对,小于中值则在其左边,大于它则在其右边,若等于就返回中值下标
if (target < array[mid]) {
//分割的关键
right = mid - 1;
} else if (target > array[mid]) {
//分割的关键
left = mid + 1;
} else {
return mid;
}
}
}
//若array为空,其中无元素或序列中没有要查询的值则返回-1
return -1;
}
}