浅谈二分算法

我的梦境 提交于 2020-01-25 22:08:57

二分算法

程序或算法的时间复杂度

基本概念

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

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

  • 示例代码

    #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(n1000000)n(n\leq 1000000)个数,找出其中的两个数,他们之和等于整数mm,(假定肯定有解)。题中所有的整数都能用int表示

  • 解法一

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

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

    • 将数组排序,复杂度是O(nlog(n))O(n*log(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;
    }
    

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!