浅谈二分算法

二分算法

程序或算法的时间复杂度

基本概念

  • 一个程序或算法的时间效率,也称“时间复杂度”,有时简称“复杂度”
  • 复杂度常用大写字母 O O O和小写字母 n n n来表示, n n n代表问题的规模
  • 时间复杂度是用算法运行过程中,某种时间固定的操作需要被执行的次数和 n n n的关系来衡量的。在无序数列中查找某个数,复杂度是 O ( n ) O(n) O(n)
  • 计算复杂度的时候,只统计执行次数最多的( n n n 足够大时)那种固定操作的次数。比如某个算法需要执行加法 n 2 n^2 n2次,除法 n n n次,那么就记其复杂度是 O ( n 2 ) O(n^2) O(n2)的。
  • 复杂度有“平均复杂度”和“最坏复杂度”两种。两者可能一致,也可能不一致。一般情况下,只需考虑“平均复杂度”,只有在要求极为严格的情况下,才需要考虑“最坏复杂度”
  • 如果复杂度是多个 n n n的函数之和,则只关心随 n n n增长增长得最快的那个函数
    • O ( n 2 + n 3 ) = > O ( n 3 ) O(n^2+n^3)=>O(n^3) O(n2+n3)=>O(n3)
    • O ( 2 n + n 2 ) = > O ( 2 n ) O(2^n+n^2)=>O(2^n) O(2n+n2)=>O(2n)
    • O ( n ! + 3 n ) = > O ( n ! ) O(n!+3^n)=>O(n!) O(n!+3n)=>O(n!)
  • 复杂度有如下分类
    • 常数复杂度: O ( 1 ) O(1) O(1) ,时间(操作次数)和问题规模无关
    • 对数复杂度: O ( l o g ( n ) ) O(log(n)) O(log(n))
    • 线性复杂度: O ( n ) O(n) O(n)
    • 多项式复杂度: O ( n k ) O(n^k) O(nk)
    • 指数复杂度: O ( a n ) O(a^n) O(an)
    • 阶乘复杂度: O ( n ! ) O(n!) O(n!)
  • 常见的程序时间复杂度
    • 在无序数列中查找某个数(顺序查找): O ( n ) O(n) O(n)
    • 平面上有 n n n个点,要求出任意两点之间的距离: O ( n 2 ) O(n^2) O(n2)
    • 插入排序、选择排序、冒泡排序: O ( n 2 ) O(n^2) O(n2)
    • 快速排序: O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n))
    • 二分查找: O ( l o g ( n ) ) O(log(n)) O(log(n))

二分查找

**情景导入:**A 心里想一个1~1000之间的数,B来猜,可以问问题,A只能回答是或否,怎么猜才能问的问题次数最少?

  • 解决方法:大于500吗?大于750吗?大于625吗?…每次缩小猜测范围到上次的一半,只需要10次

原理及适用情况

  • 原理:二分查找每次缩小猜测范围到上次的一半
  • 限制:所查找的数组必须是有序的

实现

  • BinarySearch:在包含size个元素的,从小到大排序的int数组a里面查找元素p,如果找到,则返回元素下标,如果找不到,则返回-1

    int BinarySearch(int a[], int size, int p) {
    	int L = 0;
    	int R = size - 1;
    	while(L <= R) {
    		int mid = L + (R - L)/2;
    		if(p == a[mid]) {
    			return mid;
    		} else if(p > a[mid]) {
    			L = mid + 1;
    		} else {
    			R = mid - 1;
    		}
    	}
    }
    
  • LowerBound:包含size个元素的,从小到大排序的int数组a里面查找比给定整数p小的,下标最大的元素。找到则返回其下标,找不到则返回-1

    int LowerBound(int a[], int size, int p) {	// 复杂度 O(log(n)) 
    	int L = 0;
    	int R = size -1;
    	int lastPos = -1;
    	while(L <= R) {
    		int mid = L + (R -L)/2;
    		if(a[mid] >= p) {
    			R = mid - 1;
    		} else {
    			lastPos = mid;
    			L = mid + 1;
    		}
    	}
    	return lastPos;
    } 
    
  • 代码细节

    • int mid = (L+R)/2:取查找区间正中元素的下标,此种方法可能会溢出
    • 为了防止(L+R)过大溢出:int mid = L+(R-L)/2

基本例题

case 1:二分法求方程的根

**问题描述:**求该方程的根: f ( x ) = x 3 − 5 x 2 + 10 x − 80 = 0 f(x)=x^3-5x^2+10x-80=0 f(x)=x35x2+10x80=0,若求出的根是 a a a,则要求 ∣ f ( a ∣ ≤ 1 0 − 6 |f(a|\leq10^{-6} f(a106

  • 使用二分法求解该问题是有限制的:方程必须是单调递增或递减的

  • 示例代码

    #include <iostream>
    #include <cmath>
    #include <cstdio>
    #include <cstdlib>
    using namespace std;
    double EPS = 1e-6;
    double f(double x) {
    	return x*x*x - 5*x*x + 10*x - 80;
    } 
    int main() {
    	double root, x1 = 0, x2 = 100, y;
    	root = x1+(x2-x1)/2;
    	int triedTimes = 1;
    	y = f(root);
    	while(fabs(y) > EPS) {
    		if(y > 0) {
    			x2 = root;
    		} else {
    			x1 = root;
    		}
    		root = x1+(x2-x1)/2;
    		y = f(root);
    		triedTimes ++;
    	}
    	printf("%.8f\n", root);
    	printf("%d", triedTimes);
    	return 0;
    }
    

case 2:找一对数

**问题描述:**输入 n ( n ≤ 1000000 ) n(n\leq 1000000) n(n1000000)个数,找出其中的两个数,他们之和等于整数 m m m,(假定肯定有解)。题中所有的整数都能用int表示

  • 解法一

    • 用两重循环,枚举所有的取数方法,复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 解法二

    • 将数组排序,复杂度是 O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n))
    • 对数组中的每个元素a[i],在数组中二分查找m-a[i],看能否找到。复杂度 l o g ( n ) log(n) log(n),最坏要查找 n − 2 n-2 n2次,故查找这部分的时间复杂度也是 O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n))
    • 该解法总的复杂度为 O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n))
  • 解法三

    • 将数组排序,复杂度是 O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n))
    • 查找的时候,设置两个变量iji的初值为0j的初值为n-1。看a[i]+a[j],如果大于m,就让j--,如果小于m,就让i++,直至a[i]+a[j]=m
  • 示例代码

    #include <iostream>
    #include <algorithm>	// 包含排序的库函数 
    using namespace std;
    
    int BinarySearch(int a[], int size, int p) {
    	int L = 0;
    	int R = size-1;
    	while(L <= R) {
    		int mid = L+(R-L)/2;
    		if(a[mid] == p) {
    			return a[mid];	
    		} else if(a[mid] > p) {
    			R = mid - 1;
    		} else {
    			L = mid + 1;
    		}
    	}
    	return -1;
    }
    
    void solution_one(int a[], int n, int m) {
    	for(int i=0; i<n-1; i++) {
    		for(int j=i+1; j<n; j++) {
    			if(a[i] + a[j] == m) {
    				cout << a[i] <<" "<< a[j] << endl;
    			}
    		}
    	} 
    }
    
    void solution_two(int a[], int n, int m) {
    	sort(a,a+n);	// 排序 
    	for(int i=0; i<n-1; i++) {
    		int other = m - a[i];
    		if(BinarySearch(a,n,other) != -1) {
    			cout << a[i] <<" "<< other<<endl;
    			break;
    		}
    	}
    }
    
    void solution_three(int a[], int n, int m) {
    	sort(a,a+n);
    	int i=0, j=n-1;
    	while((a[i]+a[j])!=m) {
    		if(a[i] + a[j] < m) {
    			i++;
    		}
    		if(a[i] + a[j] > m) {
    			j--;
    		}
    	}
    	cout << a[i] <<" "<< a[j] <<endl; 
    }
    
    int main() {
    	int a[10] = {9,6,3,8,5,2,7,4,1,0};
    	solution_three(a,10,13);
    	return 0;
    }
    

最后有需要工程文件的朋友可以在评论里说明(记得指明邮箱),小编看到后会第一时间发送到指定邮箱。文章如有不严谨之处,欢迎大家指正!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

steven_moyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值