《编程之美》 2.19 解法二需要在一个数组 arr[] 中找到最后一个≤ value 的值,可以顺序查找,也可以使用二分查找。但标准的二分查找用来查找 =value 的值,因此这里需要改造一下:
1. 标准二分查找:
注意:退出原因有两个——“ arr[mid]==value ,即找到” 或者 “ l==r 且仍未找到”
(这个版本代码根据《编程珠玑》)
private static void find(int[] arr, int value){ int l=0,r=arr.length-1; int mid=(l+r)/2; while(arr[mid]!=value && l<=r){ if(arr[mid]<value){ l=mid+1; }else{ //arr[mid]>value r=mid-1; } mid=(l+r)/2; } if(arr[mid]==value){ System.out.println(arr[mid]); }else{ //退出原因:l==r System.out.println("不存在!"); } }
2. 查找最后一个属性为true的元素
如果一个数组的某个维度属性值依次为:{true, true, ... , true, false, false, ... , false}. 查找最后一个属性为true的元素的位置。
这种变体和标准二分查找最大的区别在于每次进入循环时不用判断是否找到满足条件的点。在变体中,这样的点往往不只一个,而有很多,我们要找的是最后一个满足条件的点:)
(如果至少有一个true的话)
1). 每次进入循环while(l<=r)时,闭区间[l,r]都至少包括了“最后一个true的位置”和“第一个false的位置”两者之一。
2). 任意满足l<=r的l,r都可以转化成以下两大类情况,即最后一次进入while(l<=r)时必定是以下两种情况之一:
情况一: r-l==0
情况二: r-l==1
后面的示意图中会对这两种情况进行详细讨论,可以看到循环结束时mid均指向最后一个true的位置。
注:对于数组a[],对于任意i∈[0,n-2],都有(a[i]+a[i+1])/2=a[i]. 这和i的奇偶性是无关的。
这种变体在二分答案中经常用到,并且可以看做是下面“查找最后一个≤value值”的基础。
代码: 综上,调用bin_search(arr, 0, len),将返回最后一个true的位置;若不存在true则返回0.
#include<iostream> using namespace std; bool arr1[]={true,true,true,true,false,false,false,false}; bool arr2[]={true,true,false,false,false}; int bin_search(bool* arr, int l, int r){ int mid=(l+r)>>1; while(l<=r){ cout<<"l="<<l<<", r="<<r<<", mid="<<mid<<endl; if(arr[mid]) l=mid+1; else r=mid-1; mid=(l+r)>>1; } //退出上面while(l<=r)循环时,mid指向最后一个true的位置 if(arr[mid]) return mid; else return 0; } int main(){ cout<<bin_search(arr1,0,7)<<endl; cout<<bin_search(arr2,0,4)<<endl; system("pause"); return 0; }
其实根本没有上面那么麻烦,只需要最后判断“mid当前位置”和“mid-1位置”即可
#include <iostream> #include <algorithm> using namespace std; int bs(int *arr,int ll, int rr, int v){ int res=-1; int l = ll; int r = rr; int mid=(l+r)>>1; while(l<r){ if(arr[mid]<=v){ l=mid+1; }else if(arr[mid]>v){ r=mid-1; } mid=(l+r)>>1; } if(arr[mid]<=v){ res=mid; }else{ if(mid-1>=l){ if(arr[mid-1]<=v){ res=mid-1; }else{ res=-1; } }else{ res=-1; } } return res; } int main() { int arr0[]={4}; cout<<bs(arr0,0,0,8); //0 int arr1[]={4,5}; cout<<bs(arr1,0,1,8); //1 int arr2[]={9,10,23}; cout<<bs(arr2,0,2,8); //-1 int arr3[]={8,9,10,23}; cout<<bs(arr3,0,3,8); //0 int arr4[]={8,8,9,10,23}; cout<<bs(arr4,0,4,8); //1 int arr5[]={8,8,8,8,8}; cout<<bs(arr5,0,4,8); //4 int arr6[]={7,8,9,10,23}; cout<<bs(arr6,0,4,8); //1 int arr7[]={8}; cout<<bs(arr7,0,0,8); //0 int arr8[]={10}; cout<<bs(arr8,0,0,8); //-1 return 0; }
3. 查找最后一个≤ value 的元素
将“≤value的元素”看做属性为true,将“>value的元素”看做属性为false,这样“3 查找最后一个≤value的元素”就转换为“2. 查找最后一个属性为true的元素”. 直接套用上面的过程即可,分析同上:)
代码:
#include<iostream> using namespace std; int arr1[]={1,2,3,4,6,7,8}; int arr2[]={1,2,3,4,6,7}; int bin_search(int* arr, int l, int r, int k){ int mid=(l+r)>>1; while(l<=r){ cout<<"l="<<l<<", r="<<r<<", mid="<<mid<<endl; if(arr[mid]<=k) l=mid+1; else r=mid-1; mid=(l+r)>>1; } if(arr[mid]<=k) return mid; else return 0; } int main(){ cout<<bin_search(arr1,0,6,5)<<endl; cout<<bin_search(arr2,0,5,5)<<endl; system("pause"); return 0; }
另外,思考一个问题:如果二分查找是数组中允许重复元素,这种方法还适用吗?
4. 二分查找“有序二维数组”
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
例如下面的二维数组就是每行、每列都递增排序。如果在这个数组中查找数字7,则返回true;
如果查找数字5,由于数组不含有该数字,则返回false。
我的解题思路是这样的矩阵行列都是从小到大排好序的,要查找的话自然用二分效率比较高,而且这样的矩阵有个性质,最左上角的元素必定是最小值,最右下角的是最大值 ,在一个n*n的矩阵中,对角线的元素也是排好序的 ,找到对角线上的一个元素,使得这个元素小于待查找的key,并且下一元素大于待查找的key,那么只要在这个元素的左下角矩阵和右上角矩阵递归继续对角线查找就可以了,例如上图例子里查找7,只要找到对角线的元素4,然后递归查找红圈的矩阵就可以了 ,左上角矩阵最大值4<7,右下角矩阵最小值10>7,无需查找了。
但是此题并没有告诉我们原始矩阵是n*n的,这是比较麻烦的地方,不过思路是一样的,无非不能用对角线查找这样简单的办法了,假设m*n的矩阵,对角线查找的办法改进为i = (m1+m2)/2,j = (n1+n2)/2 进行查找就可以了,(m1,n1)为矩阵最左上角元素下标,(m2,n2)为最右下角元素下标
总结一下基本思路:
(1)对于n*n的有序二维数组 : 函数bin_search()在对角线上运用“二分”过程,找到i使得a[i][i]<k<a[i+1][i+1],然后对于“a[i][i]的左下角矩阵”和“a[i][i]的右上角矩阵”分别递归调用bin_search()查找即可。
(2)对于m*n的有序二维数组 : 基本同上,只是bin_search()不是对对角线“二分”,而是对矩阵m*n进行“二分缩放”(Sam: 这个词是我发明的:)示意图如下)
贴一下(2)的代码:
int bin_search(int value, int *a, int n, int m1, int n1, int m2, int n2) { int begin_m1 = m1, begin_n1 = n1, end_m2 = m2, end_n2 = n2; int left_result = 0, right_result = 0; int i = (m1+m2)/2, j = (n1+n2)/2; if (a == NULL) return 0; if (value < *(a+m1*n+n1) || value > *(a+m2*n+n2)) return 0; else if (value == *(a+m1*n+n1) || value == *(a+m2*n+n2)) return 1; while ((i!=m1 || j!=n1) && (i!=m2 || j!=n2)){ if ( value == *(a+i*n+j) ) return 1; else if ( value < *(a+i*n+j) ){ m2 = i; n2 = j; i = (i+m1)/2; j = (j+n1)/2; } else{ m1 = i; n1 = j; i = (i+m2)/2; j = (j+n2)/2; } } //search left & right if ( i<end_m2 ) left_result = bin_search(value, a, n, i+1, begin_n1, end_m2, j); if ( j<end_n2 ) right_result = bin_search(value, a, n, begin_m1, j+1, i, end_n2); if (left_result | right_result ) return 1; else return 0; }
顺便提一句,我先前还看到一个笨娃娃说:可以变量所有的a[][]的行,对每一列进行二分查找,这样的复杂度是O(nlog(n)),呜呼!当然不错啦,但一看就学艺不精。
相关推荐
二分查找算法,也称为折半查找,是计算机科学中一种高效的搜索算法,尤其适用于已排序的数据集合。这个算法的基本思想是通过不断...通过提供的"二分查找.swf"文件,初学者可以通过动画演示更直观地理解和掌握这一算法。
下面我们将深入探讨二分查找的原理、C语言实现及其优化。 **二分查找的原理:** 1. 首先,我们需要一个已排序的数组。这是二分查找的前提条件,因为只有在有序的情况下,才能通过比较中间元素快速缩小查找范围。 2....
二分查找,也被称为折半查找,是一种在有序数组中搜索特定元素的高效算法。它利用了数组的线性特性,通过不断缩小搜索范围来快速定位目标值。以下是关于二分查找的详细知识: 1. **基本思想**: - 二分查找的基本...
二分查找算法是一种在有序数组中寻找特定元素的搜索算法,其效率远高于线性查找。这个算法基于分治策略,将查找范围不断减半,直到找到目标元素或者确定目标不存在。Java 中实现二分查找的基本步骤如下: 1. 首先,...
C++标准库提供了`std::upper_bound`和`std::lower_bound`函数,它们分别实现了上述两种二分查找变体。这两个函数接受一个范围(如数组或向量的迭代器)和一个值作为参数,返回相应位置的迭代器。例如: ```cpp #...
二分查找,也称为折半查找,是一种在有序数组中搜索特定元素...通过深入理解二分查找的原理和Java实现,开发者可以提高程序的运行效率,尤其在处理大量数据时。对于Java程序员来说,掌握这一核心算法是非常重要的技能。
- **变种**:二分查找有多种变体,如二分查找插入位置、二分查找最大/最小元素等,这些变体在处理特定问题时非常有用。 - **性能分析**:虽然二分查找的时间复杂度是O(log n),但空间复杂度为O(1),因为只需要几个...
二分查找是一种基础且实用的算法,理解和掌握它对于提升编程能力及解决实际问题具有重要意义。在实际应用中,根据具体需求,我们可以灵活地调整和扩展二分查找算法,以满足各种场景下的数据处理需求。
此外,二分查找还可以有多种变体,例如在插入、删除等操作后维护有序数组时,可以使用二分查找辅助定位。同时,二分查找也可以应用于解决一些其他问题,如查找最近的元素、求解最大/最小元素等。了解并熟练掌握二分...
在本文中,我们将深入探讨二分查找的几种常见变体,以及如何根据不同的问题需求来选择合适的实现方式。 首先,让我们回顾一下二分查找的基本思想。假设我们有一个升序排列的数组`nums`,我们要查找的目标是`target`...
#### 一、二分查找算法概述 二分查找(Binary Search),也称折半查找,是一种在有序数组中查找某一特定元素的搜索算法。它的工作原理是将数组分成两部分,通过比较中间元素与目标值来决定搜索方向,从而达到减少...
本文将深入探讨二分查找的基本概念、实现方式及其在不同场景下的应用,结合提供的代码文件,我们将分析`chessBoard.cpp`、`largest sum continuous subarray.cpp`以及`binary search.cpp`中的二分查找实例。...
在二分排序中,我们首先将待排序的数组分为已排序和未排序两部分,然后从未排序的部分取出第一个元素,使用二分查找在已排序部分找到其正确位置,并将其插入。 二分查找的基本思想是将数组分为两半,根据中间元素与...
对分查找算法是一种高效的数据搜索方法,主要应用于已排序的数组或列表中。...在后续课程中,可以进一步探讨对分查找在实际问题中的应用,如二分查找在搜索、排序等场景的应用,以及其变体和优化方法。
总的来说,理解并熟练掌握二分查找算法及其变体,对于解决实际问题和应对面试具有重要意义。二分查找不仅在面试中常见,而且在很多数据处理和搜索应用中都发挥着关键作用,如数据库索引、排序算法等。通过深入学习和...
这里主要涉及了选择排序、冒泡排序以及几种查找算法,包括二分查找、顺序查找和双向顺序查找。 1. **STL排序算法**: - **选择排序**:选择排序是一种简单直观的排序算法,它的工作原理是每一次从待排序的数据元素...
- **定义**:二分查找是一种效率较高的查找算法,适用于有序数组。其基本思想是将数组分成两部分,每次比较中间元素与目标值的大小,缩小搜索范围,直到找到目标值或确定目标值不存在于数组中。 - **实现**:在`find...
除了基本的二分查找外,还可以实现寻找特定位置(如第一个等于、第一个小于或最后一个等于)的二分查找变体。 ###### 1.1.3 索引查找 索引查找基于排序的数据集构建索引结构,通过索引来加速查找过程。这种方法...
在MATLAB编程中,二分查找及其变体的实现不仅要求熟悉算法本身,还要求对MATLAB语言及其数据结构有深入的理解。正确地利用MATLAB的矩阵和数组操作能力,可以进一步提高算法的效率和性能。此外,对于大规模数据处理...