js实现二分法(递归和非递归)
1.算法使用范围:
二分法查找使用数据量较大的时候,但是条件是数据需要先排好顺序。
2.算法描述:
-
确定该区间的中间位置,每次查找中间的值,数组被分为左边和右边两部分。
-
将查找的值与中间值比较。
(1)如果相等,就返回对应的下标。
(2)如果查找的值大于中间的值,则将右边部分数组继续分成左右两部分,确定中间位置的值,继续步骤2。
(3)如果小于,则在数组左半边继续二分查找。
-
重复上述步骤,直到找到要查找的值位置。
总而言之,每一次查找与中间值比较,判断是否查找成功,不成功当前查找区间缩小一半,循环查找,即可!!!
使用beautiful_lady的博文图解截图如下:
3.二分法的时间复杂度
时间复杂度:O(log2n)。
-
最坏情况是查找到最后一个元素(或者第一个元素),Master定理T(n)=T(n/2)+O(1),所以时间复杂度T(n)=O(log2n)。
-
最好情况是查找的元素等于中间元素,只需要比较一次(奇数长度数列的正中间,偶数长度数列的中间靠左的元素)。
4.while循环实现代码实现
while循环实现二分查找是非递归的方式,实现代码如下:
function divisionTwo(arr,searchNum){
//获取数组长度
var len = arr.length;
//起始位置
var beginIndex = 0;
//结束位置
var lastIndex = len - 1;
//其实位置总是小于等于结束位置的
while(beginIndex<=lastIndex){
//中间位置
//floor(x):返回小于等于x的最大整数
var mid =Math.floor((beginIndex+lastIndex)/2) ;
//查找的值searchNum与中间值arr[mid]相比较
if (searchNum == arr[mid]){
return mid
}else if(searchNum > arr[mid]){
//如果查找的值searchNum大于中间值,在右侧使用二分法,
beginIndex = mid +1;
}else{
//如果查找的值searchNum小于中间值,在左侧使用二分法
lastIndex = mid -1;
}
}
return arr;
}
//例如要在arry数组中查找数字18
var arry = [1,2,3,5,7,11,15,18,22,45]
console.log(divisionTwo(arry,18))
console.log(arry[divisionTwo(arry,18)])
输出结果:
5.递归算法代码实现
要想理解递归实现二分法,首先要理解什么是递归。
-
递归算法
概念:函数通过直接调用自身,或者两个函数之间的互相调用,来达到一定的目的,比如排序,阶乘等。
在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
递归次数过多容易造成栈溢出。
阶乘是最简单的递归,如下:
function factorial(n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } }
上面程序,n不等于0时,factorial函数调用自身,此时参数传入n-1,即第一次是返回n*(n-1),第二次是返回n*(n-1)*(n-2)。。。。依次类推,直到n等于0,返回1。这个思想就是递归。
有两个关键点:
(1)函数调用自身
(2)函数的参数要做缓存,下次调用的时候必须是上一次调用参数变化后的值,否则不做缓存,每次调用自身的时候,n都是初始化时设置的那个值,导致输出结果错误。
-
递归优化
递归优化: 函数可以用对象去记住先前操纵的成果,从而能避免无谓的运算,避免重复工作,将执行过的运算或操作,缓存起来,如果后续有相同的操作可直接从缓存中查找,没有相同操作则进行递归,可大大减少递归的工作量,提高性能。
-
递归实现二分法
递归的方法实现二分查找,代码如下:
//递归方式 function divisionTwo(arr,searchNum,beginIndex,lastIndex){ //获取数组长度 var len = arr.length; //起始位置,参数缓存,下次调用时使用 beginIndex = beginIndex||0; //结束位置,参数缓存,下次调用时使用 lastIndex = lastIndex||len - 1; //中间位置 //floor(x):返回小于等于x的最大整数 var mid =Math.floor((beginIndex+lastIndex)/2) ; //searchNum与中间值arr[mid]相比较,一直没有找到要查找的元素 //beginIndex值会增大,lastIndex值会减小,beginIndex > lastIndex时说明查找的元素不在数组中,返回-1 if (beginIndex > lastIndex) { return -1; } //查找的值searchNum与中间值arr[mid]相比较 if (searchNum == arr[mid]){ return mid }else if(searchNum > arr[mid]){ //查找的值searchNum大于中间值,在右侧查找,起始位置想右侧移动mid+1位 beginIndex = mid +1; }else{ //查找的值searchNum小于中间值,在左侧查找,结束位置向左侧移动mid-1位 lastIndex = mid -1; } //递归,函数调用自身 return divisionTwo(arr,searchNum,beginIndex,lastIndex) } //例如要在arry数组中查找数字18 var arry = [1,2,3,5,7,11,15,18,22,45] console.log(divisionTwo(arry,18))//7 console.log(arry[divisionTwo(arry,18)])//18
分析:上面程序与while循环实现二分查找的不同之处就是取消了while循环,使用递归调用自身函数代替。while循环实现二分查找是非递归的方法。
输出结果分析:
对于有序字符串也可以使用二分查找。如下:
对数字和字符串混合的数组也可以使用二分查找算法,建议要各自排好序。如下: