java的二分查找源碼分析

前言:

        之前用到二分查找的時候,都是自己手寫一個,雖然並不難,但是有的時候會忽略邊界條件,然後時間久了還會忘記,然後今天發現,Java其實已經實現了數組的二分查找,這裏就分析一下它的源碼

 

1:該方法在 Arrays.java 這個類裏面,調用的話可以直接使用 Arrays.binarySearch(), 它有好多實現,這裏就拿Arrays.binarySearch(int[] a, int key) 舉例。測試代碼如下:

public class test {
    @Test
    public void test() {
        int a[] = {1, 4, 8};
        System.out.println(Arrays.binarySearch(a, 1));
        System.out.println(Arrays.binarySearch(a, 4));
        System.out.println(Arrays.binarySearch(a, 8));
        System.out.println(Arrays.binarySearch(a, 9));
        System.out.println(Arrays.binarySearch(a, 0));
        System.out.println(Arrays.binarySearch(a, 5));
    }
}

對應的輸出結果:
0
1
2
-4
-1
-3

我們發現,當要搜索的值正好是在數組中出現過的話,那麼就會返回其對應的下標,但是如果要搜索的值不在數組中,竟然返回了負數,這裏就需要看下源碼來探索下它後面到底做了什麼了,源碼如下:

首先調用了該方法,然後該方法緊接着調用了binarySearch0方法
public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
}


private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];

            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
}

 

當看到 binarySearch0 方法的時候,我們其實已經應該就很熟悉了,它就是使用雙指針的方法,取兩邊邊界座標,然後取平均值,即中間節點,如果中間節點小於要搜索的目標值 key, 則 low = mid + 1, 即左邊指針向右移動,在剩下的範圍裏面找,同理,如果中間節點的值大於目標值key,說明 key 只可能出現在 [low, mid - 1] 這個區間內,這也是二分搜索的核心,一下排除一半,然後如果 midVal == key, 說明我們找到了,就返回對應的下標值,這跟我們上邊看到的結果是一致的,那麼我們就來看下爲什麼會返回負數?

 

我們留意到該方法最後一行返回了 -(low + 1), 這對應了三種情況,

1:要搜索的值大於數組中所有的值, 此時,在遍歷的過程中只會發生 low 的移動,直到 low > high 跳出 while 循環,而 high 的初始值爲 a.length - 1, 故此時 low = a.length,此時返回的爲 -(a.length + 1)

2:要搜索的值小於數組中所有的值,此時在遍歷過程中只會發生 high 向前移動,直到 high < low 跳出 while 循環,而 low 的初始值爲 0, 故最後返回 -(low + 1) 即爲返回 -1

3:要搜索的值的大小範圍處於數組之中,但並沒有完全相等的值與之匹配,此時我們只需要考慮最後一步,最後一步有兩種情況:

3-1:low = high: 此時 mid = low = high,這種情況說明數組中當前位置是最有可能與 key 值匹配的位置了,因爲其他的已經被排除了,如果當前元素小於key, 就會觸發如下操作,low = mid + 1,這個位置對應數組中第一個大於 key 的元素下標,如果當前元素大於 key, 那麼 low 的值不變,high = mid - 1, 此時 low 對應的還是數組中第一個大於 key 的元素的下標。

3-2: low = high - 1: 此時 mid = low  = high - 1, 此時如果 midVal > key, 會發生 high = high - 1,就會觸發 high = mid - 1的操作,然後跳出循環,此時 low 也是對應數組中第一個大於key的元素的下標,而如果midVal < key, 會觸發 low = mid + 1的操作,然後就又會出現 3-1 提到的 low = high的情況,而上面已經分析過了,low 對應數組中第一個大於key的元素的下標

綜上所述,循環結束之後,low 對應的是數組中第一個大於 key 值的元素的下標, 然後其實第三種情況跟情況 1 和 2是類似的,按理說直接返回這個 low 對應的下標豈不是美滋滋,java 的開發人員偏偏又給他加了個1 然後又取了個負數,即 -(low + 1),那麼爲什麼要這樣多此一舉呢?直接返回 low 它不香麼?

爲什麼要返回 -(low + 1)?

首先返回負數是告訴你,數組中沒有匹配到的值,這樣能理解吧?那你是不是又要問那返回 -low 不就行了麼,多方便,可是別忘了還有這麼一種情況,你要搜索的值比數組中任何一個都要小,即上面情況 2 ,此時 low = 0, 那麼 -low 呢,哈哈,還是0,所以說這裏取 -(low + 1) 還是很合理的

所以如果我們想要用該方法找到數組中第一個大於指定 key 元素的值的話,別忘了對返回值再取個相反數,然後再減個 1 哦,即-(-(low + 1)) - 1 = low + 1 - 1 = low, 就是我們想要的了。

 

總結:

1: 如果要查找的元素能在數組中匹配到,那麼就返回對應值的下標

2:如果要查找的元素在數組中匹配不到, 就先找到數組中第一個大於該元素的值的下標 low, 然後取 -(low + 1) 返回

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章