一、原理
如下图所示的就是二分查找算法的原理:
注意:二分查找算法中一个重要的思想:数组和函数是一样的概念,对可以使用二分法查找要求如下所示。
(1)数组f[i]是有序数组
(2)函数f(x)是单调的函数
(3)f[i]=d,由x找d容易,由d找x较难
(4)f(x)=y,由x找y容易,由y找x较难
二、代码示例
2.1、二分查找算法对有序数据的查找
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
void output(int* arr, int n, int ind) {
int len = 0;
for (int i = 0; i < n; i++) {
len += printf("%4d", i);
}
printf("\n");
for (int i = 0; i < len; i++) printf("-");
printf("\n");
for (int i = 0; i < n; i++) {
if (i == ind) printf("\033[1;32m");
printf("%4d", arr[i]);
if (i == ind) printf("\033[0m");
}
printf("\n");
return;
}
/* 二分查找算法 */
int binary_search(int* arry, int len, int findData)
{
int head = 0, tail = len - 1, mid;
while (head <= tail)
{
mid = (head + tail) / 2;
printf("\r\n[%d, %d], mid = %d, arr[%d] = %d\n",
head, tail, mid,
mid, arry[mid]);
if (arry[mid] == findData) return mid;
if (arry[mid] > findData) tail = mid - 1;
else head = mid + 1;
}
return -1;
}
void test(int n)
{
int findData = 0, ret=0;
int* arr = (int*)malloc(sizeof(int) * n);
arr[0] = rand() % 100;
for (int i = 1; i < n; i++)
{
arr[i] = arr[i - 1] + rand() % 100;
}
output(arr, n, -1); //打印数组
while (~scanf_s("%d", &findData))
{
ret = binary_search(arr, n, findData); //二分法查找
if (-1 == ret) {
printf("未找到\r\n");
break;
}
output(arr, n, ret);
}
free(arr);
}
运行结果;
2.2、二分查找算法对单调函数的查找
二分查找算法对函数的查找,就是x取较大值和较小值,然后两者逼近函数的输出值,最终将输出值与目标值对比,只到达到最终的精度要求。
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#define EXP 1e-4 //最后二分法对函数查找的最小精度
#define min(a, b) ((a) < (b) ? (a) : (b))
double fun(double x) {
if (x >= 0) x -= min(x, 3000) * 0.03;
if (x > 3000) x -= (min(x, 12000) - 3000) * 0.1;
if (x > 12000) x -= (min(x, 25000) - 12000) * 0.2;
if (x > 25000) x -= (min(x, 35000) - 25000) * 0.25;
if (x > 35000) x -= (min(x, 55000) - 35000) * 0.3;
if (x > 55000) x -= (min(x, 80000) - 55000) * 0.35;
if (x > 80000) x -= (x - 80000) * 0.45;
return x;
}
/* 二分查找算法 */
double binary_algorithm(double Belast)
{
double head = 0, tail = 1000000, mid; //头为税后最小,尾为税后最大
while (tail - head >= EXP) //这里是循环的退出条件,而不是在函数内部
{
mid = (head + tail) / 2.0;
if (fun(mid) > Belast) tail = mid;
else head = mid;
}
return head;
}
/* 根据税后收入查税前收入是多少 */
void test1()
{
double Belast=0.0, Befirst = 0.0;
printf("\r\n请输入税后收入:");
while (~scanf_s("%lf", &Belast))
{
Befirst = binary_algorithm(Belast);
if (Befirst == -1) printf("未找到");
else printf("Befist:%lf\r\n", Befirst);
printf("\r\n请输入税后收入:");
}
}
int main()
{
test1();
return 0;
}
最终输出结果:
三、二分查找算法的泛式情况
定义head为数组的头下表,tail为数组的尾部的下表,mid为数组中位数的下表。下面是原始二分法算法,针对“0123456789”形式数组:
while(head <= tail)
{
mid = (head + tail) / 2;
if(num[mid] == target) return true;
if(num[mid] > target) tail = mid-1;
else head = mid+1;
}
(1)情况一:如果二分查找的问题是“11111111000000”,查找数组中最后一个1的位置。在二分法算法中需要做下面的调整
head = mid;
mid = (head + tail + 1) / 2;
(2)情况二:如果二分查找的问题是“00000001111111”,查找数组中第一个1的位置。
trail= mid
3.1、示例一
将数组中分为比target大的数,和比target小的数。也就成了“0000001111111”的问题,因为插入数据的下表的索引肯定是中位数靠后一个位置。
/* 使用二分法:有序数组查找是否存在,不存在返回插入位置 */
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int head = 0, trail = nums.size(), mid;
while (head < trail)
{
mid = (head + trail) / 2;
if (nums[mid] > target) head = mid + 1;
else trail = mid; //找第一个符合要求的元素。
}
return head;
}
};
3.2、示例二
检查数组中元素出现的次数,就是“1111111000000”的问题。
/* 二分查找法:无限字符串 */
class Solution {
public:
bool check(string& s, int len)
{
int cnt[256] = {0}, k = 0; //这里cnt必须全部初始化为0
for (int i = 0; s[i]; i++)
{
cnt[s[i]] += 1;
if (cnt[s[i]] == 1) k+=1;
if (i >= len) //这里必须是大于等于
{
cnt[s[i - len]] -= 1;
if (cnt[s[i - len]] == 0) k -= 1;
}
if (len == k) return true;
}
return false;
}
int lengthOfLongestSubstring(string s) {
int head = 0, trail = s.size(), mid;
while (head < trail)
{
mid = (head + trail + 1) / 2;
if (check(s, mid)) head = mid;
else trail = mid - 1;
}
return head;
}
};