zoukankan      html  css  js  c++  java
  • 查找算法

    数据结构中提供了很多查找算法,诸如插值查找,顺序查找,斐波那契查找等。

    按查找的操作方式分类,分为:

    静态查找:数据集合稳定,不需要添加,删除元素的查找操作。

    动态查找:数据集合在查找的过程中,需要同时添加,或者删除元素的查找操作。

    例如当在维基百科中查找东西,当查找目标不存在时,会提示用户:是否创建一个?

    为了提高查找效率,特为其设置了查找结构:

    对于静态查找来说,我们不妨可以用线性表结构组织数据,这样可以使用顺序查找算法,如果我们再对关键字进行排序,则可以使用折半查找算法或是斐波那契查找算法等来提高查找的效率。

    对于动态查找来说,我们则可以考虑使用二叉排序树的查找技术,另外我们还可以使用散列表结构来解决一些查找问题。

    一:(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),待查元素有多少个,就进行多少次的匹配
     平均查找长度(Average Search Length,ASL)
         需和指定key进行比较的关键字的个数的期望值,成为查找算法在查找成功时的平均查找长度。
         对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
         Pi:查找表中第i个数据元素的概率。
         Ci:找到第i个数据元素时已经比较过的次数。
         顺序查找 查找成功时的平均查找长度为:
         (假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
         当查找不成功时,需要n+1次比较,时间复杂度为O(n);
    二:有序表查找:
    1:折半查找:
    前提:线性表中的记录必须是关键字有序(通常从小到大),线性表必须采用顺序存储。
    基本思想:取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录左半区继续查找;否则,在右半区查找。不断重复,知道查找成功或者查找失败为止。
     
     1 /*折半查找,非递归*/  
     2 int Binary_Search(int *a, int n, int key)  
     3 {  
     4      int low, high;  
     5      int mid;  
     6      low = 1;  
     7      high = n;  
     8       
     9      while(low < high)<span style="white-space:pre"> //可以有等号 low < = high ,不影响判断。  
    10      {  
    11           mid = (low + high) / 2; <span style="white-space:pre">    </span>//可以修正为 mid = (low + high) >> 1;  
    12           if(a[mid] == key)  
    13                return mid;  
    14           if(a[mid] > key)  
    15                high = mid - 1;  
    16           if(a[mid] < key)  
    17                low = mid + 1;<span style="white-space:pre">     </span>//对于if else语句,可以考虑条件表达式,减少代码的行数。  
    18      }  
    19      return 0;  
    20 }  
    21 /*折半查找,递归实现*/  
    22 int Binary_Search2(int *a, int low, int high, int key)  
    23 {  
    24      int mid = (low + high) / 2;  
    25      if(a[mid] == key)  
    26           return mid;  
    27      if(a[mid] > key)  
    28           return Binary_Search2(a, low, mid-1, key);     //有没有return都可以。  
    29      else  
    30           return Binary_Search2(a, mid+1, high, key);     //有没有return都可以。  
    31 }  
        ASL: “具有n个节点的完全二叉树的深入为[log2n]+1,尽管折半查找判定二叉树不是完全二叉树,但同样相同的推导可以得出,
                   最坏情况是查找到关键字或查找失败的次数为[log2n]+1”,最好情况是1,所以折半查找的时间复杂度为o(logN)>o(n).

         注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,这样的算法已经比较好了。但对于需要
         频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》

    2:插值查找(按比例查找)

    二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

    首先考虑一个新问题,为什么一定要是折半,而不是折四分之一或者折更多呢?
    打个比方,在英文字典里面查“apple”,你下意识翻开字典是翻前面的书页还是后面的书页呢?如果再让你查“zoo”,你又怎么查?很显然,这里你绝对不会是从中间开始查起,而是有一定目的的往前或往后翻。
    同样的,比如要在取值范围1 ~ 10000 之间 100 个元素从小到大均匀分布的数组中查找5, 我们自然会考虑从数组下标较小的开始查找。
    经过以上分析,折半查找这种查找方式,还是有改进空间的,并不一定是折半的!
    mid = (low+high)/ 2, 即 mid = low + 1/2 * (high - low);
    改进为 下面的计算机方案(不知道具体过程):mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low),也就是将上述的比例参数1/2改进了,根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次 数。
     
    分析:从时间复杂度上来看,它也是o(n),但是对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么差值查找未必是很合适的选择。

     

     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 }

    该算法的时间复杂度和折半查找的时间复杂度相同,至于采用什么样的算法,应该根据题目要求。

    3:斐波那契查找(黄金分割查找)

    原理:利用斐波那契数列的性质,黄金分割的原理来确定mid的位置。

     斐波那契查找的前提是待查找的查找表必须顺序存储并且有序。

        相对于折半查找,一般将待比较的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),在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。

    总结:
    折半查找进行加法与除法运算(mid = (low + high) / 2),插值查找进行复杂的四则运算( mid = low + (key - a[low] / (a[high] - a[low]) * (high - 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. 倒排索引

    引子:搜索引擎如何进行搜索,能够在非常快的速度之下显示搜索的最优匹配内容。

    索引项的结构是次关键字码和记录号表,其中记录号表存储具有相同关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键字),这样的索引方法就是倒排索引。


     

    倒排索引源于实际应用中需要根据属性(或字段、次关键码)的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性来确定记录的位置,因而称为倒排索引。

    倒排索引的优点显然是查找记录非常快,基本等于生成索引表后,查找时都不用去读取记录,即可以得到结果。但是它的缺点是这个记录号不定长,可多可少。

     
     
    这世界上有一种鸟是没有脚的,它只能够一直的飞呀飞呀,飞累了就在风里面睡觉,这种鸟一辈子只能下地一次,那一次就是它死亡的时候。
  • 相关阅读:
    关于各种编程语言调用C星寻路插件的例子
    练习作品11:语音识别 准确度70%
    练习作品10:被一个傻叉坑了 要求把串口 封装到DLL中调用;
    Dynamics CRM 构建IN查询
    初识Spark2.0之Spark SQL
    从Dynamics CRM2011到Dynamics CRM2016的升级之路
    Dynamics CRM2011 导入解决方案报根组件插入错误的解决方法
    基于hadoop的BI架构
    Dynamics CRM 不同的站点地图下设置默认不同的仪表板
    Dynamics CRM 打开数据加密报错及修改用户邮件保存报错的解决方法
  • 原文地址:https://www.cnblogs.com/xuyinghui/p/4591095.html
Copyright © 2011-2022 走看看