引言
为什么散列函数采用取模运算?又为什么取模运算的被取模数最好是素数?素数是如何在取模运算中很好的规避冲突的?
这些问题可能困扰诸多程序员很久了。我们总是说素数可以更好的避免冲突,但总是对各种长篇大论的分析望而却步。
这篇文章是我在学习散列时针对素数在哈希函数中的如何成功避免大量冲突的原因总结。
尽可能言简意赅地描述为什么素数那么香。
一、结论
素数能够在取模运算中避免冲突并不是一个数学定律,而且能够避免冲突也不是绝对的。
从规律上来看,如果待存储的数列间隔恰好是是被取模数的因子大小,那么合数要比素数更容易呈现周期性的取模重复。
这仅仅是一个规律,目前数学家也无法对这一规律进行严格定义,毕竟这个规律也并不是绝对的。
二、演示
我们通过一个简单的例子来印证一下上面的这个规律:
从规律上来看,如果待存储的数列间隔恰好是是被取模数的因子大小,那么合数要比素数更容易呈现周期性的取模重复。
这个规律不是绝对的。下面选取了一个合数和一个素数,待存储的数列间隔为 2 或 3,请仔细观察规律:
1 | 数列间隔 3 (3是12的因子) | 数列 | 34 | 37 | 40 | 43 | 46 | 49 | 52 | 55 | 58 |
2 | mod 11 = | 1 | 4 | 7 | 10 | 2 | 5 | 8 | 0 | 3 | |
3 | mod 12 = | 10 | 1 | 4 | 7 | 10 | 1 | 4 | 7 | 10 | |
4 | mod 13 = | 8 | 11 | 1 | 4 | 7 | 10 | 0 | 3 | 6 | |
5 | mod 14 = | 6 | 9 | 12 | 1 | 4 | 7 | 10 | 13 | 2 | |
6 | 数列间隔 2 (2是12、14的因子) | 数列 | 67 | 69 | 71 | 73 | 75 | 77 | 79 | 81 | 83 |
7 | mod 11 = | 1 | 3 | 5 | 7 | 9 | 0 | 2 | 4 | 6 | |
8 | mod 12 = | 7 | 9 | 11 | 1 | 3 | 5 | 7 | 9 | 11 | |
9 | mod 13 = | 2 | 4 | 6 | 8 | 10 | 12 | 1 | 3 | 5 | |
10 | mod 14 = | 11 | 13 | 1 | 3 | 5 | 7 | 9 | 11 | 13 |
上图中,数列代表待存储的整型数据,一般在很多散列表(如HashMap)中,都是通过对关键字进行某种变换得到一个整型数字,比如,如果key是字符串,那么可以通过计算字符编码得到一个整数值。
mod 11 代表对11取模,mod 12 代表对 12 取模,依此类推。
我们分别选取了比较普通的两组数列,分别对合数(12、14)和素数(11、13)进行取模运算,可以看到,取模结果重复的已经使用红色标记。
当数列间隔为 3 时,由于 3 是 12 的因子,因此,可以看到表中 mod 12 的结果呈现了周期性的模冲突。而其他的 11、13、14,并没有发现明显的冲突问题,而是很好地分散了取模结果。
当数列间隔为 2 时,由于 2 是 12、14 的因子,因此,可以看到表中 mod 12 和 mod 14 的结果都呈现了周期性的模冲突,而 11、13 两个素数并没有发现明显的冲突问题,而是很好地分散了取模结果。
总结
从实验结果可以清晰的看到,素数要比合数更适合取模运算。在不知道数列间隔的情况下,拥有较少因子的素数可以有效的避免规律性的取模冲突。
大家如果对我的结论感兴趣,可以通过对比试验来尝试寻找数列间隔与因子之间的关系。