你真的會寫二分查找嗎


1 二分查找

  二分查找是一個基礎的算法,也是面試中常考的一個知識點。二分查找就是將查找的鍵和子數組的中間鍵作比較,如果被查找的鍵小於中間鍵,就在左子數組繼續查找;如果大於中間鍵,就在右子數組中查找,否則中間鍵就是要找的元素。

(圖片來自《算法-第4版》)

/**
 * 二分查找,找到該值在數組中的下標,否則為-1
 */
static int binarySerach(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] == key) {
            return mid;
        }
        else if (array[mid] < key) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }

    return -1;
}

  每次移動left和right指針的時候,需要在mid的基礎上+1或者-1, 防止出現死循環, 程序也就能夠正確的運行。

  注意:代碼中的判斷條件必須是while (left <= right),否則的話判斷條件不完整,比如:array[3] = {1, 3, 5};待查找的鍵為5,此時在(low < high)條件下就會找不到,因為low和high相等時,指向元素5,但是此時條件不成立,沒有進入while()中。

 

2 二分查找的變種

  關於二分查找,如果條件稍微變換一下,比如:數組之中的數據可能可以重復,要求返回匹配的數據的最小(或最大)的下標;更近一步, 需要找出數組中第一個大於key的元素(也就是最小的大於key的元素的)下標,等等。 這些,雖然只有一點點的變化,實現的時候確實要更加的細心。

  二分查找的變種和二分查找原理一樣,主要就是變換判斷條件(也就是邊界條件),如果想直接看如何記憶這些變種的竅門,請直接翻到本文最后。下面來看幾種二分查找變種的代碼:

2.1 查找第一個與key相等的元素

  查找第一個相等的元素,也就是說等於查找key值的元素有好多個,返回這些元素最左邊的元素下標。

// 查找第一個相等的元素
static int findFirstEqual(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] >= key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    if (left < array.length && array[left] == key) {
        return left;
    }
    
    return -1;
}

2.2 查找最后一個與key相等的元素

  查找最后一個相等的元素,也就是說等於查找key值的元素有好多個,返回這些元素最右邊的元素下標。

// 查找最后一個相等的元素
static int findLastEqual(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] <= key) {
            left = mid + 1;
        }
        else {
            right = mid - 1;
        }
    }
    if (right >= 0 && array[right] == key) {
        return right;
    }

    return -1;
}

2.3 查找最后一個等於或者小於key的元素

  查找最后一個等於或者小於key的元素,也就是說等於查找key值的元素有好多個,返回這些元素最右邊的元素下標;如果沒有等於key值的元素,則返回小於key的最右邊元素下標。

// 查找最后一個等於或者小於key的元素
static int findLastEqualSmaller(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] > key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return right;
}

2.4 查找最后一個小於key的元素

  查找最后一個小於key的元素,也就是說返回小於key的最右邊元素下標。

// 查找最后一個小於key的元素
static int findLastSmaller(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] >= key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return right;
}

2.5 查找第一個等於或者大於key的元素

  查找第一個等於或者大於key的元素,也就是說等於查找key值的元素有好多個,返回這些元素最左邊的元素下標;如果沒有等於key值的元素,則返回大於key的最左邊元素下標。

// 查找第一個等於或者大於key的元素
static int findFirstEqualLarger(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] >= key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return left;
}

2.6 查找第一個大於key的元素

  查找第一個等於key的元素,也就是說返回大於key的最左邊元素下標。

// 查找第一個大於key的元素
static int findFirstLarger(int[] array, int key) {
    int left = 0;
    int right = array.length - 1;

    // 這里必須是 <=
    while (left <= right) {
        int mid = (left + right) / 2;
        if (array[mid] > key) {
            right = mid - 1;
        }
        else {
            left = mid + 1;
        }
    }
    return left;
}

 

3 二分查找變種總結

// 這里必須是 <=
while (left <= right) {
    int mid = (left + right) / 2;
    if (array[mid] ? key) {
        //... right = mid - 1;
    }
    else {
        // ... left = mid + 1;
    }
}
return xxx;

  二分查找變種較多,不過它們的“套路”是一樣的,以上代碼就是其套路,如何快速寫出二分查找的代碼,只需按照以下步驟即可:

1 首先判斷出是返回left,還是返回right

  因為我們知道最后跳出while (left <= right)循環條件是right < left,且right = left - 1。最后right和left一定是卡在"邊界值"的左右兩邊,如果是比較值為key,查找小於等於(或者是小於)key的元素,則邊界值就是等於key的所有元素的最左邊那個,其實應該返回left。

  以數組{1, 2, 3, 3, 4, 5}為例,如果需要查找第一個等於或者小於3的元素下標,我們比較的key值是3,則最后left和right需要滿足以下條件:

  我們比較的key值是3,所以此時我們需要返回left。

2 判斷出比較符號

int mid = (left + right) / 2;
if (array[mid] ? key) {
    //... right = xxx;
}
else {
    // ... left = xxx;
}

  也就是這里的 if (array[mid] ? key) 中的判斷符號,結合步驟1和給出的條件,如果是查找小於等於key的元素,則知道應該使用判斷符號>=,因為是要返回left,所以如果array[mid]等於或者大於key,就應該使用>=,以下是完整代碼

// 查找小於等於key的元素
int mid = (left + right) / 2;
if (array[mid] >= key) {
    right = mid - 1;
}
else {
    left = mid + 1;
}

 

參考:

  1、你真的會寫二分檢索嗎?

  2、http://www.cnblogs.com/luoxn28/category/802645.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM