#include<cstdio>
int binSearch1(int a[], int e, int lo, int hi){
while (lo < hi){
int mi = (lo + hi) >> 1;
if (e < a[mi])hi = mi;
else if (a[mi] < e)lo = mi + 1;
else return mi;
}
return -1;
}
斐波那契查找是利用斐波那契数列将有序数组进行分割查找。核心是通过斐波那契数确定每次查找的分割点,逐步缩小查找范围,直到找到目标元素或确定元素不存在。
int fib(int n){
if (n == 0)return 0;
if (n == 1)return 1;
int a = 0, b = 1;
for (int i = 2; i <= n; i++){
int temp = b;
b = a + b;
a = temp;
}
return b;
}
• 初始化 a 为斐波那契数列的第0项(值为0),b 为第1项(值为1) 。
• while 循环目的是生成大于等于查找区间长度(hi - lo)的斐波那契数。每次循环中,temp 暂存当前的 b 值,然后让 b 变为下一个斐波那契数(即 a + b ),a 变为原来的 b 值(即 temp ) 。当 b 大于等于查找区间长度时,循环结束。此时 b 就是合适的斐波那契数,a 是它的前一项斐波那契数。
int fibSearch1(int* a, int e, int lo, int hi){
int fib_num = 0, i = 0;
while ( fib(i) < hi - lo )i++;
fib_num = fib(i);
while (lo < hi){
int mi = lo + fib(i - 1) - 1;//按黄金比例分割
if (e < a[mi]){
hi = mi; i--;
}
else if (a[mi] < e){
lo = mi + 1; i -= 2;
}
else return mi;
}
return -1;
}
int fibSearch2(int* A, int e, int lo, int hi){
int a = 0, b = 1;
while (b < hi - lo){
int temp = b;
b = a + b;
a = temp;
}
while (lo < hi){
int mid = lo + a - 1;
if (e < A[mid]){
lo = mid + 1;
int temp = a;
a = b - a;
b = temp;
}
else if (A[mid] < e){
hi = mid;
b = a;
a = b - a;
}
else return mid;
}
return -1;
}
• 确定分割点:每次进入循环,通过 int mid = lo + a - 1; 计算分割点索引 mid 。这里利用 a (当前合适的斐波那契数的前一项)来按斐波那契数列的比例确定分割位置。
• 元素比较与区间调整:
◦ 当 A[mid] < e 时,说明目标元素 e 在 mid 右侧,更新查找区间左边界 lo = mid + 1 。同时,为了下次查找能正确利用斐波那契数列特性,更新 a 和 b 。temp 暂存 a ,a 更新为 b - a (新的合适的斐波那契数的前一项),b 更新为原来的 a 值(即 temp ) 。
◦ 当 A[mid] > e 时,说明目标元素 e 在 mid 左侧,更新查找区间右边界 hi = mid 。然后更新 b 为当前的 a ,a 为 b - a ,同样是为了适应新查找区间的斐波那契数列特性。
◦ 当 A[mid] == e 时,说明找到了目标元素,直接返回 mid 。
int binSearch2(int* a, int e, int lo,int hi){
while (1 < hi - lo){
int mi = (lo + hi) >> 1;
e < a[mi] ? hi = mi : lo = mi;
return e == a[lo] ? lo : -1;//出口时hi=lo+1,查找·区间仅含一个元素a[lo]
}
}
int binSearch3(int* a, int e, int lo, int hi){
while (lo < hi){
int mi = (lo + hi) >> 1;
e < a[mi] ? hi = mi : lo = mi + 1;
}
return --lo;
}
double binarySearch(double L, double R,int a){
while (R - L>1e-6){
double mid = (L + R) / 2;
if (mid*mid - a>0)R = mid;
else L = mid;
}
return L;
}
练习:利用二分求有序集合的交集
法1
左闭右开区间 [lo, hi)
进行二分查找。循环条件 lo < hi
意味着 hi
所指向的元素不在当前查找范围内。当 e < a[mid]
时,将 hi
更新为 mid
,缩小查找范围到左半部分;当 e >= a[mid]
时,将 lo
更新为 mid + 1
,缩小查找范围到右半部分。调用时传入的区间是 [0, m]
,保证了整个数组元素都能被包含在查找范围内。
#include <stdio.h>
int binSearch(int a[], int e, int lo, int hi) {
while (lo < hi) {
int mid = (lo + hi) >> 1;
(e < a[mid]) ? (hi = mid) : (lo = mid + 1);
}
return --lo;
}
int main() {
int n, m;
scanf("%d", &n);
int a[n];
for (int i = 0; i < n; i++)scanf("%d", &a[i]);
scanf("%d", &m);
int b[m];
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
for (int i = 0; i < n; i++) {
if (b[binSearch(b, a[i], 0, m )] == a[i]) printf("%d ", a[i]);
}
return 0;
}
Q:将 binSearch(b, a[i], 0, m )
改成 binSearch(b, a[i], 0, m - 1)
会不会出错呢?
A:会,是边界没处理好。
当把 binSearch(b, a[i], 0, m )
改成 binSearch(b, a[i], 0, m - 1)
时,传递给 binSearch
函数的查找区间变为 [0, m - 1)
,而实际上数组 b
的合法下标范围是 [0, m - 1]
(这里是常规的左闭右闭区间理解)。这就导致当目标元素 a[i]
恰好是 b
数组的最后一个元素时,在 [0, m - 1)
这个区间内无法找到它,因为查找范围根本没有覆盖到最后一个元素所在的下标位置 ,从而导致错误的结果。
例如,假设 b
数组为 {1, 2, 3}
,如果要查找元素 3
,按照修改后的调用 binSearch(b, 3, 0, 2)
,在 [0, 2)
这个区间内是找不到 3
的,尽管 3
是 b
数组中的元素。而原代码调用 binSearch(b, 3, 0, 3)
,在 [0, 3)
区间内是可以正确找到 3
这个元素的。
所以,为了保证二分查找函数能正确在整个 b
数组范围内查找元素,应该使用 binSearch(b, a[i], 0, m )
,维持左闭右开区间 [0, m)
来匹配函数内部的查找逻辑。
当 b
数组为 {1, 2, 3}
,调用 binSearch(b, 3, 0, 2)
时二分查找的过程:
第一轮循环
- 初始时
lo = 0
,hi = 2
。 - 计算中间位置
mid = (0 + 2) >> 1 = 1
。 - 此时
a[mid]
即b[1]
,值为2
,要查找的元素e = 3
,因为3 > 2
,根据函数逻辑(e < a[mid])? (hi = mid) : (lo = mid + 1)
,会执行lo = mid + 1
,此时lo
更新为2
。
第二轮循环
- 此时
lo = 2
,hi = 2
,不满足循环条件lo < hi
,循环结束。
最终返回结果
- 循环结束后执行
return --lo
,lo
先自减为1
然后返回。但实际上我们要查找的元素3
在b
数组中的下标应该是2
,由于查找区间设置为[0, 2)
,根本没有覆盖到b
数组最后一个元素的下标位置,导致无法正确找到元素3
,返回了错误的结果。
引入:那如果改成 lo <= hi ,是不是就可以解决这个问题了呢?
可以是可以,但是要改一下binSearch的写法,请看 法2
相似的法2
左闭右闭区间 [lo, hi]
进行二分查找。在循环条件 lo <= hi
下,lo
和 hi
都代表有效元素的索引。当 e < a[mi]
时,将 hi
更新为 mi - 1
,缩小查找范围到左半部分;当 e >= a[mi]
时,将 lo
更新为 mi + 1
,缩小查找范围到右半部分。调用时传入的区间是 [0, m - 1]
,这与左闭右闭区间的逻辑相符。
#include <stdio.h>
int binSearch(int a[], int e, int lo, int hi) {
while (lo <= hi) {
int mi = (lo + hi) >> 1;
(e < a[mi]) ? (hi = mi-1) : (lo = mi + 1);
}
return --lo;
}
int main() {
int n, m;
scanf("%d", &n);
int a[n];
for (int i = 0; i < n; i++)scanf("%d", &a[i]);
scanf("%d", &m);
int b[m];
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
for (int i = 0; i < n; i++) {
if (b[binSearch(b, a[i], 0, m-1 )] == a[i]) printf("%d ", a[i]);
}
return 0;
}
演示:
假设有一个有序数组 b = {1, 3, 5, 7, 9}
,想要查找元素 7
。
左闭右闭区间 [lo, hi]
#include <stdio.h>
int binSearch(int a[], int e, int lo, int hi) {
while (lo <= hi) {
int mi = (lo + hi) >> 1;
(e < a[mi]) ? (hi = mi - 1) : (lo = mi + 1);
}
return --lo;
}
int main() {
int b[] = {1, 3, 5, 7, 9};
int m = sizeof(b) / sizeof(b[0]);
int e = 7;
int index = binSearch(b, e, 0, m - 1);
printf("找到元素 %d 的索引是 %d\n", e, index);
return 0;
}
查找过程
- 初始状态:
lo = 0
,hi = 4
(因为数组长度m = 5
,m - 1 = 4
),要查找的元素e = 7
。
- 第一次循环:
- 计算中间位置
mi = (0 + 4) >> 1 = 2
。 a[mi] = a[2] = 5
,因为7 > 5
,所以执行lo = mi + 1
,即lo = 3
。
- 计算中间位置
- 第二次循环:
- 计算中间位置
mi = (3 + 4) >> 1 = 3
。 a[mi] = a[3] = 7
,因为7 >= 7
,所以执行lo = mi + 1
,即lo = 4
。
- 计算中间位置
- 第三次循环:
- 计算中间位置
mi = (4 + 4) >> 1 = 4
。 a[mi] = a[4] = 9
,因为7 < 9
,所以执行hi = mi - 1
,即hi = 3
。
- 计算中间位置
- 循环结束:
- 此时
lo = 4
,hi = 3
,不满足lo <= hi
的条件,循环结束。 - 执行
return --lo
,lo
先减 1 变为 3,然后返回 3,即找到了元素7
的索引。
- 此时
左闭右开区间 [lo, hi)
#include <stdio.h>
int binSearch(int a[], int e, int lo, int hi) {
while (lo < hi) {
int mid = (lo + hi) >> 1;
(e < a[mid]) ? (hi = mid) : (lo = mid + 1);
}
return --lo;
}
int main() {
int b[] = {1, 3, 5, 7, 9};
int m = sizeof(b) / sizeof(b[0]);
int e = 7;
int index = binSearch(b, e, 0, m);
printf("找到元素 %d 的索引是 %d\n", e, index);
return 0;
}
查找过程
- 初始状态:
lo = 0
,hi = 5
(因为采用左闭右开区间,要包含整个数组元素),要查找的元素e = 7
。
- 第一次循环:
- 计算中间位置
mid = (0 + 5) >> 1 = 2
。 a[mid] = a[2] = 5
,因为7 > 5
,所以执行lo = mid + 1
,即lo = 3
。
- 计算中间位置
- 第二次循环:
- 计算中间位置
mid = (3 + 5) >> 1 = 4
。 a[mid] = a[4] = 9
,因为7 < 9
,所以执行hi = mid
,即hi = 4
。
- 计算中间位置
- 第三次循环:
- 计算中间位置
mid = (3 + 4) >> 1 = 3
。 a[mid] = a[3] = 7
,因为7 >= 7
,所以执行lo = mid + 1
,即lo = 4
。
- 计算中间位置
- 循环结束:
- 此时
lo = 4
,hi = 4
,不满足lo < hi
的条件,循环结束。 - 执行
return --lo
,lo
先减 1 变为 3,然后返回 3,即找到了元素7
的索引。
- 此时