終極找 bug 大法 - 二分

lucifer發表於2022-02-14

大家好,我是 lucifer。今天給大家分享我的日常工作中常用的 debug 殺器!

二分程式碼

當我發現一個難以理解和發現的 bug 的時候,我的終極辦法就是二分程式碼。

比如先刪去檔案 a 的一半程式碼(大約),然後測試問題是否還在。

  • 如果問題不在了,那麼我們就找到了出問題的程式碼。
  • 如果問題還在,那麼我們繼續使用同樣的辦法,繼續刪去檔案 a 的一半程式碼(大約),然後測試問題是否還在。
  • 重複上述步驟,直到找到出問題的程式碼

這個方法在我一時沒有思緒或者幫助別人定位問題的時候非常有用。由於這種做法時間複雜度大致是 logn,因此只需要短短几次我們就可以大致定位到問題程式碼。類似地,我們也可以在多個檔案中同時進行二分。

二分提交

更多的時候,我們是在兩次釋出之間發現了一個 bug。而這兩個釋出之間是有若干 commit 的,並且 commit 還不少(幾十個甚至上百個)。

我們也可以使用類似的方法。

  • 先切換到兩次釋出之間的中間提交 x(即使得提交 x 相對於兩次釋出之間的距離差最小)。
實際上這個最小距離差要麼是 0, 要麼是 1
  • 驗證問題是否存在。如果不存在,我們不能確定就是這個提交的問題,不妨先標記當前提交 c 為 good。如果存在,不妨標記當前提交 c 為 bad。

  • 經過上面的標記,我們就可以找到最早呈現 bad 的那次提交,並且最關鍵的是複雜度為 logn,其中 n 為我們需要驗證的提交的總次數。顯然,這個工作量比逐個檢查 commit 要快很多。
不理解其中原理?稍後我們會講。

Git 中的二分查詢

git 的開發者也想到了這一點,因此提供了 bisect 命令來幫助我們做上面這個事情。

使用 bisect 進行問題查詢主要依賴於下面的三個命令:

  1. git bisect start

啟動一個查詢,start 後面可以加上 good 和 bad 的提交點:

git bisect start [rev bad] [rev good]

如果不加 good 和 bad 的提交點,那麼 git 將會等到我們使用 good 和 bad 命令指定對應的提交點後開始進行查詢

  1. git bisect good

用來標記一個提交點是正確的,後面可以加上 rev 來指定某個特定的提交點,如果不加,則預設標記當前的提交點。

  1. git bisect bad

用來標記一個提交點是包含問題的,如果 bad 後可以加上 rev 來指定某個特定的提交點,如果不加,則預設標記當前的提交點。

背後原理

我們來補前面的坑。為什麼經過這樣的標記,我們就可以找到第一個有問題(標記 bad)的提交?並且時間複雜度為 $O(logn)$。

正好力扣有一道原理,我們直接用它來講吧。

題目地址(278. 第一個錯誤的版本)

https://leetcode-cn.com/probl...

題目描述

你是產品經理,目前正在帶領一個團隊開發新的產品。不幸的是,你的產品的最新版本沒有通過質量檢測。由於每個版本都是基於之前的版本開發的,所以錯誤的版本之後的所有版本都是錯的。

假設你有 n 個版本 [1, 2, ..., n],你想找出導致之後所有版本出錯的第一個錯誤的版本。

你可以通過呼叫 bool isBadVersion(version) 介面來判斷版本號 version 是否在單元測試中出錯。實現一個函式來查詢第一個錯誤的版本。你應該儘量減少對呼叫 API 的次數。

 

示例 1:

輸入:n = 5, bad = 4
輸出:4
解釋:
呼叫 isBadVersion(3) -> false
呼叫 isBadVersion(5) -> true
呼叫 isBadVersion(4) -> true
所以,4 是第一個錯誤的版本。


示例 2:

輸入:n = 1, bad = 1
輸出:1


 

提示:

1 <= bad <= n <= 231 - 1

思路

可以看出,這個過程和我們上面的描述是一樣的。並且我們的目標都是找到第一個出錯的提交。

需要明確的是:

  • 如果一個版本 x 是 good 的,那麼 [1, x] 之間的所有提交肯定都是 good 的,因此待檢測版本變為 [x+1,n]
  • 如果一個版本 x 是 bad 的,那麼 [x, n] 之間所有的提交肯定都是 bad 的。由於我們要找到的是第一個有問題的版本,因此待檢測版本變為 [1,x-1]

因此無論我們檢測的版本是 good 還是 bad,我們都可以將待檢測的版本數量變為一半,也就是說我們可以在 $logn$ 次內找到第一個有問題的版本。

如果你看過我的二分專題,應該知道這就是我總結的最左二分

程式碼

  • 語言支援:Python3

Python3 Code:


class Solution:
    def firstBadVersion(self, n):
        l, r = 1, n
        while l <= r:
            mid = (l + r) // 2
            if isBadVersion(mid):
                # 收縮
                r = mid - 1
            else:
                l = mid + 1
        return l

複雜度分析

  • 時間複雜度:$O(logn)$
  • 空間複雜度:$O(1)$

總結

二分大法在日常工作中應用還是蠻多的,二分找 bug 是其中一個很實用的技巧。最簡單的二分找 bug 可以通過刪除檔案內容的方式進行。而如果檔案很多,就很不方便了,這個時候我們可以使用二分提交來完成。

其中的原理其實也很簡單,就是一個樸素的最左二分。如果大家對此不熟悉,強烈建議看下文章中提到的二分專題,兩萬字總結絕對讓你有所收穫。

相關文章