傳說中的二分法查詢

2021-08-07 17:20:49 字數 3423 閱讀 5231

採用二分法查詢資料元素並不是乙個陌生的行為,而可能只是乙個陌生的詞彙而已。

二分法說的是從乙個有序序列中查詢某個元素時,先和這個序列的中間值比較,如果中間值小,則再從中間位置到末尾的一半序列中查詢,仍然先和中間值比較,以此類推。如果中間值大,則再從開始位置到中間位置的一半序列中查詢,仍然先和中間值比較,以此類推。直到最後找到目標值所在的位置,或者最後確定該序列並不包含該目標值。

假如有20個寫著不同數字的號碼牌,按從小到大或者從大到小的順序排列好,然後背面朝上蓋好,找乙個人來,告訴他這20個號碼牌已經按順序排列好了,要求以最快的方法從裡面找乙個數(可以不是號碼牌上的數)。他將很輕鬆地搞定這件事,而他採用的方法,自然而然就是二分法。很顯然,我們的大腦很擅長處理這種情況。

但是,把我們的大腦處理這件事的思維過程用**的形式表示出來並不是一件簡單的事。

我昨天只是在筆記本上畫畫草圖,想了乙個晚上沒想明白(中間包括以為想明白了立刻發現其實是有問題的)。今天又想了一上午,勉強寫出來了乙個很麻煩的版本。其實昨天晚上沒想出來以及今天上午寫出來的麻煩版本,基本上都是糾結於中間值小了向右查詢以後發現中間值又大了變成向左查詢,以及更複雜的左右來回查詢的時候,具體這些變數該怎麼儲存的問題。即使在中間值一直小於目標值的情況下,也需要乙個變數temp來儲存當前中間值的下標跳躍到下乙個中間值下標之前,也就是當前下標的位置,以便發現跳到下乙個中間值以後發現中間值大於目標值的時候,可以回到左邊的一半序列來查詢。而此時,左邊的一半的開始位置的下標已經不能以0計算了,而必須是目標值變換之間的下標,即上一次的下標。也正是這個讓我頭腦混亂,因為我會想,下一次中間值和目標值比較的結果又是兩種可能,那麼我是否還要儲存上上次的下標位置,以及上次的下標位置和現在的中間值的下標其實也有兩種位置關係,或者在左邊或者在右邊。。。

上午的**儲存在另外一台電腦上,所以就不展示了。晚上回來後又仔細想了想,理順了思路,改進了**。貼在下面,和  arrays 類提供的原始碼做個比較,看一下普通人和大神的思維區別。

直接在main方法裡面寫了,沒有封裝成方法。前半部分主要是隨機建立乙個型別為int長度為10的陣列,陣列元素的取值範圍是從0到20。主要看變數定義和迴圈的部分。

int target = 10;

int temp = 0;

int index = (int)(arrays.length/2);

這裡定義了3個變數,target指的是要查詢的目標值,temp用來儲存中間值下標變化之前的位置,初始值為0,index表示當前中間值的下標,初始值是陣列的中間位置,這裡考慮到奇數偶數,加了強制資料型別轉型,最後看了原始碼才發現其實沒有必要,因為計算的結果賦值給了乙個int型別的變數,會自動省略小數部分。另外由於我只想判斷目標值在陣列中是否存在,所以雖然變數名起的是index(索引,下標),但其實我簡單地使用了陣列長度除以2代表中間值。當然這並不影響查詢到目標值的結果,但是從返回下標的角度來說,還是應該盡量直接用下標值計算index。

while(index != temp)  else if(arrays[index]

int j = index;

index = index + (int)(math.abs(temp-index)/2);

temp = j;

這是在當前這次迴圈中(即上次中間值跳到當前的中間值位置以後),中間值如果比目標值小,那麼要向右跳轉,把當前index的值加上當前index和當前temp的值相減除以2(temp減index也是一樣的),就是index要跳轉到的地方。因為是向右,所以是加法。temp要始終儲存index變化前的位置,而index尋找下乙個中間值位置需要借助當前的temp的值,所以先把當前index的值存到臨時變數裡了。結果是index到了下乙個中間值位置,而temp成功地儲存了index上一次的位置。

} else
向左的情況正好反過來,中間值位置向左移動,所以是減法。

這樣,如果下一次應該向右,就會去執行向右的**。再下一次如果向左,就會去執行向左的**。直到最後temp和index的位置重合了,就證明目標值不在陣列內。否則,它就會在某一次迴圈裡被找到,然後通過break跳出迴圈。

arrays 裡面關於二分法查詢有很多過載的靜態方法,以 int 型別為例看一下。

private static int binarysearch0(int a, int fromindex, int toindex int key) 

return -(low + 1); // key not found.

}

原始碼用到了3個變數,即目標陣列(每次折半後的陣列)的開始位置,終了位置和中間位置,很巧妙很聰明。

int low = fromindex;

int high = toindex - 1;

binarysearch0

這個靜態方法是用private修飾的,因為它主要是用來被其它含有int型別引數的方法呼叫的。看了一下,具體傳遞過來的值是fromindex = 0 和 toindex = a.length,所以是把 low 和 high 初始化為原始陣列的開始位置和終了位置。

while (low <= high) {

int mid = (low + high) >>> 1;

int midval = a[mid];

它的思路是,每次中間值和目標值比較以後,調整開始位置或者終了位置。如果中間值小於目標值,需要向右繼續確認,則開始位置向右調整為當前中間值+1,反之如果中間值大於目標值,需要向左繼續確認,則終了位置向左調整為當前中間值-1,終了位置使得 a[low] 到 a[high] 始終代表折半以後的目標陣列。就像乙個口袋,不斷地縮小口兒,直到開始位置等於終了位置。然後迴圈體內會繼續開始位置變大或者終了位置變小,從而導致開始位置大於終了位置,迴圈就結束了。所以while迴圈的條件是開始位置小於終了位置。而且,中間值一直等於本次迴圈中物件陣列的開始位置和終了位置的中間值,相當於求和除以2,用到了位運算。。。

if (midval < key)

low = mid + 1;

else if (midval > key)

high = mid - 1;

else

return mid; // key found

這裡就是比較判斷。如果中間值小於目標值,把開始位置向右移動,變成新的物件陣列的開始位置。如果中間值大於目標值,則把終了位置向左移動,變成新的物件陣列的終了位置。如果相當,則返回下標。

return -(low + 1);  // key not found.
如果返回負數,表示沒找到。

原始碼就是原始碼,簡約而不簡單。

普通人思考問題,一般就抓住一點,最多能抓住兩點,就像我寫的那樣,通過中間值的下標和上一次中間值的下標來推演**以解決問題。而大神則能同時考慮3個元素,通過中間值和目標值的比較結果不斷改變開始位置和終了位置來定位目標值。

傳說中的二分查詢

很久以前在csdn的編碼挑戰欄裡第一次看到 90 的程式設計師無法正確實現二分查詢 這種挑釁性的論斷,那時候我還是個演算法盲,基本二分查詢也只是聽說而已,但還是對此深表懷疑。出於各種原因也沒有動手試一試。近期開始練基本功,到查詢這一塊時,特別留意了下這個話題,原來出自 程式設計珠璣 計算機程式設計藝...

C 二分法查詢,遞迴二分法

用二分法來求需要查詢的值.includeusing namespace std 查詢key元素是否存在 int findkey const int buf 100 const int ilen,const int key else right left mid 1 查詢失敗 return 1 查詢k...

python二分法查詢 Python 二分法查詢

二分法查詢主要的作用就是查詢元素 lst 1,3,5,7,12,36,68,79 資料集 百萬級資料 num int input 請輸入你要查詢的元素資訊 for el in lst if num el print 存在 break else print 不存在 len lst 0 1 2 3 4 ...