把二分查詢演算法寫正確需要注意的地方
阿新 • • 發佈:2018-12-27
今天再次解決一個需要使用二分查詢的問題,再一次的,我又沒有一次過寫對.(為什麼我說"又"?)
抓狂了,似乎開始有一些"二分查詢恐懼症".
為了以後能夠一次將這個基本的演算法寫對,我決定再仔細研究一下.我之前有寫過一個二分查詢的演算法,在這裡,這一次再以這個問題為例來說明.
我今早寫下的錯誤程式碼類似於下面的樣子:
#include <stdio.h>int search(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n;
while (left < right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle -1;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
}
return-1;
}
int main()
{
int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 13, 19};
int m = search(array, sizeof(array)/sizeof(array[0]), 1);
printf("m = %d\n", m);
return0;
}
實際上,如果使用測試用例來測試,這個演算法並不是在所有情況下都會出錯的,還是有時可以得到正確的結果的.但是,你能看出來它錯在哪兒嗎?
在這裡,迴圈的開始處,把迴圈遍歷的序列區間是這樣的:
left =0, right = n;
while (left < right)
{
// 迴圈體} 也就是說,這是一個左閉右開的區間:[0, n).
但是,在迴圈內部, 卻不是這樣操作的:
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle -1;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
當array[middle] > v條件滿足時, 此時v如果存在的話必然在左閉右開區間[left, middle)中, 因此,當這個條件滿足時, right應該為middle, 而在這裡, right賦值為middle - 1了, 那麼, 就有可能遺漏array[middle - 1] = v的情況.
因此,這種錯誤的寫法並不是在所有的情況下都會出錯,有時還是可以找到正確的結果的.
這是一種典型的二分查詢演算法寫錯的情況,迴圈體是左閉右開區間,而迴圈體內部卻是採用左閉右閉區間的演算法進行操作.
下面給出的兩種正確的演算法,演算法search是左閉右閉區間演算法,而演算法search2是左閉右開區間演算法,可以對比一下差異.
int search(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n -1;
while (left <= right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle -1;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
}
return-1;
}
int search2(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n;
while (left < right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
}
return-1;
}
下面再給出另一種典型的錯誤的二分查詢演算法,當查詢的元素不在序列內時,它可能造成程式的死迴圈.
int search(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n -1;
while (left <= right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle;
}
elseif (array[middle] < v)
{
left = middle;
}
else
{
return middle;
}
}
return-1;
}
為什麼會造成死迴圈?
從迴圈條件來看,這個演算法的操作區間是左閉右閉區間的,因此當array[middle] > v時,v如果存在的話應該在[left, middle- 1]中,因此此時right應該是middle - 1,而不是middle;類似的,當array[middle] < v時,下一次操作的區間應該是[middle + 1, right]中.而當元素不存在這個序列中時,演算法在一個錯誤的區間中迴圈,但是又不能終止迴圈,於是就造成了死迴圈.
因此,要將二分查詢演算法寫對,其實很多人都大概知道思想,具體到編碼的時候,就會被這些看似微小的地方搞糊塗.因此,需要注意這一點:
演算法所操作的區間,是左閉右開區間,還是左閉右閉區間,這個區間,需要在迴圈初始化,迴圈體是否終止的判斷中,以及每次修改left,right區間值這三個地方保持一致,否則就可能出錯.
抓狂了,似乎開始有一些"二分查詢恐懼症".
為了以後能夠一次將這個基本的演算法寫對,我決定再仔細研究一下.我之前有寫過一個二分查詢的演算法,在這裡,這一次再以這個問題為例來說明.
我今早寫下的錯誤程式碼類似於下面的樣子:
#include <stdio.h>int search(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n;
while (left < right)
{
middle = (left + right) /2;
if (array[middle]
{
right = middle -1;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
}
return-1;
}
int main()
{
int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 13, 19};
int m = search(array,
printf("m = %d\n", m);
return0;
}
實際上,如果使用測試用例來測試,這個演算法並不是在所有情況下都會出錯的,還是有時可以得到正確的結果的.但是,你能看出來它錯在哪兒嗎?
在這裡,迴圈的開始處,把迴圈遍歷的序列區間是這樣的:
left =0, right = n;
while (left < right)
{
// 迴圈體} 也就是說,這是一個左閉右開的區間:[0, n).
但是,在迴圈內部, 卻不是這樣操作的:
middle = (left + right) /2;
{
right = middle -1;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
當array[middle] > v條件滿足時, 此時v如果存在的話必然在左閉右開區間[left, middle)中, 因此,當這個條件滿足時, right應該為middle, 而在這裡, right賦值為middle - 1了, 那麼, 就有可能遺漏array[middle - 1] = v的情況.
因此,這種錯誤的寫法並不是在所有的情況下都會出錯,有時還是可以找到正確的結果的.
這是一種典型的二分查詢演算法寫錯的情況,迴圈體是左閉右開區間,而迴圈體內部卻是採用左閉右閉區間的演算法進行操作.
下面給出的兩種正確的演算法,演算法search是左閉右閉區間演算法,而演算法search2是左閉右開區間演算法,可以對比一下差異.
int search(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n -1;
while (left <= right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle -1;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
}
return-1;
}
int search2(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n;
while (left < right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle;
}
elseif (array[middle] < v)
{
left = middle +1;
}
else
{
return middle;
}
}
return-1;
}
下面再給出另一種典型的錯誤的二分查詢演算法,當查詢的元素不在序列內時,它可能造成程式的死迴圈.
int search(int array[], int n, int v)
{
int left, right, middle;
left =0, right = n -1;
while (left <= right)
{
middle = (left + right) /2;
if (array[middle] > v)
{
right = middle;
}
elseif (array[middle] < v)
{
left = middle;
}
else
{
return middle;
}
}
return-1;
}
為什麼會造成死迴圈?
從迴圈條件來看,這個演算法的操作區間是左閉右閉區間的,因此當array[middle] > v時,v如果存在的話應該在[left, middle- 1]中,因此此時right應該是middle - 1,而不是middle;類似的,當array[middle] < v時,下一次操作的區間應該是[middle + 1, right]中.而當元素不存在這個序列中時,演算法在一個錯誤的區間中迴圈,但是又不能終止迴圈,於是就造成了死迴圈.
因此,要將二分查詢演算法寫對,其實很多人都大概知道思想,具體到編碼的時候,就會被這些看似微小的地方搞糊塗.因此,需要注意這一點:
演算法所操作的區間,是左閉右開區間,還是左閉右閉區間,這個區間,需要在迴圈初始化,迴圈體是否終止的判斷中,以及每次修改left,right區間值這三個地方保持一致,否則就可能出錯.