开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第26天,点击查看活动详情
11.4 散列表的性能分析
平均查找长度(ASL)用来度量散列表查找效率:成功、不成功
- 成功:查找的元素再散列表里
- 不成功:查找的元素不在散列表里
关键词的比较次数,取决于产生冲突的多少。影响产生充裕多少有以下三个因素:
- 散列函数是否均匀(不均匀的话冲突会多,性能就会差)
- 处理冲突的方法
- 散列表的装填因子α(装了多少元素,装的元素少那么冲突少。装的元素多则冲突多)
分析:不同冲突处理方法、装填因子对效率的影响
-
线性探测法的查找性能
可以证明,线性探测法的期望探测次数满足下列公式:
对于线性探测,如果当前装填因子值为0.654321, 此时不成功情况下的期望探测次数小于成功情况下的期望探测次数。错误
-
平方 探测法和双散列探测法的查找性能
可以证明,平方探测法和双散列探测法探测次数 满足下列公式:
ASCu:成功 ASLs:失败
期望探测次数与装填因子α的关系
横坐标:装填因子α
纵坐标:期望探测次数
当装填因子α<0.5的时候,各种探测法的期望探测次数都不大,也比较接近
合理的最大装入因子α应该不超过0.85(对线性探测来说)
-
分离链接法的查找性能
所有地址链表的平均长度定义成装填因子α,α有可能超过1
不难证明:其期望探测次数p为:
总结
哈希查找(散列查找)特点
选择合适的h(key),散列法的查找效率期望是常数O(1),它几乎与关键词的空间的大小n无关!也适合于还建瓷直接比较计算机大的问题
优点:
- 不像搜索树一样或者平衡二叉树一样,他的查找基本上是跟问题的规模有关系。所以不发生冲突的话基本上是一次成功
特点:
- 跟问题规模无关,是一个常量时间
- 散列查找在很多情况下,用于字符串的管理(例如web地址名关键词搜索这种字符串管理),关键词查找
- 它是以较小的α为前提。因此,散列方法是一个以空间换时间
- 散列方法的存储对关键字是随机的,不便于顺序查找关键字,也不适合于范围查找,或最大值最小值查找
开放地址法
优:散列表是一个数组,存储效率高,随机查找
缺:散列表有"聚集"现象
分离链法
优:关键字删除不需要"懒惰删除"法,从而没有存储"垃圾"
缺:散列表是顺序存储和链式存储的结合,链表部分的存储效率和查找效率都比较低
太小的α可能导致空间浪费,大的α又将付出更多的时间代价。不均匀的链表长度导致时间效率的严重下降
11.5 应用实例:词频统计
【例】给定一个英文文本文件,统计文件中所有单词出现的频率,并输出词频最大的前10%的单词及其词频。
假设单词字符定义为大小写字母、数字和下划线,其它字符均认为是单词分隔符,不予考虑。
【分析】关键:对新读入的单词在已有单词表中查找,如果已经存在,则将该单词的词频加1,如果不存在,则插入该单词并记词频为1。
如何设计该单词表的数据结构才可以进行快速地查找和插入?散列表
int main(){
int TableSize = 10000;//散列表的估计大小
int wordcount = 0,length;
HashTable H;
ElementType word;
FILE*fp;
char document[30] = "HarryPotter.txt";//要被统计词频的文件名
H = Initialize Table( TableSize );//建立散列表(也就是初始化散列表)
if((fp = fopen(document,"r")) == NULL) FatalError("无法打开文件!\n");
while(!feof(fp)){//对文件进行处理,读到不是字母跟数字或者下划线而是分隔符的话,那就返回,获得到一个完整的word
length = GetAWord(fp,word);//从文件中读取一个单词
if(length > 3){//只考虑适当长度的单词
wordcount++;//统计文件中单词总数
InsertAndCount(word,H);//插入哈希表
//InsertAndCount这个函数作用:到哈希表里去找这个元素单词在不在,不在就插入,如果在就词频加1
}
}
fclose(fp);
printf("该文档共出现%d个有效单词,",wordcount);
Show(H,10.0/100);//显示词频前10%的所有单词
//Show一共两个参数,一个是我们的散列表,另外一个是我们的要求词频前10%
//这个函数一共做4件事情:1.统计最大词频;2.用一组数统计从1到最大词频的单词数;3.计算前10%的词频应该是多少;4.输出前10%词频的单词
DestroyTable(H);//销毁散列表
return 0;
}