二分查找针对的是一个 有序的数据集合,查找思想和分治的思想类似,每一个通过和集合中间元素的对比,将带查找的区间减小一半,直到找到元素或者区间长度为0。
时间复杂度
O(logn) 指数时间复杂度,在某一些情况下比常数级别的时间复杂度还要高效。
=> 如果数组足够多 ,常数级别需要一一遍历,而二分查找需要较少次数就看可以找到结果。
应用场景
底层依赖数组,而数组必须是连续的存储空间。( 数组通过下标访问,则存储空间一定连续 )
二分查找可以解决的问题,散列表和二叉树也可以,但是散列表和二叉树 的需要较多的额外存储空间。
数据必须有序,如果无序则首先排序,然后使用二分查找。
静态数据最好,如果频繁的插入删除,则需要先排序然后查找,维护成本较高。
数据量太小不适合二分查找,小的话使用顺序遍历就行了。
数据量 太大不适合二分查找,太大的话 对内存空间要求比较苛刻,空间必须连续。
代码
// 循环
function erFenChaoZhao(arr, val) {
let height = arr.length - 1;
let low = 0;
while (low <= height) {
let mid = parseInt(low + (height - low) / 2);
if (arr[mid] == val) {
return val;
} else if (val > arr[mid] ) {
low = mid + 1
} else if (val < arr[mid] ) {
height = mid - 1
} else {
return '没有找到';
}
}
return '没有找到';
}
let arr = [1, 2, 3, 4, 6, 7, 8, 9, 0];
console.log(erFenChaoZhao(arr, 5)); // 没有找到
console.log(erFenChaoZhao(arr, 1)); // 有递归
function erFenChaoZhaoCircle(arr, val) {
let height = arr.length - 1;
let low = 0;
while (low <= height) {
let mid = parseInt(low + (height - low) / 2);
if (arr[mid] == val) {
return val;
} else if (val > arr[mid]) {
low = mid + 1;
erFenChaoZhaoCircle(arr.splice(low, height));
} else if (val < arr[mid]) {
height = mid - 1;
erFenChaoZhaoCircle(arr.splice(0, height));
} else {
return '没有找到';
}
};
return '没有找到';
};
let arr = [ 1, 2, 3, 4, 6];
console.log(erFenChaoZhaoCircle(arr, 5)); // 没有找到
console.log(erFenChaoZhaoCircle(arr, 1)); // 有注意点
代码的终结条件 low <= height
mid 的取值,如果数组过大,那么 low + height 可能造成溢出 ,使用 low + (height - low )/2
low=mid+1,high=mid-1。注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。比如,当 high=3,low=3 时,如果 a[3]不等于 value,就会导致一直循环不退出。
变形例题
1、查找数组中第一个和给定元素相等的值? ( 假设数组有序 )
function firstValEqualVal(arr, val) {
let low = 0,
height = arr.length - 1;
while (low <= height) {
let mid = parseInt(low + (height - low) / 2);
if (val > arr[mid]) {
low = mid + 1;
} else if (val < arr[mid]) {
height = mid - 1;
} else {
if (mid === 0 || arr[mid - 1] != val) {
return mid;
} else {
height = mid - 1;
}
}
}
}
let arr1 = [1, 2, 3, 4, 5, 5, 5, 5, 6, 9];
console.log(firstValEqualVal(arr1, 5)) // 4在基础形式上稍稍做修改,重点理解
if (mid === 0 || arr[mid - 1] != val) {
return mid;}
else {
height = mid - 1;
}if 条件中 mid === 0 如果找到的元素是第一个,那么这个就是我们要找的元素;
if 条件中 arr[ mid - 1 ] != val 如果找的元素的前一个元素不等于 要找的元素,那么就是找到的元素就是我们要找的元素。
else 说明 该数组中存在下一个和要找的值相等的元素,有因为数组是有序的,那么缩小区间,就是 height = mid - 1 ;
2、查找最后一个值等于给定值的元素
function lastValEqualVal(arr, val) {
let low = 0;
let height = arr.length - 1;
while (low <= height) {
let mid = parseInt(low + (height - low) / 2);
if (arr[mid] > val) {
height = mid - 1;
} else if (arr[mid] < val) {
low = mid + 1;
} else {
if ((mid == 0) || (arr[mid + 1] != val)) {
return mid
} else {
low = mid + 1;
}
}
}
return -1;
}
let arr2 = [1, 2, 3, 4, 5, 5, 5, 9, 9];
console.log(lastValEqualVal(arr2, 9))那就找后面一个是否等于给定值,同时还要看mid是数组的第一位
3、查找第一个大于等于给定值的元素
function firstValBiggerThanVal(arr, val) {
let low = 0;
let height = arr.length - 1;
while (low <= height) {
let mid = parseInt(low + (height - low) / 2);
if (arr[mid] >= val) {
if ((mid == 0) || (arr[mid - 1] < val)) return mid;
else height = mid - 1;
} else {
low = mid + 1
}
}
return -1;
}
let arr3 = [1, 2, 3, 4, 2, 3, 6, 6, 9];
console.log(firstValBiggerThanVal(arr3, 7))那就是查找前一位是否是小于给定值,同时也要注意是否是数组第一位
4、查找最后一个小于等于给定值的元素
function lasterValSmallhanVal(arr, val) {
let low = 0;
let height = arr.length - 1;
while (low <= height) {
let mid = parseInt(low + (height - low) / 2);
if (arr[mid] > val) {
height = mid - 1;
} else {
if ((mid != arr.length - 1) || (arr[min + 1] > val)) return mid;
else low = mid + 1
}
}
return -1;
}
let arr4 = [1, 2, 3, 4, 5, 5, 6, 6, 6, 9];
console.log(lasterValSmallhanVal(arr4, 6))那就是查找后一位是否是大于给定值,同时mid 是否是数组最后一位。