二分法

2017-08-06    22:48:56

 

一、定义

  二分查找 又称为折半查找 , 是一种查找效率较高的方法 。

  要求 : 1 . 所查找的序列为有序序列

       2. 只能在顺序存储结构上实现

二、基本思想

  每次将给定的 key 值与有序表中间位置上记录的数据进行比较 ,确定待查记录所在的范围 , 然后逐渐缩小查找范围 , 直到确定找到或找不到 。

三、查找过程

  每次执行的操作是将  key  依次与  mid  对应的数据进行比较 。

 

注意 :

  mid = (  low + high ) / 2  , 可能会因为 low + high 超范围溢出 , 所以在写的时候 mid = low + ( high - low ) /  2 ;

举一个例题 :

  输入n ( n<= 100,000) 个整数, 找出其中的两个数,它们之和等于 整数m( 假定肯定有解) 。 题中所有整数都能用 int 。

  方法一 : 二分找数 。

  方法二 :1 . 对数组进行排序 , 复杂度为 n * log n  ; 

       2 . 设置两个下标 i 和 j , 另 i = 0 , j = n - 1  , 判断 a[i] + a[j] == m  , 若 a[i] + a[j] > m , j--  , 否则如果 a[i] + a[j] < m , i++ 。 这样下来复杂度为  n  。

      综合起来复杂度为  n * log n 。

 

代码示例 :

  非递归版本

  

/*******************************/
// 非递归版的二分法 
void search ( int low , int high , int key ) {
	
	while ( low <= high ) {
		int mid = ( low + high ) / 2 ;
		if ( key == a[mid] ) {
			cout << "找到" ;
			return ;  // return 的作用是为了跳出函数,并且返回值为空类型 
		}  
		if ( key > a[mid] ) low = mid + 1 ;
		else high = mid - 1 ;
	}
	cout << "未找到" ;  // 若前面所要找的数未找到,则函数不会返回,此时会执行到这一步。 
} 

 

递归版本

 

/*******************************/
// 递归版的二分法 
void search ( int low , int high , int key ) {
	int mid = ( low + high ) / 2 ;
	
	if ( low >= high ) {
		cout << "未找到" ;
		return ; 
	}
	if ( a[mid] == key ) {
		cout << "找到" ;
		return ;
	}
	if ( a[mid] > key ) search ( low , mid - 1 , key ) ;
	else search ( mid + 1 , high , key ) ;
}

 

时间复杂度:

  由于二分法每次是折半分 , 设复杂度为 k , 二分的所有数据总数为 n , 则 2^k = n  , 复杂度 k = lg n 。

 

二分查找 : 

  在做二分题的时候会遇到一类二分查找的题 , 此类题的特点是 直接找到要二分的首区间与尾区间 , 一直二分找最优解 ,直到退出循环条件 。

  方法一 :

int fun ( int key , int l , int r ) {
	int mid ;
	while ( l <= r ) {
		mid = l + ( r - l ) / 2 ;
		if ( a[mid] == key ) return mid ;
		if ( a[mid] < key ) l = mid + 1 ;
		else r = mid - 1 ;		
	}
	return -1 ;
}

// 在不提前返回的前提下 , 二分的退出条件是 l > r  , 退出循环的前一次是 l = r = mid  

  方法二:

int fun ( int key , int l , int r ) {
	int mid ;
	
	while ( l < r ) {   // 在二分的过程中不返回值的情况 
		mid = l + ( r - l ) / 2 ;
		if ( a[mid] < key ) l = mid + 1 ;
		else r = mid ;
	}
	if ( a[mid] == key ) return mid ;
	else return -1 ;
}
/// 这种方式写的二分 , 循环最终退出是因为 l 一直向左移 , 最终等于 r , 此时 l 等于 r ,结束循环 
/// 而此时 r 也记录着最后一次 mid 值  

  

有一种情况也很特殊 :

int fun ( int key , int l , int r ) {
	int mid ;
	while ( l <= r ) {
		mid = l + ( r - l ) / 2 ;
		if ( a[mid] == key ) return mid ;
		if ( a[mid] < key ) l = mid;   //若二分这样写 , 则可能造成循环退不出去  
		else r = mid ;		// 同上 , 但 有种情况可以这样写 ,就是在求一个方程的零点时
							// 因为零点不会是整数点 ,此时这样写不会有问题 ,但为了确保
							// 肯定没问题 , 循环可以不用 while 控制 , 直接二分100次 
	}
	return -1 ;
}

  

 

例题 :

问题描述
  有N个城市,每个城市有Ai个人。
  现在要开始投票,每个人有一张票。
  作为领导者,你有B个箱子,你必须要将这B个箱子分发到N个城市去,每个城市至少需要一个箱子。
  每个人都必须要投票,不能弃票,也就是说要把票丢进箱子里去(每个城市有Ai张票)。
  现在问你,怎么分配这B个箱子才能使这些所有箱子里中票数最大的那个箱子里的票最少?

输入
  输入包含一个数N(1<=N<=500,000)和B(N<=B<=2,000,000)。
  接下来N行包含N个数,每个数Ai(1<=Ai<=5,000,000)表示每个城市的人数。
  输入N为-1,B为-1时结束。

输出
  输出那个带兵量最多的将领所带的士兵数。

样例输入
  2 7
  200000
  500000
  4 6
  120
  2680
  3400
  200
  -1 -1

样例输出
  100000
  1700

 

/**************************************/

补充一道 HDU 2141   http://acm.hdu.edu.cn/showproblem.php?pid=2141         // 

 

//*******************************************************//

 

 

  

   

posted @ 2017-08-06 12:45  楼主好菜啊  阅读(680)  评论(0编辑  收藏  举报