一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
二分查找是一种效率较高的方法。二分查找要求线性表是有序表,即表中结点按关键字有序,并且要用向量作为表的存储结构。如果利用其对数组中的元素进行查找,则数组中的元素必须按照递增或者递减的规则进行排列。
上图简单描述了一下二分查找的过程。
1、题目分析
二分查找通过上图能够比较清晰地了解其查找过程,这里不做过多的解释,相信大家都能看明白。一定注意数组中元素是有序的,不要在图书馆丢书的时候使用二分查找哦。
2、解题
这里提供本质上是一样的两种思路,一种使用递归,一种使用循环。最后简单比较一下其性能怎么样。
思路一:循环
1.代码
//非递归、循环
function binarySearch1(arr: number[], target: number): number{
let low: number = 0;//设置第一个位置
let high: number = arr.length;//设置第二个位置
let len = arr.length;
//如果数组长度为0,那么不会存在目标值,直接返回-1
if(len == 0) return -1
for(let i = low; i < high; i++){
let mid = Math.floor((low + high) / 2);//获得中间
let midVal = arr[mid]
if( midVal < target){
//说明目标值可能在中间值的右侧,那么将变量low改变为中间位置(循环本身会再加1,不会再计算midVal),再继续循环,下次循环会从i=low+1开始
low = mid;
}else if(midVal > target){
//说明目标值可能在中间值的左侧,那么将改变high改变为中间位置,i正常增加
high = mid;
}else{
//说明目标值和中间值相等,直接返回对应的index
return mid
}
}
//如果循环结束,依然没有找到则直接返回-1
return -1
}
这里也可以使用while循环,里边逻辑一样,循环条件为low<=high,可能使用while循环更好理解一些,感兴趣的同学可以自行尝试一下,也可以参考下方的递归方式去写一下while循环,还是比较简单的。
2.功能测试
//功能测试
const arrTest = [1,2,3,4,5,6,7,8,9];
const ind = binarySearch1(arrTest, 7)
console.log(ind) //6
const ind1 = binarySearch1(arrTest, 4)
console.log(ind1)//3
const ind2 = binarySearch1(arrTest, 100)
console.log(ind2) //-1
const ind3 = binarySearch1([], 100)
console.log(ind3) //-1
//符合预期
3.复杂度分析
思路二:递归
1.代码
function binarySearch2(arr: number[], target: number, low?: number, high?: number): number{
const len = arr.length;
if(len == 0) return -1
if(low == null) low = 0;
if(high == null) high = arr.length -1
if(low > high) return -1;//当low大于high的时候说明结束,相等的时候需要继续进行
const mid = Math.floor((low + high) / 2);//获取中间index
const midVal = arr[mid];//获取中间值
if(target < midVal){
//说明目标值在左侧
return binarySearch2(arr, target, low, mid - 1)
}else if(target > midVal){
//说明目标值在右侧
return binarySearch2(arr, target, mid + 1, high)
}else{
return mid;
}
}
2.功能测试
const arrTestAgain = [1,2,3,4,5,6,7,8,9];
const ind4 = binarySearch2(arrTestAgain, 7)
console.log(ind4) //6
const ind5 = binarySearch2(arrTestAgain, 4)
console.log(ind5)//3
const ind6 = binarySearch2(arrTestAgain, 100)
console.log(ind6) //-1
const ind7 = binarySearch2([], 100)
console.log(ind7) //-1
//符合预期
3.复杂度分析
两种方案
3、性能比较
const arrTimeTest = [10, 20, 30, 40, 50, 60, 70, 80];
const target = 20;
console.time('binarySearch1')
for(let i = 0; i< 1000 * 10000; i++){
binarySearch1(arrTimeTest, target)
}
console.timeEnd('binarySearch1') //binarySearch1: 79.932861328125 ms
console.time('binarySearch2')
for(let i = 0; i< 1000 * 10000; i++){
binarySearch2(arrTimeTest, target)
}
console.timeEnd('binarySearch2')//binarySearch2: 154.64990234375 ms
这里可以简单的看出,二者执行的时间大约是个2倍的关系,虽然有差距,但是二者处于同样的数量级。这里做性能比较,显而易见有点较真的意思,但也正是这样较真,我们可以看出递归的效率相对而言是比较低的。
4、总结
- 通过最后的性能比较,我们可以发现使用循环比使用递归要稍微快一些,虽然他们的复杂度是一样的。递归调用了多次函数,执行时间较长,说明函数的调用时是会消耗性能的.
- 考察排序,凡有序,必二分;
- 凡二分,时间复杂度必包含O(logn),注意包含。
- 递归和非递归的对比
- 递归的代码相对比较简洁
- 非递归性能会更好一点(和递归时同级别的)