二分查找01.基本二本查找及其变种

基本的二分查找

我们假设数据大小是 n,每次查找后数据都会缩小为原来的一半,也就是会除以 2。最坏情况下,直到查找区间被缩小为空,才停止。

可以看出来,这是一个等比数列。其中 n/2k=1 时,k 的值就是总共缩小的次数。而每一次缩小操作只涉及两个数据的大小比较,所以,经过了 k 次区间缩小操作,时间复杂度就是 O(k)。通过 n/2k=1,我们可以求得 k=log2n,所以时间复杂度就是 O(logn)。

<?php
function bsearch($arr,$n){
    $low=0;
    $high=count($arr)-1;
    //注意边界
    while($low<=$high){
//        $mid=intval(($low+$high)/2);
        $mid=$low+(($high-$low)>>1);
        if($arr[$mid]==$n){
            return $mid;
        }elseif($n<$arr[$mid]){  //n在 [low,mid-1]
            $high=$mid-1;
        }else{  //n 在[mid+1,high]
            $low=$mid+1;
        }
    }
    return -1;
}

易出错的三点

1. 循环退出条件

注意是 low<=high,而不是 low2.mid 的取值

实际上,mid=(low+high)/2 这种写法是有问题的。
因为如果 low 和 high 比较大的话,两者之和就有可能会溢出。
改进的方法是将 mid 的计算方式写成 low+(high-low)/2。
更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 low+((high-low)>>1)。因为相比除法运算来说,计算机处理位运算要快得多。

3.low 和 high 的更新

low=mid+1,high=mid-1。 注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环 比如,当 high=3,low=3 时,如果 a[3] 不等于 value,就会导致一直循环不退出

递归方式实现

//递归方式实现
function bsearch_recursion($arr,$n){
    return bsearch_recursion_internally($arr,0,count($arr)-1,$n) ;
}
function bsearch_recursion_internally($arr,$low,$high,$n){
    if($high<$low){
        return -1;
    }
    $mid=$high-intval(($high-$low)>>1);
    if($n==$arr[$mid]){
        return $mid;
    }
    if($n<$arr[$mid]){
        return bsearch_recursion_internally($arr,$low,$mid-1,$n);
    }else{
       return  bsearch_recursion_internally($arr,$mid+1,$high,$n);
    }
    return -1;
}

二分查找应用场景的局限性

  • 1.二分查找依赖的是顺序表结构,简单点说就是数组。

那二分查找能否依赖其他数据结构呢?比如链表。
答案是不可以的,主要原因是二分查找算法需要按照下标随机访问元,数组按照下标随机访问数据的时间复杂度是 O(1),而链表随机访问的时间复杂度是 O(n)。
所以,如果数据使用链表存储,二分查找的时间复杂就会变得很高。
二分查找只能用在数据是通过顺序表来存储的数据结构上。如果你的数据是通过其他数据结构存储的,则无法应用二分查找。

  • 2.二分查找针对的是有序数据
  • 3.数据量太小不适合二分查找

如果要处理的数据量很小,完全没有必要用二分查找,顺序遍历就足够了。
比如我们在一个大小为 10 的数组中查找一个元素,不管用二分查找还是顺序遍历,查找速度都差不多。只有数据量比较大的时候,二分查找的优势才会比较明显。
不过,这里有一个例外。如果数据之间的比较操作非常耗时,不管数据量大小,我都推荐使用二分查找。
比如,数组中存储的都是长度超过 300 的字符串,如此长的两个字符串之间比对大小,就会非常耗时。我们需要尽可能地减少比较次数,而比较次数的减少会大大提高性能,这个时候二分查找就比顺序遍历更有优势。

  • 4.数据量太大也不适合二分查找

二分查找的底层需要依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻。
比如,我们有 1GB 大小的数据,如果希望用数组来存储,那就需要 1GB 的连续内存空间

  • 1.查找第一个值等于给定值的元素
  • 2.查找最后一个值等于给定值的元素
  • 3.查找第一个大于等于给定值的元素
  • 4.查找最后1个小于等于给定值的元素

变形二分查找

1.查找第一个值等于给定值的元素

关键点在于在找到等于的值后,还继续怎么找

//第1个等于给定值
function bsearch_first_eq($arr,$value){
    $low=0;
    $high=count($arr)-1;

    while($high>=$low){
        $mid=$low+(($high-$low)>>1);
        if($arr[$mid]==$value){
            //因为是要找第一个,所以是从mid往前找,
            if($mid==0 ||$arr[$mid-1]!=$value){
                return $mid;
            }
            $high=$mid-1;
        }else if($arr[$mid]>$value){
            $high=$mid-1;
        }else{
            $low=$mid+1;
        }

    }
    return -1;
}

2.查找最后一个值等于给定值的元素

function bsearch_last_eq($arr,$value){
    $low=0;
    $high=count($arr)-1;

    while($high>=$low){
        $mid=$low+(($high-$low)>>1);

        if($arr[$mid] ==$value){
            //因为要找最后1个,需要往后找
            if($mid == $high || $arr[$mid+1] !=$value){
                return $mid;
            }
            $low=$mid+1;

        }elseif($arr[$mid]<$value){
            $low=$mid+1;
        }else{
            $high=$mid-1;
        }

    }
    return -1;
}

3.查找第一个大于等于给定值的元素

function bsearch_first_egt($arr,$value){
    $low=0;
    $high=count($arr)-1;

    while($high>=$low){
        $mid=$low+(($high-$low)>>1);
        if($arr[$mid]>=$value){
            if($mid==0 ||$arr[$mid-1]<$value){
                return $mid;
            }
            $high=$mid-1;

        }else{
            $low=$mid+1;
        }


    }

    return -1;
}

4.查找最后1个小于等于给定值的元素

function  bsearch_last_elt($arr,$value){
    $low=0;
    $high=count($arr)-1;

    while($high>=$low){
        $mid=$low+(($high-$low)>>1);

        if($arr[$mid]<=$value){
            if($mid==$high||$arr[$mid+1]>$value){
                return $mid;
            }

            $low=$mid+1;

        }else{
            $high=$mid-1;
        }
    }

    return -1;
}
posted @ 2020-10-20 08:51  H&K  阅读(146)  评论(0编辑  收藏  举报