目录
1.线性查找
遍历整个数组或集合,逐一比对每个元素,直到找到目标元素或遍历完所有元素。
2.二分查找
适用于有序的数组或集合,通过反复将查找范围减半来定位目标元素。
public static int Binary_Search(int [] arr,int number){
int left=0;//数组左下标
int right=arr.length-1;//数组右下标
while (left<=right){
int mid=(left+right)/2;//数组中间下标
if(arr[mid]<number){//查找的数据在arr[mid]的右边
left=mid+1;//舍去arr[mid]左边的数据
} else if (arr[mid]>number) {//查找的数据在arr[mid]的左边
right=mid-1;//舍去arr[mid]右边的数据
}else {
return mid;//查找成功,返回查找数据的下标
}
}
return -1;//查找失败,返回-1
}
二分查找的优点:
高效性:对于有序数组,二分查找的时间复杂度为O(log n),比线性查找的O(n)更高效。
简洁性:算法实现相对简单,易于理解和编码。
二分查找的缺点:
依赖排序:二分查找要求数组必须是有序的,如果数组未排序,需要先进行排序,这会增加额外的时间复杂度。
不适用于动态数据:对于频繁插入、删除操作的数据集,维护有序性成本较高,二分查找可能不是最佳选择。
3.插值查找
改进的二分查找,使用插值公式估计目标元素的位置,适用于均匀分布的数组。
适用条件:有序性
均匀分布或近似均匀分布
public static int Binary_Search_Improve(int [] arr,int number){
int left=0;//数组左下标
int right=arr.length-1;//数组右下标
while (left<=right){
int mid=(number-arr[left])*(right-left)/(arr[right]-arr[left])+left;//int mid=(left+right)/2;//数组中间下标
if(arr[mid]<number){//查找的数据在arr[mid]的右边
left=mid+1;//舍去arr[mid]左边的数据
} else if (arr[mid]>number) {//查找的数据在arr[mid]的左边
right=mid-1;//舍去arr[mid]右边的数据
}else {
return mid;//查找成功,返回查找数据的下标
}
}
return -1;//查找失败,返回-1
}
常见BUG:
数组无序:插值查找要求数组必须是有序的,如果数组无序,插值查找的结果将无法保证正确。
数组范围问题:当查找的值小于数组中的最小值或大于数组中的最大值时,插值查找的计算公式可能会导致数组越界,从而引发错误。
计算精度问题:在计算mid位置时,由于涉及到浮点数运算,可能会存在精度问题。虽然这通常不会影响最终结果(因为mid最终会被取整为数组索引),但在某些极端情况下,精度问题可能导致查找路径偏离最优。
递归深度问题:如果数组很大且目标值远离数组中间位置,插值查找可能会进行多次递归调用,这可能导致栈溢出错误,尽管这种情况相对较少见。
二分查找VS差值查找
数据分配均匀
案例:
class Main {
public static void main(String[] args) {
int [] arr=new int[1000000];
for (int i = 0; i <arr.length ; i++) {
arr[i]=i;
}
long begain1=System.nanoTime();//获取开始时间
Binary_Search(arr,2);//运行程序
long end1=System.nanoTime();//结束时间
long begain2=System.nanoTime();
Binary_Search_Improve(arr,2);
long end2=System.nanoTime();
long time1=end1-begain1;//程序执行时间
long time2=end2-begain2;
System.out.println("二分查找法消耗时间:"+time1);
System.out.println("插值查找法消耗时间:"+time2);
}
public static int Binary_Search_Improve(int [] arr,int number){
int left=0;//数组左下标
int right=arr.length-1;//数组右下标
while (left<=right){
int mid=(number-arr[left])*(right-left)/(arr[right]-arr[left])+left;//int mid=(left+right)/2;//数组中间下标
if(arr[mid]<number){//查找的数据在arr[mid]的右边
left=mid+1;//舍去arr[mid]左边的数据
} else if (arr[mid]>number) {//查找的数据在arr[mid]的左边
right=mid-1;//舍去arr[mid]右边的数据
}else {
return mid;//查找成功,返回查找数据的下标
}
}
return -1;//查找失败,返回-1
}
public static int Binary_Search(int [] arr,int number){
int left=0;//数组左下标
int right=arr.length-1;//数组右下标
while (left<=right){
int mid=(left+right)/2;//数组中间下标
if(arr[mid]<number){//查找的数据在arr[mid]的右边
left=mid+1;//舍去arr[mid]左边的数据
} else if (arr[mid]>number) {//查找的数据在arr[mid]的左边
right=mid-1;//舍去arr[mid]右边的数据
}else {
return mid;//查找成功,返回查找数据的下标
}
}
return -1;//查找失败,返回-1
}
}
运行结果:
分块查找
分块查找通过将查找表分为若干个子块(或称为索引表),每个子块内部元素可以无序,但子块之间是有序的(即第一个子块中的最大元素小于第二个子块中的最小元素,以此类推),并且建立一个索引表,索引表中的每个元素包含对应子块中的最大(或最小)关键字和该子块在查找表中的起始位置。查找过程分为两步:首先,在索引表中进行查找,确定待查关键字可能存在的子块;然后,在已确定的块中采用顺序查找,找到对应的节点。
import java.util.ArrayList;
class Main {
public static void main(String[] args) {
ArrayList<Block> list=new ArrayList<Block>();
int [] arr={7,5,1,12,9,16,14,29,20,25};
Block block1=new Block(7,0,2);
list.add(block1);
Block block2=new Block(14,3,6);
list.add(block2);
Block block3=new Block(29,7,9);
list.add(block3);
System.out.println(Block_Search(list,28,arr));
}
public static int Block_Search(ArrayList<Block> list,int number,int [] arr){
for (int i = 0; i < list.size(); i++) {
Block block=list.get(i);//取出分块
//分块中的数据最大值有小于number中值
if(number<=block.getMax()){
//在分快中查找数据
return Search_in_Block(block,number,arr);
}
}
return -1;
}
public static int Search_in_Block(Block block,int number,int [] arr){
for (int i = block.getBegin(); i <=block.getEnd() ; i++) {
//分块中存在要查找的数
if(arr[i]==number){
return i;
}
}
//分块中不存在要查找的数
return -1;
}
}
class Block{
private int max;//最大值
private int begin;//开始下标
private int end;//结束下标
public Block() {
}
public Block(int max, int begin, int end) {
this.max = max;
this.begin = begin;
this.end = end;
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getBegin() {
return begin;
}
public void setBegin(int begin) {
this.begin = begin;
}
public int getEnd() {
return end;
}
public void setEnd(int end) {
this.end = end;
}
}
分块原则: 块数数量一般等于数字的个数开根号。比如:16个数字一般分为4块左右
斐波那契查找(了解)
使用斐波那契数列确定查找范围,通过缩小查找区间来定位目标元素,适用于分块或近似均匀分布的数组。
public static int fibonacciSearch(int[] arr, int key) {
int low = 0;
int high = arr.length - 1;
int k = 0; // 斐波那契数列的下标
int[] fib = generateFibonacciArr(arr.length);
// 找到大于等于数组长度的最小斐波那契数列元素
while (arr.length > fib[k] - 1) {
k++;
}
// 将数组扩展到斐波那契数列元素的长度
int[] temp = Arrays.copyOf(arr, fib[k] - 1);
// 将扩展部分用数组最后一个元素填充
for (int i = arr.length; i < temp.length; i++) {
temp[i] = arr[arr.length - 1];
}
// 查找过程
while (low <= high) {
int mid = low + fib[k - 1] - 1;
if (key < temp[mid]) {
high = mid - 1;
k -= 1;
} else if (key > temp[mid]) {
low = mid + 1;
k -= 2;
} else {
return Math.min(mid, arr.length - 1);
}
}
return -1;
}
// 生成斐波那契数列
private static int[] generateFibonacciArr(int n) {
int[] fib = new int[n];
fib[0] = 1;
fib[1] = 1;
for (int i = 2; i < n; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib;
}
哈希查找(了解)
利用哈希表实现快速查找,通过哈希函数将元素映射到特定位置,适用于需要高效查找的场景。
// 哈希查找函数
public static int hashSearch(int[] array, int target) {
// 创建一个HashMap用于存储数组元素及其对应的索引
HashMap<Integer, Integer> map = new HashMap<>();
// 遍历数组,将元素及其索引放入HashMap中
for (int i = 0; i < array.length; i++) {
map.put(array[i], i);
}
// 查找目标值是否在HashMap中存在
if (map.containsKey(target)) {
// 如果存在,返回目标值在数组中的索引
return map.get(target);
} else {
// 如果不存在,返回-1表示未找到
return -1;
}
}