二分查找(折半查找)
public class BinarySearch {
public int search(int[] arr, int findVal) {
int left = 0;
int right = arr.length - 1;
return binary(arr, left, right, findVal);
}
public int binary(int[] arr, int left, int right, int findVal) {
if (left > right) {
return -1;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
//目标值小于中轴值向左递归
return binary(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
//目标值小于中轴值向右递归
return binary(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
}
- 传入目标数组,左边界,右边界和目标值
- 左右边界相加对 2 取整获得中轴值索引
- 如果目标值小于中轴值向左递归大于中轴值向右递归
- 找到目标值返回其下标
如果数组中存在相同的目标值如何返回所有索引
public class BinarySearch {
public List<Integer> search(int[] arr, int findVal) {
int left = 0;
int right = arr.length - 1;
return binary(arr, left, right, findVal);
}
public List<Integer> binary(int[] arr, int left, int right, int findVal) {
if (left > right) {
return null;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binary(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binary(arr, left, mid - 1, findVal);
} else {
//创建结果集
List<Integer> resIndexList = new ArrayList<Integer>();
//从目标值左边开始遍历
int temp = mid - 1;
while (true) {
//遍历目标值左边所有的值
if (temp < 0 || arr[temp] != findVal) {
break;
}
resIndexList.add(temp);
--temp;
}
//添加当前目标值
resIndexList.add(mid);
temp = mid + 1;
while (true) {
//遍历目标值右边所有的值
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
resIndexList.add(temp);
temp++;
}
//返回结果集
return resIndexList;
}
}
}
插值查找
public class InsertValSearch {
public int search(int[] arr, int findVal) {
int left = 0;
int right = arr.length - 1;
return insertVal(arr, left, right, findVal);
}
public int insertVal(int[] arr, int left, int right, int findVal) {
if (left > right) {
return -1;
}
// mid自适应
int mid = left + (findVal - arr[left]) / (arr[right] - arr[left]) * (right - left);
int midVal = arr[mid];
if (findVal > midVal) {
return insertVal(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return insertVal(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
}
相较于二分查找唯一的不同点在于 mid 的取值策略不同
mid自适应公式:
注:对于数据量较大,关键字分布较均匀的表来说采用插值查找速度较快。
但在关键字分布不均匀的情况下,该方法不一定要比二分查找好。
斐波那契查找
public class FibSearch {
//创建斐波那契数组
public int[] fib() {
int[] f = new int[20];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < 20; ++i) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
public int serach(int[] arr, int findVal) {
int left = 0;
int right = arr.length - 1;
//获取一个长度为20的斐波那契数组,最大数为4181
int[] fib = fib();
//中轴值索引
int mid = 0;
//计数器
int k = 0;
//选择合适的斐波那契数
while (right + 1 > fib[k] - 1) {
k++;
}
//把原数组拷贝到长度为fib[k]的辅助数组中
int[] temp = Arrays.copyOf(arr,fib[k]);
//把辅助数组中含0位置用原数组最后一位补齐
for (int i = right + 1; i < temp.length; ++i) {
temp[i] = arr[right];
}
while (left <= right) {
//斐波那契中轴值索引
mid = left + fib[k - 1] - 1;
if (findVal < temp[mid]) {
right = mid - 1;
k--;
} else if (findVal > temp[mid]) {
left = mid + 1;
k-=2;
} else {
if (mid > right) {
return right;
} else {
return mid;
}
}
}
return -1;
}
}
- 事先准备一个长度为20的斐波那契数组,最大值为4181
- 从斐波那契数组中选一个合适的值(至少大于等于原数组的长度)作为辅助数组的长度
并把原数组的内容copy到辅助数组,辅助数组多出部分的0用原数组最右边的值填充 - mid 取值策略如图