# 二元搜尋法(Binary Search)
兩種方式實現:遞迴、非遞迴,下面主要講遞迴的方式實現。
## 舉例說明
請對一個有序數組進行二分搜尋 `{1,8, 10, 89, 1000, 1234}` ,輸入一個數看看該數組是否存在此數,並且求出索引,如果沒有就提示"沒有這個數"。
### 思路
1. 首先確定該陣列的中間索引 `mid = (left + right) /2`
2. 然後讓需要搜尋的數 findVal 和 arr[mid] 比較
2.1 `findVal > arr[mid]` ,說明要搜尋的數在mid右邊,因此需要遞迴的向右搜尋。
2.2 `findVal < arr[mid]` ,說明要搜尋的數在mid左邊,因此需要遞迴的向左搜尋。
2.3 `findVal == arr[mid]`,說明找到,就返回。
**什麼時候結束遞迴?**
1. 找到要搜尋的值。
2. 遞迴完整個陣列,仍然沒有找到findVal,也需要結束遞迴(當left > right就需要退出)。
## 程式碼實現
```java=
package Search;
//注意:使用二元搜尋的前提是該陣列是有序的。
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1, 8, 10, 89, 1000, 1234};
int index = binarySearch(arr, 0, arr.length - 1, 8);
System.out.printf("搜尋的數索引為: %d\n",index);
int index2 = binarySearch(arr, 0, arr.length - 1, 1000);
System.out.printf("搜尋的數索引為: %d\n",index2);
int index3 = binarySearch(arr, 0, arr.length - 1, 5); // 沒有找到
System.out.printf("搜尋的數索引為: %d\n",index3);
}
/**
*
* @param arr 陣列
* @param left 左邊的索引
* @param right 右邊的索引
* @param findVal 要搜尋的值
* @return 如果找到就返回索引,如果沒找到就返回 -1
*/
public static int binarySearch(int[] arr, int left, int right, int findVal) {
int mid = (left + right) / 2;
int midVal = arr[mid];
// 當 lfet > right 代表遞迴整個陣列但是沒有找到
if(left > right) {
return -1;
}
if(findVal > midVal) { // 向右遞迴
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
}
```
output
```java=
搜尋的數索引為: 1
搜尋的數索引為: 4
搜尋的數索引為: -1
```
## 陣列中有多個相同數值
如果陣列中有多個相同的數值,那麼搜尋時只會返回第一個找到的索引,例如:
```java=
int arr[] = {1, 8, 10, 89, 1000, 1000, 1000, 1234};
int index = binarySearch(arr, 0, arr.length - 1, 1000);
System.out.printf("搜尋的數索引為: %d\n",index);
```
output
```java=
搜尋的數索引為: 4
```
現在我們需要來完善它,能讓我們找出陣列中所有相同的數。
### 思路分析
1. 在找到 mid 索引值不要馬上返回
2. 向 mid 索引值的左邊遍歷,將所有滿足的元素的索引加入到ArrayList
3. 向 mid 索引值的右邊遍歷,將所有滿足的元素的索引加入到ArrayList
4. 將 ArrayList 返回
首先,我們把方法返回的型態從 int 改為 `ArrayList<Integer>`
```java=
public static ArrayList<Integer> binarySearch(int[] arr, int left, int right, int findVal) {
//
// code
//
}
```
就像上面思路分析第一點提到的,在找到 mid 索引值不要馬上返回,因此我們要來修正剛剛 else 裡面的內容:
```java=
if(findVal > midVal) { // 向右遞迴
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
ArrayList<Integer> resIndexList = new ArrayList<Integer>();
int temp = mid - 1;
while(true) {
if(temp < 0 || arr[temp] != findVal) { // 向左掃到底還是沒找到
break;
}
// 否則,把 temp 放入 resIndexList
resIndexList.add(temp);
temp -= 1;
}
resIndexList.add(mid);
// 向 mid 索引值的右邊掃描, 將所有滿足的元素索引加入到 resIndexList
temp = mid + 1;
while(true) {
if(temp > right || arr[temp] != findVal) { // 向右掃到底還是沒找到
break;
}
// 否則,把 temp 放入 resIndexList
resIndexList.add(temp);
temp += 1;
}
return resIndexList;
}
```
還有搜尋的數找不到原先是返回 `-1` ,現在因為返回型態改為 ArrayList 所以我們改成返回一個空list。
```java=
// 當 lfet > right 代表遞迴整個陣列但是沒有找到
if(left > right) {
return new ArrayList<Integer>(); // 返回空list
}
```
試著跑跑看,在陣列中多加幾筆相同的數值後搜尋:
```java=
public static void main(String[] args) {
int arr[] = {1, 8, 10, 89, 1000, 1000, 1000, 1234};
List<Integer> resIndexList = binarySearch(arr, 0, arr.length - 1, 1000);
System.out.println("1000 在陣列中的索引為: "+resIndexList);
}
```
output
```java=
1000 在陣列中的索引為: [4, 5, 6]
```
### 完整程式碼
```java=
package Search;
import java.util.ArrayList;
import java.util.List;
//注意:使用二元搜尋的前提是該陣列是有序的。
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1, 8, 10, 89, 1000, 1000, 1000, 1234};
List<Integer> resIndexList = binarySearch(arr, 0, arr.length - 1, 1000);
System.out.println("1000 在陣列中的索引為: "+resIndexList);
}
/**
*
* @param arr 陣列
* @param left 左邊的索引
* @param right 右邊的索引
* @param findVal 要搜尋的值
* @return 如果找到就返回索引,如果沒找到就返回 -1
*/
public static ArrayList<Integer> binarySearch(int[] arr, int left, int right, int findVal) {
int mid = (left + right) / 2;
int midVal = arr[mid];
// 當 lfet > right 代表遞迴整個陣列但是沒有找到
if(left > right) {
return new ArrayList<Integer>(); // 返回空list
}
if(findVal > midVal) { // 向右遞迴
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
ArrayList<Integer> resIndexList = new ArrayList<Integer>();
int temp = mid - 1;
while(true) {
if(temp < 0 || arr[temp] != findVal) { // 向左掃到底還是沒找到
break;
}
// 否則,把 temp 放入 resIndexList
resIndexList.add(temp);
temp -= 1;
}
resIndexList.add(mid);
// 向 mid 索引值的右邊掃描, 將所有滿足的元素索引加入到 resIndexList
temp = mid + 1;
while(true) {
if(temp > right || arr[temp] != findVal) { // 向右掃到底還是沒找到
break;
}
// 否則,把 temp 放入 resIndexList
resIndexList.add(temp);
temp += 1;
}
return resIndexList;
}
}
}
```
## LeetCode - 704. Binary Search
一樣的思路,程式碼如下:
```java=
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int mid = (left + right) / 2;
while(left <= right) {
if(target < nums[mid]) { // 向左
right = mid - 1;
mid = (left + right) / 2;
} else if (target > nums[mid]) { // 向右
left = mid + 1;
mid = (left + right) / 2;
} else if (target == nums[mid]) {
return mid;
}
}
return -1;
}
}
```
* Runtime: `0 ms`, faster than 100.00% of Java online submissions for Binary Search.
* Memory Usage: `51.7 MB`, less than 7.04% of Java online submissions for Binary Search.
## 時間複雜度
T(n) = T(n/2) + Ο(1) = `Ο(log2n)`