對於二分法的一些感想

SpicyJelly發表於2021-02-15

前言

​ 本人最近一直在做一些演算法方面的學習,最近也刷了一些力扣題目,我將我做過的題目分享到了我的GitHub上:演算法題解可以供大家參考。最近在刷題的過程當中,我發現我老是在二分法的邊界條件上出問題,經常是出現棧溢位的情況。所以想寫一篇文章,記錄一下我的學習心得與體會。

二分法用來幹嘛

二分法往往是對一個有序的資料形式,進行查詢特定值target的演算法。

​ 該演算法的優勢是時間複雜度僅為O(log n),相較於順序查詢O(n)的時間複雜度有著明顯的提升。

二分法的分類

​ 二分法有4個重要的值:target,start, end, mid.

​ 我將二分法的區間劃分,分為3鍾型別:

  1. 劃分為[start,mid]和[mid+1,end]這兩個區間

    情況1

  2. 劃分為[start,mid-1]和[mid,end]這兩個區間

    情況2

  3. 劃分為[start,mid-1]和mid和[mid+1,end]這三個區間

    情況3

    ​ 這三種分法的區別就是:mid該放在哪裡

    ​ 該如何劃分割槽間,是得視具體情況進行的。有的經典二分完全可以使用第三種分法(個人最喜歡的,因為邊界條件最簡單),但是有的時候,必須得用第一種和第二種形式,如:力扣第35題 。這題需要將mid歸到左區間當中。(start<target <= mid)。

邊界條件的難點

​ 情況3邊界條件比較簡單,當(start>end)的時候跳出迴圈即可,不會造成死迴圈或者棧溢位。但是情況2和情況3往往會造成邊界條件分析不清晰導致產生死迴圈!

​ 為什麼說邊界條件難呢?如果mid值取得不對,容易造成死迴圈。且造成死迴圈往往是在剩下兩個值的時候產生。這裡舉個例子:

在條件2的時候,下面的程式碼就會造成死迴圈!!!!

//在情況2的時候,該程式碼會造成死迴圈!!!
//假設array是從小到大排序的有序陣列
static int BinarySearch(int[] array,int target,int start,int end){
    if(target < array[start] || target > array[end]) return -1;
    if(start == end){
        if(array[start] == target) return start;
        else return -1;
    }
    int mid = (start + end) / 2;
    if(array[mid] <= target)
        return BinarySearch(array,target,mid,end);
    else
        return BinarySearch(array,target,start,mid-1);
}

主函式為:

public static void main(String[] args) {
        int[] array = {0,2};
        int re = BinarySearch(array,0,0,1);
    	System.out.println(re);
    }

報錯為:

Exception in thread "main" java.lang.StackOverflowError
	at demo.BinarySearch(demo.java:14)
	at demo.BinarySearch(demo.java:14)
	at demo.BinarySearch(demo.java:14)
	at demo.BinarySearch(demo.java:14)

但是我們將程式碼換成第一種情況,即:將第一個條件的 <= 變為 < 並將區間變化修改成第一種情況,將會是正確的!

//在情況1的時候,程式碼會成功執行!!!
//假設array是從小到大排序的有序陣列
static int BinarySearch(int[] array,int target,int start,int end){
    if(target < array[start] || target > array[end]) return -1;
    if(start == end){
        if(array[start] == target) return start;
        else return -1;
    }
    int mid = (start + end) / 2;
    //將這裡的<=該成<,並修改區間
    if(array[mid] < target)
        return BinarySearch(array,target,mid+1,end);
    else
        return BinarySearch(array,target,start,mid);
}

同樣用第一個主函式跑,結果是正確的!

0

Process finished with exit code 0

我之前在這裡老是理解很糊塗,走了不少彎路。但是,現在我再也不會在這裡栽跟頭了!!!!

解決邊界條件

​ 大家也看見了,我陣列會造成棧溢位,說明,這裡的邊界條件導致的棧溢位就是在陣列剩下兩個元素的時候發現。為什麼會發生這樣的情況其實可以這樣區分:當只剩下兩個元素的時候,用mid能不能使得這兩個元素分開!

看第一個會造成死迴圈的例子:

​ 當陣列是[left,left+1]這種情況的時候,mid的值為left,這時候,劃分為[left,left-1]和[left,left+1]這兩個區間,這樣兩個區間,沒辦法將left和left+1這兩個元素分開,說明這樣的mid是沒有意義的。

​ 這時候我們需要將mid = mid+1即mid = left + 1可分開!因為這樣,區間可以劃分為[left,left]和[left+1,left+1]這樣兩個區間。這樣才能將兩個元素分開。

​ 而針對情況1,mid的值為left就可以分開,這時候,劃分為[left,left]和[left+1,left+1]這樣兩個區間。

​ 以後碰到這種情況,牢記分開元素準則,我們只需要當場覆盤一下這個過程即可避免棧溢位的產生!

相關文章