顺序查找
顺序查找(Sequential Search)又叫线性查找,是最简单直接暴力的查找方法。就是从头至尾挨个寻找,找到位置。当遍历完没找到就说明要找的数据不存在。它不要求数据有序。该算法的时间复杂度为O(n)。
public static int seqSearch(int[] arr, int value) {
for(int index = 0; index < arr.length; index++) {
if(arr[index] == value) {
return index;
}
}
return -1;
}
二分查找(折半查找)
自我感觉是相对用的比较广泛的,效率也挺高的。不过它要求数据是有序的。在有序的情况下,一半一半地找。先与中间值对比,判断要找的值是在前半部分还是后半部分。然后就根据判断结果锁定下一次查找区间,这相当于一次缩小一半的查找量。
public static int binarySearch(int[] arr, int value) {
int low = 0;
int high = arr.length - 1;
if(value < arr[low] || value > arr[high]) {
return -1;
}
int v;
int mid = 0;
while(low <= high) {
mid = low + (high - low) / 2;
if((v = arr[mid]) < value) {
//继续从右边查
low = mid + 1;
}else if(v == value) {
return mid;
}else {
high = mid - 1;
}
}
return -1;
}
以查找数组中20为例。流程跟踪:
插值查找
基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。差值查找也需要数据有序。二分查找是每次都选择一个区间的中间来缩小查找范围,不论数据如何都是从中间劈开。而差值查找是一种自适应地选择分割区间值。两种思路对比:
对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快。关键字分布不均匀的情况下,该方法不一定比折半查找要好。 没有更好更坏,只有合适。
public static int insertValueSearch(int[] arr, int value) {
int low = 0;
int high = arr.length - 1;
/*
* 必须判断。针对公式mid = low + (high - low) * (value - arr[low])/(arr[high] - arr[low]);
* value如果为无穷大,那么mid很可能为无穷大。会下标越界。
*/
if(value < arr[low] || value > arr[high]) {
return -1;
}
int v;
while(low <= high) {
int mid = low + (high - low) * (value - arr[low])/(arr[high] - arr[low]);
if((v = arr[mid]) < value) {
//继续从右边查
low = mid + 1;
}else if(v == value) {
return mid;
}else {
high = mid - 1;
}
}
return -1;
}
下面查找分布均匀和不均匀为例测试,查找次数:
public static void main(String[] args) {
int[] arr2 = {1,2,3,4,5,6,7,8,9,11,20,88};
int[] arr3 = new int[100];
for(int index = 0; index < 100; index ++) {
arr3[index] = index * 2;
}
System.out.println();
int flag = insertValueSearch(arr2, 20);
System.out.println(flag == -1 ? "没有这个数" : "找到了 下标为:" + flag);
flag = insertValueSearch(arr3, 20);
System.out.println(flag == -1 ? "没有这个数" : "找到了 下标为:" + flag);
}
斐波那契查找
黄金分割:
黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之.比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割。这是一个神奇的数字,会带来意向不到的效果。
斐波那契数列{1,1,2,3,5,,8,13, 21, 34, 55}可以发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618
斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid) 的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列, k代表的是斐波那契数列的第k个元素),如下图所示
对F(k-1)-1的理解:
- 由斐波那契数列 F[k]=F[k- 1]+F[k-2]的性质,可以得到(F[k]-1) = (F[k-1]-1) + (F[k-2]-1) +1。该式说明:只要顺序表的长度为F[k]-1, 则可以将该表分成长度为F[K-1]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid=low+F[k-1]-1
- 类似的, 每一子段也可以用相同的方式分割
- 但顺序表长度n不一定刚好等于F[k]-1, 所以需要将原来的顺序表长度n增加至FlK1-1.这里的k值只要能使得F[k)-1恰好大于或等于n即可
public class fibSearch {
private static int maxSize = 20;
public static int[] fib() {
int[] fib = new int[maxSize];
fib[0] = 1;
fib[1] = 1;
for(int index = 2; index < maxSize; index++) {
fib[index] = fib[index - 1] + fib[index - 2];
}
return fib;
}
//斐波那契数列管理索引。 数组必须是有序的
public static int fSearch(int[] arr, int value) {
int low = 0;
int high = arr.length - 1;
int k = 0; //表示斐波那契分割数值的下标
int mid = 0;
int[] fib = fib();
while(high > fib[k] - 1) {
k++;
}
//因为fib[k]的值可能大于arr的长度,所以需要构造新的数组
int[] tmp = Arrays.copyOf(arr, fib[k]);
for(int index = high + 1; index < tmp.length; index++) {
tmp[index] = arr[high];
}
while(low <= high) { //只要这个条件满足就可以找
mid = low + fib[k - 1] - 1;
if(value < tmp[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 = f[k-1-1]-1
*/
k--;
}else if(value > tmp[mid]) {
low = mid + 1;
/**
* 为什么是k -=2
1.全部元素=前面的元素+后边元素
2. f[k] = f[k-1] + f[k-2]
3.因为后面我们有f[k-2]所以可以继续拆分f[k-1] = f[k-3] + f[k-4]
4. 即在f[k-2] 的前面进行查找k -=2
5.即下次循环mid = f[k- 1-2]- 1
*/
k -= 2;
}else {
if(mid <= high) {
return mid;
}else {
return high;
}
}
}
return -1;
}
流程分析:
【我们见到的太阳是8分钟之前的太阳,见到的月亮是1.3秒之前的月亮,见到一英里以外的建筑是5微妙之前的存在,即使你在我一米之外,我见到的也是3纳米秒之前的你,在宇宙的尺度上,我们所见的都是过去。】