二分查找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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章