写在前面
查找算法
- 顺序(线性)查找
- 二分查找/折半查找
- 插值查找
- 斐波那契(黄金分割)查找
顺序查找
public static int seqSearch(int[] arr, int val) {
for (int i = 0; i < arr.length; i++) {
if (val == arr[i]) {
return i;
}
}
return -1;
}
二分查找
public static int binarySearch(int[] arr, int left, int right, int findVal) {
//当此时有两个值,且都不=findVal,则mid=left,传进去递归的就一个值,
//如果findVal > midVal 且此时right=left(mid+1),进入递归后,
//再进入一层递归,就会出现left(mid+1)>right的情况,才会跳出
//如果findVal < midVal 进入递归后,left>right(mid+1),将直接跳出
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
int mid = (left + right) / 2;
int midVal = arr[mid];
if (findVal > midVal) {
return binarySearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
return binarySearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
数组中有相同的数
- 找到mid值时不要马上返回,向左右继续扫描,把满足
arr[temp] == findVal
都找出来
public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return new ArrayList<>()
}
int mid = (left + right) / 2
int midVal = arr[mid]
if (findVal > midVal) {
return binarySearch2(arr, mid + 1, right, findVal)
} else if (findVal < midVal) {
return binarySearch2(arr, left, mid - 1, findVal)
} else {
//mid == findVal
List<Integer> resIndexlist = new ArrayList<>()
//向左扫描
int temp = mid - 1
while (temp > 0 && arr[temp] == findVal) {
resIndexlist.add(temp)
temp -= 1
}
resIndexlist.add(mid)
//向右扫描
temp = mid + 1
while (temp < arr.length && arr[temp] == findVal) {
resIndexlist.add(temp)
temp += 1
}
return resIndexlist
}
}
插值查找
- 优化二分查找,适用于分布比较均匀,接近于一元一次函数,如果不接近,不一定比二分查找好,说不定需要查找的值与理想值,差距较大
- 自适应mid,调整获取mid的公式
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left])
,趋近于要找的值
- 本质上就是,在
left与right
之间画一条一元一次方程
,x轴为值,y轴为索引
。假设在arr[0]
与arr[arr.length - 1]
连接,那么这条线上的所有值,为理想上索引和值的分布,那么(right - left) / (arr[right] - arr[left])
是斜率,通过(findVal - arr[left])
理想状态中,findVal
在这条函数上,可以找到准确的mid
值,但是在实际中,也可以找到周围的mid值
- 公式变换,
(mid - left) / (right - left) = (findVal - arr[left]) / (arr[right] - arr[left])
-> (mid - left) / (findVal - arr[left]) = (right - left) / (arr[right] - arr[left])
,放在坐标系上就是(findVal, mid)
、arr[left], left
与arr[right], right
三个点的关系
public static int insertSearch(int[] arr, int left, int right, int findVal) {
if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
return -1;
}
int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
int midVal = arr[mid];
if (findVal > midVal) {
//向右
return insertSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
//向左
return insertSearch(arr, left, mid - 1, findVal);
} else {
return mid;
}
}
斐波那契查找
- 黄金分割查找算法
- 斐波那契数列,相邻两个数的比例无限接近0.618
- 修改mid的获取,将一个顺序表的长度分割为
两段符合黄金分割比例的长度
,假设原表长度F[k]
,根据斐波那契数列性质F[k]=F[k-1]+F[k-2]
,那么也就是说,将这个表分割成长度为F[k-1]
和F[k-2]
的两部分,得下标(F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1
,,那么中间mid的下标就是mid=low+F[k-1]-1
,low为起始的偏移
- 如果表的长度n不一定为
F[k]
,那么就要将表长度增加至满足要求
public static int maxSize = 20
//获取斐波那契数列
public static int[] fib() {
int[] f = new int[maxSize]
f[0] = 0
f[1] = 1
for (int i = 2
f[i] = f[i - 1] + f[i - 2]
}
return f
}
/**
* @param arr 目标数组
* @param key 要查找的值
* @return
*/
public static int fibSearch(int[] arr, int key){
int low = 0
int high = arr.length - 1
int k = 0
int mid = 0
int f[] = fib()
//获取斐波那契数列的下标,high代表数组的长度,在数列中找到相应的数组长度的那个值的下标
//例如 high=6 arr.length=7,那么数列中f[6]对应为8,下标就为6,但是实际上f[k]>arr
while (arr.length > f[k] || k < 2) {
k++
}
//如果f[k] > arr的长度,构造一个新的数组,使新的数组长度为数列中的值,多出来的部分用0填充
//例如,arr.length=7,而对应f[6]=8,那么需要增加数组的长度
int[] temp = Arrays.copyOf(arr, f[k])
//但是不能用0填充,需要用a数组最后的数值填充temp
for (int i = high + 1
temp[i] = arr[high]
}
//找到目标数
while (low <= high) {
//f[k-1]为前面那段的长度,-1 + low为获取下标,第一次长度为8 对应长度前5,后3
mid = low + f[k - 1] -1
if (key < temp[mid] && k >= 0) {
//向前面查找,但是mid后面包括mid就不查找了
high = mid - 1
//前面有f[k-1]元素,需要继续拆分 f[k-1] = f[k-2] + f[k-3]
//例如,此时数组元素个数为8,mid为下标为4,low至mid有5个元素`f[k-1]`的长度
//此时,向前查找,要将这5个元素再次分割,那么数组长度就为`f[k-1]`
//对应的,mid=f[k-1-1]-1 + low
k --
} else if (key > temp[mid]) {
//向后查找
low = mid + 1
//后面的长度为`f[k-2]`,要针对后面的长度进行分割
//mid=f[k-2-1]-1 + low
k -= 2
} else {
if (mid <= high) {
return mid
} else {
//如果mid>high,表示已经在查找后面填充的元素,那么要返回原始数组最长的下标
return high
}
}
}
return -1
}