1.1 概述
也叫折半查找
每次排除一半的查找范围
前提条件:元素必须是有序的,从小到大,或者从大到小都是可以的。
如果是无序的,也可以先进行排序。但是排序之后,会改变原有数据的顺序,查找出来元素位置跟原来的元素可能是不一样的,所以排序之后再查找只能判断当前数据是否在容器当中,返回的索引无实际的意义。
基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。
优缺点:
- 优点是比较次数少,查找速度快,平均性能好;
- 其缺点是要求待查表为有序表,且插入删除困难。
- 因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
1.2 基本版实现
public class Test01 {
public static void main(String[] args) {
// 基础版:二分查找算法
int[] arr = { 1,2, 6,66};
int i = binarySearch(arr, 6);
System.out.println(i);//1
}
// 定义方法,查找目标数字的索引,、
// 找到返回对应的索引
// 没有找到返回-1
public static int binarySearch(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引
int end = arr.length-1;
// start索引和end索范围内有东西执行查找
while (start <= end){
// 定义中间索引
int mid = (start +end)/2;
// int mid = (start +end)>>>1;
// 目标函数 小于 中间值
if (target <arr[mid]){
end = mid - 1;
}else if (arr[mid] < target){
start = mid + 1;
}else {
return mid;
}
}
return -1;
}
// BinarySearch() 结束
}
//假设binarySearch()方法中的while循环执行了k次
//则 while中的if 分别比较了多少次?
//当查找的值在数组最左侧,共需要比较k次
//当查找的值在数组最右侧,共需要比较2k次。
//所以比较不平衡,平衡版见1.4
-
问题1: 为什么是 i<=j 意味着区间内有未比较的元素, 而不是 i<j ?
i==j 意味着 i,j 它们指向的元素也会参与比较
i<j 只意味着 m 指向的元素参与比较 -
问题2: (start + end) / 2 有没有问题?
如果 int end = Integer.MAX_VALUE-1;在运行(start + end)会出现内存溢出。
同一个二进制数:1011-111-1111-1111-1111-1111-1111-1110
不把高位视为符号位,代表:3221225470
把最高位视为符号位,代表:-1073741826
解决办法:
采用右移一位,右移一位的效果相当于除以2向下取整。
只需将 int mid = (start + end) / 2 改成 int mid = (start + end) >>> 1
1.3 变动版
public class Test02 {
public static void main(String[] args) {
// 变动版:二分查找算法
int[] arr = {1,2, 6,66};
int i = binarySearch(arr, 6);
System.out.println(i);//1
}
// 定义方法,查找目标数字的索引,、
// 找到返回对应的索引
// 没有找到返回-1
public static int binarySearch(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引;
// 第一处变动,end指针不参与运算,只作为边界
int end = arr.length;
// start索引和end索范围内有东西执行查找
// 第二处变动 <= 变为 <
while (start < end){
// 定义中间索引
// int mid = (start +end)/2;
int mid = (start +end) >>> 1;
// 目标函数 小于 中间值
if (target <arr[mid] ){
//第三变动,
end = mid;
}else if (arr[mid] < target){
start = mid + 1;
}else {
return mid;
}
}
return -1;
}
// BinarySearch() 结束
}
递归实现二分查找
public class Test03 {
public static void main(String[] args) {
// 递归实现二分查找
int[] arr = {1,2,3,66};
int i = binarySearch(arr, 3, 0, arr.length-1);
System.out.println(i);//2
}
/**
* 使用递归的二分查找
*title:recursionBinarySearch
*@param arr 有序数组
*@param key 待查找关键字
*@return 找到的位置
*/
public static int binarySearch(int[] arr,int key,int low,int high){
if(key < arr[low] || key > arr[high] || low > high){
return -1;
}
int middle = (low + high) >>>1 ; //初始中间位置
if(arr[middle] > key){
//比关键字大则关键字在左区域
return binarySearch(arr, key, low, middle - 1);
}else if(arr[middle] < key){
//比关键字小则关键字在右区域
return binarySearch(arr, key, middle + 1, high);
}else {
return middle;
}
}
}
1.4 平衡版
public class Test04 {
public static void main(String[] args) {
// 平衡版二叉查找;
int[] arr = {1,2,3,66};
int i = binarySearch(arr, 66);
System.out.println(i);//3
}
public static int binarySearch(int[] arr,int target){
int start = 0;
// end:不参与运算,只作为边界
int end = arr.length;
// end - start:表示该范围内待查找的元素个数
//等范围内只剩start时退出循环,
// 在循环外比较arr[start]与target
//将原来的if三分支改为二分支,循环内的平均比较次数减少了
while (1 < end - start){
int mid = (start + end) >>>1;
if (target < arr[start]){
end = mid;
}else {
start = mid;
}
}
// 在循环外比较arr[start]与target
if (arr[start] == target){
return start;
}else {
return -1;
}
}
}
1.5 Java中二分查找
Java中Arrays类中的二分查找
public class Test05 {
public static void main(String[] args) {
/*
[2,5,8] arr
[2,0,0,0] b
[2,4,0,0] b
[2,4,5,8] b
*/
int[] arr = {2,5,8};
int target = 4 ;
int i = binarySearch(arr, target);
System.out.println(i);//-2
if (i < 0){
// target的插入点
int insertIndex = Math.abs(i + 1);
// 定义新的数组
int[] b = new int[arr.length + 1];
// 将旧数组插入点之前的数赋值到新数组
System.arraycopy(
arr,//旧数组
0,//从旧数组的0索引开始拷贝
b,//拷贝到b数组
0,//拷贝b数组的0索引
//将旧数组的 insertIndex-0 范围内拷贝到b数组
insertIndex-0
);//此时b数组为 [2,0,0,0]
// 将目标值插入b数组,此时b 数组为[2,4,0,0]
b[insertIndex] = target;
// 将旧数组中插入点以及插入点之后的数复制到新数组b中
System.arraycopy(
arr,
insertIndex,
b,
insertIndex+1,
arr.length-insertIndex
);
System.out.println(Arrays.toString(b));
//[2, 4, 5, 8]
}
//Java中Arrays类中的二分查找
int i1 = Arrays.binarySearch(arr, 4);
System.out.println(i1);//-2
}
public static int binarySearch(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引;
// 第一处变动,end指针不参与运算,只作为边界
int end = arr.length;
// start索引和end索范围内有东西执行查找
// 第二处变动
while (start < end){
// 定义中间索引
// int mid = (start +end)/2;
int mid = (start +end) >>> 1;
// 目标函数 小于 中间值
if (target <arr[mid] ){
//第三变动
end = mid;
}else if (arr[mid] < target){
start = mid + 1;
}else {
return mid;
}
}
//返回(-(插入点) - 1)
return -(start + 1 );
}
// BinarySearch() 结束
}
1.6 二分查找查最左边元素
假如数组有多个相同的值,返回目标值最左边的元素索引
public class Test06 {
public static void main(String[] args) {
int[] arr ={1,2,2,2,6,6,6,6,8};
int i = binarySearchLeftmost(arr, 2);
System.out.println(i);//1
}
public static int binarySearchLeftmost(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引
int end = arr.length-1;
// 记录待查找元素的候选位置
int candidate = -1;
// start索引和end索范围内有东西执行查找
while (start <= end){
// 定义中间索引
int mid = (start +end)>>>1;
// 目标函数 小于 中间值
if (target <arr[mid] ){
end = mid - 1;
}else if (arr[mid] < target){
start = mid + 1;
}else {
// 记录候选位置
candidate = mid;
// 向左缩小范围
end = mid-1;
}
}
//循环结束返回最左边的查找目标值的候选位置
return candidate;
}
// BinarySearch() 结束
}
查找最最左边时,改进返回值
public class Test08 {
public static void main(String[] args) {
// 假如数组有多个相同的值,返回目标值最左边的元素索引
int[] arr ={1,2,2,2,6,6,6,6,8};
int i = binarySearchLeftmost(arr, 2);
System.out.println(i);//1
int j = binarySearchLeftmost(arr, 3);
System.out.println(j);//4
}
public static int binarySearchLeftmost(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引
int end = arr.length-1;
// start索引和end索范围内有东西执行查找
while (start <= end){
// 定义中间索引
int mid = (start +end)>>>1;
// 目标函数 小于 中间值
if (target <= arr[mid] ){
end = mid - 1;
}else {
start = mid + 1;
}
}
//循环结束,假如找到在数组中找到目标值则返回最左边的查找目标值索引
//如果没有找到返回的是目标值要插入的位置。
//总的来说返回的是 >= target的最靠左索引
return start;
}
// BinarySearch() 结束
}
1.7 二分查找最右边元素
假如有多个相同的值,返回最右边的元素索引,没有找到返回-1
public class Test07 {
public static void main(String[] args) {
int[] arr ={1,2,2,2,6,6,6,6,8};
int i = binarySearchLeftmost(arr, 2);
System.out.println(i);//3
}
public static int binarySearchLeftmost(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引
int end = arr.length-1;
// 记录待查找元素的候选位置
int candidate = -1;
// start索引和end索范围内有东西执行查找
while (start <= end){
// 定义中间索引
int mid = (start +end)>>>1;
// 目标函数 小于 中间值
if (target <arr[mid] ){
end = mid - 1;
}else if (arr[mid] < target){
start = mid + 1;
}else {
// 记录候选位置
candidate = mid;
// 向左缩小范围
start = mid + 1;
}
}
//循环结束返回最右边的查找目标值的候选位置
return candidate;
}
// BinarySearch() 结束
}
查找最最右边时,改进返回值
public class Test09 {
public static void main(String[] args) {
// 假如数组有多个相同的值,返回目标值最右边的元素索引
int[] arr ={1,2,2,2,6,6,6,6,8};
int i = binarySearchLeftmost(arr, 2);
System.out.println(i);//3
int j = binarySearchLeftmost(arr, 3);
System.out.println(j);//3
}
public static int binarySearchLeftmost(int[] arr, int target){
// 起始索引
int start = 0;
// 末尾索引
int end = arr.length-1;
// start索引和end索范围内有东西执行查找
while (start <= end){
// 定义中间索引
int mid = (start +end)>>>1;
// 目标函数 小于 中间值
if (target < arr[mid] ){
end = mid - 1;
}else {
start = mid + 1;
}
}
// 返回 <= target 的最靠右的索引
return start - 1;
}
// BinarySearch() 结束
}