找回密码
 欢迎注册
查看: 12096|回复: 7

[求助] HASH code 的选择

[复制链接]
发表于 2010-2-17 07:40:54 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?欢迎注册

×
目前我正在做一个推箱子工具,算法在运行时要尝试很多次,每次是一个节点,每个节点都要有一个值来与其他节点区分开,区分的方法就是对当前的地图进行描述,也就是要描述当前每个箱子的位置。我的地图最大是128*128个格子。(当然目前我还没看到这么大的推箱子地图,但不妨以此作为极限)。 箱子的坐标表示方法:坐标place=X坐标 + Y坐标*128,记为:place=x+y*128 一开始,我只求尽快完成程序,使用了一个简单的方法来描述: 我把每个箱子按坐标排序(这是必不可少的),从坐标最小的箱子开始,把箱子的坐标转化为一字符串,后面接一个“,”号,再接下一个坐标,再接一个逗号,。。。。。直到结束。最后得到一个字符串,而后我把求hash code的工作丢给系统了,因为我用的是java,里面的Hashmap自己会处理这个字符串。 现在,这个这个方法终于暴露了它的不足,它占的内存太多了!一般我搜索到100万个节点,就用了2G内存,后来查资料,知道即使一个空的字符串就有占40多byte,占了我一个节点的多半! 请问有什么好的算法,能得到较短的hash code,要求在42亿个不同节点内不重复,最好是一个64位整数!(java里32位和64位的整数的对象一样大,不过如果是使用32位,每个节点或许能节约4byte)
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-2-17 16:13:25 | 显示全部楼层
本帖最后由 KeyTo9_Fans 于 2010-2-17 16:15 编辑 我没有开发过工具,只做过大量的ACM在线评测的题目,所以只能把做题的经验与你分享。 按照你的思路,记$place=x+y*128$。 其中$x$的范围为$0$到$127$,$y$的范围也是$0$到$127$。 所以$place$的范围是$0$到$16383$。 把每个箱子按$place$值从小到大排序,得到一个数列,如: $647$,$649$,$1027$,$1540$,$1545$,...... 记为$a_1$、$a_2$、$a_3$、......、$a_n$。 把这个数列看成一个$K$进制数,其中每一项是一个数位。 由于$place$的范围是$0$到$16383$,所以这个数列天然地就是一个$16384$进制的数,每个数位可以是$0$、$1$、$2$、$3$、......、$16383$,共$16384$种可能。 把这个$16384$进制的数转化成$10$进制的数,然后除以$2^64$取余数即可。 但$2^64$是$16384$的倍数,所以高位对余数没有影响,这样取到的余数丢失了高位的信息。 所以我们取$K=16411$,这样每一位都对余数有影响,不会丢失信息。 最终哈希值的计算公式如下: $code=(n+a_1*K+a_2*K^2+a_3*K^3+......+a_n*K^n)mod 2^64$ 值域为$0$到$2^64-1$,恰好是一个$64$位整数。 冲突概率分析: 考虑到$K$值的随意性,可以使取模结果在$0$到$2^64-1$内分布均匀。 所以 $1$个节点不冲突的概率为$1$; $2$个节点不冲突的概率为$(2^64-1)/2^64$≈$1$; $3$个节点不冲突的概率为$(2^64-1)/2^64*(2^64-2)/2^64$≈$1$; $4$个节点不冲突的概率为$(2^64-1)/2^64*(2^64-2)/2^64*(2^64-3)/2^64$≈$1$; …… $4294967296$个节点不冲突的概率为$(2^64-1)/2^64*(2^64-2)/2^64*(2^64-3)/2^64*......*(2^64-4294967295)/2^64$≈$0.60653066$。 即$4294967296$个节点有$60%$左右的概率完全不冲突。 如果觉得这个概率不够保险,可以另取一个$K$,得到另一个$64$位整数。 用两个$64$位整数来标识一个节点,$4294967296$个节点不冲突的概率为$0.999...9$(至少连续$18$个$9$),可以放心使用了。 不知道真正实现起来还会有什么问题?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-2-17 19:56:14 | 显示全部楼层
谢谢!真正实现起来的话,其实我会把每个箱子的place,乘以16,再加一个“可接触值”,所谓“可接触值”,是描述此箱子能否被人接触到的状态,一个箱子有4个方向客被接触,所以这个“可接触值”的所有组合有16种,所以每个箱子的place要乘16。现在place的最大值是16384*16+15,得到的数列的就是262160进制的数,请问这时要如何选取那个素数k?另外,其实42亿个节点有点多了,目前我估计最多搜到1000万就要内存溢出
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-2-17 20:00:06 | 显示全部楼层
还有,选取k需要什么原理?为什么是要比16384大的16411?比16484小行吗?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-2-17 20:12:55 | 显示全部楼层
随便取。 只要不丢失全局信息,求出来的哈希值概率分布均匀即可。 你取$7$、$97$、$997$等都没问题的。 $10^7$个节点不冲突的概率约为$(10^7*10^7)/(2*2^64)$≈$2.71e-6$。 所以尽管放心使用,没有必要用两个$64$位整数来标识了,一个就足够了。 一般来说,当冲突概率较小的时候,可以这样来估计: 冲突概率$p$=$n^2/(2m)$。 其中$n$是节点数,$m$是哈希值的值域大小。
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-2-17 21:10:28 | 显示全部楼层
刚刚测试了下,发现用3,很快就出现了冲突,换了11,晚一点还是冲突,换103后就没冲突。我估计可能还是k越大越好。可能因为箱子不多,k又太小,导致code=(n+a1⋅K+a2⋅K2+a3⋅K3+......+an⋅Kn)更本没超过2^64.不知对不对?另外的原因可能是我算法错了,我是这样实现的: 假设最多10000个箱子: 以我取k=3为例: 先循环10000次求出一个k值数组: Kn=(Kn-1)*3 mod 2^64。 然后计算hashcode的时候, 循环每个箱子: hash=hash+place * Kn 这里我并没有用hash=(hash+place * Kn)mod 2^64,因为我以为如果hash+place * Kn大于2^64,系统会自己只留下64位以内的那部分
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
发表于 2010-2-19 14:12:46 | 显示全部楼层
>>> 再加一个“可接触值”,所谓“可接触值”,是描述此箱子能否被人接触到的状态 --------------------------- 像这种辅助信息,没有必要添加到HASH code里面。 也许,你这里需要的不是HASH code,而是一种信息压缩算法。也许你可以试一下算术编码压缩,应该可以达到非常高的压缩比
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
 楼主| 发表于 2010-2-21 19:27:46 | 显示全部楼层
本帖最后由 ssikkiss 于 2010-2-21 20:24 编辑 读了开源的推箱子工具JSoko(很成熟强大),发现它的hashcode算法是这样的: 1.先对一个大小为X*Y的地图的每一个格子赋值,赋值的方法是采用系统里的一个随机数发生器,使得每个格子都有一个不同的,随机的值。 2.以后要计算hashcode的时候,对每个箱子,查到他们所在的格子的值,把这些值异或,然后得到一个hashcode。 他这种算法速度十分快,而且也不用对箱子排序了,因为异或的结果是与顺序没关系的。 我照搬它的方法试了下,发现仍然有很多冲突。 不过JSoko是自己写的哈希表,出现冲突时,他把这个节点以链表的方式挂在hashcode相同的那个节点下面。而我是用的系统提供的哈希表,所有我添加到哈希表里的节点都应该不同,至于如何处理冲突则是系统的事。不过发现了他的这个算法后我有点想也像它那样自己弄hash表了。 请问,以128*128的地图为例,使用JSoko的算法,生成128*128个随机数(32位),任意选其中100个数拿出来异或,得到的值,再 mod 2^22,(2^22是哈希表的桶的数目),然后添加到哈希表对应的桶里,添加100万次后,求最长链表的长度,以及链表的平均长度?
毋因群疑而阻独见  毋任己意而废人言
毋私小惠而伤大体  毋借公论以快私情
您需要登录后才可以回帖 登录 | 欢迎注册

本版积分规则

小黑屋|手机版|数学研发网 ( 苏ICP备07505100号 )

GMT+8, 2024-11-22 14:10 , Processed in 0.026709 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表