1.二分法查找定义
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。 它是对一组有序的数字中进行查找,传递相应的数据,进行比较查找到与原数据相同的数据,查找到了返回对应的数组下标,没有找到返回-1;
优点: 比较次数少,查找速度快,平均性能好 ,占用系统内存较少
缺点: 待查表为有序表,插入删除困难
适用: 不经常变动而查找频繁的有序列表
2.二分查找的思想
生活中二分查找的思想无处不在。一个最常见的就是猜数游戏,我随机写一个 0 到 99 的数,然后你来猜我写的是什么。猜的过程中,我会告诉你每次是猜大了还是猜小了,直到猜中为止。假如我写的数是 23,猜数过程如下所示。
次数 | 猜测范围 | 中间数 | 对比大小 |
---|---|---|---|
第1次 | 0-99 | 49 | 49>23 |
第2次 | 0-48 | 24 | 24>23 |
第3次 | 0-23 | 11 | 11<23 |
第4次 | 12-23 | 17 | 17<23 |
第5次 | 18-23 | 20 | 20<23 |
第6次 | 21-23 | 22 | 22<23 |
第7次 | 23 | √ |
最多只需要 7 次就猜出来了,这个过程是很快的。同理,要查找某个数据是否在给定的数组中,我们同样也可以利用这个思想。
二分查找针对的是一个有序的数据集合,查找思想有点类似于分治,每次都通过和中间元素进行比较,将待查找区间缩小为之前的一半,直到找到要查找的元素或者区间缩小为 0 为止。
3.简单二分查找的算法实现
kotlin
版
//第一种:
/**
* @param array 有序数组
* @param left 数组低地址下标
* @param right 数组高地址下标
* @param key 查找元素
*/
fun binarySearch(data: IntArray, left: Int, right: Int, key: Int): Int {
var low = left
var high = right
while (low <= high) {
val mid = (low + high) / 2
when {
key == data[mid] -> return mid
key > data[mid] -> low = mid + 1
else -> high = mid - 1
}
}
return -1
}
//第二种:递归法:
/**
* @param array 有序数组
* @param left 数组低地址下标
* @param right 数组高地址下标
* @param key 查找元素
*/
fun binarySearch(data: IntArray, left: Int, right: Int, key: Int): Int {
while (left <= right) {
val mid = (left + right) / 2
return when {
key == data[mid] -> mid
key > data[mid] -> binarySearch(data, mid + 1, right, key)// left = mid + 1
else -> binarySearch(data, left, mid - 1, key) //right = mid - 1
}
}
return -1
}
注意事项
-
循环退出条件 left <= right
-
mid = left + ((right-left) >> 1),用移位运算优化计算性能
-
left 和 right 的更新分别是 mid+1 和 mid-1
-
Java版
public static void main(String[] args) {
int srcArray[] = {3, 5, 11, 17, 21, 23, 28, 30, 32, 50, 64, 78, 81, 95, 101};
System.out.println(binSearch(srcArray, 0, srcArray.length - 1, 111));
System.out.println(binSearch(srcArray, 78));
}
/**
* 二分查找普通实现。
*
* @param srcArray 有序数组
* @param key 查找元素
* @return 不存在返回-1
*/
private static int binSearch(int srcArray[], int key) {
int mid;
int start = 0;
int end = srcArray.length - 1;
while (start <= end) {
mid = (end - start) / 2 + start;
if (key < srcArray[mid]) {
end = mid - 1;
} else if (key > srcArray[mid]) {
start = mid + 1;
} else {
return mid;
}
}
return -1;
}
/**
* 二分查找递归实现。
*
* @param srcArray 有序数组
* @param start 数组低地址下标
* @param end 数组高地址下标
* @param key 查找元素
* @return 查找元素不存在返回-1
*/
private static int binSearch(int srcArray[], int start, int end, int key) {
int mid = (end - start) / 2 + start;
if (srcArray[mid] == key) {
return mid;
}
if (start >= end) {
return -1;
} else if (key > srcArray[mid]) {
return binSearch(srcArray, mid + 1, end, key);
} else if (key < srcArray[mid]) {
return binSearch(srcArray, start, mid - 1, key);
}
return -1;
}
4.二分查找的应用场景
二分查 找依赖的是顺序表结构,也就是数组,需要能够按照下标随机访问元素。
二分查找针对的是有序数据,如果数据无序,需要先进行排序。而如果有频繁的插入、删除操作,则每次查找前都需要再次排序,这时候,二分查找将不再适用。
数据量太小可以直接遍历查找,没有必要用二分查找。但如果数据之间的比较操作非常耗时,比如数据为长度超过 300 的字符串,则不管数据量大小,都推荐使用二分查找。
数据量太大不适合用二分查找。二分查找底层依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻。比如,我们有 大小的数据,如果希望使用数组来存储,那就需要 的连续内存空间来存储。
参考资料:
1.程杰 .大话数据结构. 北京:清华大学出版社, 2011