引入:对于在数组中查找某个待定值,在Java中我们通常会使用遍历的方式去寻找,但是运行效率会低很多。这时候我们可以用二分查找去找到目标。
一、二分查找前提:查找区域为一个有序数组
有序数组满足:A0A1
A2
........
An
图解:
以下是基础版二分查找(左闭右闭):
import java.util.Arrays;
import java.util.Scanner;
public class erfenchazhao {
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]= {1,34,2,31,15,24,52,17,39,28,37,102,45};
Arrays.sort(a);
System.out.print("[");
for(int i=0;i<a.length;i++) {
if(i<a.length-1) {
System.out.print(a[i]+",");
}else {
System.out.print(a[i]);
}
}
System.out.println("]");
Scanner sc=new Scanner(System.in);
System.out.println("请输入查询的目标值:");
int target=sc.nextInt();
System.out.println(erfenchazhao.Check(a,target));
}
//普通二分查找法
public static int Check(int a[],int target) {
int i=0,j=a.length-1;
while(i<=j) {
int m=(i+j)>>>1;
if(target<a[m]) {
j=m-1;
}else if(target>a[m]) {
i=m+1;
}else {
return m;
}
}return -1;
}
代码解读:
1、设置i=0,j=n-1,带查找值target(n为数组长度,以上代码我们采用输入流的方式自定义n)
2、如果i>j,则结束查找(这里的i,j相当于两个指针,指向数组的头和尾)
3、设置m=floor(),m为中间索引,floor为向下取整即
。也可以写成>>>1即右移一位。
4、如果target<中间值,则设置j=m-1(相当于数学中的二分法,不断缩小范围(尾缩),直至找到目标)
5、如果target>中间值,则设置i=m+1(头缩)
6、如果target=中间值则结束查找
以上代码我们会发现一个问题:循环的比较次数太多,导致查找效率不稳定。那我们能否对代码进行改进,去减少循环比较次数。答案当然是:可!!!
以下让我们来演示平衡版二分查找(左闭右开):
//平衡二分法查找
public static int Checking(int a[],int target ) {
int i=0,j=a.length;
while(1<j-i) {//ij成为相邻的两元素是循环截止
int m=(i+j)>>>1;
if(target<a[m]) {
j=m;//也是由于边界怕跳过m-1对应的索引的数
}else {
i=m;//当目标等于a[m]或大于a[m]
}
}//在循环外面去比较目标值与数组中元素的值
if(a[i]==target) {
return i;
}else {
return -1;
}
}
代码解读:
我们改进的方面主要是将目标值与中间值放到循环外去比较,以减少其其循环次数从而降低时间复杂度:
平衡版的二分查找确保了在最好和最坏的情况下其时间复杂度可以达到O(logn),而不是基础版二分查找在最好的情况下可以达到O(1)的时间复杂度。
二、二分查找对重复元素的处理
如果数组中有重复元素,我们可以编写Rightmost或者Leftmost方法去寻找重复元素中最右端、最左端的重复值。
Rightmost方法:
public static int CheckRightmost(int b[],int target) {
int i=0,j=b.length-1;
while(i<=j) {
int m=(i+j)>>>1;
if(target<b[m]) {
j=m-1;
}else {
i=m+1;
}
}
return i-1;//合法数字则为最右端,非合法数字则为比他小的最右端:前任
}
代码解读:
Rightmost:若找到则为最靠右的重复元素索引位置,若没找到则返回比目标小且最右的索引
Leftmost方法:
//数组中有重复元素
public static int CheckLeftmost(int b[],int target) {
int i=0,j=b.length-1;
while(i<=j) {
int m=(i+j)>>>1;
if(target>b[m]) {
i=m+1;
}else {
j=m-1;
}
}
return i;//合法数字则为最左端,非合法数字则为比他大的最左端:后任
}
代码解读:
Leftmost:若找到则为最靠左的重复元素索引位置,若没找到则返回比目标元素大且最靠左的索引
那么这些对于重复元素的处理有什么实际用途吗??
必须得必有!!!例如:可以求排名、最近邻居、以及范围查询.........