二分搜索
二分搜索是运用分治思想的典型例题。
分治法的思想是将一个难以解决的大问题分割成一些规模较小的有相同求解方式的问题,以逐个求解。
二分搜索,也叫做二分查找,要求是输入非降序排列的n个元素的数组,以及要找的元素x,输出已找到,或者未找到。
二分搜索的做法很简单,就是将要找的元素X跟已有非降序的数组的中位数进行比较,如果X>中位数,则x位于大于中位数的那一半数组中,如果X<中位数,X位于小于中位数的那一半数组里,如果X=中位数,那太好了,这就是我们希望的,直接输出找到了即可。
那么这种做法有什么巧妙之处呢?其妙处在于每次查找都能将数组待查找元素减半,从而缩小问题规模。
举例,现在有0、1、2、3、4、5、6、7、8、9十个元素,我们要找的数字是0,算法执行过程如下:
当前中位数为4,0<4,所以0在小于4的那一半数组里。(也可以是5,在偶数时,我们默认取小的中位数)
数组减半,让数组变成0、1、2、3,现在中位数是2,0<2,所以0位于小于2的一半的数组里。
新的数组为0,1,中位数为0,0=0,我们找到了要找的元素!
这虽然看起来十分繁琐,甚至不如我们暴力的挨个比较,但是在数据很大的时候,这种查找的效率是非常高的。因为每次查找过后数组规模都以2的指数缩小,在10个数字时虽然最多需要比较4次,但是在1000个数字时,就仅仅需要比较10次了(2^10=1024>1000),而数据达到1亿的时候最坏情况是比较20次,达到10亿时最多就需要30次。这是很惊人的效率!
OK,那么我们应该如何用代码实现呢?
可以用迭代法,但这里我们只列举递归的方法。
#include<iostream>
using namespace std;
int binarySearch(int arr[] , int low , int high , int target)//递归实现 arr是数组,low是数组第一个元素的序号,high是数组最后一个元素的序号,target是要找的元素
{
int middle = (low + high)/2;
if(low > high)
return -1;
if(arr[middle] == target)
return middle;
if(arr[middle] > target)
return binarySearch( arr , low , middle - 1 , target);
if(arr[middle] < target)
return binarySearch( arr , middle + 1 , high , target);
};
int main()
{
int a[10]={0,1,2,3,4,5,6,7,8,9};
int x=0;
int y=binarySearch(a,0,9,x);
if(y!=-1) {
cout<<"we find it";
}
else {
cout<<"we can't find it";
}
return 0;
}
小结:
分治法的适用条件:
1.问题的规模缩小到一定程度就可以容易地解决;
2.该问题可以分解为若干规模较小的相同的问题,即该问题具有最优子结构性质
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题都是相互独立的,即子问题之间不包含公共子问题(这条特征涉及到分治法的效率,如果各个子问题之间不独立,则分治法需要做很多不必要的工作,重复地求解公共子问题,此时虽然可以用分治法,但是用动态规划更好)
分治法的基本步骤:
1.解决规模较小的问题(也就是找到递归中的边界条件)
2.分解问题,减小问题规模
3.递归的求解子问题
4.合并子问题的解为原问题的解
分治法的复杂度:
f(n)可以用master原理求解;