参考:
博客 https://blog.csdn.net/sayhello_world/article/details/77200009
《大话数据结构》
顺序表查找分为:
顺序查找
有序查找
- 折半查找
- 插值查找
- 斐波那契查找
- 线性索引查找
- 稠密索引查找
- 分块索引查找
- 倒序查找
1.顺序查找
基本思想:顺序查找是最基本的查找技术,查找过程是:从表中的第一个或者最后一个元素开始,依次将元素和待查找的值进行比较,若相同,则查找成功;若直到表尾或表头,元素和待查找的值都不相同,则查找不成功
代码实现:
public class SequentialSearch {
public static void main(String[] args) {
int[] a = {0,1,16,24,35,47,59,62,73,88,99};
System.out.println(sequential(a,62));
}
public static int sequential(int[] a, int target){
int len = a.length;
for(int i=0;i<len;i++){
if(a[i]==target){
return i;
}
}
return -1;
}
}
复杂度分析:
查找成功的平均查找时间为(1+2+3+…+n)/n = (n+1)/2;
查找失败需要进行n+1次比较;
所以顺序查找的时间复杂度为O(n)
2.有序查找
2.1折半查找
折半查找又叫二分查找,其前提是序列为有序的,若为无需序列,需要先进行排序。
基本思想: 因为序列为有序序列,首先将待查找的数据k与序列中间元素a[mid]进行比较,如相同,则查找成功;若k > a[mid],则表明其存在于后半部分,否则在前半部分,由此继续递归查找,直到查找到或者查找失败。
代码实现:
import java.util.Arrays;
public class BinarySearch {
public static void main(String[] args) {
int[] a = {0,1,16,24,35,47,59,62,73,88,99};
System.out.println(binary(a,62));
}
public static int binary(int[] a, int target){
int len = a.length;
int i = 0;
int j = len-1;
int mid;
//首先对数据进行排序
Arrays.sort(a);
while(i<=j){
mid = (i+j)/2;
if(a[mid] == target)
return mid;
if(target > a[mid]){
i = mid+1;
}else{
j = mid - 1;
}
}
return -1;
}
}
时间复杂度分析: 每次比较可以排除掉一半的数据,所以时间复杂度为O(logn)。
注意:二分查找的前提是元素是有序的,所以对于静态表,一次排序后不再变化,其查找效率是不错的,但是对于需要频繁插入和删除的数据来说,单纯维护其序列有序就会带来不小的开销,所以在这种情况下,二分查找并不适用。
2.2 插值查找
插值查找是根据待查找的数据key和序列中最大最小值比较后的查找方法,是二分查找的一种变形。
基本思想: 对于折半查找来说,每次都从1/2处进行,但是例如对于1~10000中间的100数里查找数值5来说,首先需要考虑从数组下标较小的开始查找,而不是直接从1/2处,这样才可以提供查找的效率。
所以将查找点的计算改为:(key - a[low]) / (a[high] - a[low]);
所以 mid = low + (key - a[low]) * (high - low) / (a[high] - a[low])
代码实现:
只需要将二分查找中的mid = (i+j)/2 改为
mid = i + (j-i)*(target - a[i])/(a[j]-a[i]);
时间复杂度分析: 其平均时间复杂度O(logn),对于表较长,且数值均匀分布的序列来说,插值查找平均性能要比折半查找好得多,但是对于极端不均匀分布的数据,差值查找不一定是是合适的选择。
2.3斐波那契查找
斐波那契查找也是二分查找的一种变形,也是一种有序查找算法。
首先介绍下黄金分割:是指饰物各部分之间存在的一定数学比例关系,即将整体一分为二,较大部分/较小部分 = 整体 / 较大部分,其比值约为1.618:1。
对有斐波那契数列,后面每一个数是前面两个数字的和,并且可以发现,随着斐波那契数列的递增,前后两个数字的比值越来约接近0.618,在斐波那契查找中,便是利用到这一特性。
基本思想: 将黄金分割的概念运用在查找点的计算上,提高查找效率。
如下图所示(图片来源https://blog.csdn.net/sayhello_world/article/details/77200009,侵权删),序列的长度为斐波那契数列中的一个数值减1,即n = F(k) - 1; 那么插入点mid = low + (F(k-1) - 1)。
若 key == a[mid], 查找成功;
若 key > a[mid], 则在右半部分,所以有left = mid+1,且 k = k-2;
若 key < a[mid], 则在左半部分,所以有right = mid-1,且 k = k-1;
代码实现:
import java.util.Arrays;
public class FibonacciSearch {
public static void main(String[] args) {
int[] a = {0,1,16,24,35,47,59,62,73,88,99};
System.out.println(Fibonacci(a,62));
}
public static int Fibonacci(int[] a, int target){
int len = a.length;
int[] F = creatFibonacci();
int k = 0;
//遍历斐波那契数列,找到k的值
for(int i = 0;i<F.length;i++){
if(len > F[k]-1){
k++;
}
}
//当F[k]-1>n的是否,需要不全待查找的序列,Arrays类中的copyOf方法将a数组补长到F[k]-1
int[] temp = Arrays.copyOf(a, F[k]-1);
for(int i = len;i<F[k]-1;i++){
temp[i] = temp[len-1];
}
int left = 0;
int right = F[k]-1;
int mid;
while(left<right){
mid = F[k-1] - 1;
if(temp[mid] == target)
return mid;
if(target > temp[mid]){
left = mid+1;
k -= 2;
}else{
right = mid - 1;
k -= 1;
}
}
return -1;
}
//初始化一个斐波那契数组,长度为20,F[19] = 6765,已经挺大的了,所以这里定义的长度20
public static int[] creatFibonacci(){
int[] F = new int[20];
F[0] = 0;
F[1] = 1;
for(int i = 2;i<20;i++){
F[i] = F[i-1] + F[i-2];
}
return F;
}
}
复杂度分析: 时间复杂度为O(logn)
总结:顺序查找不需要序列为有序的;而折半查找、插值查找以及斐波那契查找则均是在序列有序的基础上进行的,只是查找点上有不同,各有各的优势,在实际使用当中,需要综合考虑来做出选择。
3.线性索引查找
在现实中,数据是不断处于动态增长且无序的,前面有序查找算法需要基于有序的基础上,所以对于大量增长非常快的数据集来说,维护数据有序的代价是非常非常大的。所以出现了索引。
索引就是把一个关键字与它对应的记录相关联的过程,加快查找速度。
线性索引查找就是将索引项集合组织为线性结构,称为索引表。
3.1稠密索引
稠密索引是指在现象索引中,将数据集中的每个记录对应一个索引项。
索引项按照关键码有序排列,当查找关键字时,可以通过折半、插值、斐波那契等有序算法进行查找,大大提高效率。
对于稠密所以,意味着索引和数据集具有同样的长度,当数据非常大时,稠密索引的查找效率大大降低。
3.2分块索引
为减少索引个数,将数据集进行分块,对每块建立索引项。
这些块需要满足一下两个条件:
- 快内无序,即对每一块内的记录不要求有序,维护快内有序需要付出大量的时间和空间代价;
- 块间有序,快间有序,才能在查找中带来效率。
分块索引查找步骤:
(1)在分块索引表中查找关键字所在块,因为块间有序,所以同样可以使用有序查找算法提高效率;
(2)根据块首指针找到相应的块,因为块中无序,所以在块中顺序查找的得到关键码。
分块查找的时间复杂度为:O(√n)
3.3倒序索引
倒序索引的典型应用就是搜索引擎。
倒序索引是建立被搜索的关键词和含这些关键字的页面之间的映射,其中倒序指的是从关键词中查找到对应的源,而不是从源中查找到相应的关键词。
将倒序索引表排序后,同样可以通过有序查找算法快速查找到关键字,进而查找到包含关键字的页。
例如:为了检索关键词java,首先从倒序索引表中,找到该关键词,然后便可以查找到java所在的页了。