1.二分查找
问题概述:在一个有序的数组内,例如{5,13,19,21,37,56,64,75,80,88,92}中,查找出指定元素。如果存在范围该元素的索引,否则返回null.
例如:
算法描述
-
前提条件:数组为一个有序数组a[],数组长度为n,目标元素为target。
-
令low指向数组的开头索引,high指向数组结尾索引,mid指向数组中间索引.(floor表示向下取整)
l o w = 0 ; h i g h = n − 1 ; m i d = f l o o r ( ( l o w + h i g h ) / 2 ) low=0;high=n-1;mid=floor((low+high)/2) low=0;high=n−1;mid=floor((low+high)/2) -
如果target>a[mid],low=mid+1,即目标元素在索引mid和high之间。
-
如果target<a[mid],high=mid-1,即目标元素在索引low和mid之间。
-
重复上述target和a[mid]的比较,利用二分法不断缩小目标元素所在区域,从而找到目标元素。即a[mid]=target.
-
如果low>high,即目标元素不在这个数组中,因为遍历结束也没有找到。
算法实现
非递归
/**
* @BelongsProject: algorithm
* @BelongsPackage: com.victorzl.algorithm
* @Author: VictorZl
* @CreateTime: 2023-10-24 21:14
* @Description: 二分查找法
* @Version: 1.0
*/
public class BinarySearch {
/**
*
* @param a
* @param target
* @return 如果找到目标元素返回目标元素所在数组的索引,否则返回-1
*/
public static int binarySearch(int[] a,int target)
{
int low=0,high=a.length; //设置初始状态的指针
while (low<=high) //如果low<high,说明查找未结束
{
int mid=(low+high)/2; //根据low和high指针不断更新中间索引指针
if(target<a[mid]) //目标元素在左区域
{
high=mid-1;
}
else if (target>a[mid]) //目标元素在右区域
{
low=mid+1;
}
else {
return mid; //找到目标值
}
}
return -1;
}
}
问题:
int(整型)的数据范围:-2147483648——2147483647;
int a = 2147483647;//定义一个int型变量a的值为int型能够保存的最高值
a+1=-2147483648 //由于java中32位中最高位表示正负
在计算机中,整数通常使用补码表示。正数的补码就是其本身。
因此,2147483647的二进制补码表示为0111 1111 1111 1111 1111 1111 1111 1111。
而1的二进制补码表示为0000 0000 0000 0000 0000 0000 0000 0001。
将这两个补码相加,得到1000 0000 0000 0000 0000 0000 0000 0000。这个结果的最高位是1,表示负数。根据补码的转换规则,我们需要将这个补码转换回原码。
转换过程如下:
首位符号位不变,其余位置取反:1111 1111 1111 1111 1111 1111 1111 1111。
末尾加1,得到1 0000 0000 0000 0000 0000 0000 0000 0000。
舍弃溢出的部分:0000 0000 0000 0000 0000 0000 0000 0000。
对于int类型的范围,当发生溢出时,它会循环回到最小值。因此,2147483647 + 1的结果是-2147483648。
所以在上述算法中int mid=(low+high)/2,可能会出现负数,从而出现问题;
改进:利用二进制向右移位的性质,相当于除以2,即使是溢出为负数,但是他的二进制不会变动,右移后最高位变为0,变为正数,达到除以2的效果,所以不受影响。
/**
* @BelongsProject: algorithm
* @BelongsPackage: com.victorzl.algorithm
* @Author: VictorZl
* @CreateTime: 2023-10-24 21:14
* @Description: 二分查找法改进
* @Version: 1.0
*/
public class BinarySearch {
/**
*
* @param a
* @param target
* @return 如果找到目标元素返回目标元素所在数组的索引,否则返回-1
*/
public static int binarySearch(int[] a,int target)
{
int low=0,high=a.length; //设置初始状态的指针
while (low<=high) //如果low<high,说明查找未结束
{
int mid=(low+high)>>>1; //根据low和high指针不断更新中间索引指针
if(target<a[mid]) //目标元素在左区域
{
high=mid-1;
}
else if (target>a[mid]) //目标元素在右区域
{
low=mid+1;
}
else {
return mid; //找到目标值
}
}
return -1;
}
}
递归
package com.victorzl.algorithm;
/**
* @BelongsProject: algorithm
* @BelongsPackage: com.victorzl.algorithm
* @Author: VictorZl
* @CreateTime: 2023-10-26 09:34
* @Description: 递归二分查找法
* @Version: 1.0
*/
public class RecursionBinarySearch {
public static int recursionBinarySearch(int[] a,int low,int high, int target) {
if (low <= high) {
int mid = (low + high)>>>1;
if (a[mid] == target) {
return mid;
} else if (a[mid] > target) {
return recursionBinarySearch(a, low, mid - 1, target);
} else {
return recursionBinarySearch(a, mid - 1, high, target);
}
}
return -1;
}
}
时间复杂度分析
在二分法中,每次将待查找区间缩小一半,因此需要进行log n次查找才能找到目标元素。具体来说,假设待查找区间大小为n,每次查找后区间大小减半,直到区间大小为1,需要进行k次查找才能找到目标元素,即:
n/2^k = 1 解得k=log n。