二分查找

211 阅读4分钟

 

二分查找针对的是一个 有序的数据集合,查找思想和分治的思想类似,每一个通过和集合中间元素的对比,将带查找的区间减小一半,直到找到元素或者区间长度为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 是否是数组最后一位。