数据结构(三十七)静态查找表

  静态查找表有5种查找方法:顺序查找、二分查找、插值查找、斐波那契查找、线性索引查找

  一、顺序查找

  1.顺序查找的定义

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

  2.顺序表查找算法的实现

  (1)顺序查找算法的实现

    // 顺序查找,a为数组,key为要查找的关键字,查找成功则返回i,否则返回-1
    private static int seqSearch(int[] a, int key) {
        for (int i = 0; i < a.length; i++) {
            if (a[i] == key) {
                return i;
            }
        }
        return -1;
    }

  (2)带监视哨的顺序查找算法

    // 有哨兵顺序查找数组a中从a[1]到数组末尾的key,无哨兵每次循环都需要对i是否越界,即是否小于a.length做判断。
    // 设置一个哨兵可以解决不需要每次让i与a.length作比较。
    // 返回-1说明查找失败,注意: 只能从数组下标为1的位置开始查找
    private static int seqSearchWithGuard(int[] a, int key) {
        int i = a.length - 1;    // 设置循环从数组尾部开始
        a[0] = key;                // 设置a[0]为关键字值,称之为"哨兵"
        while (a[i] != key) {
            i--;
        }
        if (i>0) {
            return i;
        } else {
            return -1;
        }
    }

  3.顺序查找算法的时间复杂度

  对于顺序查找算法来说,查找成功最好的情况就是在第一个位置就找到了,算法时间复杂度为O(1),最坏的情况是在最后一位才找到,需要n次比较,时间复杂度为O(n),需要n+1次比较,时间复杂度为O(n),由于关键字在任意一位置的概率是相等的,所以平均查找次数为(n+1)/2,所以最终时间复杂度还是O(n)。当n很大时,查找效率极为低下,对一些小型数据的查找时,这种查找方式是可以适用的。

  二、二分查找

  1.二分查找的定义

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

  2.二分查找的实现

    // 二分查找:数组a中所有数据中包含key的数组下标,如果没有,返回-1
    private static int binarySearch(int[] a, int key) {
        int low = 0;                          // 定义最低下标为记录首位
        int high = a.length-1;                // 定义最高下标为记录末位
        while (low<=high) {
            int mid = (low + high)/2;        // 折半得到中间记录的下标
            if (key < a[mid]) {                // 若查找值比中值小
                high = mid - 1;                // 最高下标调整到中间下标小一位
            } else if (key > a[mid]) {        // 若查找值比中指大
                low = mid + 1;                // 最低下标调整到中间下标大一位
            } else {
                return mid;                    // 若相等则说明中间记录的下标即为查找到的值
            }
        }
        return -1;
    }

  3.二分查找算法的时间复杂度

  二分查找等于是把静态有序查找分成了两棵子树,即查找结果只需要找其中的一半数据记录即可,等于工作量少了一半,然后继续折半查找,效率当然非常高了。

  根据二叉树的性质4,即“具有n个结点的完全二叉树的深度为【Log2n】+1”,可以得到二分查找最坏情况下查找到关键字或查找失败的次数是【Log2n】+1,最好的情况当然是1次了,因此二分查找的时间复杂度为O(Logn),显然远远好于顺序查找的O(n)时间复杂度了。

  三、插值查找

  1.插值查找的定义

  在基于二分查找算法的基础上,考虑到这么一种情况:要在取值范围0~10000之间100个元素从小到大均匀分布的数组中查找5,自然会考虑从数组下标比较小的位置开始查询。

  插值查找(Interpolation Search)是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式:(high-low)/(a[high]-a[low])

  2.插值查找算法的实现

    // 插值查找:数组a中所有数据中包含key的数组下标,如果没有,返回-1
    private static int interpolationSearch(int[] a, int key) {
        int low = 0;                          // 定义最低下标为记录首位
        int high = a.length-1;                // 定义最高下标为记录末位
        while (low<=high) {
            int mid = low + (high-low)*(key-a[low])/(a[high]-a[low]);// 插值计算公式
            if (key < a[mid]) {                // 若查找值比中值小
                high = mid - 1;                // 最高下标调整到中间下标小一位
            } else if (key > a[mid]) {        // 若查找值比中指大
                low = mid + 1;                // 最低下标调整到中间下标大一位
            } else {
                return mid;                    // 若相等则说明中间记录的下标即为查找到的值
            }
        }
        return -1;
    }

  3.插值查找的时间复杂度

  从时间复杂度来看,插值查找和二分查找的时间复杂度一样都是O(LOGn),但对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法平均性能比二分查找要好得多。反之,如果数组中分布极端不均匀的数据,用插值查找也未必是很合适的选择。

  四、斐波那契查找

  1.斐波那契查找算法的定义

  除了插值查找外,还有另外一种有序查找,斐波那契(Fibonacci Search)利用了黄金分割原理也可以解决二分查找算法的问题。

  

  斐波那契算法的核心在于:在给定了斐波那契数列的情况下:

  • 若key=a[mid]时,则查找成功
  • 若key<a[mid]时,则新范围是第low个到第mid-1个,此时范围个数为F[k-1]-1个
  • 若key>a[mid]时,则新范围是第mid+1个到第high个,此时范围个数为F[k-2]-1个

  2.斐波那契查找算法的实现

    private static int FibonacciSearch(int[] a, int key) {
        // 创建一个斐波那契数列的数组
        int[] F = new int[100];        // 斐波那契数列
        F[0]=0;
        F[1]=1;
        for (int i = 2; i < F.length; i++) {
            F[i] = F[i-1] + F[i-2];
        }
        // 斐波那契算法准备工作
        int low = 0;                                      // 定义最低下标为记录首位
        int high = a.length-1;                            // 定义最高下标为记录末位
        int k = 0;
        int[] a_temp = new int[F.length];
        System.arraycopy(a, 0, a_temp, 0, a.length);    // 将数组a复制到a_temp数组中,因为要保证a_temp的长度和F数组长度一致
        while (a.length - 1 > F[k] - 1) {                // 计算要查找的最大下标位于斐波那契数列中的位置
            k++;
        }
        for (int i = a.length -1; i < F[k] - 1; i++) {    // 将不满的数值补全
            a_temp[i] = a_temp[a.length - 1];
        }
        // 斐波那契算法开始,可以结合二分查找算法对比着理解
        while (low<=high) {
            int mid = low + F[k-1] - 1;        // 计算当前分隔的下标
            if (key < a_temp[mid]) {        // 若查找值比中值小
                high = mid - 1;                // 最高下标调整到中间下标小一位
                k = k - 1;                    // 斐波那契数下标减一位
            } else if (key > a_temp[mid]) {    // 若查找值比中指大
                low = mid + 1;                // 最低下标调整到中间下标大一位
                k = k - 2;                    // 斐波那契数下标减二位
            } else {
                if (mid <= a.length - 1) {    // 若相等或小于数组下标最大值则说明mid即为查找到的位置
                    return mid;
                } else {                    // 若min>数组下标最大值则说明是补全值,返回数组中最后一位
                    return a.length - 1;
                }
            }
        }
        return -1;
    }

  3.斐波那契算法的时间复杂度

   如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去,对处于当中的大部分数据,其工作效率要高一些。所以尽管斐波那契查找算法的时间复杂度也为O(LOGn),但就平均性能来说,斐波那契查找要由于二分查找。但是,如果是最坏情况,比如key=1,那么始终都处于左侧长半区查找,则查找效率要低于二分查找。

  

  五、线性索引查找

  1.索引的定义

  顺序查找和二分查找都是基于有序的基础上,但事实上,很多数据集可能增长非常快,例如一些服务器的日志信息记录可能是海量数据,要保证记录全部是按照当中的某个关键字有序,其时间代价是非常高昂的,所以这种数据通常都是按先后顺序存储。对于这种表,使用索引可以快速查找到需要的数据。

  索引就是把一个关键字与它对应的记录相关联的过程,一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息。索引技术使大型数据库以及磁盘文件的一种重要技术。

  索引按照结构可分为线性索引、树形索引和多级索引。

  2.线性索引的定义

  线性索引就是将索引项集合组织为线性结构,也称为索引表。线性索引包括稠密索引、分块索引和倒排索引。

  

猜你喜欢

转载自www.cnblogs.com/BigJunOba/p/9265631.html