二分法查找
#include <iostream>
#include <cstdio>
using namespace std;
// a是有序数组(从小到大排序),length是数组长度,num是指定元素
int BinarySearch(int a[], int length, int num)
{
// 初始查找区间:[0, length-1]
int L = 0; // 查找区间的左端点
int R = length - 1; // 查找区间的右端点
while(L <= R) { // 如果查找区间不为空,就继续查找
int mid = L + (R - L) / 2; // 查找区间的中间值
// int mid = (L + R) / 2; // L+R的值可能过大导致溢出,因此不使用该语句
// cout << "L = " << L << ", R = " << R << ", mid = " << mid << endl;
if(num > a[mid]) { // 如果num在a[mid]的右边
// 设置新的查找区间:[mid+1, R]
L = mid + 1;
}
else if(num < a[mid]) { // 如果p在a[mid]的左边
// 设置新的查找区间:[L, mid-1]
R = mid - 1;
}
else { // 如果正好找到,返回下标
return mid;
}
}
return -1; // 如果没有找到,返回-1
}
int main()
{
// 在有序数组a中查找指定元素num的下标
int a[10] = {2, 6, 11, 23, 32, 44, 51, 73, 90, 108}; // 有序数组
int length = sizeof(a) / sizeof(int); // 数组长度
int num = 23; // 指定元素
int r = BinarySearch(a, length, num);
if(r < 0) // 如果没有找到
cout << "No Found." << endl;
else
cout << "Index of " << a[r] << " is " << r << endl;
return 0;
}
例题:找一对数
问题:输入 n(n≤100,000) 个整数,找出其中的两个数,它们的和等于整数 m(假定肯定有解)。
解法1:用两重循环,枚举所有的取数方法,复杂度是 O(n2)。
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int n, m;
cout << "Input n: "; cin >> n; // 数组长度
cout << "Input m: "; cin >> m; // 两个数的和
int a[n];
cout << "Input array: ";
for(int i = 0; i < n; i++) { // 输入n个整数作为无序数组的元素
cin >> a[i];
}
for(int i = 0; i < n; i++) { // 循环遍历第一个数
for(int j = i + 1; j < n; j++) { // 循环遍历第二个数
if(a[i] + a[j] == m) { // 如果这两个数正好符合条件
cout << a[i] << ", " << a[j] << endl;
break;
}
}
}
return 0;
}
解法2:将数组排序,复杂度是 O(n×log(n))。对数组中的每个元素 a[i],用二分法查找 m-a[i],复杂度是 O(n×log(n))。总的复杂度是 O(n×log(n))。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
// a是有序数组(从小到大排序),length是数组长度,num是指定元素
int BinarySearch(int a[], int length, int num)
{
int L = 0; // 查找区间的左端点
int R = length - 1; // 查找区间的右端点
while(L <= R) { // 如果查找区间不为空,就继续查找
int mid = L + (R - L) / 2; // 查找区间的中间值
if(num > a[mid]) // 如果num在a[mid]的右边
L = mid + 1;
else if(num < a[mid]) // 如果p在a[mid]的左边
R = mid - 1;
else // 如果正好找到,返回下标
return mid;
}
return -1; // 如果没有找到,返回-1
}
int main()
{
int n, m;
cout << "Input n: "; cin >> n; // 数组长度
cout << "Input m: "; cin >> m; // 两个数的和
int a[n];
cout << "Input array: ";
for(int i = 0; i < n; i++) { // 输入n个整数作为无序数组的元素
cin >> a[i];
}
sort(a, a+n); // 将数组排序
for(int i = 0; i < n; i++) { // 循环遍历第一个数
int j = BinarySearch(a, n, m-a[i]); // 用二分法查找第二个数
if(j >= 0) { // 如果查找结果不为-1,即找到第二个数
cout << a[i] << ", " << a[j] << endl;
break;
}
}
return 0;
}
解法3:将数组排序,复杂度是 O(n×log(n))。设置两个变量 i 和 j 进行查找,i 的初值是0,j 的初值是 n-1,计算 a[i]+a[j],如果大于 m 则 j-1,如果小于 m 则 i+1,直到等于 m,复杂度是 O(n)。总的复杂度是 O(n×log(n))。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
// a是有序数组(从小到大排序),length是数组长度,num是指定元素
int BinarySearch(int a[], int length, int num)
{
int L = 0; // 查找区间的左端点
int R = length - 1; // 查找区间的右端点
while(L <= R) { // 如果查找区间不为空,就继续查找
int mid = L + (R - L) / 2; // 查找区间的中间值
if(num > a[mid]) // 如果num在a[mid]的右边
L = mid + 1;
else if(num < a[mid]) // 如果p在a[mid]的左边
R = mid - 1;
else // 如果正好找到,返回下标
return mid;
}
return -1; // 如果没有找到,返回-1
}
int main()
{
int n, m;
cout << "Input n: "; cin >> n; // 数组长度
cout << "Input m: "; cin >> m; // 两个数的和
int a[n];
cout << "Input array: ";
for(int i = 0; i < n; i++) { // 输入n个整数作为无序数组的元素
cin >> a[i];
}
sort(a, a+n); // 将数组排序
int i = 0, j = n - 1; // 用i从前往后查找,用j从后往前查找
while(i < j) {
if(a[i] + a[j] > m) // 如果结果大于m
j--; // j应该往前移使结果减小
else if(a[i] + a[j] < m) // 如果结果小于m
i++; // i应该往后移使结果增大
else { // a[i] + a[j] == m
cout << a[i] << ", " << a[j] << endl;
break;
}
}
return 0;
}
输入:
n = 10,m = 50,a[n] = {11, 54, 23, 32, 44, 6, 10, 13, 2, 26}
输出:
例题:农夫和奶牛
问题:农夫建造了一座很长的畜栏,有 N(2≤N≤100,000) 个隔间,这些隔间的位置是 x0, x1, …, xN-1(0≤xi≤1,000,000,000),xi 均为整数,各不相同。有 C(2≤C≤N) 头牛,每头牛分到一个隔间,如何使任意两头牛之间的最小距离尽可能大?这个最大的最小距离是多少?
解法1:将数组排序,得到排序后的隔间坐标 x[N]。从 1,000,000,000/C 到 1 依次尝试最大的最小距离 D,直到找到第一个可行的值,复杂度是 1,000,000,000/C×N。
尝试方法:第 1 头牛放在 x0,……若第 k 头牛放在 xi,则找到 xi+1 到 xN-1 中第一个位于 [Xi+D, 1,000,000,000] 的 xj,第 k+1 头牛放在 xj,如果找不到这样的 xj 则 D-1,继续尝试。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define MaxXi 100 // xi的最大值
// 判断D是否可行
bool JudgeDis(int D, int N, int C, int x[], int cow[])
{
cow[0] = 0; // 第0号牛放在第0号隔间,隔间坐标是x[0]
int i, j, k;
for(k = 1; k < C; k++) { // 对其余每头牛(第1~C-1号)分配隔间
i = cow[k-1]; // 如果第k-1号牛放在第i号隔间,隔间坐标是x[i]
for(j = i + 1; j < N; j++) { // 从第i号隔间向后查找
if(x[j] >= x[i] + D) { // 如果找到符合条件的x[j]
cow[k] = j; // 第k号牛放在第j号隔间,隔间坐标是x[j]
break;
}
}
if(j == N) // 如果找不到x[j],即D不可行
return false;
}
if(k == C) // 如果所有牛都能放在隔间中,即D可行
return true;
}
int main()
{
int N, C;
cout << "Input N: "; cin >> N; // 隔间的数量
cout << "Input C: "; cin >> C; // 牛的数量
int x[N];
cout << "Input array: ";
for(int i = 0; i < N; i++) { // 输入N个隔间的位置或坐标
cin >> x[i];
}
int cow[C]; // 牛的隔间序号
sort(a, a+n); // 将数组排序
for(int D = MaxXi / C; D > 0; D--) { // 尝试最大的最近距离
if(JudgeDis(D, N, C, x, cow)) { // 如果D可行
cout << "Maximum nearest distance: " << D << endl;
break;
}
}
for(int i = 0; i < C; i++) {
cout << "Cow number: " << i + 1 << ", Cow location: " << x[cow[i]] << endl;
}
return 0;
}
解法2:将数组排序,得到排序后的隔间坐标 x[N]。在 [L, R] 内用二分法尝试最大的最小距离 D=(L+R)/2,如果 D 可行则记录该 D 并且 L=D+1,如果 D 不可行则 R=D-1,复杂度是 log(1,000,000,000/C)×N。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define MaxXi 100 // xi的最大值
// 判断D是否可行
bool JudgeDis(int D, int N, int C, int x[], int cow[])
{
cow[0] = 0; // 第0号牛放在第0号隔间,隔间坐标是x[0]
int i, j, k;
for(k = 1; k < C; k++) { // 对其余每头牛(第1~C-1号)分配隔间
i = cow[k-1]; // 如果第k-1号牛放在第i号隔间,隔间坐标是x[i]
for(j = i + 1; j < N; j++) { // 从第i号隔间向后查找
if(x[j] >= x[i] + D) { // 如果找到符合条件的x[j]
cow[k] = j; // 第k号牛放在第j号隔间,隔间坐标是x[j]
break;
}
}
if(j == N) // 如果找不到x[j],即D不可行
return false;
}
if(k == C) // 如果所有牛都能放在隔间中,即D可行
return true;
}
int main()
{
int N, C;
cout << "Input N: "; cin >> N; // 隔间的数量
cout << "Input C: "; cin >> C; // 牛的数量
int x[N];
cout << "Input array: ";
for(int i = 0; i < N; i++) { // 输入N个隔间的位置或坐标
cin >> x[i];
}
int cow[C]; // 牛的隔间序号
sort(a, a+n); // 将数组排序
int maxD; // 最大的最近距离
int cowD[C]; // 在找到最大的最近距离时的牛的间隔序号
int L = 1, R = MaxXi / C; // 二分法的初始区间
while(L <= R) { // 如果查找区间不为空,就继续尝试D
int D = L + (R - L) / 2;
if(JudgeDis(D, N, C, x, cow)) { // 如果D可行
maxD = D; // 记录该D
memcpy(cowD, cow, C*sizeof(int)); // 记录该D时的牛的间隔序号
L = D + 1;
}
else { // 如果D不可行
R = D - 1;
}
}
cout << "Maximum nearest distance: " << maxD << endl;
for(int i = 0; i < C; i++) {
cout << "Cow number: " << i + 1 << ", Cow location: " << x[cowD[i]] << endl;
}
return 0;
}
输入:
N = 10,C = 4,x[N] = {8, 2, 13, 87, 46, 39, 27, 31, 17, 5}
输出: