数据结构和算法学习笔记十二:查找

一.概述

  在工程中对数据库的操作主要有增删查改几类,其中增删改的操作都依赖查找,毕竟得先找到数据才能进行其他操作.

  在大话数据结构一书中对查找(Searching)的定义是:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录).相对来说概念很好理解.

  查找表分为静态查找表和动态查找表两类:

    1.静态查找表(Static Search Table):只作查找工作的查找表,主要有根据给定键查找数据元素是否存在和查找数据元素的各种属性两种.

    2.动态查找表(Dynamic Search Table):查找的同时进行其他操作,可以进行增删改等操作.

  针对主要进行静态查找的查找表,使用线性表结构组织数据更方便,但是针对主要进行动态查找的查找表,则需要使用二叉树等更为复杂的结构组织数据.

  注:本文中实现使用的代码为C#代码.查找实现均为从一堆使用特定数据结构组织的整型数字中查找特定的数字.

二.顺序表查找

  1.简介:顺序表查找(Sequential Search)又叫线性查找,数据使用线性结构组织,查找时从表的第一个数据依次遍历.

  2.代码实现:

        /// <summary>
        /// 线性表查找
        /// </summary>
        /// <param name="table">线性表</param>
        /// <param name="key"></param>
        /// <param name="index">查找到数据所在的下标,没有查找到时返回-1</param>
        /// <returns>是否查找到数据</returns>
        public static bool SequentialSearch(List<int> table, int key, out int index)
        {
            index = -1;
            int length = table.Count;
            for (int i = 0; i < length; i++)
            {
                if(table[i] == key)
                {
                    index = i;
                    return true;
                }
            }
            return false;
        }

  3.线性表查找优化:在for循环中,每次都要检查是否i越界了,实际上可以将index当作哨兵,这样不需要每次比较i和length了.当然,数组中下标为0的位置不能用于存储.

  4.线性表查找优化代码实现:

        /// <summary>
        /// 线性表查找优化
        /// </summary>
        /// <param name="table">线性表</param>
        /// <param name="key"></param>
        /// <param name="index">查找到数据所在的下标,没有查找到时返回-1</param>
        /// <returns>是否查找到数据</returns>
        public static bool SequentialSearch2(List<int> table, int key, out int index)
        {
            int count = table.Count;
            table.Add(key);
            index = 0;
            while(table[index] != key)
            {
                index++;
            }
            if(index == count)
            {
                index = -1;
                return false;
            }
            return true;
        }

  5.线性查找的时间复杂度:O(n)

三.有序表查找:对于表中数据的存储,如果我们使用一定的方式优化就可以使数据变得有序,这样可以有效缩小查找范围.

  1.折半查找:折半查找又称二分查找.我们在存放数据时,将数据的键值按照从小到大排列,这样每次查找后将剩余数据平均分为两部分,判断当前数据在哪部分,接着继续将这部分数据平均分为两部分,判断要查找的数据在哪部分...折半查找的时间复杂度是O(logn).

  2.折半查找代码实现:

        /// <summary>
        /// 折半查找
        /// </summary>
        /// <param name="table">线性表</param>
        /// <param name="key"></param>
        /// <param name="index">查找到数据所在的下标,没有查找到时返回-1</param>
        /// <returns>是否查找到数据</returns>
        public static bool BinarySearch(List<int> table, int key, out int index)
        {
            int low = 0;
            int high = table.Count - 1;
            int mid;
            while(low <= high)
            {
                mid = (low + high) / 2;
                if(key < table[mid])
                {
                    high = mid - 1;
                }
                else if(key > table[mid])
                {
                    low = mid + 1;
                }
                else
                {
                    index = mid;
                    return true;
                }
            }
            index = -1;
            return false;
        }

  3.插值查找:我们针对折半查找进行改进,原来每次没有查找到数据时mid会置为low和high的中间位置,如果我们充分考虑到要查找的key值和当前的low\high指针指向的值的差距,那么我们可以采取这样的方案:当key值和low指针指向的值更接近时,让mid指针离low指针更近一些,key值和low指针指向的值越接近,就让mid和low离得越近,同理,当key值和high指针指向的值更接近时我们让mid指针离high指针更近一些.理论上这样要比每次都将mid指针置为low和high的中间位置运算次数更少,实际上大多数情况下也是如此,虽然它的时间复杂度仍然为O(logn).至于使用这种方式时得到mid指针的位置的公式,见下方的实现代码:

  4.插值查找代码实现:

        /// <summary>
        /// 插值查找
        /// </summary>
        /// <param name="table">线性表</param>
        /// <param name="key"></param>
        /// <param name="index">查找到数据所在的下标,没有查找到时返回-1</param>
        /// <returns>是否查找到数据</returns>
        public static bool InterpolationSearch(List<int> table, int key, out int index)
        {
            int low = 0;
            int high = table.Count - 1;
            int mid;
            while (low <= high)
            {
                //公式一
                mid = low + (key - table[low]) * (high - low) / (table[high] - table[low]);
                //公式二
                //mid = high - (table[high] - key) * (high - low) / (table[high] - table[low]);
                if (key < table[mid])
                {
                    high = mid - 1;
                }
                else if (key > table[mid])
                {
                    low = mid + 1;
                }
                else
                {
                    index = mid;
                    return true;
                }
            }
            index = -1;
            return false;
        }

  5.斐波那契查找:对于折半查找优化,除了使用插值查找的方案外,还可以使用斐波那契数列优化.斐波那契查找的mid指针位置取法使用了斐波那契数列,斐波那契数列的特点是除了前两个数字外,任意数字都等于前两个数字的和,因此首先找到当前数据的长度在哪两个斐波那契数字之间,然后取其中较大的那一个作为当前数据长度,每次比较完后将数据分为数量分别等于当前斐波那契数字前两个数字的两部分,再确定key在哪部分数字中...

  6.斐波那契查找代码实现:

        /// <summary>
        /// 斐波那契查找
        /// </summary>
        /// <param name="table">线性表</param>
        /// <param name="key"></param>
        /// <param name="index">查找到数据所在的下标,没有查找到时返回-1</param>
        /// <returns>是否查找到数据</returns>
        public static bool FibonacciSearch(List<int> table, int key, out int index)
        {
            int count = table.Count;    //取出当前table的长度
            int low = 0;
            int high = count - 1;
            int mid;
            //构造斐波那契数列,数列长度足够覆盖所有数据即可,数列前两个数初始化为0和1
            List<int> fibonacci = new List<int>();
            fibonacci.Add(0);
            fibonacci.Add(1);
            int fibonacciIndex = 1;     //当前斐波那契数列下标指向1
            while(count > fibonacci[fibonacciIndex])
            {
                fibonacci.Add(fibonacci[fibonacciIndex] + fibonacci[fibonacciIndex - 1]);
                fibonacciIndex++;
            }
            for(int i = count;i < fibonacci[fibonacciIndex]; i++)
            {
                table.Add(table[count - 1]);
            }
            while (low <= high)
            {
                mid = low + fibonacci[fibonacciIndex - 1];
                if (key < table[mid])
                {
                    high = mid - 1;
                    fibonacciIndex--;
                }
                else if (key > table[mid])
                {
                    low = mid + 1;
                    fibonacciIndex = fibonacciIndex - 2;
                }
                else
                {
                    index = mid < count ? mid : count - 1;
                    return true;
                }
            }
            index = -1;
            return false;
        }

四.线性索引查找:

  对于一些增长非常迅速的数据,如果想要直接使用有序查找不太现实,因为数据量大时将数据按照某个关键字有序排列的时间代价很高,所以我们需要采取其他的方式查找数据:

  1.稠密索引:使用一种数据结构将所有数据的key集中存放,这种数据结构包含key值和地址指针两部分,地址指针指向实际的数据位置,这样在使key有序的过程中就不用将整条数据移动位置,而只需要让指针和key一起移动即可.这个集中存放数据key值和数据地址指针的表称为索引表.这种方式有效地减少了排列数据的时间成本.与稠密索引相对的还有稀疏索引,稀疏索引将经常被查找的数据存放在索引表中,当在索引表中找不到key时再顺序查找一次整个表.稀疏索引降低了索引表的数据量,但是在索引表中找不到数据时却只能顺序遍历所有数据.

  2.分块索引:我们很容易想到对于一个非常庞大的索引表,可以采用类似图书馆存放图书的方式存放所有索引,也就是将索引分类存放,这种索引存放方式称为分块索引.分块的数据块内可以是无序的也可以是有序的.

  3.倒排索引:通常情况下,数据的key都是id,每条数据的id都不相同,我们可以将id称为数据的主关键码.但是在实际的查找应用中我们往往更多的需求是查找某个字段含有特定值的所有数据记录,如name是"张三"或性别是男.这种情况下,这些查找的关键字("张三"或男)我们可以称为次关键字,并且我们可以建立一个索引表,这个索引表以次关键字为键,值是指向所有含有这个次关键字的数据指针或者是所有含有这个次关键字的数据的主关键字,这样的索引方法就是倒排索引.

 

posted @ 2021-07-21 22:26  movin2333  阅读(89)  评论(0编辑  收藏  举报