7.查找
7.1概念
7.1.1查找的分类
查找表可分为两类:
- 静态查找表:
仅作"查询”(检索) 操作的查找表。 - 动态查找表:
作"插入"和“删除”操作的查找表。
注:有时在查询之后,还需要将"查询”结果为“不在查找表中”的数
据元素插入到查找表中;或者,从查找表中删除其"查询”结果为
"在查找表中”的数据元素,此类表为动态查找表。
7.1.2评价查找算法
查找算法的评价指标:
关键字的平均比较次数,也称平均查找长度ASL(Average Search Length)
(关键字比较次数的期望值)
n:记录的个数
pi:查找第i个记录的概率(通常认为pi =1/n )
ci:找到第i个记录所需的比较次数
7.2线性表的查找(静态)
应用范围:
- 顺序表或线性链表表示的静态查找表
- 表内元素之间无序
7.2.1顺序查找
在顺序表ST中查找值为key的数据元素(从最后一个元素开始比较)
例如: 查找13:找到,返回5 ;查找60:未找到,返回0
int Search _Seq( SSTable ST , KeyType key ){
//若成功返回其位置信息,否则返回0
for( i=ST.length; i>=1; --i )
if ( ST.R[i].key==key ) return i;
return 0;
int Search. Seq (SSTable ST, KeyType key)
{//在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
ST.R[0] . key=key; //“哨兵”
for (i=ST .1ength;ST.R[i] .key!=key;--i); //从后往前找
return i ;
}
优点: 算法简单,逻辑次序无要求,且不同存储结构均适用。
缺点: ASL太长,时间效率太低。
7.2.2折半查找(二分或对分查找)
折半查找: 每次将待查记录所在区间缩小一半。
mid = (low + high)/ 2
key<mid则: high = mid - 1
key>mid则:low= mid + 1
key==mid,找到
high < low,结束
//非递归查找
int Search_ Bin ( SSTable ST, KeyType key) {
low= 1 ; high = ST.length; // 置区间初值
while (low <= high) {
mid = (low + high)/ 2;
if (ST.R[mid].key == key) return mid; // 找到待查元素
else if (key < ST.R[mid].key) //缩小查找区间
high=mid-1; //继续在前半区间进行查找
else low=mid+1;
//继续在后半区间进行查找
}
return 0 ; //顺序表中不存在待查元素
} // Search Bin
长度为11的有序表,折半查找的ASL = 1/11(1+22+34+4*4)
折半查找优点: 效率比顺序查找高。
折半查找缺点: 只适用于有序表,且限于顺序存储结构(对线性链表无效)
7.2.2.1判定树
判定树: 折半查找过程可用二叉树来描述。树中每一结点对应表中一个记录,但结点值不是记录的关键字,而是记录在表中的位置序号。
方法: 把当前查找区间的中间位置作为根,左子表和右子表分别作为根的左子树和右子树,由此得到的二叉树称为折半查找的判定树。
7.2.3分块查找(索引顺序查找)
条件:
- 将表分成几块,且表或者有序,或者分块有序;
若i<j,则第j块中所有记录的关键字均大于第i块中的最大
关键字。 - 建立"索引表” (每个结点含有最大关键字域和指向本
块第一个结点的指针,且按关键字有序)。
查找过程: 先确定待查记录所在块(顺序或折半查找);再在块内查找(顺序查找)。
查找效率 = 对索弓|表查找的ASL+对块内查找的ASL
优点: (插入和删除比较容易,无需进行大量移动。
缺点: 要增加一个索引表的存储空间并对初始索弓|表进行排序运算。
适用情况: 如果线性表既要快速查找又经常动态变化,则可采用分块查找。
7.3树表的查找
当表插入、删除操作频繁时,为维护表的有序性,需要移动表
中很多记录。
改用动态查找表——几种特殊的树表结构在查找过程中动态生成,
对于给定值key,若表中存在,则成功返回;否则,插入关键字等于key的记录。
7.3.1二叉排序树(Binary Sort Tree)
二叉排序树(Binary Sort Tree)又称为二叉搜索树、二叉查找树,
7.3.1二叉排序树定义:
二叉排序树或是空树,或是满足如下性质的二叉树:
(1)若其左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若其右子树非空,则右子树上所有结点的值均大于等于根结点的值;
(3)其左右子树本身又各是一棵二叉排序树
7.3.2二叉排序树的性质:
中序遍历非空的二_叉排序树所得到的数据元素序列是一个按关键字排列的递增有序序列。
中序遍历:3, 12 , 24 , 37,45,53,61,78,90,100
递增
7.3.3二叉排序树的操作一查找
- 若查找的关键字等于根结点,成功
否则
- 若小于根结点,查其左子树
- 若大于根结点,查其右子树
- 在左右子树上的操作类似
7.3.3.1二叉排序树的递归查找:
[算法思想]
(1)若二叉排序树为空,则查找失败,返回空指针。
(2) 若二叉排序树非空,将给定值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-> Ichild,key);//在左子树中继续查找
else return SearchBST(T-> rchild,key); //在右子树中继续查找
} // SearchBST
7.3.4二叉排序树的操作
7.3.4.1插入
步骤:
①若二叉排序树为空,则待插人结点*S作为根结点插入到空树中。
②若二叉排序树非空,则将key与根结点的关键字T->data.key进行比较:
若key小于T->data.key,则将 S插入左子树;
若key大于T->data.key,则将S插入右子树。
45, 24, 53, 45, 12, 24, 90 按上述算法生成一颗二叉排序树
7.3.4.2删除
方法:
叶子节点:直接删;
只有一颗子树:直接删;
有两个子树:以左子树最大或右子树最小者代替,删完任然是BS树。
7.4平衡二叉树(balanced binary tree)
7.4.1术语
1.平衡二叉树的定义:
又称AVL树(Adelson-Velski and Landis)。
一棵平衡二叉树或者是空树, 或者是具有下列性质的二叉 排序树:
①左子树与右子树的高度之差的绝对值小于等于1;
②左子树和右子树也是平衡二叉排序树。
2.平衡因子
为了方便起见,给每个结点附加一一个数字,给出该结点左子树子右子树的高度差。这个数字称为结点的平衡因子(BF) 。
平衡因子=结点左子树的高度-结点右子树的高度
根据平衡二叉树的定义,平衡二叉树 上所有结点的平衡因子只能是-1、0,或1。
例 是不是平衡二叉树,每个结点的平衡因子是多少?
7.4.2失衡二叉排序树的分析与调整
当我们在一个平衡叉排序树 上插入一个结点时,有可能导致失衡,即出现平衡因子绝对值大于1的结点,如: 2、-2。
如果在一棵AVL树中插入一个新结点后造成失衡,则必须重新调整树的结构,使之恢复平衡。
调节的四种类型:
- RR型
把被破坏节点的右子树的根节点提到被破坏节点的位置; - LL型
把被破坏节点的左子树的根节点提到被破坏节点的位置; - LR型
左子树的右子树的根节点提到被破坏节点的位置; - RL型
右子树的左子树的根节点提到被破坏节点的位置;
总原则:调节以后仍然是一颗平衡二叉树。
例1:LL
例2:RR
例3:LR
例4:RL
7.5散列表的查找
7.5.1术语
1.散列方法(杂凑法):
选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;
查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行比,确定查找是否成功。
2.散列函数(杂凑函数):
H(Key) = K
散列方法中使用的转换函数
3.散列表(杂凑表):
按上述思想构造的表。
4.冲突:
不同的关键码映射到同一一个散列地址;
如key1≠key2,但是H(key1)= H(key2)
例:有6个元素的关键码分别为: (25,21, 39,9,23,11) 选取关键码与元素位置间的函数为H(k)= k mod 7,地址编号从0-6。
7.5.2直接定址法
Hash(key) = a.key + b (a、 b为常数)
优点: 以关键码key的某个线性函数值为散列地址,不会产生冲突。
缺点: 要占用连续地址空间,空间效率低。
例: {100, 300, 500,700,800,900},
散列函数Hash(key)=key/100 (a=1/100, b=0)
7.5.3除留余数法
Hash(key)= key mod p
(p是一个整数)
设表长为m,取p≤m且为质数
例:写出散列表
{15,23,27,38 ,53,61 ,70},散列函数Hash(key)=key mod 7
7.6处理冲突
7.6.1开放定址法(开地址法)
1.基本思想: 有冲突时就去寻找下一个空的散列地址,只要散列表定哆大,空的散列地址总能找到,并将数据元素存入。
2.常用方法:
线性探测法;二次探测法;伪随机探测法
7.6.1.1线性探测法
Hi=(Hash(key)+d) mod m( 1≤i< m)
其中: m为散列表长度;di为增量序列1, 2,…m-1且di=i。
一旦冲突,就找下一个地址,直到找到空地址存入
例:关键码集为{47, 7, 29, 11, 16, 92,22, 8, 3},散列表长度为m=11;散列函数为Hash(key)=key mod p,p = 11;拟用线性探测法处理冲突。建散列表如下:
线性探测法的ASL:
- 成功:ASL(succ) = 查找次数和 / 关键字个数
- 失败:ASL(unsucc) = 每次查找失败的长度相加 / 散列表中记录的个数(p)
- 装填因子:p=n/m 其中n 为关键字个数,m为表长。
解释:
①47、7均是由散列函数得到的没有冲突的散列地址;
②Hash(29)=7,散列地址有冲突,需寻找下一个空的散列地址:由H1=(Hash(29)+ 1) mod 11=8,散列地址8为空,因此将29存入。
③11、16、 92均是由散列函数得到的没有冲突的散列地址;
④另外,22、8、3同样在散列地址上有冲突,也是由H找到空的散列地址的。
平均查找长度ASL(succ)=(1+2 +1 +1 +1 +4 +1 +2 +2)/9=1.67
ASL(unsucc) =(3+2+1+8+7+6+5+4+3+2)/10=。。。
7.6.1.2二次探测法
关键码集为{47, 7, 29, 11, 16, 92,22,8,3},
设:散列函数为Hash(key)=key mod 11;
Hi=(Hash(key)+di}) mod m
其中: m为散列表长度,m要求是某个4k+ 3的质数;
di为增量序列1^2, -1^2, 2^2, -2^2, … q^2
7.6.2链地址法
1.基本思想: 相同散列地址的记录链成一-单链表
m个散列地址就设m个单链表,然后用一个数组将m个单链表的表头指针存储起来,形成一个动态的结构。
2.链地址法建立散列表步骤
- Step1:取数据元素的关键字key,计算其散列函数值(地址)。若该
地址对应的链表为空,则将该元素插入此链表;否则执行Step2解决
冲突。 - Step2: 根据选择的冲突处理方法,计算关键字key的下一个存储地址。
若该地址对应的链表为不为空,则利用链表的前插法或后插法将该元
素插入此链表。
例如: 一组关键字为 {19,14,23,1,68,20,84,27, 55,11,10,79}, 散列函数为
Hash(key)=key mod P (p=13,即为表长)
链地址法的ASL:
- 成功:ASL(succ) = (1x6+2x4+3x1+4x1)/12
- 失败:ASL(unsucc) = (1+6+1+4+1+1+4+3+1+1+4+3+1)/13 (p = 13)
链地址法的优点:
- 非同义词不会冲突,无“聚集”现象
- 链表上结点空间动态申请,更适合于表长不确定的情况