数据结构·查找

文章介绍了查找的基本概念,包括静态和动态查找表,关键字和主次关键字的概念。详细讨论了顺序查找、折半查找以及二叉排序树的查找方法,强调了它们的性能分析,如平均查找长度和适用条件。最后,提到了哈希表作为快速查找的手段,及其处理冲突的策略,如开放定址法和链地址法。
摘要由CSDN通过智能技术生成

查找的基本概念

查找表:由同一类型的数据元素(或记录)构成的集合
静态查找表:查找的同时对查找表不做修改操作(如插入和删除)
动态查找表:查找的同时对查找表具有修改操作

关键字:数据元素(或记录)中某个数据项的值,可用来标识一个数据元素(或记录)
主关键字:唯一标识数据元素
次关键字:可以标识若干个数据元素

查找:也叫检索,是根据给定的某个值,在表中确定一个关键字等于给定值的记录或数据元素

关键字的平均比较次数,也称平均查找长度ASL。

ASL=\sum_{i=1}^{n}p_{i}c_{i}

n:记录的个数
pi:查找第i个记录的概率(通常认为pi=1/n)
ci:找到第i个记录所需的比较次数

线性表的查找

顺序查找(线性查找)

应用范围:顺序表或线性链表表示的静态查找表,表内元素之间无序

顺序表的表示:

typdef struct
{
    KeyType key;//关键字域
    InfoType otherinfo;//其他域
}ElemType;
typedef struct
{
    ElemType *R;//表基址
    int length;//表长
}SSTable;

顺序查找的过程:从表的一端开始逐个进行记录的关键字和给定值的比较

/*不用监视哨法,在顺序表中查找关键字等于key的元素*/
int SeqSearch ( sSTable ST,KeyType key)
{
    i=ST.length;
    while (i>=1&&ST.R[i].key!=key) i--;
    return i;
}

把待查关键字key存入表头或表尾(俗称“哨兵”)这样可以加快执行速度。若将待查找的特定值key存入顺序表的首部(如0号单元),则顺序查找的实现方案为从后向前逐个比较。
 

int Search_Seq(SSTable ST,KeyType key)
{
    ST.R[0].key=key;
    for(i=ST.length;ST.R[i].key!=key;--i);
    return i;
}

顺序查找的性能分析:

空间复杂度:一个辅助空间。

时间复杂度:查找成功时的平均查找长度设表中各记录查找概率相等ASL=(1+2+...+n)/n =(n+1)/2
,时间复杂度为O(n)。

顺序查找算法特点:

优点:算法简单,对表结构无任何要求(顺序和链式均可),无论记录是否按关键字有序均可应用

缺点:平均查找长度较大,n很大时查找效率较低

折半查找(二分查找、对分查找)

查找过程:每次将待查记录所在区间缩小一半直到查找成功或不成功为止

适用条件:采用顺序存储结构的有序表

算法实现:
先给数据排序(例如按升序排好),形成有序表,折半查找时,先求位于查找区间正中的对象的下标mid,用其关键码与给定值x比较:
ST.R[mid].key==x,查找成功;
ST.R[mid].key>x,把查找区间缩小到表的前半部分,继续;
ST.R[mid].key<x,把查找区间缩小到表的后半部分,继续。
如果查找区间已缩小到一个对象,仍未找到要查找对象,则查找失败

int Search_Bin(SSTable ST,KeyType key)//若找到,则函数值为该元素在表中的位置,否则为0
{
    low=1;
    high=ST.length;
    while(low<=high)
    {
        mid=(low+high)/2;
        if(key==ST.R[mid].key) return mid;
        else if(key<ST.R[mid].key) high=mid-1;//前一子表查找
        else low=mid+1;//后一子表查找
    }
    return 0;//表中不存在待查元素
}
int Search_Bin (SSTable ST,keyType key,int low,int high)//递归算法
{
    if(low>high) return 0; //查找不到时返回0
    mid=(low+high)/2;
    if(key=-ST.elem[mid].key) return mid;//查找成功
    else if(key<ST.elem[mid].key) return Search_Bin (ST,key,low,mid-1);//递归
    else return Search_ Bin(ST,key,mid+1,high);//递归
}

折半查找的性能分析——判定树

若所有结点的空指针域设置为一个指向一个方形结点的指针,称方形结点为判定树的外部结点。对应的,圆形结点为内部结点。

查找成功时比较次数:为该结点在判定树上的层次数,不超过树的深度d=⌊log_{2}n⌋+1。

查找不成功的过程就是走了一条从根结点到外部结点的路径,其中外部结点不进行比较。比较次数仍然不超过⌊log_{2}n⌋+1。

折半查找的性能分析:

查找过程:每次将待查记录所在区间缩小一半,比顺序查找效率高,时间复杂度O(log_{2}n)。

适用条件:采用顺序存储结构的有序表,不宜用于链式结构。

树的查找

动态查找表:

特点:表结构在查找过程中动态生成。

操作:检索、插入和删除。

二叉排序树(二叉查找树、检索树)

若其左子树非空,则左子树上所有结点的值均小于根结点的值。若其右子树非空,则右子树上所有结点的值均大于根结点的值。其左右子树本身又各是一棵二叉排序树。

中序遍历二叉排序树得到一个关键字的递增有序序列。

二叉排序树的操作:

[查找]

BSTree SearchBST(BSTree T,KeyType key)
{
    if(!T || key=T->data.key) return T;
    else if(key < T->data.key) return SearchBST(T->lchild,key);//在左子树中继续查找
    else return SearchBST(T->rchild,key);//在右子树中继续查找
}

[插入]

void InsertBST (BSTree &T,ElemType e)
{
    if(!T)
    {
        S= (BSTNode *)malloc(sizeof(BSTNode));
        S->data=e;
        S->lchild=S->rchild=NULL;
        T=S;
    }
    else if(e.key < T->data.key) InsertBST(T->lchild,e);
    else if(e.key > T->data.key) InsertBST(T->rchild,e);
}

[生成]

从空树出发,经过一系列的查找、插入操作之后,可生成一棵二叉排序树。

中序遍历二叉排序树可得到一个关键字的有序序列。这就是说,一个无序序列可以通过构造一棵二叉排序树而变成一个有序序列,构造树的过程即为对无序序列进行排序的过程。

每次插入的新结点都是二叉排序树上新的叶子结点。则在进行插入操作时,不必移动其它结点,仅需改动某个结点的指针。这就相当于在一个有序序列上插入一个记录而不需要移动其它记录。

二叉排序树既拥有类似于折半查找的特性,又采用了链表作存储结构,因此是动态查找表的一种适宜表示。

[删除]

原则:在二叉排序树上删除一个结点后依旧要保持二叉排序树的特性。

假设:*p表示被删结点,PL和PR分别表示的左、右孩子指针,*f表示*的双亲结点。假定*p是*f的左孩子。

*p为叶子:删除此结点时,直接修改*f指针域即可。

*p只有一棵子树(或左或右):令PL或PR为*f的左子树即可。

*p有两棵子树:

 二叉排序树查找的性能分析:

二叉排序树上查找某关键字等于结点值的过程,其实就是走了一条从根到该结点的路径。

比较的关键字次数=此结点的层次数

最多的比较次数=树的深度(或高度)

          

第i层结点需比较i次。在等概率的前提下,上述两图的平均查找长度为:

左图:\sum_{i=1}^{n}p_{i}c_{i}=(1+2\times 2+3\times 2)/5=2.2

右图:\sum_{i=1}^{n}p_{i}c_{i}=(1+2+3+4+5)/5=3

平均查找长度和二叉树的形态有关,即:

最好:与log_{2}n成正比(形态匀称,与二分查找的判定树相似)

最坏:(n+1)/2(单支树)

平衡二叉树(AVL树)

提高二叉排序树的查找效率:尽量让二叉树的形状均衡——平衡二叉树(AVL树)

定义:n=0,一棵空树。n>0,左子树和右子树的深度之差的绝对值不超过1,且左右子树都是平衡二叉树。

平衡因子(BF):某结点的BF为其左子树和右子树的深度之差。平衡二叉树中所有结点的BF值只取-1,0,1。

对于一棵有n个结点的AVL树,其高度保持在O(log_{2}n)数量级,ASL也保持在O(log_{2}n)量级。

如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转:LL平衡旋转、RR平衡旋转、LR平衡旋转、RL平衡旋转。

哈希(散列/杂凑)表的查找

哈希表可不经过任何比较,一次便能得到所查记录。它既是一种查找方法,又是一种存贮方法。

基本思想:记录的存储位置与关键字之间存在对应关系,Loc(i)=H(keyi) ,即哈希函数。

优点:查找速度极快O(1),查找效率与元素个数n无关。

对不同的关键字可能得到同一哈希地址,即key1!=key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该哈希函数来说,称做同义词。

冲突是不可能避免的,只能尽量减少。构造哈希表必须解决以下两个问题:构造好的哈希
函数(简单、哈希地址分布均匀)、制定一个好的解决冲突方案。

哈希表的构造

哈希函数的构造方法:直接定址法、数字分析法、平方取中法、折叠法、除留余数法、随机数法。

[直接定址法] Hash(key)=a·key+b(a、b为常数)

优点:以关键码key的某个线性函数值为哈希地址,不会产生冲突。

缺点:要占用连续地址空间,地址集合与关键字集合大小相等,空间效率低,适用范围窄。

[除留余数法] Hash(key)=key mod p(p是一个整数)

关键:如何选取合适的p

技巧:若设计的哈希表长为m,则一般取p为小于等于表长m的最大质数(也可以是不包含小于20质因子的合数)

处理冲突的方法:

1.开放定址法:有冲突时就去寻找下一个空的哈希地址,直至哈希地址不发生冲突为止。

线性探测法

H_{i}=(Hash(key)+d_{i}) mod m(1≤i<m)
其中:Hash(key)为哈希函数,m为哈希表长度,d_{i}为增量序列1,2,...,m-1,且d_{i}=i。

例:

关键码集为{47,7,29,11,16,92,22,8,3},设:哈希表表长为m=11。

哈希函数为Hash(key)=key mod 11。

①47、7(以及11、16、92)均是由哈希函数得到的没有冲突的哈希地址、
②Hash(29)=7,哈希地址有冲突,需寻找下一个空的哈希地址:由H_{i}=(Hash(29)+1) mod 11=8,哈希地址8为空,因此将29存入。
③另外,22、8、3同样在哈希地址上有冲突,也是由H_{i}找到空的哈希地址的。
其中3还连续移动了三次(二次聚集)

优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素。

缺点:可能使第i个哈希地址的同义词存入第i+1个地址,这样本应存入第i+1个哈希地址的元素变成了第i+2个哈希地址的同义词(二次聚集),因此,可能出现很多元素在相邻的哈希地址上“堆积”起来,大大降低了查找效率。

解决方案:二次探测法或伪随机探测法

二次探测法

H_{i}=(Hash(key)+d_{i}) mod m

其中:d_{i}为增量序列1²,-1²,2²,-2²,...,q²或1²,2²,...,q²

注:只有3这个关键码的冲突处理与上例不同,Hash(3)=3,哈希地址上冲突,由H_{1}=(Hash(3)+1²) mod 11=4,仍然冲突,H_{2}=(Hash(3)-1²) mod 11=2,找到空的哈希地址,存入。

伪随机探测法

二次探测法和伪随机探测法的特点:

优点:可以缓解“二次聚集”现象。

缺点:在哈希表没有填满的情况下,不能保证一定找到不发生冲突的地址。

2.链地址法:相同哈希地址的记录链成一单链表,m个哈希地址就设m个单链表,然后用用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。

例:设{47,7,29,11,16,92,22,8,3,50,37,89}的哈希函数为:Hash(key)=key mod 11,
用链地址法处理冲突,则建表如下图所示。

注:有冲突的元素可以插在表尾,也可以插在表头。

特点:
1.非同义词不会冲突,无“二次聚集”
2.现象链表上结点空间动态申请,更适合于表长不确定的情况
3.附加指针域,存储效率较低

哈希表的查找

哈希查找的速度不是为真正的O(1) 。由于冲突的产生,使得哈希表的查找过程仍然要进
行比较,仍然要以平均查找长度ASL来衡量。在哈希表上进行查找的过程和哈希造表的过程基本一致。给定待查找关键字K值,根据造表时设定的哈希函数求得哈希地址;若表中此位置上为空,则查找不成功;若等于K值,则查找成功;否则根据造表时设定的处理冲突的方法找“下一地址”,直至哈希表中某个位置为“空”或者表中所填记录的关键字等于给定值时为止。

#define NULLKEY 0
typedef struct
{
    int key;
}HashTable[m];


int SearchHash(HashTable HT, int key)
{
    H0=H(key);//H(key)为哈希函数
    if(HT[H0].key==NULLKEY) return -1;
    else if(HT[H0].key==key) return H0;
    else
    {
        for(int i=1;i<m;++i)
        {
            Hi=(H0+i)%m;
            if(HT[Hi].key==NULLKEY) return -1;
            else if(HT[Hi].key==key) return Hi ;
        }
        return -1;
    }
}

效率分析:使用平均查找长度ASL来衡量查找算法,ASL取决于:哈希函数、处理冲突的方法、哈希表的装填因子。α越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yhan计算机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值