Hash中的一些概率计算

时间:2021-11-22 01:56:49

  Hash是把锋利的刀子,处理海量数据时经常用到,大家可能经常用hash,但hash的有些特点你是否想过、理解过。我们可以利用我们掌握的概率和期望的知识,来分析Hash中一些有趣的问题,比如:

  • 平均每个桶上的项的个数
  • 平均查找次数
  • 平均冲突次数
  • 平均空桶个数
  • 使每个桶都至少有一个项的项个数的期望

  本文hash的采用链地址法发处理冲突,即对hash值相同的不同对象添加到hash桶的链表上。

每个桶上的项的期望个数

  将n个不同的项hash到大小为k的hash表中,平均每个桶会有多少个项?首先,对于任意一个项items(i)被hash到第1个桶的概率为1/k,那么将n个项都hash完后,第1个桶上的项的个数的期望为C(项的个数)=n/k,这里我们选取了第一个桶方便叙述,事实上对于任一个特定的桶,这个期望值都是适用的。这就是每个桶平均项的个数。

  用程序模拟的过程如下:

     /***
* 对N个字符串hash到大小为K的哈希表中,每个桶上的项的期望个数
*
* @return
*/
private double expectedItemNum() {
// 桶大小为K
int[] bucket = new int[K];
// 生成测试字符串
List<String> strings = getStrings(N);
// hash映射
for (int i = 0; i < strings.size(); i++) {
int h = hash(strings.get(i), 37);
bucket[h]++;
}
// 计算每个桶的平均次数
long sum = 0;
for (int itemNum : bucket)
sum += itemNum;
return 1.0 * sum / K;
} /***
* 多次测试计算每个桶上的项的期望个数,
*/
private static void expectedItemNumTest() {
MyHash myHash = new MyHash();
// 测试100次
int tryNum = 100;
double sum = 0;
for (int i = 0; i < tryNum; i++) {
double count = myHash.expectedItemNum();
sum += count;
}
// 取100次测试的平均值
double fact = sum / tryNum;
System.out.println("K=" + K + " N=" + N);
System.out.println("程序模拟的期望个数:" + fact);
double expected = N * 1.0 / K;
System.out.println("估计的期望个数 n/k:" + expected);
}

  输出的结果如下,可以看到我们用公式计算的期望与实际是很接近的,这也说明我们的期望公式计算正确了,毕竟实践是检验真理的唯一标准。

K=1000 N=618
程序模拟的期望个数:0.6180000000000007
估计的期望个数 n/k:0.618

空桶的期望个数

  将n个不同的项hash到大小为k的hash表中,平均会有多少个空桶?我们还是以第1个桶为例,任意一个项item(i)没有hash到第一个桶的概率为(1-1/k),hash完n个项后,所有的项都没有hash到第一个桶的概率为(1-1/k)^n,这也是每个桶为空的概率。桶的个数为k,因此期望的空桶个数就是C(空桶的个数)=k(1-1/k)^n,这个公式不好计算,用程序跑还可能被归零了,转化一下就容易计算了:\begin{equation} C(空桶的个数)=k(1-\frac{1}{k})^n=k(1-\frac{1}{k})^{-k(-\frac{n}{k})}=ke^{(-\frac{n}{k})}\end{equation}  同样我们模拟测试一下:

     /***
* 计算期望的空桶个数
*
* @return
*/
private int expectedEmputyBuckts() {
// 桶大小为K
int[] bucket = new int[K];
// 生成测试字符串
List<String> strings = getStrings(N);
// hash映射
for (int i = 0; i < strings.size(); i++) {
int h = hash(strings.get(i), 37);
bucket[h]++;
}
// 记录空桶的个数
int count = 0;
for (int itemNum : bucket)
if (itemNum == 0)
count++;
return count;
} /***
* 多次测试求空桶的期望个数
*/
private static void expectedEmputyBucktsTest() {
MyHash myHash = new MyHash();
// 测试100次
int tryNum = 100;
long sum = 0;
for (int i = 0; i < tryNum; i++) {
int count = myHash.expectedEmputyBuckts();
sum += count;
}
// 取100次测试的平均值
double fact = sum / tryNum;
System.out.println("K=" + K + " N=" + N);
System.out.println("程序模拟的期望空桶个数:" + fact);
double expected = K * Math.exp(-1.0 * N / K);
System.out.println("估计的期望空桶个数ke^(-n/k):" + expected);
}

 输出结果:

K=1000 N=618
程序模拟的期望空桶个数:539.0
估计的期望空桶个数ke^(-n/k):539.021403076357

冲突次数期望

  我们这里的n个项是各不相同的,只要某个项hash到的桶已经被其他项hash过,那就认为是一次冲突,直接计算冲突次数不好计算,但我们知道C(冲突次数)=n-C(被占用的桶的个数),而被占用的桶的个数C(被占用的桶的个数)=k-C(空桶的个数),因此我们的得到:\begin{equation} C(冲突次数)=n-(k-ke^{-n/k}) \end{equation}  程序模拟如下:

     /***
* 期望冲突次数
*
* @return
*/
private int expextedCollisions() {
// 桶大小为K
int[] bucket = new int[K];
int count = 0;
// 生成测试字符串
List<String> strings = getStrings(N);
for (int i = 0; i < strings.size(); i++) {
// hash映射
int h = hash(strings.get(i), 37);
// 桶h没有被占用
if (bucket[h] == 0)
bucket[h] = 1;
// 桶h已经被占用,发生了冲突
else
count++;
}
return count;
} private static void expextedCollisionsTest() {
MyHash myHash = new MyHash();
// 测试100次
int tryNum = 100;
long sum = 0;
for (int i = 0; i < tryNum; i++) {
int count = myHash.expextedCollisions();
sum += count;
}
// 取100次测试的平均值
double fact = sum / tryNum;
System.out.println("K=" + K + " N=" + N);
System.out.println("程序模拟的冲突数:" + fact);
double expected = N - (K - K * Math.exp(-1.0 * N / K));
System.out.println("估计的期望冲突次数n-(k-ke^(-n/k)):" + expected); }

 输出结果:

K=1000 N=618
程序模拟的冲突数:157.89
估计的期望冲突次数n-(k-ke^(-n/k)):157.02140307635705

不发生冲突的概率

  将n个项hash完后,一次冲突也没有发生的概率,首先对第一个被hash的项item(1),item(1)可以hash到任意桶中,但一旦item(1)固定后,第二个项item(2)就只能hash到除item(1)所在位置的其他k-1个位置上了,依次类推,可以知道$$P(不发生冲突的概率)=\frac{k}{k}\times\frac{k-1}{k}\times\frac{k-1}{k}\times\frac{k-2}{k}\times\cdot\cdot\cdot\times\frac{k-(n-1)}{k}$$ 这个概率也是不好计算,但当k比较大、n比较小时,有$$P(不发生冲突的概率)=e^{\frac{-n(n-1)}{2k}}$$  模拟过程:

     /***
* hash N个字符串的过程是否产生冲突
*
* @return
*/
private boolean containsCollison() {
// 桶大小为K
int[] bucket = new int[K];
// 生成测试字符串
List<String> strings = getStrings(N);
for (int i = 0; i < strings.size(); i++) {
// hash映射
int h = hash(strings.get(i), 37);
// 桶h没有被占用
if (bucket[h] == 0)
bucket[h] = 1;
// 桶h已经被占用,发生了冲突,直接返回
else
return true;
}
return false;
} /***
* 重复调用多次containsCollison,计算不发生冲突的概率
*/
private static void probCollisionTest() {
MyHash myHash = new MyHash();
// 测试100次
int tryNum = 100;
// 不冲突的次数
int count = 0;
for (int i = 0; i < tryNum; i++) {
if (!myHash.containsCollison())
count++;
}
// 取100次测试的平均值
double fact = 1.0 * count / tryNum;
System.out.println("K=" + K + " N=" + N);
System.out.println("程序模拟的不冲突概率:" + fact);
double expected = Math.exp(-1.0 * N * (N - 1) / (2 * K));
System.out.println("估计的期望不冲突概率e^(-n(n-1)/(2k)):" + expected);
System.out.println("程序模拟的冲突概率:" + (1 - fact));
System.out.println("估计的期望冲突冲突概率1-e^(-n(n-1)/(2k)):" + (1 - expected)); }

 输出结果如下,这个逼近公式只有在k比较大n比较小时误差较小。

K=1000 N=50
程序模拟的不冲突概率:0.29
估计的期望不冲突概率e^(-n(n-1)/(2k)):0.29375770032353277
程序模拟的冲突概率:0.71
估计的期望冲突冲突概率1-e^(-n(n-1)/(2k)):0.7062422996764672

使每个桶都至少有一个项的项个数的期望

  实际使用Hash时,我们一开始并不知道要hash多少个项,如果把桶设置过大,会浪费空间,一般都是设置一个初始大小,当hash的项超过一定数量时,将桶的大小扩大一倍,并将桶内的元素重新hash一遍。查看Java的HashMap源码可以看到,每次调用put添加数据都会检查大小,当n>k*装置因子时,对hashMap进行重建。

 public V put(K key, V value) {
if(...)
return ...;
...
modCount++;
addEntry(hash, key, value, i);
return null;
}
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
} createEntry(hash, key, value, bucketIndex);
}

  现在我们不是直接当n大于某一个数时对Hash表进行重建,而是预计Hash表的每一个桶都至少有了一个项时,才对hash表进行重建,现在问当n为多少时,每个桶至少有了一个项。要计算这个n的期望,我们先设$X_i$表示从第一次占用$i-1$个桶到第一次占用$i$个桶所插入的项的个数。首先,很容易理解$X_1=1$,对于$X_2$表示从插入第一个元素后,占用两个桶所需要的插入次数,理论上它可以是任意大于1的值,我们一次接一次的插入项,每次插入有两种独立的结果,一个结果是映射到的桶是第一次映射的桶;另一个是映射到的桶是新的桶,当占用了新桶时插入了项的个数即为$X_2$,又因为此时映射到新桶的概率$p=\frac{k-1}{k}$,因此$X_2$的期望$E(X_2)=\frac{1}/{p}=\frac{k}{k-1}$;同样的道理,占用两个桶后,对任意一次hash映射到新桶的概率为$\frac{k-2}{k}$,因此$E(X_2)=\frac{k}{k-2}$。

  现在定义随机变量$X=X_1+X_2+\cdot\cdot\cdot+X_k$,我们可以看出$X$实际上就是每个桶都填上项所需要插入的项的个数。$$E(X)=\sum_{j=1}^k E(X_j)$$$$=\sum_{j=1}^k \frac{k}{k-j+1}$$$$=k\sum_{j=1}^k \frac{1}{k-j+1}$$$$\overset{\underset{\mathrm{令i=k-j+1}}{}}{=}k\sum_{i=1}^k \frac{1}{i}$$  上面这个数是一个有趣的数,叫做调和数(Harmonic_number),这个数(常记做$H_k$)没有极限,但已经有数学家给我们确定了它关于n的一个等价近似值:$$\frac{1}{4}+\ln k\le H_k \le 1+\ln k$$  因此$E(X)=O(k\ln k)$,当项的个数为$k\ln k$时,平均每个桶至少有一个项。

结论总结

  • 每个桶上的项的期望个数:将n个项hash到大小为k的hash表中,平均每个桶的项的个数为${\frac{n}{k}}$
  • 空桶的期望个数:将n个项hash到大小为k的hash表中,平均空桶个数为$ke^{(-\frac{n}{k})}$
  • 冲突次数期望:当我们hash某个项到一个桶上,而这个桶已经有项了,就认为是发生了冲突,将n个项hash到大小为k的hash表中,平均冲突次数为$n-(k-ke^{-n/k})$
  • 不发生冲突的概率:$$P(不发生冲突的概率)=e^{\frac{-n(n-1)}{2k}}$$
  • 调和数:$H_k=\sum_{i=1}^k \frac{1}{i}$称为调和数,$\sum_{i=1}^k \frac{1}{i}=\Theta{logk}$

  本文主要参考自参考文献[1],写这边博客复习了一下组合数学和概率论的知识,对hash理解得更深入了一点,自己设计hash结构时能对性能有所把握。另外还学会了在博客园插入公式,之前都是在MathType敲好再截图。

  希望本文对您有所帮助,欢迎评论交流!

转载请注明出处:http://www.cnblogs.com/fengfenggirl

参考文献:

[1].http://www.math.dartmouth.edu/archive/m19w03/public_html/Section6-5.pdf

[2].http://preshing.com/20110504/hash-collision-probabilities/

Hash中的一些概率计算的更多相关文章

  1. HMM的概率计算问题和预测问题的java实现

    HMM(hidden markov model)可以用于模式识别,李开复老师就是采用了HMM完成了语音识别. 一下的例子来自于<统计学习方法> 一个HMM由初始概率分布,状态转移概率分布, ...

  2. 隐马尔可夫模型HMM(二)概率计算问题

    摘自 1.李航的<统计学习方法> 2.http://www.cnblogs.com/pinard/p/6955871.html 一.概率计算问题 上一篇介绍了概率计算问题是给定了λ(A,B ...

  3. JAVA实现概率计算(数字不同范围按照不同几率产生随机数)

    程序中经常遇到随机送红包之类的情景,这个随机还得指定概率,比如10%的机率可以得到红包.那么java怎么实现一个简单的概率计算了,见如下例子: int randomInt = RandomUtils. ...

  4. 算法笔记&lowbar;155&colon;算法提高 概率计算(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 问题描述 生成n个∈[a,b]的随机整数,输出它们的和为x的概率. 输入格式 一行输入四个整数依次为n,a,b,x,用空格分隔. 输出格式 输出一行 ...

  5. jquery抽奖插件&plus;概率计算

    写了一个抽奖的jquery插件和计算概率的方法, 结合起来就是一个简单的概率抽奖, 不过实际项目中基本不会把抽奖概率的计算放在前端处理~. demo lottery.jquery.js $.fn.ex ...

  6. 条件随机场&lpar;CRF&rpar; - 3 - 概率计算问题

    声明: 1,本篇为个人对<2012.李航.统计学习方法.pdf>的学习总结,不得用作商用,欢迎转载,但请注明出处(即:本帖地址). 2,由于本人在学习初始时有很多数学知识都已忘记,所以为了 ...

  7. nyoj 概率计算

    概率计算 时间限制:1000 ms  |  内存限制:65535 KB 难度:1   描述 A和B两个人参加一场答题比赛.比赛的过程大概是A和B两个人轮流答题,A先答.一旦某人没有正确回答问题,则对手 ...

  8. PTA 社交网络图中结点的&OpenCurlyDoubleQuote;重要性”计算(30 分)

    7-12 社交网络图中结点的“重要性”计算(30 分) 在社交网络中,个人或单位(结点)之间通过某些关系(边)联系起来.他们受到这些关系的影响,这种影响可以理解为网络中相互连接的结点之间蔓延的一种相互 ...

  9. Excel中最精确的计算年龄的公式

    身份证算年龄 假设A1是身份证号所在单元格 =IF(MONTH(NOW())<INT(MID(A1,11,2)),INT(YEAR(NOW())-INT(MID(A1,7,4)))-1,IF(M ...

随机推荐

  1. 微信共享收货地址 edit&lowbar;address&colon;fail 吐白沫级解决方案

    又被微信坑了一整天,看完官方文档怎么测试都不通过,我一直怀疑是新版本微信支付我没有设置“共享收货地址”开关造成的. 后来经过验证,新版本不需要做这件事了. 那么,我没错,是微信的文档没及时更新... ...

  2. PyQt4入门

    PyQt4入门教程(6)_对话框 文中译者的话将用方括号[]标出.对话框(Dialogs)是现代GUI程序中不可缺少的一部分.对话本来指的是两个或者更多人之间的交流,而在计算机应用中,对话是一个可以让 ...

  3. QT TableWidget 应用笔记

    QT TableWidget应用笔记 分类: QT2013-05-21 16:22 2561人阅读 评论(0) 收藏 举报 1.设置表头及大小 QStringList header; header&l ...

  4. Mac 下使用sourcetree操作git教程

    SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端,同时也是Mercurial和Subversion版本控制系统工具.支持创建.克隆.提交.push.pu ...

  5. shopex商城的部署和安装

    1.在网站上下载最新的压缩包: 2.shopex是商业软件,不开源,且源代码是加密的! 3.如果你出现zend的错误,是因为你的php环境没有安装此插件,推荐使用phpstudy最新版本,我使用的ph ...

  6. AppleWatch开发教程之Watch应用对象新增内容介绍以及编写运行代码

    AppleWatch开发教程之Watch应用对象新增内容介绍以及编写运行代码 添加Watch应用对象时新增内容介绍 Watch应用对象添加到创建的项目中后,会包含两个部分:Watch App 和 Wa ...

  7. &lbrack;UVa1213&rsqb;Sum of Different Primes(递推,01背包)

    题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...

  8. static和public

    static:静态.   可以设置:静态类.静态变量.静态方法.   没有使用static修饰的成员为实例成员. 静态成员的使用:通过类名.   1.不加static修饰的成员是对象成员,归每个对象所 ...

  9. jquery 在ie10中post数据,最终数据丢失的BUG修复

    最近在做项目的时候,发现ie10或者360之类套壳的浏览器(ie10) 在jquery调用post数据的时候,真实的请求并没有上传数据,原因不表,请见 http://*.com ...

  10. PHP pear安装出现 Warning&colon; require&lowbar;once&lpar;Structures&sol;Graph&period;php&rpar;&period;&period;&period;错误

    今天在WINDOWS安装pear,一路无阻很顺利安装完成,接着想安装下pear email包来玩下,但接下来却报: Warning: require_once(Structures/Graph.php ...