概述
二分查找(Binary Search)算法是一种针对有序数据集合的查找算法,也叫折半查找算法。
二分查找是一种非常高效的查找算法,时间复杂度仅仅为O(logn),这是一个非常“恐怖”的数量级,即便n非常非常大,对应的logn也很小。比如n等于2的32次方,大约是42亿。也就是说,如果我们在42亿个数据中用二分查找一个数据,最多需要比较32次。
局限性
(1)首先,二分查找依赖的是顺序表结构,简单点说就是数组,二分查找只能用在数据是通过顺序表来存储的数据结构上。如果你的数据是通过其他数据结构存储的,则无法应用二分查找。
(2)其次,二分查找针对的是有序数据。二分查找对这一点的要求比较苛刻,数据必须是有序的。如果数据没有序,我们需要先排序。所以,如果我们针对的是一组静态的数据,没有频繁地插入、删除,我们可以进行一次排序,多次二分查找。这样排序的成本可被均摊,二分查找的边际成本就会比较低。
但如果数据集合有频繁的插入和删除操作,要想用二分查找,要么每次插入、删除操作之后保证数据仍然有序,要么在每次二分查找之前都先进行排序。针对这种动态数据集合,无论哪种方法,维护有序的成本都是很高的。
所以,二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用。
(3)再次,数据量太小或者太大都不适合二分查找。
如果要处理的数据量很小,完全没有必要用二分查找。
二分查找的底层需要依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻。如果数据量太大,内存就比较吃力了。
二分查找变式问题
查找第一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == 0) || (a[mid - 1] != value)) {
return mid;
}else {
high = mid - 1;
}
}
return -1;
}
}
查找最后一个值等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == n - 1) || (a[mid + 1] != value)){
return mid;
}else {
low = mid + 1;
}
}
return -1;
}
}
查找第一个大于等于给定值的元素
public int bsearch(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] >= value) {
if ((mid == 0) || (a[mid - 1] < value)){
return mid;
}else {
high = mid - 1;
}
} else {
low = mid + 1;
}
}
return -1;
}
查找最后一个小于等于给定值的元素
public int bsearch7(int[] a, int n, int value) {
int low = 0;
int high = n - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else {
if ((mid == n - 1) || (a[mid + 1] > value)){
return mid;
}else {
low = mid + 1;
}
}
return -1;
}
}