一种新的哈希算法,欢迎拍砖

romandion 2010-06-24 01:11:28
好久没有在论坛发表帖子,最近研究一种哈希算法,偶有所得,探讨下。
http://blog.csdn.net/romandion/archive/2010/06/23/5689160.aspx

哈希在查找算法中,可以算是最高效,不过它受哈希函数和冲突解决函数限制,对于普适性的场景具有很大的限制。在通用传输平台中,用来解决海量会话,句柄,线程的定位确有先天的优势。因为这些场景的值,具有很高的连续性。基本上不会出现上一个是1,而下一个却变成222222。而且,在增删的场景下,不象树以及其他结构,需要对管理器进行锁定,可以实现无锁操作。

哈希算法以及原理就不需要介绍了。这里关键的是要设计一种算法,来处理值比较聚集情况下,一种哈希的冲突解决办法。我们知道,哈希值可以假定为一种无符号32位整数,通常都是这样。对于H(key)的设计来说,要解决算法简单,而且耗时较小,不然性能必然会很大折扣。另外还必须减少冲突。对于一个长度为M的哈希表,必须将N个元素尽可能聚集在一起,避免溢出。当A=N/M越大,造成的空间浪费也越大。

在APR的哈希实现中,使用取模的方式,模数为2^N - 1。当发生冲突时,就扩展哈希表,新模数为2^(N+1) -1。这个模数好处,比较明显,直接用KEY & MOD就能得到地址,算法简单,而且快速。不过问题很明显,冲突的概率很大。

现在,我们仔细研究下,在哈希值聚集比较明显的情况。

1、哈希值如果聚集的情况,那么他们会出现多个聚集带,针对不同聚集带采用不同的H函数,将减少M的值。

2、当N越大的事情,M将增加,冲突将增加,我们增加定位的次数,将被允许。不过,次数必须受到限制,否则将和树的性能相类似。

3、如果值比较密集的情况下,那么低比特位的值会比较集中。而高比特位的值会相对一致。

4、当一个冲突无法解决情况,我们要对原来值进行分裂,那么必须保证分裂出来的值尽可能的少。

5、在很糟糕的情况下,哈希值呈现等距分布,比如1M ,2M ...,那么我们会出现频繁分裂的情况,必须予以解决。

我的想法是这样的,将32位分为10 + 10 + 12三个段,每个段的比特位就成这样了,A段[0-->9],B段[10-->19],C段[20-->31]。基于我们原先假定哈希值密集的情况。而且4K是很多物理页的基本尺寸,有底层的优势。每个段,都会存在掩码,而且这个掩码可能发生变化,这个变化是扩展性的。比如原来是 00110000 ,后面就变成了00111000。

在一个已经存在的,均匀分布的哈希表中,新增一个哈希值,引起冲突,那么这个冲突其实只会跟其中一个冲突。那么,我们比较下这2个冲突的哈希值,很容易发现,究竟在那个段,以及哪个掩码是不一样。于是,我们只要简单的修改下那个段的掩码,使得这2个哈希值不一致。但糟糕的是,掩码的变化,使得很多已经存在的哈希值,必须重新定位到新分裂出来的桶中。而段掩码的选择,必然影响分裂到新桶中哈希值的多少。分裂出来的越多,那么性能就越低,这种情况有点象磁盘整理,是很恐怖的事情。针对这种情况,我们必须增加一个计数器,这个计算器记录每个比特位,含有多少个哈希值。那么新掩码生成时,在段的掩码中选择一个哈希值最少的比特位。那么需要分裂出来的哈希值就少很多。

我们知道,哈希表的值是很稀疏的,如果要遍历所有的节点,来判断是否需要分裂,也是件很恐怖的事情。所以,我们必须为这个哈希值增加一个后续哈希值的指针。当新增哈希值的时候,直接推送入栈。删除,只要删除哈希值所挂接的节点,而不需要删除指针,如果相邻的多个指针被删除,才需要进行合并。因为通常这种情况发生的情况很少。

如果假设,已经有1M被均匀分布在哈希表,新增的哈希值,导致分裂,会出现什么状况呢?所有的掩码比特位都被充满了,掩码实际上已经失去了作用。原来在该比特位为1的哈希值都要被重新定位到新分裂出来的值,理论上,分配的空间接近原来已经分配的空间。这是很恐怖的事情。这种情况,将导致插入效率急剧下降。而最好的方式,就是只为新插入的哈希值分配空间。那么新的问题就是,原来已经存在的哈希值不在新的掩码范围内,导致查找失败。因此,我们必须保存掩码的所有的历史。查找的时候,从最新的掩码开始,如果没有找到,那么原先保存在旧掩码节点的哈希值就需要被分裂出来。如此策略,会存在2种情况,一种是查找失败的事情,按照掩码的历史往前回溯,找到正确的哈希值,将该哈希值分裂到新的节点中。这种分裂,只要执行一次,就能适配到最新的掩码。另外一种情况,是新增的哈希值,按照新的掩码,跟旧的掩码冲突,那么同样需要将旧哈希值分裂到新的节点中。这种策略,避免新增一个掩码,需要将大量的哈希值分裂到新的节点中。

单个哈希值独占一个段的情况将会存在。是否存在合适算法或者策略来收缩这种空间浪费呢?我们将这个段的比特位清零,那么,这个比特位上,所有的值必须解决冲突,是个大工程。不过我们可以解决一个问题,如果某个段只有值,那么我们将这个段指针指向值,而不是缓冲区。那么对稀疏的哈希以及等步长的哈希有很大的空间改善。当一个哈希值被删除的时候,可能整个段只有它一个哈希值,那么这时候就必须进行收缩,回收内存,将该哈希值,挂接到父节点中。
...全文
99 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
通用型哈希表,最早发出的哥们儿已经不知去向(感谢他),目前的更和优化好像都是酷宝贝在做,不过我经常要导入模块,所以一直想用一个不需要类直接复制粘贴程序集就能用的版本,经过一段时间打磨,总算自己写了一个,调试过程还是有不少曲折的,但是考虑到 易语言 都过了二十年了,知名度依旧不高,所以没什么好吝啬的,分享出来,人海茫茫,你能碰到的都是兄弟,不是竞争对手。 碰巧写TCP组包,考虑用哈希表存储数据,这也引出了 将原先的哈希读写拆分出 取指针 和 指针操作  的需求。 这个程序集的特点: ' 分层次展现 ' 中文函数(公开)为哈希表主要函数 ' 中文函数(未公开)为哈希表扩展功能函数 ' 英文函数为独立的工具函数 ' 带_的英文函数是哈希表的附属函数 ' 1.对于取 哈希值的算法 进行了扩展,变成4种 ' 2.增加了 CRC32_PTR 汇编算法,使用查表方式计算,速度与按字节计算的传统哈希相近,比RtlComputeCrc32快且兼容性强,比传统的哈希算法碰撞率低 ' 3.增加了 hash_PTR 汇编算法,按字节计算哈希结果和之前一样,按字和按3字计算可用于文本key(但要注意对齐,比如Unicode是字对齐) ' 4.增加了 哈希_更改  子程序,这是 将key作为handle使用,用于快速管理资源 的一种理解方式 ' 5.在哈希取值之前分离出 哈希_取指针 ,返回数据的内存地址,同时让哈希_取值命令更加简单易读 ' 6.增加了 取素数不小于 的函数,用于创建和扩展链表,最早的创建方式不科学,遇到有规律的数会产生严重碰撞 ' 7.修复了内存泄露的bug,具体位置在 哈希_添加 中找到相同key数据之后,补充了heapfree命令 ' 8.更改取值方式,这个我看到酷宝贝也改了,取值直接返回指针,而不是再度申请内存(该操作同样会泄露) 阐述一下CRC32_PTR,综合考量了 速度、资源占用 ,将表作为数据置入,用CALL/POP取出,比API快,已对比过各易论坛、资源网、目前是最快最稳定的,欢迎高手拍砖

33,027

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧