插入算法介绍
在Java中,我们常见的有四种方法:
1)顺序(线性)查找
2)二分查找/折半查找
3)插值查找
4)斐波那契查找
下面我将逐一介绍
1.顺序(线性)查找
这个查找思路很简单,对要查找的序列进行循环匹配即可
顺序查找代码
//找到一個满足条件的就返回
public static int seqSearch(int[] arr, int value) {
for(int i = 0; i < arr.length; i++){
if(arr[i] == value){
return i;
}
}
return -1;
}
2.二分查找/折半查找
首先明确一点:二分查找只能应用于有序数列
二分查找思路如下:
- 首先确定数组的中间下标mid=(left+right)/2
- 将要查找的数value和数组中间的值arr[mid]进行比较
- 1)value>arr[mid]则向右递归
- 2)value<arr[mid]则向左递归
- 3)value==arr[mid]说明找到,则返回
- 退出递归的条件:1)找到元素;2)遍历完整个数组当left>right时说明没找到,也需要退出
二分查找代码
//改进后,可以返回所有同一元素的索引
public static ArrayList<Integer> binarySearch2(int[] arr, int left, int right,int value) {
int l = left;
int r = right;
int mid = (l+r)/2;
//结束递归的条件
if(l > r){
return new ArrayList<Integer>();
}
if(value > arr[mid]) {
return binarySearch2(arr, mid+1, right, value);
} else if (value < arr[mid]) {
return binarySearch2(arr, left, mid-1, value);
}else{
ArrayList<Integer> indexList = new ArrayList<Integer>();
//向左边扫描
int temp = mid-1;
while(true){
if(temp < 0 || arr[temp] != value){
break;
}else{
indexList.add(temp);
temp--;
}
}
indexList.add(mid);//加入中间的
//向右边扫描
temp = mid+1;
while(true){
if(temp > arr.length - 1 || arr[temp] != value){
break;
}else{
indexList.add(temp);
temp++;
}
}
return indexList;
}
}
3.插值查找
对于二分查找,如果待查找元素位于有序序列的头部或者尾部,将会进行最多次的递归之后才能得到,在数据量大的情况下往往会造成时间浪费,因此插值查找在这一方面改进了二分查找。
原理:插值查找算法类似于二分查找, 不同的是插值查找每次从自适应 mid 处开始查找
将二分查找中的求 mid 索引的公式 , low 表示左边索引 left, high 表示右边索引 right,key 就是待查找的value
代码上的改动如下
插值插值代码
//插值查找算法也需要数组有序
public static int insertValueSearch(int[] arr, int left, int right, int value){
int l = left;
int r = right;
int mid = l + (r - l) * (value - arr[l]) / (arr[r] - arr[l]);
//结束递归的条件
//注意:value < arr[0] || value > arr[arr.length-1]重要,防止mid越界
if(l > r || value < arr[0] || value > arr[arr.length-1]){
return -1;
}
if(value > arr[mid]) {//向右递归
return insertValueSearch(arr, mid+1, right, value);
} else if (value < arr[mid]) { //向左递归
return insertValueSearch(arr, left, mid-1, value);
}else{
return mid;
}
}
4.斐波那契查找算法
首先介绍斐波那契数列,它是和黄金分割比例有关的一个无穷数列,生成斐波那契数列的程序如下
// 非递归的 方式得到斐波那契数列
public static int[] fib() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < f.length; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f;
}
斐波那契数列 示例如下{1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } ,他的第一个第二个元素都是1,并且斐波那契数列的两个相邻数的比例,无限接近黄金分割值
0.618
介绍这个数列的原因呢,就是通过它我们能对二分、插值查找算法进行一个改进,斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。在斐波那契数列找一个等于略大于查找表中元素个数的数F[n],将原查找表扩展为长度为Fn,完成后进行斐波那契分割,即F[n]个元素分割为前半部分F[n-1]个元素,后半部分F[n-2]个元素,找出要查找的元素在那一部分并递归,直到找到。
要注意这里的数列可能不够F[k]的长度,又比F[k-1]要长,因此需要对原先数列进行一个填充,使用数列最大值进行填充。
关于斐波那契的优势,我想相对于二分查找来说,他们的时间复杂度都是O(logn),但斐波那契只有赋值与减法运算,而二分查找有除法运算,这可能是斐波那契的优势之一。
斐波那契查找代码
// 斐波那契查找算法(非递归方式编写)
public static int fibSearch(int[] arr, int value) {
int low = 0;
int high = arr.length - 1;
int k = 0;// 斐波那契分割数值的下标
int mid = 0;
int[] f = fib();// 获取斐波那契数列
// 获取斐波那契分割数值的下标k
while (high > f[k] - 1) {
k++;
}
// 因为f[k]值的大小可能会超出arr,因此使用Arrays类构造一个新的数组并指向temp
// 不足的部分会使用0填充
int[] temp = Arrays.copyOf(arr, f[k]);
// 实际上还需要对0进行替换,使用arr最后一个数进行替换
for (int i = high + 1; i < temp.length; i++) {
temp[i] = arr[high];
}
while(low<=high){
mid = low+f[k-1]-1;
if(value > temp[mid]) {//向右递归
low = mid+1;
//说明:
//全部元素 = 前部分 + 后部分 f[k] = f[k-1]+f[k-2]
//因为mid右边有f[k-2]个元素,需要进行f[k-2] = f[k-3]+f[k-4]拆分
k -= 2;
} else if (value < temp[mid]) { //向左递归
high = mid-1;
//说明:
//全部元素 = 前部分 + 后部分 f[k] = f[k-1]+f[k-2]
//因为mid左边有f[k-1]个元素,需要进行f[k-1] = f[k-2]+f[k-3]拆分
k--;
}else{
if(mid <= high){
return mid;
}else{
return high;
}
}
}
return -1;
}