看到《剑指off》基础知识部分,发现没对常用的查找算法详细讲解,因此从别的地方学习一下,总结在这里。
平均时间复杂度 | |
---|---|
顺序查找 | O(N) |
二分查找 | O(logN) |
插值查找 | O(loglogN) |
斐波那契查找 | O(logN) |
树表查找 | |
分块查找 | |
哈希查找 |
顺序查找
基本思想
从前往后遍历,最简单的一种查找算法
Java实现
public class SequenceSearch {
public static int search(int[] l,int target){
for (int o :
l) {
if (o == target) {
return o;
}
}
return -1;
}
public static void main(String[] args) {
int[] l=new int[]{1,5,47,5,23,4};
System.out.println(SequenceSearch.search(l, 888));
}
}
说明
- 复杂度分析:时间复杂度O(n)
- 属于无序查找,适用于顺序存储或链式存储
二分查找
基本思想
二分查找,又叫折半查找,属于有序查找方法。用目标值与中间值进行比较,若相等则直接结束算法,若不相等则根据结果递归执行算法。
Java实现
引用一句话,注意实现时的边界问题。
Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky…
public class BinarySearch {
//普通实现
public static int Search1(int[] l,int target,int n){
int low=0;
int high=n-1;
while(low<=high){
int mid=(low+high)/2; //mid=low+(high-low)/2 可以防止溢出
if(target<l[mid]){
high=mid-1; //已经比较过mid则排除,下同
}else if(target>l[mid]){
low=mid+1;
}else{
return mid+1;
}
}
return -1;
}
//递归实现
public static int Search2(int[] l,int target,int low,int high){
int mid=(low+high)/2;
if(low>high){
return -1;
}
if(target==l[mid]){
return mid+1;
}else if(target<l[mid]){
return Search2(l,target,0,mid-1);
}else{
return Search2(l,target,mid+1,high);
}
}
public static void main(String[] args) {
int[] l=new int[]{1,2,3,4,5,11,15654,1646541};
System.out.println(BinarySearch.Search1(l, 456,8));
System.out.println(BinarySearch.Search2(l, 456,0,7));
}
}
说明
- 复杂度分析:时间复杂度O(logn)
- 属于有序查找,需要有大小顺序
插值查找
基本思想
例子:
在英文字典里面查“apple”,你下意识翻开字典是翻前面的书页还是后面的书页呢?如果
再让你查“zoo”,你又怎么查?很显然,这里你绝对不会是从中间开始查起,而是有一定
目的的往前或往后翻。
二分查找的每次选择中间点进行比较,插值查找将二分查找优化,使二分查找的1/2能够根据目标值进行自适应调整。
mid=(low+high)/2
==>>
mid=(low+high)*(target-a[low])/(a[high]-a[low])
Java实现
public class InsertionSearch {
public static int Search(int[] l,int target,int n){
int low=0;
int high=n-1;
if(target<l[low]||target>l[high]){
return -1;
}
while(low<=high&&l[low]!=l[high]){
int mid=low+(high-low)*(target-l[low])/(l[high]-l[low]);
if(target<l[mid]){
high=mid-1;
}else if(target>l[mid]){
low=mid+1;
}else{
return mid+1;
}
}
return -1;
}
public static int Search2(int[] l,int target,int low,int high){
if(target<l[low]||target>l[high]||low>high||l[low]==l[high]){
return -1;
}
int mid=low+(high-low)*(target-l[low])/(l[high]-l[low]);
if(target<l[mid]){
return Search2(l,target,low,mid-1);
}else if(target>l[mid]){
return Search2(l,target,mid+1,high);
}else{
return mid+1;
}
}
public static void main(String[] args) {
int[] l=new int[]{1};
System.out.println(InsertionSearch.Search(l, 1, 1));
System.out.println(InsertionSearch.Search2(l, 1, 0, 0));
}
说明
- 注意循环条件,要满足l[low]!=l[high]防止除0
- 时间复杂度O(loglogN)
- 适用于表长且分布均匀的数据
斐波那契查找
基本思想
斐波那契查找与插值查找类似,也是对二分查找的优化。人们发现斐波那契数列两个相邻数的比例,无限接近黄金分割值0.618。于是利用斐波那契数列将1/2替换为0.618。
Java实现
public class FibSearch {
public static int[] fib(int max){
int[] res=new int[max];
res[0]=1;
res[1]=1;
for(int i=2;i<max;i++){
res[i]=res[i-1]+res[i-2];
}
return res;
}
public static int Search(int[] l,int target,int n){
int low=0;
int high=n-1;
int[] fib=FibSearch.fib(999);
int k=0;
while(n>fib[k]-1){
k++;
}
int[] temp;
temp=Arrays.copyOf(l,fib[k]-1);
for (int i=n;i<temp.length;i++){
temp[i]=l[n-1];
}
while(low<=high){
int mid=low+fib[k-1]-1;
if(target<temp[mid]){
high=mid-1;
k--;
}else if(target>temp[mid]){
low=mid+1;
k--;
k--;
}else{
if(mid<n){
return mid+1;
}else {
return mid;
}
}
}
return -1;
}
public static void main(String[] args) {
int[] l=new int[]{1,2,3,4,55,77,666};
System.out.println(FibSearch.Search(l, 5555, 7));
}
说明
- 时间复杂度O(logN)
树表查找
待总结。。。
分块查找
基本思想
顺序查找的一种改进方法。将n个数据元素 “按块有序” 划分为 m 块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";第 i 块中的每个元素一定比第 i-1 块中的任意元素大(小)。
1) 先选取各块中的最大关键字构成一个索引表;
2) 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。
哈希查找
哈希函数
哈希函数的规则是:通过某种转换关系,使关键字适度的分散到指定大小的的顺序结构中,越分散,则以后查找的时间复杂度越小,空间复杂度越高。
基本思想
哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。