数据结构算法学习-1. 查找(Search)概论与三种顺序查找算法

写在前面

这个新的类别主要是将平时学的数据结构的知识概念与数据结构引发的各种算法进行一个整理总结。这一个类别参考的资料是《大话数据结构》。由于PDF是扫描版,所以主要内容都是手打,比较麻烦,会耗费一些时间。所以主要是要提纲挈领,将要点整理出来,以便在做题的时候,能将这些知识融会贯通一下。毕竟不能当个只会复制粘贴的码农,还是要花费时间与精力来提升一下自己的知识水平。
数据结构算法的学习我不会按照资料的顺序,照本宣科的讲,只会按照做题的顺序来一步步学习。由于做题刚刚做完了Hash Table的简单题目。所以就先来学习一下“查找”这一部分的知识。

查找概论

查找表(Search Table): 是由同一类型的数据元素(或记录)构成的集合。说白了,就是一张表,要查找的数据都在这里面。

关键字(Key): 是数据元素中某个数据项的值,又称为键值,可以用它来表示一个数据元素。也可以表示一个记录的某个数据项(字段),称之为关键码。如果关键字可以唯一标识一个记录,则称之为主关键字(Primary Key)。可以表示多个数据元素(或记录)的关键字,称之为次关键字(Secondary Key)。

查找(Searching): 根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。

查找表根据操作方式可以分为两大类:静态查找表与动态查找表。

静态查找表(Static Search Table): 只作查找操作的查找表。其主要操作有:

  • 查询某个“特定的”数据元素是否在查找表中。
  • 检索某个“特定的”数据元素和各种属性。

动态查找表(Dynamic Search Table): 在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。其主要操作有:

  • 查找时插入数据元素。
  • 查找时删除数据元素。

查找所基于的数据结构是集合,集合中的记录之间没有本质关系。所以要想获得较高的查找性能,我们需要将查找集合组织成表、树等结构。对于静态查找表来说,可以使用线性表结构来组织数据;对于动态查找表来说,可以考虑二叉排序树的查找技术。最后还可以使用散列表结构来解决一些查找问题,这就是哈希表。

顺序表查找

顺序查找(Sequential Search): 就是线性查找,也是最基本的查找技术,其查找过程是:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字与给定值相等,则查找成功。如果直到最后一个(或第一个)记录,关键字与给定值比较都不等,则表中没有所查的记录,查找不成功。

顺序表查找算法 :顺序查找的算法实现如下:

int Sequentital_Search(int *a,int n, int key){
    for(int i=0;iif(a[i]==key)
            return i;
    }
    return 0;
}

这段代码就很基础,也是很常用的普遍遍历算法。但是,这个算法也还有改进的地方,就是因为每一次要做两次比较:一次是i与n比较,检查i是否越界;一次是a[i]与key比较,检查是否查找成功。那么能不能简化这样的比较呢?可以设置一个哨兵,减少i与n的比较。优化算法如下:

int Sequentital_Search(int *a,int n, int key){
    int i=n;
    a[0]=sentinel;
    while(a[i]!=sentinel){
        i--;
    }
    return i;       //这里返回0说明查找失败
}

这段优化的代码不仅减少了i与n的比较,还通过反转遍历顺序,将优化前的算法需要返回两种不同的情况,简化成一种。代码更加简介美观了。
顺序查找算法的平均查找次数是:(n+1)/2;时间复杂度为:O(n)。

有序表查找

折半查找(Binary Search),二分查找: 前提设定线性表中记录必须是关键码有序(常为从小到大),线性表采用顺序存储。二分查找基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若小于,则在中间记录的左半区继续查找;若大于,则在中间记录的右半区继续查找。不管重复上述过程直到查找成功,或所有查找区域无记录,即查找失败。
算法实现代码如下:

int Binary_Search(int *a, int n, int key){
    int low = 1,high = n,mid;
    while(low <= high){
        mid=(low + high) / 2;
        if(key < a[mid]) 
            high=mid - 1;
        else if(key > a[mid]) 
            low=mid + 1;
        else 
            return mid;
    }
    return 0;
}

二分查找等于是把静态有序查找表分成了两颗子树,即查找结果只需要找其中一半数据记录即可,相等于工作量少了一半,而后不断重复,从而提升了查找效率,降低了算法复杂度。二分查找的最坏情况查询次数是:log2n+1。时间复杂度是:O(logn)。

插值查找

虽然二分查找与顺序查找相比提高了提高了效率,但是,二分查找太具有一般性,比如0~100000,查找5,如果还是二分查找,那么首先还是会查找50000。而如果能直接从下标较小的情况开始查找,比如找100,那么效率可能会进一步提高。将二分查找的mid的公式变换有:
这里写图片描述

插值查找就是对公式进行改进。
这里写图片描述

插值查找(Interpolation Search)是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式:
这里写图片描述

从时间复杂度来看也是:O(logn)。对于关键字分布比较均匀的查找表来说,插值查找算法的平均性能比二分查找要好多。但如果数组中关键字分布不均匀,或者说数组存在很多极端大或小的树,就不合适了。

斐波那契查找

斐波那契查找(Fibonacci Search): 是利用了黄金分割原理来实现的。

int Fibonacci_Search(int ^a, int n, int key){
    int low=1 ,high=n ,mid,i, k=0;     //定义最低下标与最高下标为记录首位与记录末位
    while(n>F[k]-1)                    //计算n位于斐波那契数列的位置
        k++;
    for(i=m;i1;i++)              //将不满的数字补全
        a[i]=a[n];

    while(low<=high){
        mid=low+F[k-1]-1;              //计算当前分隔的下标
        if(key//若查找记录小于当前分隔记录
            high=mid-1;                //最高下标调整到分隔下标mid-1处
            k=k-1;                     //斐波那契数列下标减一位,此时1位
        }
        else if(key>a[mid]){           //若查找记录大于当前分隔记录
            low=mid+1;                 //最低下标调整到分隔下标mid+1处
            k=k-2;                     //斐波那契数列下标减两位,此时2位
        }
        else{
            if(mid<=n)
                return mid;            //若相等说明mid即为查找结果
            else
                return n;              //若mid>n说明是补全数值,返回n
        }
    }
}

斐波那契查找算法的核心在于:

  1. 当key = a[mid],查找成功;
  2. 当key < a[mid],新范围是第low个到第mid-1个,此时范围个数为F[k-1]-1个;
  3. 当key > a[mid],新范围是第mid+1个到第high个,此时范围个数为F[k-2]-1个。
  4. k代表low与high之间元素个数,个数对应到斐波那契数列中。然后mid就是利用斐波那契数列前一项与后一项相比为黄金分割比来确定的。

关于斐波那契数列,网上也有比较好的解释:算法–查找–斐波那契查找。

其实,二分查找、插值查找与斐波那契查找都是想通过确定合适的分隔点来适应不同的查找情况。二分查找是将(a[low]+a[high])/2作为分隔点。插值查找其实就是根据(key-a[low])/(a[high]-a[low])的比值,“按比例”来确定分隔点。而斐波那契查找就是根据黄金分隔比来确定分隔点的位置。

你可能感兴趣的:(Data,Structure)