查找技术
一、基本概念
1.关键码:数据元素中的某个数据项,可以标识列表中的一个或一组数据元素。
2.静态查找 :不涉及插入和删除操作的查找 。
动态查找 :涉及插入和删除操作的查找。
3.查找结构 :
线性表:适用于静态查找,主要采用顺序查找技术、折半查找技术。
树表:适用于动态查找,主要采用二叉排序树的查找技术。
散列表:静态查找和动态查找均适用,主要采用散列技术。
4.查找算法的性能 :
平均查找长度:ASL=
∑
i
=
1
n
p
i
c
i
\sum_{i=1}^{n} p_ic_i
∑i=1npici
pi为查找第i个记录的概率,ci为查找第i个记录所需的比较次数。
二、线性表查找
1.顺序查找
1.1普通顺序查找
从线性表一端向另一端逐个比较。(一般倒着比)
int SeqSearch(int k)
{
int i=length;
while(i>0&&data[i]!=k)
i--;
return i;
}
1.2带监视哨查找
改进思想:设置哨兵,哨兵等于待查值,将哨兵放在查找方向的尽头处,免去了判断越界的步骤,查找仍是从线性表一端向另一端逐个比较。(一般倒着比)
哨兵设置在下标0处:
int SeqSearch(int k)
{
int i=length;
data[0]=k;
while(data[i]!=k)
i--;
return i;
}
哨兵设置在下标高处:
int Search(int r[],int n,int k)
{
int i=1;
r[n+1]=k;
while(r[i]!=k)
i++;
return i%(n+1);
}
1.3查找算法性能
- 查找成功时:
pi= 1 n \frac{1}{n} n1,查找第i个记录需要n-i+1次比较。(因为是倒着比)
ASL= 1 n \frac{1}{n} n1 ∑ i = 1 n ( n − i + 1 ) \sum_{i=1}^{n} (n-i+1) ∑i=1n(n−i+1)= n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)* 1 n \frac{1}{n} n1= n + 1 2 \frac{n+1}{2} 2n+1=O(n) - 查找失败时:
比较次数是n-1次
ASL=O(n)
优点:
对存储结构没有任何要求,顺序存储和链接存储均可;对记录的有序性也没有要求,无论记录是否按关键码有序均可。
缺点:
平均查找长度较大,特别是当待查找集合中元素较多时,查找效率较低。
2.折半查找
2.1基本思想
1.二分思想,序列已经按照关键码升序排列,去中间位置的值比较,相等就停止,小于中间的就去左半区,大于就去右半区,继续找中间位置比较。
适用条件:有序、顺序存储
递归:
int binsearch(int low,int high,int k,int data[])
{
if(low>high)
return 0;
else {
int mid=(low+high)/2;
if(k<data[mid])
return binsearch(low,mid-1,k,data);
else if(k>data[mid])
return binsearch(mid+1,high,k,data);
else return mid;
}
}
非递归:
int BinSearch(int k,int data[],int n)
{
int mid,low=1,high=n;
while(low<=high)
{
mid=(low+high)/2;
if(k<data[mid])
high=mid-1;
else if(k>data[mid])
low=mid+1;
else return mid;
}
return 0;
}
2.折半查找判定树,low>high时判定树为空,low<=high时判定树根结点位mid,左子树是r[low] ~ r[mid-1]对应的判定树,右子树是r[mid+1] ~ r[high]对应的判定树。
判定树性质:
·任意结点的左右子树中结点个数最多相差1
·任意结点的左右子树的高度最多相差1
·任意两个叶子所处的层次最多相差1
3.与给定值的比较次数等于该记录结点在树中的层数。已知**判定树的深度为⌊log2n⌋+1 **,所以查找成功时,比较次数至多为⌊log2n⌋+1。
例:长度为11的有序表,比较3次就知道成功的情况有4种。(树的深度为4,第3层是满的)
2.2查找算法性能
平均查找长度:
ASL=
∑
i
=
1
n
p
i
c
i
\sum_{i=1}^{n} p_ic_i
∑i=1npici=
1
n
\frac{1}{n}
n1
∑
j
=
1
k
j
∗
2
j
−
1
\sum_{j=1}^{k} j*2^{j-1}
∑j=1kj∗2j−1=log2(n+1)-1(约等于)
平均时间复杂度:O(log2n)
例:长度为11的有序表
查找成功时:
1
∗
2
0
+
2
∗
2
1
+
3
∗
2
2
+
4
∗
4
11
\frac{1*2^0+2*2^1+3*2^2+4*4}{11}
111∗20+2∗21+3∗22+4∗4=
33
11
\frac{33}{11}
1133=3
不成功时:
3
∗
4
+
4
∗
8
12
\frac{3*4+4*8}{12}
123∗4+4∗8=
44
12
\frac{44}{12}
1244=
3
11
\frac{3}{11}
113(因为和给定值进行的关键码的比较次数等于该路径上内部结点的个数,所以分子层数最大等于树的高度)
折半查找判定树 二叉排序树 查找成功平均查找长度 查找失败平均查找长度
三、树表查找
1.二叉排序树
左子树的所有结点的值小于根结点,右子树的所有结点的值大于根结点,左右子树都是二叉排序树。
中序遍历能得到一个有序序列。
1.1性能分析
和给定值的比较次数等于给定值结点在二叉排序树中的层数,比较次数最少为1,最多不超过数的深度。具有n个结点的二叉排序树最大深度为n,最小为⌊log2n⌋+1
二叉排序树的查找性能取决于二叉排序树的形状:O(log2n) ~ O(n)
ASL=
1
+
2
+
3
+
4
+
5
5
\frac{1+2+3+4+5}{5}
51+2+3+4+5=3
ASL=
1
+
2
+
2
+
3
+
3
5
\frac{1+2+2+3+3}{5}
51+2+2+3+3=2.2
2.平衡二叉树
1.平衡二叉树: 根结点左右子树的深度最多相差1,且都是平衡二叉树。
平衡因子: 该结点左子树深度与右子树深度之差,即HL-HR
在平衡树中,结点的平衡因子可以是1,0,-1
2.最小不平衡子树: 在平衡二叉树的构造过程中,以距离插入结点最近的、且平衡因子的绝对值大于1的结点为根的子树。
设结点A为最小不平衡子树的根结点:
LL型:
RR型:
LR型:
RL型
已知关键码序列,构造平衡树:
3.B树
1.m阶B树:
·树中每个结点至多有m棵子树;
·若根结点不是终端结点,则至少有两棵子树;
·除根结点外,其他结点至少有⌈m/2⌉棵子树,即至少含有⌈m/2⌉-1个关键字;
·所有叶子结点都在同一层上,B树是高平衡的。
·所有结点都包含以下数据:(n,A0,K1,A1,K2,…,Kn,An),n为关键码的个数,K为关键码(Ki<Ki+1),A为指向子树根结点的指针,且指针Ai所指子树中所有结点的关键码均小于Ki+1大于Ki。
注意:
四、散列表(hash)查找
1.基本概念
1.采用散列技术将记录 存储在一块连续的存储空间中,这块连续的存储空间称为散列表,将关键码映射为散列表中适当存储位置的函数称为散列函数,所得的存储位置称为散列地址。
2.散列的基本思想: 在记录的存储地址和它的关键码之间建立一个确定的对应关系。这样,不经过比较,一次读取就能得到所查元素的查找方法。
散列既是一种查找技术,也是一种存储技术。散列没有完整地表达记录之间的逻辑关系,主要是面向查找的存储结构。
3.不适用于允许多个记录有同样关键码的情况,有冲突,降低了查找效率,体现不出计算式查找的优点。
不适用于范围查找,不能查找最大值、最小值,也不可能找到在某一范围内的记录。
4.冲突: 对于两个不同的关键码k1不等于k2,有H(K1)=H(K2),即两个不同的记录需要存放在同一个存储位置中
5.采用散列技术需要考虑的两个主要的问题是:散列函数的设计,冲突的处理。
6.特点:通过关键码计算记录的存储地址,并进行一定的比较
2.散列函数的设计
原则:计算简单;函数值即散列地址分布均匀。
1.直接定址法
H(key) = a * key + b (a,b为常数)
适用于:事先知道关键码,关键码集合不是很大且连续性较好。
2.保留余数法
H(key)=key mod p
选p为小于或等于表长(最好接近表长)的最小素数。
(散列表表长比关键码个数多一点)
适用于:不要求事先知道关键码的分布。是最简单、也是最常用的构造散列函数的方法。
3.平方取中法
对关键码平方后,按散列表大小,取中间的若干位作为散列地址(平方后截取)。
适用于:事先不知道关键码的分布且关键码的位数不是很大。
3.处理冲突的方法
3.1开放定址法
处理冲突得到的散列表叫做闭散列表。
Hi=(H(key)+di) % m ,闭散列表的长度为m
1.线性探测
当发生冲突时,从冲突位置的下一个位置起,依次寻找空的散列地址。 (di=1,2,…,m-1)
堆积:在处理冲突的过程中出现的非同义词之间对同一个散列地址争夺的现象。
int HashSearch1(int ht[], int m, int k)
{
int j=H(k),i;
if (ht[j]==k) return j;//没有发生冲突,比较一次查找成功
i=(j+1) % m;
while (ht[i]!=empty && i!=j)
{
if (ht[i]==k)
return i;//发生冲突,比较若干次查找成功
i=(i+1) % m;//向后探测一个位置
}
if (i==j) throw "溢出";
else ht[i]=k;//查找不成功时插入
}
2.二次探测
当发生冲突时,寻找下一个散列地址的公式为:
Hi=(H(key)+di)% m
(di=12,-12,22,-22,…,q2,-q2且q≤
m
2
\frac{m}{2}
2m)
3.随机探测
di是一个随机数
3.2拉链法(链地址法)
基本思想:将所有散列地址相同的记录,即所有同义词的记录存储在一个单链表中(称为同义词子表),在散列表中存储的是所有同义词子表的头指针。
例如:{47、7、29、11、16、92、22、8、3}
H(key)=key mod 11
构造开散列表为:
采用的是头插法
ASL=
1
∗
6
+
2
∗
3
9
\frac{1*6+2*3}{9}
91∗6+2∗3=
4
3
\frac{4}{3}
34
4.散列查找的性能分析
1.散列查找的性能取决于什么?
产生冲突后的查找仍然是给定值与关键码进行比较的过程。
在查找过程中,关键码的比较次数取决于产生冲突的概率。
2.影响冲突产生的因素有:
·散列函数是否均匀
·处理冲突的方法
·散列表的装载因子
( α=表中填入的记录数/表的长度)
装填因子越小,只能说明发生冲突的可能性越小,不能说不会引起冲突。
3.
- 手工计算等概率情况下查找成功的平均查找长度公式:
ASL= 1 表 中 置 入 元 素 个 数 n \frac{1}{表中置入元素个数n} 表中置入元素个数n1 ∑ i = 1 n ( C i ) \sum_{i=1}^{n} (C_i) ∑i=1n(Ci)
Ci为置入每个元素时所需的比较次数。 - 手工计算在等概率情况下查找不成功的平均查找长度公式:
ASL= 1 哈 希 函 数 取 值 个 数 \frac{1}{哈希函数取值个数} 哈希函数取值个数1 ∑ i = 1 r ( C i ) \sum_{i=1}^{r} (C_i) ∑i=1r(Ci)
Ci为函数取值为i时确定查找不成功时的比较次数。(闭散列表查每一个位置到空的距离,开散列表查空指针)
《哈希表:线性探测法和链地址法求查找成功与不成功的平均查找长度》
散列表 | 堆积现象 | 结构开销 | 插入/删除 | 查找效率 | 估计容量 |
---|---|---|---|---|---|
开散列表 | 不产生 | 有 | 效率高 | 效率高 | 不需要 |
闭散列表 | 产生 | 没有 | 效率低 | 效率低 | 需要 |
五、总结
1.顺序查找
- 查找成功时:
pi= 1 n \frac{1}{n} n1,查找第i个记录需要n-i+1次比较。(因为是倒着比)
ASL= 1 n \frac{1}{n} n1 ∑ i = 1 n ( n − i + 1 ) \sum_{i=1}^{n} (n-i+1) ∑i=1n(n−i+1)= n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)* 1 n \frac{1}{n} n1= n + 1 2 \frac{n+1}{2} 2n+1=O(n) - 查找失败时:
比较次数是n-1次
ASL=O(n)
2.折半查找
- 查找成功时:
ASL= 每 个 结 点 比 较 次 数 之 和 结 点 数 \frac{每个结点比较次数之和}{结点数} 结点数每个结点比较次数之和 - 查找失败时:
ASL= 空 指 针 处 比 较 次 数 之 和 空 指 针 数 \frac{空指针处比较次数之和}{空指针数} 空指针数空指针处比较次数之和 - 平均查找长度:
ASL= ∑ i = 1 n p i c i \sum_{i=1}^{n} p_ic_i ∑i=1npici= 1 n \frac{1}{n} n1 ∑ j = 1 k j ∗ 2 j − 1 \sum_{j=1}^{k} j*2^{j-1} ∑j=1kj∗2j−1=log2(n+1)-1=O(log2n)
3.二叉排序树
- 查找成功时:
ASL=(层数 * 该层结点个数) * 每个结点被查找的概率 - 查找失败时:
ASL=(底层 * 空结点个数)/(n+1)
底层指的是叶子结点的那层。 - 平均查找长度:
ASL= ∑ i = 1 n p i c i \sum_{i=1}^{n} p_ic_i ∑i=1npici= 1 n \frac{1}{n} n1 ∑ j = 1 k j ∗ 2 j − 1 \sum_{j=1}^{k} j*2^{j-1} ∑j=1kj∗2j−1=log2(n+1)-1=O(log2n)
4.散列表中地址链接法
- 查找成功时:
ASL=每个元素被访问的次数 - 查找失败时:
ASL=每个地址被访问的次数
5.散列表
- 手工计算等概率情况下查找成功的平均查找长度公式:
ASL= 1 表 中 置 入 元 素 个 数 n \frac{1}{表中置入元素个数n} 表中置入元素个数n1 ∑ i = 1 n ( C i ) \sum_{i=1}^{n} (C_i) ∑i=1n(Ci)
Ci为置入每个元素时所需的比较次数。 - 手工计算在等概率情况下查找不成功的平均查找长度公式:
ASL= 1 哈 希 函 数 取 值 个 数 \frac{1}{哈希函数取值个数} 哈希函数取值个数1 ∑ i = 1 r ( C i ) \sum_{i=1}^{r} (C_i) ∑i=1r(Ci)
Ci为函数取值为i时确定查找不成功时的比较次数。(闭散列表查每一个位置到空的距离,开散列表查空指针)
六、练习题
顺序存储平均比较次数= n + 1 2 \frac{n+1}{2} 2n+1= 16 2 \frac{16}{2} 216=8
二叉排序树:
因为散列结构是由事先准备好的散列函数关系与处理冲突方法来确定数据元素在散列表中的存储位置的,因此散列表查找方法的平均查找长度与元素的个数无关。
g同理,要会写过程
·折半查找判定树值需要考虑n的个数,结点为1~n;
·平衡二叉树,加点的每一步都要考虑平衡,不平衡就找最小不平衡子树,变换,继续加点;
·二叉排序树,根据每个元素的值建树,保证中序遍历有序
将二叉排序树 T 按前序遍历序列依次插入初始为空的二叉排序树 T '中,则 T 与 T’是相同的,这种说法
是否正确?
【解答】正确
一棵高度为 h 的平衡二叉树,最少含有 个结点。
A 2h B 2 h -1 C 2 h +1 D 2 h -1
【解答】D
折半查找判定树是平衡二叉树