二分查找的總結
-
普通的二分查找
-
普通二分查找的另一種寫法
-
第一個
= key
的,不存在返回-1
-
第一個
>= key
的 -
第一個
> key
的 -
第一個
...
的總結 -
最後一個
=
key 的 ,不存在返回- 1
-
最後一個
<= key
的 -
最後一個
< key
的 -
最後一個
...
的總結
普通的二分查找
最普通的寫法:
-
範圍在
[L,R]
閉區間中,L = 0
、R = arr.length - 1
; -
注意循環條件爲
L <= R
,而不是L < R
;
static int bs1(int[] arr,int key){
int L = 0,R = arr.length - 1; //在[L,R]範圍內尋找key
int mid;
while( L <= R){
mid = L + (R - L) / 2;
if(arr[mid] == key)
return mid;
if(arr[mid] > key)
R = mid - 1;// key 在 [L,mid-1]內
else
L = mid + 1;
}
return -1;
}
普通二分查找的另一種寫法
首先說明,這個和上面的二分查找是完全一樣的,只不過我們定義的區間不同而已:
-
上面的二分查找是在
[L,R]
的閉區間中查找,而這個二分查找是在[L,R
)的左閉右開區間查找; -
所以此時的循環條件是
L < R
,因爲R
本來是一個不可到達的地方,我們定義爲了開區間,所以R
是一個不會考慮的數,所以我們循環條件是L < R
; -
同理,當
arr[mid] > key
的時候,不是R = mid - 1
,因爲我們定義的是開區間,所以R = mid
,因爲不會考慮arr[mid]
這個數;
//和上面的完全一樣,只是一開始R不是arr.length-1 而是arr.length
static int bs2(int[] arr,int key){
int L = 0, R = arr.length; //注意這裏R = arr.length 所以在[L,R)開區間中找
int mid;
while( L < R){ //注意這裏 不是 L <= R
mid = L + (R - L)/2;
if(arr[mid] == key)
return mid;
if(arr[mid] > key)
R = mid; // 在[L,mid)中找
else
L = mid + 1;
}
return -1;
}
上面的兩種方式一般還是第一種方式用的多一點。
第一個 = key
的,不存在返回 -1
這個和之前的不同是:
-
數組中可能有重複的
key
,我們要找的是第一個key
的位置; -
和普通二分查找法不同的是在我們要
R = mid - 1
前的判斷條件不是arr[mid] > key
,而是arr[mid] >= key
; -
爲什麼是上面那樣,其實直觀上理解,我們要找的是第一個,那我們去左邊找的時候不僅僅
arr[mid] > key
就去左邊找,等於我也要去找,因爲我要最左邊的等於的; -
最後我們要判斷
L
是否越界(L
有可能等於arr.length
),而且最後arr[L]
是否等於要找的key
; -
如果
arr[L]
不等於key
,說明沒有這個元素,返回-1
;
舉個例子:
/**查找第一個與key相等的元素的下標, 如果不存在返回-1 */
static int firstEqual(int[] arr,int key){
int L = 0, R = arr.length - 1; //在[L,R]查找第一個>=key的
int mid;
while( L <= R){
mid = L + (R - L)/2;
if(arr[mid] >= key)
R = mid - 1;
else
L = mid + 1;
}
if(L < arr.length && arr[L] == key)
return L;
return -1;
}
第一個>= key
的
這個和上面那個尋找第一個等於key
的唯一的區別就是:
-
最後我們不需要判斷(
L < arr.length && arr[L] == key
),因爲如果不存在key
的話,我們返回第一個> key
的元素即可; -
注意這裏沒有判斷越界(
L < arr.length
),因爲如果整個數組都比key
要小,就會返回arr.length
的大小;
/**查找第一個大於等於key的元素的下標*/
static int firstLargeEqual(int[] arr,int key){
int L = 0, R = arr.length - 1;
int mid;
while( L <= R){
mid = L + (R - L) / 2;
if(arr[mid] >= key)
R = mid - 1;
else
L = mid + 1;
}
return L;
}
第一個 > key
的
這個和上兩個的不同在於:
-
if(arr[mid] >= key)
改成了if(arr[mid] > key)
,因爲我們不是要尋找= key
的; -
看似和普通二分法很像,但是我們在循環中沒有判斷
if(arr[mid] == key)
就返回mid
(因爲要尋找的不是等於key
的),而是在最後返回了L
;
舉個例子:
/**查找第一個大於key的元素的下標 */
static int firstLarge(int[] arr,int key){
int L = 0,R = arr.length - 1;
int mid;
while(L <= R){
mid = L + (R - L) / 2;
if(arr[mid] > key)
R = mid - 1;
else
L = mid + 1;
}
return L;
}
第一個...
的總結
上面寫了三個第一個.....
的程序,可以發現一些共同點 ,也可以總結一下它們微妙的區別:
-
最後返回的都是
L
; -
如果是尋找第一個等於
key
的,是if( arr[mid] >= key) R = mid - 1
,且最後要判斷L
的合法以及是否存在key
; -
如果是尋找第一個大於等於
key
的,也是if(arr[mid] >= key) R = mid - 1
,但是最後直接返回L
; -
如果是尋找第一個大於
key
的,則判斷條件是if(arr[mid] > key) R = mid - 1
,最後返回L
;
最後一個 = key
的 ,不存在返回 - 1
和尋找第一個 = key
的很類似,不過是方向的不同而已:
-
數組中有可能有重複的
key
,我們要查找的是最後一個= key
的位置,不存在返回-1
; -
爲了更加的直觀的理解,和尋找第一個…的形成對比,這裏是當
arr[mid] <= key
的時候,我們要去右邊查找(L = mid + 1
),同樣是直觀的理解,因爲我們是要去找到最後一個= key
的,所以不僅僅是arr[mid] < key
要去左邊尋找,等於key
的時候也要去左邊尋找; -
和第一個…不同的是,我們返回的都是
R
; -
同時我們也要判斷
R
的下標的合法性,以及最後的arr[R]
是否等於key
,如果不等於就返回-1
;
舉個例子:
/**查找最後一個與key相等的元素的下標, 如果沒有返回-1*/
static int lastEqual(int[] arr,int key){
int L = 0, R = arr.length - 1;
int mid;
while( L <= R){
mid = L + (R - L)/2;
if(arr[mid] <= key)
L = mid + 1;
else
R = mid - 1;
}
if(R >= 0 && arr[R] == key)
return R;
return -1;
}
最後一個<= key
的
這個和上面那個尋找最後一個等於key
的唯一的區別就是:
-
最後我們不需要判斷 (
R >= 0 && arr[R] == key
),因爲如果不存在key
的話,我們返回最後一個< key
的元素即可; -
注意這裏沒有判斷越界(
R >= 0
),因爲如果整個數組都比key
要大,數組最左邊的更左邊一個(也就是-1
);
/**查找最後一個小於等於key的元素的下標 */
static int lastSmallEqual(int[] arr,int key){
int L = 0, R = arr.length - 1;
int mid;
while( L <= R){
mid = L + (R - L) / 2;
if(arr[mid] <= key)
L = mid + 1;
else
R = mid - 1;
}
return R;
}
最後一個 < key
的
這個和上面兩個不同的是:
-
和上面的程序唯一不同的就是
arr[mid] <= key
改成了arr[mid] < key
,因爲我們要尋找的不是= key
的; -
注意這三個最後一個的都是先對
L
的操作L = mid + 1
,然後在else
中進行對R
的操作;
/**查找最後一個小於key的元素的下標*/
static int lastSmall(int[] arr,int key){
int L = 0, R = arr.length - 1;
int mid;
while(L <= R){
mid = L + (R - L) / 2;
if(arr[mid] < key)
L = mid + 1;
else
R = mid - 1;
}
return R;
}
最後一個...
的總結
上面三個都是求最後一個.....
的,也進行一下總結:
-
最後返回的都是
R
; -
第一個
if
判斷條件(不管是arr[mid] <= key
還是arr[mid] < key
) ,都是L
的操作,也就是去右邊尋找; -
如果是尋找最後一個 等於
key
的,if(arr[mid] <= key) L = mid + 1;
不過最後要判斷R
的合法性以及是否存在key
; -
如果是尋找最後一個 小於等於
key
的,也是if(arr[mid] <= key) L = mid + 1
;不過最後直接返回R
; -
如果是尋找最後一個 小於
key
的,則判斷條件是if(arr[mid] < key) L = mid + 1
,最後返回R
;