10.查找算法
在java中,我们常用四种查找算法:
1.顺序查找(线性)
2.二分法/折半查找
3.插值查找
4.斐波那契查找
1.线性查找.
2.二分查找算法
二分查找:
对一个 进行二分查找{1,8,10,89,1000,1234},输入一个数,看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"
样例:
public class BinarySearch {
public static void main(String[] args) {
int arr[] = {1, 8, 10, 89, 1000, 1234};
int index = binarySearch(arr, 0, arr.length-1, 5);
System.out.println("找到了,下标为:" + index);
}
/**
* 二分查找算法
*
* @param arr 数组
* @param left 左索引
* @param right 右索引
* @param searchValue 查找数字
* @return 找到的元素下标, 没找到返回-1
*/
public static int binarySearch(int[] arr, int left, int right, int searchValue) {
//当左索引大于右索引时
if (left > right) {
return -1;
}
//找到中间位置下标
int middle = (left + right) / 2;
if (searchValue < arr[middle]) {
//这里需要注意,middle-1,不这么操作会造成死递归
return binarySearch(arr, left, middle-1, searchValue);
} else if (searchValue > arr[middle]) {
//这里需要注意,middle+1
return binarySearch(arr, middle+1, right, searchValue);
} else {
return middle;
}
}
}
思考:在{1,8,10,89,1000,1000,1000,1000,1234}中输出1000的所有下标
public static List binarySearch2(int[] arr, int left, int right, int searchValue) {
//当左索引大于右索引时,没找到
if (left > right) {
return null;
}
//找到中间位置下标
int middle = (left + right) / 2;
if (searchValue < arr[middle]) {
//这里需要注意,middle-1,不这么操作会造成死递归
return binarySearch2(arr, left, middle - 1, searchValue);
} else if (searchValue > arr[middle]) {
//这里需要注意,middle+1
return binarySearch2(arr, middle + 1, right, searchValue);
} else {
/*
重点1:
这里在返回之前向左,和向右扫描,拿到所有相同的数的下标
*/
List<Integer> list = new ArrayList<>();
list.add(middle);
int i = middle;
//向左找
while (arr[i - 1] == searchValue) {
list.add(i - 1);
i -= 1;
}
//向右找
i = middle;
while (arr[i + 1] == searchValue) {
list.add(i + 1);
i += 1;
}
return list;
}
}
3.插值查找算法
示例:
public class InsertValueSearch {
private static int count=1;
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
int index=insertValueSearch(arr,0,arr.length-1,1);
System.out.println("找到了:index="+index);
}
/**
* 二分查找算法
*
* @param arr 数组
* @param left 左索引
* @param right 右索引
* @param searchValue 查找数字
* @return 找到的元素下标, 没找到返回-1
*/
public static int insertValueSearch(int[] arr, int left, int right, int searchValue) {
System.out.println("第"+(count++)+"次查找..");
//当左索引大于右索引时
if (left > right || searchValue < arr[0] || searchValue > arr[arr.length - 1]) {
return -1;
}
//这里的下标按照这个公式来查找
int middle = left + (right + left) * (searchValue - arr[left]) / (arr[right] - arr[left]);
if (searchValue < arr[middle]) {
//这里需要注意,middle-1,不这么操作会造成死递归
return insertValueSearch(arr, left, middle - 1, searchValue);
} else if (searchValue > arr[middle]) {
//这里需要注意,middle+1
return insertValueSearch(arr, middle + 1, right, searchValue);
} else {
return middle;
}
}
}
测试:
线性的数组中,找几都是一次找到,但是必须是线性增加的数组..
插值查找的注意事项:
1.对于数据量较大关键字分布比较均匀的查找表来说,采用插值查找速度较快。
2.关键字分布不均匀的情况下,该方法不一定比折半查找要好。
斐波那契(黄金分割法)查找算法
简介:
当解析到公式为:
(f[k]-1)=(f[k-1]-1)+(f[k-2]-1)+1 并且顺序表的长度为f[k]-1时
发现等式分为三部分:f[k-1]-1 f[k-2]-1 1
最后的1就是mid,对照图比可以理解!
如果队列长度不是刚好的f[k]-1呢,参考截图3
从而得到:mid=low+f[k-1]-1.
/**
* 斐波那契查找算法
* 借助额外数组(斐波那契数组:前两个元素的和等于第三个数组)来找到中间点
*/
public class FibonacciSearch {
public static void main(String[] args) {
int[] arr = {1, 9, 11, 18, 90, 91};
System.out.println("原数组:" + Arrays.toString(arr));
int index = fibSearch(arr, 18);
System.out.println("index=" + index);
}
/**
* 获取一个斐波那契数组
*
* @return 斐波那契数组
*/
public static int[] fib() {
int[] arr = new int[20];
arr[0] = 1;
arr[1] = 1;
for (int i = 2; i < arr.length; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr;
}
/**
* 借助斐波那契数组查找下标
*
* @param arr 查找数组
* @param key 查找的值
* @return 下标
*/
public static int fibSearch(int[] arr, int key) {
int low = 0;
int high = arr.length - 1;
//表示斐波那契分割数值的下标
int k = 0;
//存放mid值
int mid = 0;
//获取斐波那契数组
int f[] = fib();
/*
获取斐波那契分割值下标
根据上述的推导公式得:
1.f[k]=f[k-1]+f[k-2];
左边的等式-1,右边减两个1,再加一
2.f[k]-1=(f[k-1]-1) + (f[k-2]-1) +1
3.当前提条件,顺序表的长度==f[k]-1时,可以将其分为三部分
1:f[k-1]-1
2:f[k-2]-1
3:后面加的1,这个1就是中间的mid
4.mid=low+f[k-1]-1
*/
System.out.println("斐波那契数组:" + Arrays.toString(f));
while (high > f[k] - 1) {
k++;
}
System.out.println("斐波那契-index:" + k + " value=" + f[k]);
//因为f[k]的值可能大于arr的长度,因此将多出的部分以arr最后一个元素填充
int[] temp = Arrays.copyOf(arr, f[k]);//将数组扩充到f[k]大小,然后填充值
//例如:temp={1,2,3,0,0,0}--->{1,2,3,3,3,3}
for (int i = high + 1; i < temp.length; i++) {
temp[i] = arr[high];
}
System.out.println("转换后数组:" + Arrays.toString(temp));
//通过while循环找到我们的数key
while (low <= high) {
mid = low + f[k - 1] - 1;
System.out.println("mid:" + mid);
if (key < temp[mid]) {
//向左继续查找
high = mid - 1;
/*
为甚么k--;
说明:
1.全部元素=前面的元素+后面的元素
2.f[k]=f[k-1]+f[k-2]
因为前面有f[k-1]个元素,所以可以继续拆分f[k-1]=f[k-2]+f[k-3]
即在f[k-1]的前面继续查找k--
即下次循环 mid=low+f[k-1-1]-1
*/
k--;
} else if (key > temp[mid]) {
//向右继续查找
low = mid + 1;
/*
为什么为k-=2呢;
1.全部元素=前面的元素+后面的元素
2.f[k]=f[k-1]+f[k-2]
因为后面有f[k-2]个元素,所以可以继续拆分f[k-2]=f[k-3]+f[k-4]
*/
k -= 2;
} else {
//找到了
if (mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
}
测试输出:
原数组:[1, 9, 11, 18, 90, 91]
斐波那契数组:[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
斐波那契-index:5 value=8
转换后数组:[1, 9, 11, 18, 90, 91, 91, 91]
mid:4
mid:2
mid:3
index=3
重点总结:通过额外创建一个数组:斐波那契数组,根据其特性去查找mid