二分查找
回廊识路 12/10/2019 算法
# 基本思想
- 二分查找针对的是一个有序的数据集合,利用类似分治的思想:每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。
- 二分查找的时间复杂度为
O(logn)
,是一种极其高效的时间复杂度。
# 代码实现
int bsearchSimple(const vector<int> &a, int n, int value) {
int low = 0, high = n - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (a[mid] < value) {
low = mid + 1;
} else if (a[mid] > value) {
high = mid - 1;
} else {
return mid;
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意以下几个易错点:
- 循环退出条件
- 注意是
low<=high
,而不是low<high
。
- 注意是
- mid 的取值
mid=(low+high)/2
- 改进1:
mid=low+(high-low)/2
。防止 low 和 high 比较大的情况,两者之和有可能会溢出。 - 改进2:
mid=low+((high-low)>>1)
。将除以 2 运算转化成位运算,计算机处理位运算要比除法运算快得多。
- low 和 high 的更新
low=mid+1, high=mid-1
。注意这里的 +1 和 -1,如果直接写成low=mid, high=mid
,可能会发生死循环。
# 应用场景
- 二分查找依赖的是顺序表结构
- 二分查找底层依赖数组
- 二分查找针对的是有序数据
- 只能用在插入、删除操作不频繁,一次排序多次查找的场景中;
- 针对动态数据集合快速查找某个数据:二叉树。
- 只能用在插入、删除操作不频繁,一次排序多次查找的场景中;
- 数据量太小或太大都不适合二分查找
- 数据量小:顺序查找即可;
- 数据量大:用数组存储会比较吃力(数组要求内存空间连续),也就不适合使用二分查找了;
- 特殊情况:如果数据之间的比较操作非常耗时,不管数据量大小都推荐使用二分查找,尽可能减少比较次数。
# 经典变体
# 查找第一个等于value的元素
int bsearch_1(const vector<int> &a, int value) {
int low = 0, high = a.size() - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == 0) || (a[mid - 1] != value)) {
return mid;
} else {
high = mid - 1;
}
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查找最后一个等于value的元素
int bsearch_2(const vector<int> &a, int value) {
int low = 0, high = a.size() - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] > value) {
high = mid - 1;
} else if (a[mid] < value) {
low = mid + 1;
} else {
if ((mid == a.size() - 1) || (a[mid + 1] != value)) {
return mid;
} else {
low = mid + 1;
}
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查找第一个大于等于value的元素
int bsearch_3(const vector<int> &a, int value) {
int low = 0, high = a.size() - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] >= value) {
if ((mid == 0) || (a[mid - 1] < value)) {
return mid;
} else {
high = mid - 1;
}
} else {
low = mid + 1;
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查找最后一个小于等于value的元素
int bsearch_4(const vector<int> &a, int value) {
int low = 0, high = a.size() - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] <= value) {
if ((mid == a.size() - 1) || (a[mid + 1] > value)) {
return mid;
} else {
low = mid + 1;
}
} else {
high = mid - 1;
}
}
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# LeetCode例题 33 (opens new window)
Search in Rotated Sorted Array(搜索旋转排序数组)
- 思路1:二分查找旋转下标,分成两个有序数组,分别做两个简单二分查找。
- 注意这里
循环退出条件
和low和high的更新
不同于简单二分查找,需要仔细斟酌。
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
int rotation_index = 0;
while (l < r) {
int mid = l + ((r - l) >> 1);
if (nums[mid] > nums[mid + 1]) {
rotation_index = mid + 1;
break;
}
if (nums[mid] > nums[l]) { l = mid; }
if (nums[mid] < nums[r]) { r = mid; }
}
int l_res = binarySearch(nums, 0, rotation_index - 1, target);
int r_res = binarySearch(nums, rotation_index, nums.size() - 1, target);
return l_res == -1 ? r_res : l_res;
}
int binarySearch(vector<int> &nums, int start, int end, int target) {
int l = start, r = end;
while (l <= r) {
int mid = l + ((r - l) >> 1);
if (nums[mid] < target) { l = mid + 1; }
else if (nums[mid] > target) { r = mid - 1; }
else { return mid; }
}
return -1;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Search in Rotated Sorted Array
思路2:二分之后,总有一半是有序数组,另一半是循环数组。
class Solution {
public:
int search(vector<int>& a, int value) {
int low = 0, high = a.size() - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (a[mid] == value) { return mid; }
if (a[mid] >= a[low]) { // 前半部分为有序数组
if ((a[mid] > value) && (a[low] <= value)) { // value在有序部分
high = mid - 1;
} else {
low = mid + 1;
}
} else { // 后半部分为有序数组
if ((a[mid] < value) && (a[high] >= value)) { // value在有序部分
low = mid + 1;
} else {
high = mid - 1;
}
}
}
return -1;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27