二分法类型总结

来源:互联网 发布:沈阳市网络金融 编辑:程序博客网 时间:2024/06/05 17:12

二分法

能用二分法处理的问题都有一个共同特征即题目具有单调性

二分法的题目类型如下:

1.         基本二分:对有序数组进行查询

2.         查找下届:有序数组中如果要查找的数存在则返回第一次出现的位置,否则返回比该数大的第一个数的位置,STLlower_bound实现了此功能。

1)         SGI STL源码:

int* LowerBound(int* first,int* end,int num){int mid,size = end - first;while( size > 0 ){mid = size >> 1;if(*(first + mid) < num)  {first += (mid + 1);size -= (mid + 1);}   else    {size = mid;}}return(first);}

此版本代码最经典,但是理解很困难

2)         我实现版本:

int LowerBound(int a[],int n,int key){//特判key大于a中所有数的特殊情况if(key > a[n - 1]){return(n);}int s,e,mid;s = 0; e = n - 1;while(e - s > 1){mid = (s + e) >> 1;if(a[mid] < key){s = mid + 1;}else{e = mid;}}if(a[s] >= key){return(s);}return(e);}

3.         查找上届:和查找下届类似

4.         查找轮转后的有序数组

轮转后的有序数组(Rotated Sorted Array):有序数组,取其中某一个数为轴,将其之前的所有数都轮转到数组的末尾所得。比如{7, 11, 13, 17, 2, 3, 5}就是一个轮转后的有序数组。用二分查找思路如下:mid将数组分为两部分,其中一个是单调的,另一个是范围更小的轮转数组,在单调的那一部分,判断查找的数是否在范围内即可确定是否选择之。因为mid将数组只分成了两部分,如果不选择单调部分,那么必然选择另一部分,这样一直二分下去即可完成查找。注意:输入数据中不能存在相同元素,否则查询可能失败,原因如下图:当a[s] = a[mid] = a[e]时,没法确定单调区间和轮转区间,导致二分失败。

代码如下:

//代码能运行的前提是数组中不能存在相同的元素

// int a[N]数组是全局的

int  Search(int num,int n){int s,e,mid;s = 0; e = n - 1;while(s <= e){mid = (s + e) / 2; if(a[mid] == num){return(mid);}if(a[s] <= a[mid])  // [s,mid]单调{if(a[s] <= num && a[mid] >= num) // num在[s,mid-1]之间{e = mid - 1;}else                             //num在[mid+1,e]之间{s = mid + 1;}}else if(a[mid] <= a[e])  //[mid,e]单调{if(a[mid] <= num && a[e] >= num)   //num在[mid+1,e]之间{s = mid + 1;}else   //num在[s,mid-1]之间{e = mid - 1;}}else if(a[s] == a[mid] && a[mid] == a[e]) // a[s] = a[mid] = a[e]时不能确定区间单调性{// printf("%d %d %d %d \n",s,mid,e,a[s]);cout << "测试数据出现奇异性" << endl;return(-1);}}

5.         找出轮转后的有序数组的突变点

思路:二分时不选择单调区间,选择轮转区间。

代码://保证数组中无相同元素

int getSkip(int n){if(n == 1){cout << "n必须大于1" << endl;return(-1);}int s,e,mid;s = 0;e = n - 1;while(e - s > 1){        mid = (s + e) / 2;//寻找非单调区间进行迭代if(a[s] >= a[mid])  // [s,mid]是非单调区间{            e = mid;}else{s = mid;}}return(a[s] > a[e] ? e : 0);}

6.         找出两个有序数组中第K个数:假设有长度分为为mn的两个升序数组AB,在AB两个数组中查找第K大的数,即将AB按升序合并后的第K个数。

1)         用归并排序的合并函数,直接合并成有序数组C,即可。复杂度:O(k)

2)         二分:二分A数组,将A[mid]利用lower_bound查询A[mid]zB中的位置r1,那么A[0]…..A[mid]合并到B数组后,A[mid]位置在:r1 + mid + 1,比较r1 + mid + 1k的大小,选择二分的区间即可,可以思考下为什么可以这样二分(从开篇第一句话着手吧)?。复杂度:lg(n)*lg(m)

3)         更优的二分:

int FindKthElm(int A[], int aBeg, int aEnd, int B[], int bBeg, int bEnd, int k)  {  if (aBeg > aEnd) {  return B[bBeg + k - 1];  }  if (bBeg > bEnd)  {  return A[aBeg + k - 1];  }  //取中间位置   int aMid = aBeg + (aEnd - aBeg)/2;    int bMid = bBeg + (bEnd - bBeg)/2;  //从A和B的开始位置到两个数组中间位置的元素个数   int halfLen = aMid - aBeg + bMid - bBeg + 2;    if (A[aMid] < B[bMid])  {  if (halfLen > k)  {  // 此时在合并的数组中A[aBeg...aMid]和元素一定在B[bMid]的左侧,   // 即此时第k大的元素一定比B[bMid]这个元素小(严格来说不大于)   // 故以后没有必要搜索 B[bMid...bEnd]这些元素   return FindKthElm(A, aBeg, aEnd, B, bBeg, bMid - 1, k);  }  else   {  // 此时在合并的数组中A[aBeg...aMid]元素一定在B[bMid]的左侧,所以前K个元//素中一定包含A[aBeg...aMid](可以使用反证法来证明这点)但是无法判断//A[amid+1...aEnd]与B[bBeg...bEnd]之间的关系,//帮需要对他们进行判断此时K就剩//下除去A[aBeg...aMid]这些元素,个数为k - (aMid - aBeg + 1)   return FindKthElm(A, aMid + 1, aEnd, B, bBeg, bEnd, k - (aMid - aBeg + 1));  }  }  else  {  if (halfLen > k) {  return FindKthElm(A, aBeg, aMid - 1, B, bBeg, bEnd, k);  }  else   {  return FindKthElm(A, aBeg, aEnd, B, bMid + 1, bEnd, k - (bMid - bBeg + 1));  }  }  }

4)         3)一样的大神级代码

//return the value of kth element in union of two sorted arrayint findKthElement(int A[], int m, int B[], int n, int k) { int i = int(double(m)/(m+n)*(k -1)); int j = (k-1) - i;     //A[i] or B[j] is the Kth element, return it  详细注释见解释部分 if ((j <= 0 || B[j-1] < A[i]) && (j >= n || A[i] < B[j]))      return A[i]; if ((i <= 0 || A[i-1] < B[j]) && (i >= m || B[j] < A[i]))      return B[j];     //A[i] is too small, get rid of lower part of A and higher part of B if (0 < j && A[i] < B[j-1])      return findKthElement(A+i+1, m-i-1, B, j, k-i-1);     //B[j] is too small, get rid of higher part of A and lower part of B else //if(i > 0 && B[j] < A[i-1])      return findKthElement(A, i, B+j+1, n-j-1, k-j-1);}

      解释如下:代码始终维护i + j = k – 1,所以数组A0-i的元素加上数组B0-j的元素一共有(i+1)+(j+1) == k+1个,因为数组AB都是有序的,所以我们知道A[i] > A[0i-1]都大,B[j] > B[0j-1]。如果B[j-1] < A[i] < B[j],那么将A[0i]B[0j]合并后的新数组元素为:….B[j-1],A[i],B[j],因为A[0i]B[0j]共有(i+1)+(j+1)==k+1个元素,故第K个元素是A[i].

7.         方程根求解,求平方根。

综上所述:二分变形很多,但是万变不离其宗,一点就是要利用二分法必须需找问题的单调性,如果单调性不能满足则不能用二分法。

原创粉丝点击