数据结构中提供了很多查找算法,诸如插值查找,顺序查找,斐波那契查找等。
按查找的操作方式分类,分为:
静态查找:数据集合稳定,不需要添加,删除元素的查找操作。
动态查找:数据集合在查找的过程中,需要同时添加,或者删除元素的查找操作。
例如当在维基百科中查找东西,当查找目标不存在时,会提示用户:是否创建一个?
为了提高查找效率,特为其设置了查找结构:
对于静态查找来说,我们不妨可以用线性表结构组织数据,这样可以使用顺序查找算法,如果我们再对关键字进行排序,则可以使用折半查找算法或是斐波那契查找算法等来提高查找的效率。
对于动态查找来说,我们则可以考虑使用二叉排序树的查找技术,另外我们还可以使用散列表结构来解决一些查找问题。
一:(1)顺序查找:
1 //顺序查找:a为要查找的数组,n为要查找的数组的长度,key为要查找的关键字。
2 int Sq_Serch(int *a,int n,int key)
3 {
4 int i;
5 for(i=1;i<=n;i++)
6 {
7 if(a[i]==key)//数组元素与关键字进行对比。
8 {
9 return i;
10 }
11 }
12 return 0;
13 }
上面算法的时间复杂度是log(2n),但是有没有更简单的算法可以降低时间复杂度呢?
1 //顺序查找的优化方案,a为要查找的数组,n为要查找的数组的长度,key为要查找的关键字。
2 int Sq_Serch(int *a,int n,int key)
3 {
4 int i=n;
5 a[0]=key;//设置哨兵
6 while(a[i]==key){
7 i--;//值会减到0
8 }
9 return i;
10 }//算法的时间复杂度是o(n),待查元素有多少个,就进行多少次的匹配
对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。
顺序查找 查找成功时的平均查找长度为:
(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
当查找不成功时,需要n+1次比较,时间复杂度为O(n);
注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,这样的算法已经比较好了。但对于需要
频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
2:插值查找(按比例查找)
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
1 int Sq_Serch(int str[],int n,int key)
2 {
3 int low=0,high=n-1,mid;
4 while(low<=high)
5 {
6 mid=low+(key-a[low]/a[high]-a[low])*(high-low);//插值查找的唯一不同点
7 if(str[mid]==key)
8 {
9 return mid;
10 }
11 if(str[mid]<key)
12 {
13 low=mid+1;
14 }
15 else
16 {
17 high=mid-1;
18 }
19 return -1;
20 }
21 }
该算法的时间复杂度和折半查找的时间复杂度相同,至于采用什么样的算法,应该根据题目要求。
斐波那契查找的前提是待查找的查找表必须顺序存储并且有序。
相对于折半查找,一般将待比较的key值与第mid=(low+high)/2位置的元素比较,比较结果分三种情况
1)相等,mid位置的元素即为所求
2)> ,low=mid+1;
3) < ,high=mid-1;
斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,及n=Fk-1;
开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种
1)相等,mid位置的元素即为所求
2)> ,low=mid+1,k-=2;
说明:low=mid+1说明待查找的元素在[mid+1,hign]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找
3)< ,high=mid-1,k-=1;
说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1
个,所以可以递归 的应用斐波那契查找
斐波那契查找的算法如下:
1 [html] view plaincopy
2
3 // 斐波那契查找.cpp
4
5 #include "stdafx.h"
6 #include <memory>
7 #include <iostream>
8 using namespace std;
9
10 const int max_size=20;//斐波那契数组的长度
11
12 /*构造一个斐波那契数组*/
13 void Fibonacci(int * F)
14 {
15 F[0]=0;
16 F[1]=1;
17 for(int i=2;i<max_size;++i)
18 F[i]=F[i-1]+F[i-2];
19 }
20
21 /*定义斐波那契查找法*/
22 int Fibonacci_Search(int *a, int n, int key) //a为要查找的数组,n为要查找的数组长度,key为要查找的关键字
23 {
24 int low=0;
25 int high=n-1;
26
27 int F[max_size];
28 Fibonacci(F);//构造一个斐波那契数组F
29
30 int k=0;
31 while(n>F[k]-1)//计算n位于斐波那契数列的位置
32 ++k;
33
34 int * temp;//将数组a扩展到F[k]-1的长度
35 temp=new int [F[k]-1];
36 memcpy(temp,a,n*sizeof(int));
37
38 for(int i=n;i<F[k]-1;++i)
39 temp[i]=a[n-1];
40
41 while(low<=high)
42 {
43 int mid=low+F[k-1]-1;
44 if(key<temp[mid])
45 {
46 high=mid-1;
47 k-=1;
48 }
49 else if(key>temp[mid])
50 {
51 low=mid+1;
52 k-=2;
53 }
54 else
55 {
56 if(mid<n)
57 return mid; //若相等则说明mid即为查找到的位置
58 else
59 return n-1; //若mid>=n则说明是扩展的数值,返回n-1
60 }
61 }
62 delete [] temp;
63 return -1;
64 }
65
66 int _tmain(int argc, _TCHAR* argv[])
67 {
68 int a[] = {0,16,24,35,47,59,62,73,88,99};
69 int key=100;
70 int index=Fibonacci_Search(a,sizeof(a)/sizeof(int),key);
71 cout<<key<<" is located at:"<<index;
72 system("PAUSE");
73 return 0;
74 }
斐波那契查找的核心是:
1)当key=a[mid]时,查找成功;
2)当key<a[mid]时,新的查找范围是第low个到第mid-1个,此时范围个数为F[k-1] - 1个,即数组左边的长度,所以要在[low, F[k - 1] - 1]范围内查找;
3)当key>a[mid]时,新的查找范围是第mid+1个到第high个,此时范围个数为F[k-2] - 1个,即数组右边的长度,所以要在[F[k - 2] - 1]范围内查找。
关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当众的大部分数据,其工作效率要高一些。所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找。
还有关键一点,折半查找是进行加法与除法运算的(mid=(low+high)/2),插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low]))),而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1] - 1),在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。
线性索引查找
索引:就是把一个关键字与它对应的i记录相关联的过程,一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息。
索引按照结构可以分为:线性索引、树形索引和多级索引。
线性索引是将索引项集合组织为线性结构,也称为索引表。包括稠密索引、分块索引、倒排索引。
1. 稠密索引
一个完美的引子:
我母亲年纪大了,记忆力不好,经常在家里找不到东西,于是她想了一个办法。她用一个小本子记录了家里所有小东西放置的位置,比如户口本放在右手床头柜下面抽 屉中,针线放在电视柜中间的抽屉中,钞票放在衣柜……总之,她老人家把这些小物品的放置位置都记录在了小本子上,并且每隔一段时间还按照本子整理一遍家中 的物品,用完都放回原处,这样她就几乎再没有找不到东西。从这件事情就可以看出,家中的物品尽管是无序的,但是如果有一个小本子记录,寻找起来也是非常容 易的,而这小本子就是索引。
稠密索引是指在线性表中,将数据集中的每个记录对应一个索引项。
对于稠密索引这个索引表来说,索引项一定是按照关键码有序的排列。通过对索引项的查找,就可以找到丢应的结果地址,但是如果数据集非常大,比如说上亿,那也就意味着索引同样的数据规模,可能就需要反复查询内存和硬盘,性能可能反而下降了。
2. 分块索引
引子:图书馆如何藏书
分块有序需要满足两个条件:块内无序(有序更好,代价比较大)、块间有序
对于分块有序的数据集,将每块对应一个索引项,这种索引方法叫做分块索引。
最大关键码、块长和块首指针很容易理解。
平均查找长度ASL 为 在索引表中的平均查找长度 + 在记录(数据集)中的平均查找长度。
假设 有m块,每块中共有t条记录,显然n = m * t。
故而 ASL = (m+1)/2 + (t+1)/2 = (n/t + t)/2 + 1. 上式的最小值,即n/t = t,n = t^2,则最小的ASL = n^(1/2) + 1;
课件,分块索引的效率比顺序查找o(n) 高了很多,总的来说,分块索引在兼顾了对细分块不需要有序的情况下,大大增加了整体查找的速度,所以普遍被用于数据库查找等技术的应用当中。
3. 倒排索引
引子:搜索引擎如何进行搜索,能够在非常快的速度之下显示搜索的最优匹配内容。
索引项的结构是次关键字码和记录号表,其中记录号表存储具有相同关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键字),这样的索引方法就是倒排索引。
倒排索引源于实际应用中需要根据属性(或字段、次关键码)的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性来确定记录的位置,因而称为倒排索引。
倒排索引的优点显然是查找记录非常快,基本等于生成索引表后,查找时都不用去读取记录,即可以得到结果。但是它的缺点是这个记录号不定长,可多可少。