基础算法-二分查找
Posted xiaoranone
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基础算法-二分查找相关的知识,希望对你有一定的参考价值。
基础算法-二分查找
二分查找算法是在实践中用的最多的算法之一。因为它简单易懂,效率很高,成为很多程序员的首选。之前我们也看到过很多关于二分查找的文章,例如你真的会二分查找吗?这个看似简单的算法,却有很多需要我们注意的地方,这里我们要思考:
- 什么时候使用二分 ?
- 怎么使用二分 ?
- 二分为什么效率高?
二分查找
二分查找又名折办查找,是一种简单而且较有效的查找方法。要满足两点:循序结构存储,关键字有序排序。二分查找有很多版本,我一般使用两端闭区间的代码格式,(注意这很重要,建议使用自己喜欢的唯一一种方法,防止出错)。
给出我自己喜欢的格式:
// A 关键字有序,而且顺序存储
function binary_search(A, n, target, cmp):
left = 0
right = n - 1
while left <= right:
mid = (left + right) / 2
if cmp(A[mid], target):
left = mid + 1;
else if not cmp(A[mid], target):
right = mid - 1;
else:
return answer = A[mid]
return unsucessful
什么时候使用
1. 是否是查找问题
2. 是否能够使用顺序查找
3. (结果区间)是否有序
4. 是否是顺序存储(即通过下标可以直接定位到对应的值)
5. 怎么进行二分,这个看似简单,其实二分的关键
我们直接来看几个问题,分析是否满足这几个条件,当然有些简单的问题我们可以直接看出来是二分查找,还是希望从头分析,一起往能满足我们给出的四个规则。
- 给定一个有序的整数数组A和一个值target,判断target是否在A中,如果存在返回对应的任意一个下标,否则返回-1.
有序,数组,判断是否存在(可以使用顺序查找),结果区间是整数数组(有序),这里我们只需要根据下标二分就行了。这就是我们最常用的二分查找的原型。
- 有两个有序数组A(长度n)和B(长度m),求这两个数组合并之后的中位数,中位数我们认为是有序数组的中间的数,median = S[(n+m) / 2], S是排序后的数组。
- 可以直接看出来是查找问题
- 能使用顺序查找,顺序扫描两个数组,直到k = (n+m)/2,就得到我们要的中位数
- 结果区间是否有序,有序,我们可以假设有一个大数组是A+B排序后的结果
- A和B都是顺序存储
- 怎么进行二分,这里我们两个数组A和B同时二分,每次也是缩小一般查询区间。
A:[0, n-1], B: [0, m-1]
mid_a = (left_a + right_a) / 2
mid_b = (left_b + right_b) / 2
if judge(A[mid_a], B[mid_b]): // A[mid_a] < B[mid_b]
A: [mid_a+1, right_a]
B: [left_b, mid_b-1]
else:
A: [left_a, mid_a-1]
B: [mid_b+1, right_b]
util left_a == right_a
return max(A[left_a], B[left_b])
- 实现 int sqrt(int x) 函数,计算并返回 x 的平方根。
- 查找问题,对应x = a * a, a属于[0, x]之间,即从[0, x]中查找a,是的a*a = x
- 能否使用顺序查找,可以遍历for a in [0, x]: 直到满足条件
- 结果区间是[0, x]有序
- 结果区间有序存储,关键字就是结果
- 根据结果区间[0, x]进行二分即可。
int sqrt(int x)
long long a=0,b=x;
while(a < b)
long long m = (a+b)/2;
if(m*m == x) break;
else if(m*m > x) b = m-1; //第一个judge(x)
else a = m+1;
if(b*b > x) b-=1;
return b;
- 原木切割
题目:有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k。当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头的最大长度。
从这个问题中,我们很难想到二分查找问题,那就抽象这个问题,一些小木棒vector<int>
, 小段木头的长度length, 一定满足0 < length <=max(vector<int>)
。
- 是查找问题,从0 < length <=
max(vector<int>)
找到最合适的长度满足条件,- 能够使用顺序查找,遍历所有的长度,判断是否满足条件
- 结果区间[0, max(vector< int>)] 有序,而且顺序
- 之后就可以堆结果区间进行二分查找即可,
下面的代码是典型的转换问题之后的二分查找的问题。
int cmp(vector<int> L, int k, int length)
if(length == 0) return -1;
int cnt = 0;
for(int i = 0; i < L.size(); i ++)
cnt += L[i] / length;
if(cnt < k) return 0;
if(cnt >= k) return 1; //长度短
int woodCut(vector<int> L, int k)
long long maxlen = 0, minlen = 0, midlen = 0;
for(int i = 0; i < L.size(); i ++)
if(maxlen < L[i]) maxlen = L[i];
while(minlen <= maxlen)
midlen = (maxlen + minlen) / 2;
if(cmp(L, k, midlen) == 1)
minlen = midlen + 1;
else if(cmp(L, k, midlen) == 0)
maxlen = midlen - 1;
else
break;
return maxlen;
二分查找很简单,对于一些显而易见的问题,我们都能想到是二分查找的问题.但是有一些问题是需要我们进行抽象之后的才能看处理其本质。我们需要指出这里的有序和顺序是指结果区间有序,关键字一般都是我们需要问题的结果,不是去看这个题目给出的数组,例如题目4中我们没有关注给定的一些小木段,而是关注的结果区间的长度。
为什么有效
二分查找时间复杂度是O(logn),几乎每一个人都知道,这里我们就来公式证明一下这个显而易见的结果。
根据二分的性质,
T(n) = T(n/2) + O(1)
T(n/2) = T(n/4) + O(1)
.
.
.
T(2) = T(1) + O(1)
T(1) = O(1)
这里个高度是logn(底数是2),因此 T(n) = O(1) * logn = O(logn)
总结:
浅谈二分到这里就结束了,正所谓纸上得来终觉浅,绝知此事要躬行。多想多练必不可少。
生活如此,问题不大。喵~
以上是关于基础算法-二分查找的主要内容,如果未能解决你的问题,请参考以下文章