資料結構和演算法之——二分查詢上

seniusen發表於2018-10-24

二分查詢(Binary Search)的思想非常簡單,但看似越簡單的東西往往越難掌握好,想要靈活運用就更加困難。

1. 二分查詢的思想?

生活中二分查詢的思想無處不在。一個最常見的就是猜數遊戲,我隨機寫一個 0 到 99 的數,然後你來猜我寫的是什麼。猜的過程中,我會告訴你每次是猜大了還是猜小了,直到猜中為止。假如我寫的數是 23,猜數過程如下所示。

最多隻需要 7 次就猜出來了,這個過程是很快的。同理,要查詢某個資料是否在給定的陣列中,我們同樣也可以利用這個思想。

二分查詢針對的是一個有序的資料集合,查詢思想有點類似於分治,每次都通過和中間元素進行比較,將待查詢區間縮小為之前的一半,直到找到要查詢的元素或者區間縮小為 0 為止。

2. 二分查詢的時間複雜度?

我們假設資料大小為 n,每次查詢資料大小都會縮小為原來的一半,最壞情況下,直到查詢區間縮小為空時停止查詢。

若經過 k 次區間縮小最後變為空,則 n2k=1,k=log2n\frac{n}{2^k} = 1, k = log_2n,所以二分查詢的時間複雜度為 O(logn)O(logn)

這種對數時間複雜度的演算法是一種非常高效的演算法,有時候甚至比時間複雜度為常量級的演算法還要高效。因為常量級的時間複雜度對應的常數可能非常大,比如 O(100), O(1000),因此這些演算法有時候可能還沒有 O(logn)O(logn) 的演算法執行效率高。

2. 簡單二分查詢的演算法實現?

迴圈法

float Binary_Search(float data[], int left, int right, float value)
{
    while (left <= right)
    {
        int mid = left + (right - left) / 2;
        if (value == data[mid])
        {
            return mid;
        }
        else if (value < data[mid])
        {
            right = mid - 1;
        }
        else
        {
            left = mid + 1;
        }
    }

    return -1;
}

遞迴法

float Binary_Search(float data[], int left, int right, float value)
{
    if (left <= right)
    {
        int mid = left + (right - left) / 2;
        if (value == data[mid])
        {
            return mid;
        }
        else if (value < data[mid])
        {
           return Binary_Search(data, left, mid-1, value);
        }
        else
        {
            return Binary_Search(data, mid+1, right, value);
        }
    }

    return -1;
}

注意事項

  • 迴圈退出條件 left <= right
  • mid = left + ((right-left) >> 1),用移位運算優化計算效能
  • left 和 right 的更新分別是 mid+1 和 mid-1

3. 二分查詢的應用場景?

二分查 找依賴的是順序表結構,也就是陣列,需要能夠按照下標隨機訪問元素

二分查詢針對的是有序資料,如果資料無序,需要先進行排序。而如果有頻繁的插入、刪除操作,則每次查詢前都需要再次排序,這時候,二分查詢將不再適用。

資料量太小可以直接遍歷查詢,沒有必要用二分查詢。但如果資料之間的比較操作非常耗時,比如資料為長度超過 300 的字串,則不管資料量大小,都推薦使用二分查詢。

而如果資料量過大,則意味著我們需要用非常大的連續記憶體空間來儲存這些資料,記憶體開銷可能無法滿足

獲取更多精彩,請關注「seniusen」!

相關文章