二分查找
思路:根据中间点确定目标值范围,然后不断缩小范围直到找到。
时间复杂度
而logn这种对数时间复杂度,是一种极其高效的时间复杂度,即使n非常大,对应的logn也很小。例如:当n=42亿时,logn=32。并且有时常量级时间复杂度O(1)的执行效率都不如对数的时间复杂度。
代码(有序数组,且无重复元素):
function binarySearch(array, value) {
var low = 0;
var high = array.length-1;
while(low <= high) {
var mid = Math.ceil((low+high)/2);
if (value < array[mid]) {
high = mid - 1;
} else if (value > array[mid]) {
low = mid + 1;
} else {
return mid;
}
}
return -1;
}
复制代码
注意点:
- 循环退出条件:low<=high,不是low<high
- mid的取值:整数值、当low和high数值很大时,加和可能溢出。low+(high-low)>>1
- low和high的更新:需要+1和-1,如果直接low=mid可能死循环。如:hight=3,mid=3,而a[3]!=value时。
递归实现:
function binarySearch(array,low,high, value) {
if (low > high) return -1;
var mid = low + ((high-low)>>1);
if (array[mid] > value) {
return binarySearch(array,low, mid-1, value);
} else if (array[mid] < value) {
return binarySearch(array, mid+1, high, value);
} else {
return mid;
}
}
复制代码
二分查找使用范围
- 二分查找依赖顺序表结构,即数组
- 针对有序数据,或者一次排序多次查找的情况。动态数据集不适用。
- 适用数据量大的情况,或者比较比较耗时(长字符串比较),需要减少比较次数的情况。
练习:求解平方根
// 求解整数n的平方根
// 牛顿迭代法求 f(n): n的平方根
// f(n+1) = (f(n)+k/f(n))/2;
// 假设f(1) = n/2 可以是任意数,尽量接近
// 当 |f(n+1)-f(n)|<0.00001时,可得结果f(n)
function squareRoot1(n) {
var y = n/2;
while(Math.abs((y+n/y)/2-y) >= 0.00001) {
y = (y+n/y)/2;
}
return y;
}
// 二分法求平方根
// 整数n的平方根x x*x = n 则0<=x<=n的。因此可以转化为在0~n之间查找x,使得x*x=n,但因为有小数存在因此|x*x-n|<0.00001时,就算找到结果。
function squareRoot2(n) {
var low = 0;
var high = n;
while (low <= high) {
var mid = low+(high-low)/2;
if (Math.abs(mid*mid-n) > 0.00001) {
if (mid*mid > n) {
high = mid;
} else {
low = mid;
}
} else {
return [mid.toFixed(5), Math.sqrt(n)];
}
}
return -1;
}
复制代码