查找
一.查找的基本概念
查找算法的评价标准
关键字的平均比较次数,也称平均搜索长度ASL
n:记录的个数
pi:查找第i个记录的概率 ( 通常认为pi =1/n )
ci:找到第i个记录所需的比较次数
二.线性表的查找
1.顺序查找(线性查找)
应用范围:
顺序表或线性链表表示的静态查找表
表内元素之间无序
时间复杂度:
- 查找成功时的平均查找长度 设表中各记录查找概率相等
ASLs(n)=(1+2+ … +n)/n =(n+1)/2 - 查找不成功时的平均查找长度 ASLf =n+1
练习:
判断对错:n个数存在一维数组A[1…n]中,在进行顺序查找时,这n个数的排列有序或无序其平均查找长度ASL不同。
答案 : X
解释:
查找概率相等时,ASL相同;
查找概率不等时,如果从前向后查找,则按查找概率由大到小排列的有序表其ASL要比无序表ASL小。
2.折半查找
- 设表长为n,low、high和mid分别指向待查元素所在区间的上界、下界和中点,k为给定值
- 初始时,令low=1,high=n,mid=[(low+high)/2]
- 让k与mid指向的记录比较:
若k==R[mid].key,查找成功
若k < R[mid].key,则high=mid-1
若k > R[mid].key,则low=mid+1 - 重复上述操作,直至low>high时,查找失败
代码如下:
//非递归算法
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; //表中不存在待查元素
}
题型:
利用判定树求查找成功时的平均查找次数ASL
假定每个元素的查找概率相等,求查找成功时的平均查找长度
只看圆形结点
ASL=1/11x(1x1+2×2+4×3+4x4 )=33/11=3
查找成功时比较次数:为该结点在判定树上的层次数,不超过树的深度 d = [log2(n) ] + 1 (2是底数,n是真数)
查找不成功的过程就是走了一条从根结点到外部结点的路径d或d-1
折半查找的查找性能
查找过程:每次将待查记录所在区间缩小一半,比顺序查找效率高,时间复杂度O(log2(n))(表示形式如上)
ASL=log2(n+1)-1 (n较大)
适用条件:采用顺序存储结构的有序表,不宜用于链式结构
3.分块查找
分块有序,即分成若干子表,要求每个子表中的数值都比后一块中数值小(但子表内部未必有序)。
然后将各子表中的最大关键字构成一个索引表,表中还要包含每个子表的起始地址(即头指针)
图示如图:
分块查找过程:
① 对索引表使用折半查找法(因为索引表是有序表);
② 确定了待查关键字所在的子表后,在子表内采用顺序查找法(因为各子表内部是无序表);
优点:插入和删除比较容易,无需进行大量移动。
缺点:要增加一个索引表的存储空间并对初始索引表进行排序运算。
适用情况:如果线性表既要快速查找又经常动态变化,则可采用分块查找。
4.查找性能分析
顺序查找性能分析
ASL = (n+1)/2
折半查找性能分析
ASL = log2 (n+1) - 1
分块查找性能分析
顺序查找部分
ASL=Lb+Lw =
当s取根号n时,最小值为
折半查找部分
查找效率:ASL=Lb+Lw
Lb:对索引表查找的ASL
Lw:对块内查找的ASL
S为每块内部的记录个数,n/s即块的数目
例如,当n=9,s=3时,ASLbs=3.5,而折半法为3.1,顺序法为5
三.树表的查找
表结构在查找过程中动态形成
对于给定值key
若表中存在,则成功返回;
否则插入关键字等于key 的记录
1.二叉排序树(二叉查找树)
二叉排序树或是空树,或是满足如下性质的二叉树:
(1)若其左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若其右子树非空,则右子树上所有结点的值均大于等于根结点的值;
(3)其左右子树本身又各是一棵二叉排序树
中序遍历一个二叉排序树可以得到一个关键字递增的有序序列
二叉排序树的查找操作
若查找的关键字等于根结点,成功
否则
若小于根结点,查其左子树
若大于根结点,查其右子树
在左右子树上的操作类似
算法思想如下:
- 若二叉排序树为空,则查找失败,返回空指针。
- 若二叉排序树非空,将给定值key与根结点的关键字T->data.key进行比较:
① 若key等于T->data.key,则查找成功,返回根结点地址;
② 若key小于T->data.key,则进一步查找左子树;
③ 若key大于T->data.key,则进一步查找右子树。
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); //在右子树中继续查找
} // SearchBST
二叉排序树的插入操作
- 若二叉排序树为空,则插入结点应为根结点
- 否则,继续在其左、右子树上查找
树中已有,不再插入
树中没有,查找直至某个叶子结点的左子树或右子树为空为止,则插入结点应为该叶子结点的左孩子或右孩子
代码如下:
void InsertBST(BSTree &T,ElemType e ) {
//当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素
if(!T) { //找到插入位置,递归结束
BSTree S = new BSTNode; //生成新结点*S
S->data = e; //新结点*S的数据域置为e
S->lchild = S->rchild = NULL; //新结点*S作为叶子结点
T = S; //把新结点*S链接到已找到的插入位置
}
else if (e.key < T->data.key)
InsertBST(T->lchild, e ); //将*S插入左子树
else if (e.key > T->data.key)
InsertBST(T->rchild, e); //将*S插入右子树
}// InsertBST
插入的结点一定在叶节点上
二叉排序树的生成操作
代码如下:
void CreateBST(BSTree &T )
{
//依次读入一个关键字为key的结点,将此结点插入二叉排序树T中
T=NULL;
ElemType e;
cin>>e.key; //???
while(e.key!=ENDFLAG){ //ENDFLAG为自定义常量,作为输入结束标志
InsertBST(T, e); //将此结点插入二叉排序树T中
cin>>e.key; //???
}//while
}//CreatBST
二叉树的删除操作
- 将因删除结点而断开的二叉链表重新链接起来
- 防止重新链接后树的高度增加
缺右子树用左子女填补
缺左子树用右子女填补
删除二分支的结点,在右子树上找中序第一个结点填补
- 删除叶结点,只需将其双亲结点指向它的指针清零,再释放它即可
- 被删结点缺右子树,可以拿它的左子女结点顶替它的位置,再释放它
- 被删结点缺左子树,可以拿它的右子女结点顶替它的位置,再释放它
- 被删结点左、右子树都存在,可以在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删结点中,再来处理这个结点的删除问题
查找的性能分析
平均查找长度和二叉树的形态有关,即,
最好:log2 (n)(形态匀称,与二分查找的判定树相似)
最坏: (n+1)/2(单支树)
2.平衡二叉树
基本概念
特点:
- 左、右子树是平衡二叉树;
- 所有结点的左、右子树深度之差的绝对值≤ 1
平衡因子:该结点左子树与右子树的高度差
任一结点的平衡因子只能取:-1、0 或 1;如果树中任意一个结点的平衡因子的绝对值大于1,则这棵二叉树就失去平衡,不再是AVL树;
- 对于一棵有n个结点的AVL树,其高度保持在O(log2 (n))数量级,ASL也保持在O(log2 (n) )量级
平衡二叉树 (AVL) 或是一棵空树,或是具有下列性质二叉排序树:
- 根结点的左子树和右子树的深度最多相差 1
- 根结点的左子树和右子树也都是平衡二叉树
结点的平衡因子:该结点的左子树深度与右子树深度之差
平衡二叉树上所有结点的平衡因子只可能是 -1、0 和 1
因此只要二叉树上有一个结点的平衡因子绝对值大于 1,该二叉树就不平衡。
二叉树的平衡旋转
如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转
LL平衡旋转
RR平衡旋转
LR平衡旋转
RL平衡旋转
四.哈希表的查找
基本思想:记录的存储位置与关键字之间存在对应关系,即
Loc(i) = H(keyi)
此即为哈希函数
优点:查找速度极快O(1),查找效率与元素个数n无关
- 哈希方法(杂凑法)
选取某个函数,依该函数按关键字计算元素的存储位置, 并按此存放;
查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行比,确定查找是否成功。 - 哈希函数(杂凑函数):哈希方法中使用的转换函数
- 冲突:不同的关键码映射到同一个哈希地址
- 同义词:具有相同函数值的两个关键字
构造哈希函数的方法
直接定址法
构造方法:
哈希函数是关键字的线性函数:
H(k)=a*k+b (a、b为常数)
- 该方法的特点是单调、均匀,对于不同关键字不会产生冲突,适用于事先知道关键字分布、关键字集合较小且连续性较好的情况。
- 缺点:占用连续地址空间,空间效率低。
除留取余法(重点)
构造方法:
选择某个适当的正整数 p,以关键字除以 p 的余数作为哈希地址:
H(k)=k MOD p。
该方法的特点是不需要事先知道关键字的分布,是一种最简单,也是最常用的构造哈希函数的方法。
该方法的关键是选取 p,否则容易产生同义词。为了尽可能少地产生冲突,通常选取p≤m且为质数
数字分析法
构造方法:
根据关键字在各个位上的分布情况,选取分布比较均匀的若干位组成哈希地址
该方法适合于事先知道关键字的分布且关键字中有若干位分布较均匀的情况
平方取中法
构造方法:对关键字平方后,按哈希表大小,取中间的若干位作为哈希地址(平方后截取)。
处理冲突的方法
开放地址法
基本思想:有冲突时就去寻找下一个空的哈希地址,只要哈希表足够大,空的哈希地址总能找到,并将数据元素存入。
1.线性探测法(重点)
Hi = (Hash(key)+di) mod m ( 1 ≤ i < m )
其中:m为哈希表长度,di 为增量序列 1,2,…m-1,且di=i
一旦冲突,就找下一个空地址存入
例:
- 优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素
- 缺点:可能使第 i 个哈希地址的同义词存入第 i+1个 地址,这样本应存入第i+1个哈希地址的元素 变成了第i+2个哈希地址的同义词,……,产生“聚集”现象,降低查找效率
2.二次探测法
3.伪随机探测法
Hi = (Hash(key)+di) mod m ( 1 ≤ i < m )
其中:m为哈希表长度 , di 为随机数
开放地址法建立哈希表步骤
- step1 取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的存储空间还没有被占用,则将该元素存入;否则执行step2解决冲突。
- step2 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若下一个存储地址仍被占用,则继续执行step2,直到找到能用的存储地址为止。
链地址法(拉链法)
基本思想:相同哈希地址的记录链成一单链表,m个哈希地址就设m个单链表,然后用用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构
- step1 取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的链表为空,则将该元素插入此链表;否则执行step2解决冲突
- step2 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若该地址对应的链表不为空,则利用链表的前插法或后插法将该元素插入此链表
优点:
1.非同义词不会冲突,无“聚集”现象
2.链表上结点空间动态申请,更适合于表长不确
定的情况
例题:
哈希表的查找效率分析
使用平均查找长度ASL来衡量查找算法,ASL取决于
- 哈希函数
- 处理冲突的方法
- 哈希表的装填因子α
α越大,表中记录数越多,说明表装得越满,发生冲突的可能性就越大,查找时比较次数就越多。
ASL与装填因子α有关!既不是严格的O(1),也不是O(n)
- 链地址法优于开地址法
- 除留余数法作哈希函数优于其它类型函数
终于完结啦~~~~~~~