1、线性查找(顺序查找)
(1)算法简介:线性查找又称顺序查找,是一种最简单的查找方法,它的基本思想是从第一个记录开始,逐个比较记录的关键字,直到和给定的K值相等,则查找成功;若比较结果与文件中n个记录的关键字都不等,则查找失败。
(2)代码实现(java):
package com.haiyang.datastructure.search;
/**
* @author haiYang
* @create 2021-12-25 15:22
*/
public class SeqSearch {
public static void main(String[] args) {
int[] arr = new int[]{1, 4, 61, 31, 8564, 131, 13, 1, 6413, 1, 4};
int index = seqSearch(arr, 31);
if (index == -1) {
System.out.println("没有找到!");
} else {
System.out.println("目标值下标为" + index);
}
}
public static int seqSearch(int[] arr, int target) {
//逐个遍历数组元素与目标值匹配
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
}
2、二分查找(折半查找)
(1)算法简介:二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
(2)代码实现(java):
package com.haiyang.datastructure.search;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author haiYang
* @create 2021-12-25 15:47
*/
public class BinarySearch {
public static void main(String[] args) {
int[] ints = {2, 5, 12, 25, 36, 48, 59, 69, 89, 100, 125};
int i = binarySearch(ints, 0, ints.length, 69);
System.out.println(i);
int[] ints1 = {2, 5, 12, 25, 36, 36, 36, 36, 36, 36, 36, 48, 59, 69, 89, 100, 125};
ArrayList<Integer> resIndexList = binarySearch2(ints1, 0, ints.length, 36);
System.out.println(resIndexList);
}
//二分查找针对有序序列
public static int binarySearch(int[] arr, int left, int right, int findVal) {
//结束条件
if (left > right) {
return -1;
}
//获取中间值索引
//int mid = (left + right) / 2;
//防止越界
int mid = left + (right - left) / 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;
}
}
/**
* 二分查找的非递归实现
* @param arr 查找数组
* @param target 目标值
* @return 数组索引下标
*/
public static int binarySearch1(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (target == arr[mid]) {
return mid;
} else if (target < arr[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
//查找多个相同值
public static ArrayList<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
//结束条件
if (left > right) {
return new ArrayList<Integer>();
}
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 {
//用于存储多个目标值的索引
ArrayList<Integer> resIndexList = new ArrayList<>();
//将mid左侧所有等于目标值的元素的索引加入到列表中
int temp = mid - 1;
while (true) {
if (temp < 0 || arr[temp] != findVal) {
break;
}
resIndexList.add(temp);
temp--;
}
//将mid加入到列表中
resIndexList.add(mid);
//将mid右侧所有等于目标值的元素的索引加入到列表中
temp = mid - 1;
while (true) {
if (temp > arr.length - 1 || arr[temp] != findVal) {
break;
}
resIndexList.add(temp);
temp++;
}
return resIndexList;
}
}
}
3、插值查找
(1)算法简介:有序表的一种查找方式。插值查找是根据查找关键字与查找表中最大最小记录关键字比较后的查找方法。插值查找基于二分查找,将查找点的选择改进为自适应选择,提高查找效率。
(2)算法实现:将二分查找中的
m
i
d
=
l
e
f
t
+
r
i
g
h
t
−
l
e
f
t
2
mid = left +\frac{right-left}{2}
mid=left+2right−left
替换为
m
i
d
=
l
e
f
t
+
f
i
n
d
V
a
l
−
a
r
r
[
l
e
f
t
]
a
r
r
[
r
i
g
h
t
]
−
a
r
r
[
l
e
f
t
]
∗
(
r
i
g
h
t
−
l
e
f
t
)
mid = left +\frac{findVal-arr[left]}{arr[right]-arr[left]}*(right-left)
mid=left+arr[right]−arr[left]findVal−arr[left]∗(right−left)
(3)适用范围:适合于关键字值分布均匀的集合。
(4)代码实现(java):
package com.haiyang.datastructure.search;
/**
* @author haiYang
* @create 2021-12-25 16:29
*/
public class InsertValueSearch {
public static void main(String[] args) {
int[] ints = new int[100];
for (int i = 0; i < 100; i++) {
ints[i] = i;
}
int i = insertValueSearch(ints, 0, ints.length-1, 98);
System.out.println(i);
}
//算法思想与二分查找相同,在mid取值上改进
public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
//结束条件
//findVal < arr[0] || findVal > arr[arr.length - 1] 防止越界
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 insertValueSearch(arr, mid + 1, right, findVal);
} else if (findVal < midVal) {
//递归调用在左侧查找
return insertValueSearch(arr, left, mid - 1, findVal);
} else {
//找到目标值返回目标值索引
return mid;
}
}
}
4、斐波那契查找
(1)算法介绍:斐波那契查找,又称斐波那契搜索(Fibonacci search) ,是区间中单峰函数的搜索技术。
(2)算法实现:斐波那契查找与折半查找很相似,它是根据斐波那契序列的特点对有序表进行分割的。
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
k | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | … |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
F(k) | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 | 233 | 377 | 610 | 987 | 1597 | 2584 | 4181 | 6765 | … |
第一步:在斐波那契数列中寻找适合的数值n,要求开始表中记录的个数为某个斐波那契数小1(即
n
=
F
(
k
)
−
1
n=F(k)-1
n=F(k)−1),当待查找数组个数不足
n
n
n时,使用数组最高为填充到
n
n
n;
第二步:取
m
i
d
=
l
o
w
+
F
(
k
−
1
)
−
1
mid=low+F(k-1)-1
mid=low+F(k−1)−1,即将数组分成两部分
F
(
k
−
1
)
F(k-1)
F(k−1)和
F
(
k
−
2
)
F(k-2)
F(k−2),即
F
(
k
)
=
F
(
k
−
1
)
+
F
(
k
−
2
)
F(k) = F(k-1)+F(k-2)
F(k)=F(k−1)+F(k−2);
第三步:将
k
e
y
key
key值与第
F
(
k
−
1
)
F(k-1)
F(k−1)位置的记录进行比较(即与
m
i
d
=
l
o
w
+
F
(
k
−
1
)
−
1
mid=low+F(k-1)-1
mid=low+F(k−1)−1进行比较),比较结果也分为三种:
1、相等,则
m
i
d
mid
mid位置的元素即为所求;
2、大于,则
l
o
w
=
m
i
d
+
1
low=mid+1
low=mid+1,
k
=
k
−
2
k=k-2
k=k−2;
说明:
l
o
w
=
m
i
d
+
1
low=mid+1
low=mid+1说明待查找的元素在
[
m
i
d
+
1
,
h
i
g
h
]
[mid+1,high]
[mid+1,high]范围内,
k
=
k
−
2
k=k-2
k=k−2说明范围
[
m
i
d
+
1
,
h
i
g
h
]
[mid+1,high]
[mid+1,high]内的元素个数为
n
−
F
(
k
−
1
)
=
F
(
k
)
−
1
−
F
(
k
−
1
)
=
F
(
k
−
2
)
−
1
n-F(k-1)=F(k)-1-F(k-1)=F(k-2)-1
n−F(k−1)=F(k)−1−F(k−1)=F(k−2)−1个,所以可以递归的应用斐波那契查找。
3、小于,则
h
i
g
h
=
m
i
d
−
1
high=mid-1
high=mid−1,
k
=
k
−
1
k=k-1
k=k−1。
说明:
l
o
w
=
m
i
d
+
1
low=mid+1
low=mid+1说明待查找的元素在
[
l
o
w
,
m
i
d
−
1
]
[low,mid-1]
[low,mid−1]范围内,
k
=
k
−
1
k=k-1
k=k−1说明范围
[
l
o
w
,
m
i
d
−
1
]
[low,mid-1]
[low,mid−1]内的元素个数为
F
(
k
−
1
)
−
1
F(k-1)-1
F(k−1)−1个,所以可以递归的应用斐波那契查找。
在最坏情况下,斐波那契查找的时间复杂度还是
O
(
l
o
g
2
n
)
O(log_2{n})
O(log2n),且其期望复杂度也为
O
(
l
o
g
2
n
)
O(log_2{n})
O(log2n),但是与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。
(3)代码实现(java):
package com.haiyang.datastructure.search;
import java.util.Arrays;
/**
* @author haiYang
* @create 2021-12-25 20:20
*/
public class FibonacciSearch {
//斐波那契数列长度
public static int maxSize = 20;
public static void main(String[] args) {
int [] arr = {1,21,33,55,64,98,102,105,106,109,120,125,127,130,156,189,200};
System.out.println("index=" + fibSearch(arr, 106));// 0
}
//通过非递归定义一个斐波那契数列
public static int[] fib() {
int[] fib = new int[maxSize];
fib[0] = 1;
fib[1] = 1;
for (int i = 2; i < maxSize; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib;
}
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();
//获取斐波那契分割数值的下标
while (high > f[k] - 1) {
k++;
}
//使数组元素个数扩充到与斐波那契下标所指数值相同,不足用数组最高为填充
int[] temp = Arrays.copyOf(arr, f[k]);
for (int i = high + 1; i < temp.length; i++) {
temp[i] = temp[high];
}
//查找key的循环条件
while (low <= high) {
//将长度为f[k]数组分成f[k-1]和f[k-2]两部分
mid = low + f[k - 1] - 1;
if (key < temp[mid]) {//说明key在f[k-1]中,因此在下次循环中需将k-1
high = mid - 1;
k--;
} else if (key > temp[mid]) {//说明key在f[k-2]中,因此在下次循环中需将k-2
low = mid + 1;
k -= 2;
} else {
if (mid <= high) {//如果mid索引在当前数组范围中,直接返回
return mid;
} else {//不在其中,说明索引是在扩充那部分中,扩充部分为数组最高为值,因此返回最高为值
return high;
}
}
}
return -1;
}
}