《数据结构》第7章小结

《数据结构》--第七章小结--查找

一、基本概念

查找表:是由同一类型的数据元素(或记录)构成的集合。查找表分为静态查找表和动态查找表。
关键字(Key):是数据元素中某个数据项的值,又称为键值,用它可以标识一个数据元素。若关键字可以唯一地标识一个记录,则称此关键字为主关键字(Primary Key);若关键字对应多个记录,则称此关键字为次关键字(Secondary Key)。
关键码:主关键字对应的数据项称为主关键码;次关键字对应的数据项称为次关键码。
查找:就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
静态查找表:只做查找操作的查找表。主要操作有:
    1)查询某个“特定的”数据元素是否在查找表中;
    2)查询某个“特定的”数据元素的各种属性(值)。
动态查找表(Dynamic Search Table):在查找的过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。主要操作有:
    1)查找时插入数据元素;
    2)查找时删除数据元素。
查找结构:面向查找操作的数据结构。
    1)对于静态查找表,一般考虑使用线性表结构来组织数据;对应查找方法有:顺序表使用顺序查找(线性查找);有序表使用折半查找、插值查找、斐波那契查找。
    2)对于动态查找表,一般考虑二叉树结构来组织数据;对应查找方法有:二叉排序树、平衡二叉树、B树。
    3)散列表查找(哈希表查找)比较常用。

二、查找算法

1. 顺序查找

  顺序查找适合于存储结构为顺序存储或链接存储的线性表。

  顺序查找属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功
   
  查找成功时的平均查找长度为: ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
  
  顺序查找的时间复杂度为O(n)

2. 二分查找

  元素必须是有序的,如果是无序的则要先进行排序操作。

  二分查找即折半查找,属于有序查找算法。用给定值value与中间结点mid的关键字比较,若相等则查找成功;若不相等,再根据value与该中间结点关键字的比较结果确定下一步查找的子表

  将数组的查找过程绘制成一棵二叉树排序树,如果查找的关键字不是中间记录的话,折半查找等于是把静态有序查找表分成了两棵子树,即查找结果只需要找其中的一半数据记录即可,等于工作量少了一半,然后继续折半查找,效率高。

  根据二叉树的性质,具有n个结点的完全二叉树的深度为[log2n]+1。尽管折半查找判定二叉树并不是完全二叉树,但同样相同的推导可以得出,最坏情况是查找到关键字或查找失败的次数为[log2n]+1,最好的情况是1次。

  时间复杂度为O(log2n)

   折半计算mid的公式

    mid = (low+high)/2;
        if(a[mid]==value)
            return mid;
        if(a[mid]>value)
            high = mid-1;
        if(a[mid]<value)
            low = mid+1;

 

 

 

  折半查找判定数中的结点都是查找成功的情况,将每个结点的空指针指向一个实际上不存在的结点——外结点,所有外界点都是查找不成功的情况,如图所示。如果有序表的长度为n,则外结点一定有n+1个

 

折半查找判定数中,某结点所在的层数就是即将要比较的次数,整个判定树代表的有序表的平均查找长度即为查找每个结点的比较次数之和除以有序表的 长度。

 

  例如:长度为10的有序表的平均查找长度为

 

        ASL=(1*1+2*2+3*4+4*3)/10=29/10;

 

 

 

折半查找判定数中,查找不成功的次数即为查找相应外结点与内结点的比较次数。整个判定树代表的有序表的平均查找长度。查找失败时的有序表的平均查找长度即为查找每个外结点的比较次数之和除以外结点的个数。

 

   例如:查找失败时长度为10的有序表的平均查找长度为

 

 ASL=(3*5+4*6)/11=39/11;

 

 

 

3. 插值查找

  改进二分查找算法,将查找点的选择改进为自适应选择,差值查找也属于有序查找。 

  对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。

  折半计算mid的公式,等式变换后得到: mid = (low+high)/2 = low + (high-low)/2

   mid等于最低下标low加上最高下标high与low的差的一半。将这个 1/2 进行改进,通过类比,改进为下面的计算方案: 

  mid = low + ((key - a[low])/(a[high] - a[low]))(high - low)这样就可以大大提高查找的效率。

  时间复杂度均为O(log2n)。
 

4. 斐波那契查找

  改进二分查找算法,通过运用黄金比例选择查找点进行查找,同样地,斐波那契查找也属于一种有序查找算法。

  斐波那契查找的时间复杂也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。可惜如果是最坏情况,比如这里key=l,那么始终都处于左侧长半区在查找,则查找效率要低于折半查找。

  复杂度也为O(log2n)

 

5. 分块查找

  分块查找的平均查找长度ASL是折半查找的平均查找长度加顺序查找块内平均查找长度

6. 树表查找

  如果查找的数据集是有序线性表,并且是顺序存储的,查找可以用折半、插值、斐波那契等查找算法来实现,可惜,因为有序,在插入和删除操作上,就需要耗费大量的时间
  若查找表无序,则插入删除可无需移动大量记录,但于查找不利。因此这里一种即可以使得插入和删除效率不错,又可以比较高效率地实现查找的算法。

  我们把这种需要在查找时插入或删除的查找表称为动态查找表。动态查找不像是“查找”,更像是一个对表进行“创建、扩充、修改、删除”的过程。
 

二叉排序树(Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树。
    若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
    若它的右子树不空 ,则右子树上所有结点的值均大于它的根结点的值;
    它的左、右子树也分别为二叉排序树。

 

平衡二叉树(AVL树)

      平衡二叉树,是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。

      从平衡二叉的英文名字(AVL树),你也可以体会到,它是一种高度平衡的二叉排序树。

      高度平衡意思是说,要么它是一棵空树,要么它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1。

B-树

  B树可以看作是对查找树的一种扩展,即他允许每个节点有M-1个子节点。

  • 根节点至少有两个子节点

  • 每个节点有M-1个key,并且以升序排列

  • 位于M-1和M key的子节点的值位于M-1 和M key对应的Value之间

  • 其它节点至少有M/2个子节点

  下图是一个M=4 阶的B树:

B+树

  B+树是对B树的一种变形树,它与B树的差异在于:

  • 有k个子结点的结点必然有k个关键码;
  • 非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。
  • 树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。

  如下图,是一个B+树:

B Plus tree

三、实践

7-1 QQ帐户的申请与登陆 (30 分)
实现QQ新帐户申请和老帐户登陆的简化版功能。最大挑战是:据说现在的QQ号码已经有10位数了。

输入格式:
输入首先给出一个正整数N(≤105
​​ ),随后给出N行指令。每行指令的格式为:“命令符(空格)QQ号码(空格)密码”。其中命令符为“N”(代表New)时表示要新申请一个QQ号,后面是新帐户的号码和密码;命令符为“L”(代表Login)时表示是老帐户登陆,后面是登陆信息。QQ号码为一个不超过10位、但大于1000(据说QQ老总的号码是1001)的整数。密码为不小于6位、不超过16位、且不包含空格的字符串。

输出格式:
针对每条指令,给出相应的信息:

1)若新申请帐户成功,则输出“New: OK”;
2)若新申请的号码已经存在,则输出“ERROR: Exist”;
3)若老帐户登陆成功,则输出“Login: OK”;
4)若老帐户QQ号码不存在,则输出“ERROR: Not Exist”;
5)若老帐户密码错误,则输出“ERROR: Wrong PW”。

输入样例:
5
L 1234567890 myQQ@qq.com
N 1234567890 myQQ@qq.com
N 1234567890 myQQ@qq.com
L 1234567890 myQQ@qq
L 1234567890 myQQ@qq.com
输出样例:
ERROR: Not Exist
New: OK
ERROR: Exist
ERROR: Wrong PW
Login: OK

AC代码:

#include <iostream>
#include <cstring>
#include <map> 
using namespace std;
map<string,string> Q;//用map的方法 
int main() 
{
    
    int m;
    cin>>m;
    string a3,a2;
    char a1;
    while(m--)
    {
        cin>>a1>>a2>>a3;
        if(a1=='N')
        {
            if(Q.find(a2)==Q.end())//查找到迭代器的最后,若无则为新 
            {
                Q[a2]=a3;
                cout<<"New: OK"<<endl;
            }
            else
            {
                cout<<"ERROR: Exist"<<endl;
            }
        }
        if(a1=='L')
        {
            if(Q.find(a2)==Q.end())
            {
                cout<<"ERROR: Not Exist"<<endl;
            }
            else
            {
                map<string,string>::iterator it;//find()函数返回一个迭代器指向键值为key的元素,如果没找到就返回指向map尾部的迭代器
                it=Q.find(a2);
                if(it->second==a3)
                {
                    cout<<"Login: OK"<<endl;
                }
                else
                {
                    cout<<"ERROR: Wrong PW"<<endl;
                }
            }
         
        }
    }
    
    
    
    
    return 0;
}
View Code

其实,在写这道题时我是想开辟数组的,然后我意识到,如果开辟数组我就会内存爆炸,肯定行不通的。所以我就开始寻找其他方法,例如本章的方法。然后我就想到了哈希表,发现这个结构好像刚刚好适合这个题的。然后我就去查书和资料,然后查得到一个map函数的有意思的方法。以上代码用map函数,就很舒服。附上参考链接:https://blog.csdn.net/YC1101/article/details/79311029

三、总结

上一次的目标基本达成,还顺带学习了一下map的用法,

下一阶段的目标:备考准备,好好复习。

期末噶油!

 

原文地址:https://www.cnblogs.com/jyf2018/p/10963252.html