因为有项目需要,要做一个类似ispell 的软件,其中会产生大量的对单词的查找操作,于是经过一翻研究,得出以下HASH算法,经过验证比一般的查表的FNV HASH算法产生的分布曲线基本没什么两样,并且在大部分的不同字典下,本算法要比查表的FNV HASH算法表现出速度更快,分布更均匀。但是因为是实验结果,所以暂时还没得出有效的数学推论,但是从大量的不同的字典测试数据来看,此算法确实效率不 错。
由于以前没有涉及过相关的纯算法的设计,所以刚刚开始的时候,打算随便选用一种HASH,比如说用%除大质数,然后借此搭建一个比较强壮的测试环境,然后打算根据测试结果来改进HASH算法的模型。
最开始,我的HASH函数是这样的:
unsigned int hash_func(char *str, int len)
{
register unsigned int sum = 0;
register char *p = str;
while(p - str < len)
sum += *(p++);
return sum % MAX_PRIME_LESS_THAN_HASH_LEN;
}
非常简单,但是这是绝对不可取的,通过这个函数,我选取了一个23w词的字典做为测试,当HASH SIZE=1024的时候,得到了以下的图象:
<v:shapetype coordsize="21600,21600" o:preferrelative="t" o:spt="75" filled="f" stroked="f" path=" m@4@5 l@4@11@9@11@9@5 xe" id="_x0000_t75"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixelLineWidth 0 "></v:f><v:f eqn="sum @0 1 0 "></v:f><v:f eqn="sum 0 0 @1 "></v:f><v:f eqn="prod @2 1 2 "></v:f><v:f eqn="prod @3 21600 pixelWidth "></v:f><v:f eqn="prod @3 21600 pixelHeight "></v:f><v:f eqn="sum @0 0 1 "></v:f><v:f eqn="prod @6 1 2 "></v:f><v:f eqn="prod @7 21600 pixelWidth "></v:f><v:f eqn="sum @8 21600 0 "></v:f><v:f eqn="prod @7 21600 pixelHeight "></v:f><v:f eqn="sum @10 21600 0 "></v:f></v:formulas><v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype>
看得出震荡幅度相当大,那么如何来改进呢?首先想到可能产生的冲突的是这种情况:abcd和acbd,对于这两种单词来说,如果用上面的HASH函数,就一定会发生碰撞,为什么呢?因为每个字符少了关于它自己的位置信息,于是第一次改进版本的HASH函数就给每个字符加上了它的位置信息,将上面所描述的函数改进为:
unsigned int hash_func(char *str, int len)
{
register unsigned int sum = 0;
register char *p = str;
while(p - str < len)
sum += *(p++) * (p–str);
return sum % MAX_PRIME_LESS_THAN_HASH_LEN;
}
得到以下图象:
某种程度上来说,比不带位置信息产生的分布图要好多了,但是仍然非常的不均匀。那么接来分析产生分布不均匀的原因,因为是用的乘法,所以仍然太过于依赖字母产生的结果了。于是改用XOR操作,选用以下函数:
unsigned int hash_func(char *str, int len)
{
register unsigned int sum = 0;
register char *p = str;
while(p - str < len)
sum += (*(p++) * (p–str)) ^ sum;
return sum % MAX_PRIME_LESS_THAN_HASH_LEN;
}
得到以下图象:
<o:p>
</o:p>
上图虽然震荡幅度比较,不过做出来的regression line明显比上两张图片平得多了。但是结果仍然非常不好,从800到100的range太大。原因还是因为数据分布得不够均匀,于是思考单独的用加法来 算是不是不太好,根据其他查表类HASH算法的过程,发现其大多都用了高低位来组合成最后的结果,于是我也采用了他们的方法:
unsigned int hash_func(char *str, int len)
{
register unsigned int sum = 0;
register unsigned int h = 0;
register unsigned short *p = (unsigned short *)str;
register unsigned short *s = (unsigned short *)str;
while(p - s < len)
{
register unsigned short a = *(p++) * (p-s);
sum += sum ^ a;
h += a;
}
return ((sum << 16) | h) % MAX_PRIME_LESS_THAN_HASH_LEN;
}
得到最终近似完美的图象:
<o:p>
</o:p>
最后得出结论,不用查表的方法,而通过字符串本身的位置对字符本身进行修正的方法也能得到结果相当满意的HASH函数,之后换了几个大小不同的字典进行测试,得出的图象都大致和上图一致,非常令人满意。对于这个项目,包括如何检查单词错误,和自动修正等等相关的内容,会随着项目的完成一一在整理成文档,希望大家支持。
===============================================================================
字符串hash算法比较
<iframe width="300" scrolling="no" height="250" frameborder="0" allowtransparency="true" hspace="0" vspace="0" marginheight="0" marginwidth="0" src="http://blog.iyi.cn/tech/ad_300x250.html"></iframe>
1 概述
链表查找的时间效率为O(N),二分法为log2N,B+ Tree为log2N,但Hash链表查找的时间效率为O(1)。 |
设计高效算法往往需要使用Hash链表,常数级的查找速度是任何别的算法无法比拟的,Hash链表的构造和冲突的不同实现方法对效率当然有一定的影响,然 而Hash函数是Hash链表最核心的部分,本文尝试分析一些经典软件中使用到的字符串Hash函数在执行效率、离散性、空间利用率等方面的性能问题。
作者阅读过大量经典软件原代码,下面分别介绍几个经典软件中出现的字符串Hash函数。 |
static unsigned long hashpjw(char *arKey, unsigned int nKeyLength) |
char *arEnd=arKey+nKeyLength; |
if ((g = (h & 0xF0000000))) { |
unsigned long lh_strhash(char *str) |
if (str == NULL) return(0); |
/* The following hash seems to work very well on normal text strings |
* no collisions on /usr/dict/words and it distributes on %2^n quite |
* well, not as good as MD5, but still good. |
unsigned long lh_strhash(const char *c) |
if ((c == NULL) || (*c == '\0')) |
return(b[0]|(b[1]<<8)|(b[2]<<16)|(b[3]<<24)); |
在下面的测量过程中我们分别将上面的两个函数标记为OpenSSL_Hash1和OpenSSL_Hash2,至于上面的实现中使用MD5算法的实现函数我们不作测试。 |
#ifndef NEW_HASH_FUNCTION |
/* Calc hashvalue for a key */ |
static uint calc_hashnr(const byte *key,uint length) |
register uint nr=1, nr2=4; |
nr^= (((nr & 63)+nr2)*((uint) (uchar) *key++))+ (nr << 8); |
/* Calc hashvalue for a key, case indepenently */ |
static uint calc_hashnr_caseup(const byte *key,uint length) |
register uint nr=1, nr2=4; |
nr^= (((nr & 63)+nr2)*((uint) (uchar) toupper(*key++)))+ (nr << 8); |
* The basis of the hash algorithm was taken from an idea sent by email to the |
* IEEE Posix P1003.2 mailing list from Phong Vo (kpv@research.att.com) and |
* Glenn Fowler (gsf@research.att.com). Landon Curt Noll (chongo@toad.com) |
* later improved on their algorithm. |
* The magic is in the interesting relationship between the special prime |
* 16777619 (2^24 + 403) and 2^32 and 2^8. |
* This hash produces the fewest collisions of any function that we've seen so |
* far, and works well on both numbers and strings. |
uint calc_hashnr(const byte *key, uint len) |
for (hash = 0; key < end; key++) |
hash ^= (uint) *(uchar*) key; |
uint calc_hashnr_caseup(const byte *key, uint len) |
for (hash = 0; key < end; key++) |
hash ^= (uint) (uchar) toupper(*key); |
Mysql中对字符串Hash函数还区分了大小写,我们的测试中使用不区分大小写的字符串Hash函数,另外我们将上面的两个函数分别记为MYSQL_Hash1和MYSQL_Hash2。 |
unsigned int hash(char *str) |
register unsigned char *p; |
for(h=0, p = (unsigned char *)str; *p ; p++) |
从上面给出的经典字符串Hash函数中可以看出,有的涉及到字符串大小敏感问题,我们的测试中只考虑字符串大小写敏感的函数,另外在上面的函数中有的函数 需要长度参数,有的不需要长度参数,这对函数本身的效率有一定的影响,我们的测试中将对函数稍微作一点修改,全部使用长度参数,并将函数内部出现的计算长 度代码删除。 |
我们用来作测试用的Hash链表采用经典的拉链法解决冲突,另外我们采用静态分配桶(Hash链表长度)的方法来构造Hash链表,这主要是为了简化我们的实现,并不影响我们的测试结果。 |
测试文本采用单词表,测试过程中从一个输入文件中读取全部不重复单词构造一个Hash表,测试内容分别是函数总调用次数、函数总调用时间、最大拉链长度、 平均拉链长度、桶利用率(使用过的桶所占的比率),其中函数总调用次数是指Hash函数被调用的总次数,为了测试出函数执行时间,该值在测试过程中作了一 定的放大,函数总调用时间是指Hash函数总的执行时间,最大拉链长度是指使用拉链法构造链表过程中出现的最大拉链长度,平均拉链长度指拉链的平均长度。 |
PIII600笔记本,128M内存,windows 2000 server操作系统。 |
以下分别是对两个不同文本文件中的全部不重复单词构造Hash链表的测试结果,测试结果中函数调用次数放大了100倍,相应的函数调用时间也放大了100倍。 |
从上表可以看出,这些经典软件虽然构造字符串Hash函数的方法不同,但是它们的效率都是不错的,相互之间差距很小,读者可以参考实际情况从其中借鉴使用。
Posted by david at November 15, 2005 3:05 PM
分享到:
相关推荐
这可以通过编辑距离算法(Levenshtein Distance)或 Soundex 算法实现,前者计算两个字符串之间的差异,后者根据发音规则来匹配相似的单词。 5. **并行与分布式索引**:在大数据环境下,可以利用并行计算或分布式...
2. **基于英文单词的快速HASH索引算法**:这是一种将文本数据(如英文单词)转化为数字的方法,用于快速查找和比较。HASH索引能极大地提高数据检索速度,但需要注意处理哈希冲突的问题。 3. **动态规划**:动态规划...
根据给定的信息,我们可以分析出该段代码是关于散列算法的一个实现案例,主要通过C++语言编写(尽管代码实际采用的是C语言风格),用于构建一个基于散列表的马尔可夫链文本生成器。下面我们将详细解析这段代码中的...
其中,“建立词索引表”是一个重要的概念,用于处理文本数据,特别是当需要快速查找和检索特定词汇时。在这个例子中,我们将讨论如何用C语言实现这个功能,并补充作者严蔚敏教授在原书中可能未提供的完整源码。 ...
### JAVA英语单词(带音标) #### 描述: 本文档主要收录了JAVA开发过程中常用的英语词汇及其音标,旨在帮助开发者快速记忆并提高开发效率。 #### 核心知识点详解: ##### 1. Java基础概念 - **['ɔbdʒekt]['ɔ:...
7. **哈希表**(CuckooHashTable.java, CuckooHashTableClassic.java): 哈希表提供了一种快速查找和插入数据的方法,通过散列函数将键映射到数组的索引。Cuckoo哈希是一种特殊的哈希表实现,使用了Cuckoo算法,当冲突...
在分词中,哈希算法可以帮助快速定位潜在的词汇,提高效率。 接下来是“反向匹配算法”。反向匹配通常指的是倒排索引,它是一种用于全文搜索引擎的技术。在倒排索引中,每个词都有一个列表,包含了这个词在文档中...
中文分词系统参考实现思路包括索引结构、索引持久化、词典和索引的转化、分词算法优化、处理英文单词、阿拉伯数字和英文符号、被切分字符串的存储结构等几个方面。只有通过对这些方面的充分理解和合理设计,才能实现...
本项目主要涉及两种查找算法:哈希查找(Hash Search)和二分查找(Binary Search),并且应用在统计C语言源文件中的关键字个数。下面将详细阐述这两种查找算法以及它们在本项目中的具体应用。 哈希查找是一种高效...
3. 哈希表(Hash Table):哈希表提供快速的插入、删除和查找操作,它通过哈希函数将键映射到数组的索引上。在查找兄弟单词的问题中,我们可以用哈希表存储每个单词的字符及其出现次数,然后比较不同单词的字符集和...
`Hash()` 函数则负责将字符串转换为数组索引,确保每个单词能够正确地映射到哈希表的位置。 二、排序算法 实验要求学生实现一种内部排序算法,如插入排序或快速排序。快速排序是一种分治策略的排序算法,其基本思想...
它基于哈希函数,可以实现快速的插入、查找和删除操作,平均时间复杂度通常为O(1)。在"sanliebiao.rar_hash text find_哈希值_哈希检索_哈希表_文件哈希表"这个主题中,我们将深入探讨哈希表的原理、哈希函数、哈希...
在本研究中,提出了基于分布式散列表(Distributed Hash Table,DHT)的倒排索引算法。倒排索引是一种数据结构,它存储了一组文档和出现于其中的单词,用于快速检索包含特定单词的文档。通过DHT技术,可以将倒排索引...
- 也称为二叉索引树,用于快速查询和更新操作。 - 在数据结构领域有重要应用。 2. **字典树**: - 一种高效的多叉树结构,用于存储和检索字符串。 - 广泛应用于文本搜索、词典等场景。 3. **后缀树**: - ...
Curtmola等提出了在整个文件集合上建立加密关键词的Hash索引,搜索令牌由关键词陷门和文件拥有者的身份信息组成,可以通过关键词陷门与关键词密文的匹配来产生搜索结果。 Golle等提出2个连接关键词搜索方案,假设对...
哈希表是一种能够实现快速查找的数据结构,通过关键码值(在本例中,可能是单词的某种表示)映射到数组的索引上。当两个单词的关键码值相同导致冲突时,使用链地址法来解决,即将相同哈希值的单词链接在一起。实验中...
### C语言-VB-编程英语单词知识点概览 在信息技术领域,编程不仅是计算机科学的核心技能之一,也是连接现实世界与数字世界的桥梁。本篇将基于提供的文档内容,深入解析其中涉及的重要编程概念及其应用场景。 #### ...
传统的方法包括基于公钥算法、基于Hash算法和基于CP-ABE算法的访问控制。 - 基于公钥算法的密文访问控制依赖于一对相互配对的公钥和私钥,数据发布者使用用户的公钥加密数据解密密钥并存储于云端,用户再用私钥解密...
哈希函数是一个将任意大小输入(如字符串)转化为固定大小输出(通常是数组索引)的算法。理想情况下,哈希函数应确保不同的输入产生不同的哈希值,但现实中这很难实现,因此会出现冲突。为了解决冲突,我们通常采用...