用平均查找长度衡量算法性能。
A
S
L
=
∑
i
=
1
n
P
i
C
i
A S L=\sum_{i=1}^{n} P_{i} C_{i}
ASL=i=1∑nPiCi 其中,
P
i
P_i
Pi为查找第i个记录的概率,
C
i
C_i
Ci为找到表中其关键字与给定值相等的第i个记录时,和给定值已进行过比较的关键字个数。为了提高效率,可以在每个记录中附设一个访问频度阈。
提高检索效率的方法:
1.预排序;
2.建立索引;(检索时充分利用辅助索引信息,牺牲一点空间从而提高效率)
3.散列技术(把数据组织到一个表里,根据关键码确定表里记录位置。缺点:不适合范围查找,不允许重复关键字。)
4.当散列技术不适合基于磁盘的应用程序时,我们可以采用B数的方法。
顺序表的查找
在0位置设置哨兵,从表的最后一个记录开始,逐个进行记录的关键字和给定值进行比较。
有序表的查找
1.折半查找
A
S
L
k
=
log
2
(
n
+
1
)
−
1
A S L_{k}=\log _{2}(n+1)-1
ASLk=log2(n+1)−1 以有序表表示静态查找表时,进行查找的方法除了折半查找之外,还有斐波那契查找和插值查找。
优点:平均与最大检索长度相近,检索速度快。
缺点:要排序,顺序储存,不易更新
2.分块检索(块与块之间有序,快内无序,块与块之间二分法检索,块内之间顺序检索)
优点:插入,删除相对容易,没有大量的记录移动。
缺点:增加一个辅助数组的储存空间,初始线性表分块排序,当大量插入删除时,或节点分布不均匀时,速度下降。
3.集合检索
集合:有若干个确定的,相异的对象构成的整体。
集合的检索:确定一个值是不是某个集合的元素。
用位向量的方式来表示集合(leecode -242)
散列表
α
=
n
/
m
\alpha=n / m
α=n/m 散列表中的空间大小为m,填入表中的节点数为n。
冲突:某个散列函数对于不同的关键码,计算出来相同的散列地址,在实际应用中,不产生冲突的散列极少存在。
同义词:发生冲突的两个关键码
散列函数:把关键码值映射到存储位置的函数,
散列函数选取原则:
1.运算尽可能的简单
2.函数的阈值在表长范围内
3.尽可能使关键码不同,其散列函数值不相同。
哈希表构建方法:
1.直接定址法:
H
(
k
e
y
)
=
k
e
y
或
H
(
k
e
y
)
=
a
⋅
k
e
y
+
b
H(k e y)=k ey 或H(k e y)=a \cdot k e y+b
H(key)=key或H(key)=a⋅key+b 取关键字或者某个线性函数值为哈希地址。
2.除余法:通常选取一个质数作M值,增大均匀分布的可能。(潜在缺点:连续关键码,映射出连续的散列值,虽然能保证连续的关键码不发生冲突,但也意味着占据连续的数组单元,可能导致散列性能降低。)
3.数字分析法:假如关键字是以r为基数(如:以10为基的十进制),并且哈希表中可能出现的关键字都是事先知道的,则可去关键字的若干位组成哈希地址。例如有80个会议记录,其关键字为8位十进制,假如哈希表的表长为
10
0
1
00
100_100
100100,则可取两位十进制数组成哈希地址,取哪两位?原则是使得到的哈希地址尽量避免产生冲突,则需要从分析这80个关键字着手,假设这80个关键字的一部分如下:
对关键字全体的分析中可以发现,第1、2位都是“8 1”,第3位置可能取1、2、3或者4,第8位只可能取2、5或7,因此这4位都不可取,由于中间的4位可看成近乎随机的,因此可取其中任意两位,或取其中两位与另一位叠加求和后舍去进位作为哈希地址。
4.平方取中法:取关键字平方后的中间几位数为哈希地址,这是一种较常用的哈希函数的方法,通常在选定哈希函数时不一定能知道关键字的全部情况,取其中那几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使得随机分布的关键字的带的哈希地址也是随机的。取得位数由表长决定。
5.折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和,作为哈希地址。这个方法称为折叠地址。
6.随机数法。
冲突处理办法
1. 开放地址法
H
i
=
(
H
(
k
e
y
)
+
d
i
)
MOD
m
i
=
1
,
2
,
⋯
,
k
(
k
⩽
m
−
1
)
H_{i}=\left(H(k e y)+d_{i}\right) \text { MOD } m \quad i=1,2, \cdots, k(k \leqslant m-1)
Hi=(H(key)+di) MOD mi=1,2,⋯,k(k⩽m−1)
H
(
k
e
y
)
H(k e y)
H(key)为哈希函数,m为哈希表长,
d
i
d_i
di为增量序列。
d
i
=
1
,
2
,
3
,
⋯
,
m
−
1
d_{i}=1,2,3, \cdots, m-1
di=1,2,3,⋯,m−1(最常见的取法,也可以有其他取法)
2. 再哈希法
H
i
=
R
H
i
(
k
e
y
)
i
=
1
,
2
,
⋯
,
k
H_{i}=R H_{i}(k e y) \quad i=1,2, \cdots, k
Hi=RHi(key)i=1,2,⋯,k
R
H
i
RH_i
RHi均是不同的哈希函数。
3. 链地址法
将所有关键字为同义词的记录储存在同一线性链表中,假设某哈希函数产生的哈希地址在区间[0.m-1]上,则设立一盒指针型向量
C
h
a
i
n
C
h
a
i
n
H
a
s
h
[
m
]
Chain ChainHash[m]
ChainChainHash[m]每个向量的初始状态都是空指针,凡哈希地址为i的记录都插入到头指针为
C
h
a
i
n
C
h
a
i
n
H
a
s
h
[
i
]
Chain ChainHash[i]
ChainChainHash[i]的链表中,在链表中的插入位置可以是在表头或者表尾;也可以是在中间,以保持同义词在同一线性表中按关键字有序。
4. 建立一个公共溢出区
哈希表创建(未验证源码)
#include <stdio.h>
#include <iostream>
#include <string>
#include <string.h>
#include <stdlib.h>
#define MaxSize 100 //定义最大哈希表长度
#define NULLKEY -1 //定义空关键字值
#define DELKEY -2 //定义被删关键字值
typedef int KeyType; //关键字类型
typedef char *InfoType; //其他数据类型
typedef struct{
KeyType key; //关键字域
InfoType data; //其他数据域
int count; //探查次数域
}HashTable[MaxSize]; //哈希表类型
//将关键字k插入到哈希表中
int InsertHT(HashTable ha,int &n,KeyType k,int p){
int i,adr;
adr = k%p;
if (ha[adr].key==NULLKEY || ha[adr].key==DELKEY) //x[j]可以直接放在哈希表中
{
ha[adr].key=k;
ha[adr].count=1;
}
else //发生冲突时,采用线性探查法解决冲突
{
i=1; //i记录x[j]发生冲突的次数
do
{
adr = (adr+1)%p;
i++;
} while (ha[adr].key!=NULLKEY && ha[adr].key!=DELKEY);
ha[adr].key=k;
ha[adr].count=i;
}
n++;
return 0;
}
//创建哈希表
void CreateHT(HashTable ha,KeyType x[],int n,int m,int p){
int i,n1=0;
for (i=0;i<m;i++) //哈希表置初值
{
ha[i].key=NULLKEY;
ha[i].count=0;
}
for(i=0;i<n;i++)
InsertHT(ha,n1,x[i],p);
}
//在哈希表中查找关键字k
int SearchHT(HashTable ha,int p,KeyType k){
int i=0,adr;
adr = k%p;
while (ha[adr].key!=NULLKEY && ha[adr].key!=k)
{
i++; //采用线性探查法找下一个地址
adr = (adr+1)%p;
}
if(ha[adr].key==k) //查找成功
return adr;
else //查找失败
return -1;
}
//删除哈希表中关键字k
int DeleteHT(HashTable ha,int p,int k,int &n){
int adr;
adr = SearchHT(ha,p,k);
if (adr!=-1) //在哈希表中找到关键字
{
ha[adr].key=DELKEY;
n--; //哈希表长度减1
return 1;
}
else //在哈希表中未找到该关键字
return 0;
}
//查找成功时,平均查找长度
void CompASL(HashTable ha,int m){
int i;
int s=0,n=0;
for (i=0;i<m;i++)
if(ha[i].key!=DELKEY && ha[i].key!=NULLKEY)
{
s=s+ha[i].count;
n++;
}
printf(" 查找成功的ASL=%.3g\n",s*1.0/n);
}