C 使用哈希函数的高效直方图实现

C 使用哈希函数的高效直方图实现,c,algorithm,search,lookup,C,Algorithm,Search,Lookup,有没有一种比二进制搜索更有效的方法来计算直方图以获得非线性bin分布 实际上,我只对算法的一部分感兴趣,它将键值与传递函数中的bin匹配,i、 对于一组浮点值,我只想知道每个值对应的bin索引 我知道,对于线性箱子分布,你可以通过除以箱子宽度得到O1,对于非线性箱子,二进制搜索得到OlogN。我当前的实现在不相等的箱子宽度上使用二进制搜索 本着提高效率的精神,我很好奇,当存在宽度不等的存储箱时,是否可以使用哈希函数将值映射到相应的存储箱,并实现O1时间复杂度?取决于哈希的实现和使用的数据类型。对

有没有一种比二进制搜索更有效的方法来计算直方图以获得非线性bin分布

实际上,我只对算法的一部分感兴趣,它将键值与传递函数中的bin匹配,i、 对于一组浮点值,我只想知道每个值对应的bin索引

我知道,对于线性箱子分布,你可以通过除以箱子宽度得到O1,对于非线性箱子,二进制搜索得到OlogN。我当前的实现在不相等的箱子宽度上使用二进制搜索


本着提高效率的精神,我很好奇,当存在宽度不等的存储箱时,是否可以使用哈希函数将值映射到相应的存储箱,并实现O1时间复杂度?

取决于哈希的实现和使用的数据类型。对于较小的数据集,如果散列的查找开销平均较大,则更简单的算法(如二进制搜索)可能优于常量查找。 哈希的常见实现包括一个链表数组和一个哈希函数,该函数将字符串映射到链表数组中的索引。有一种称为加载因子的东西,它是哈希映射中的元素数/链表数组的长度。因此,对于负载系数<1的情况,您将在最佳情况下实现常量查找,因为在最佳情况下,任何链表都不会包含多个元素


只有一种方法可以找出哪个更好——实现一个哈希映射,自己看看。您应该能够获得接近常量的查找:

取决于哈希的实现和您正在处理的数据类型。对于较小的数据集,如果散列的查找开销平均较大,则更简单的算法(如二进制搜索)可能优于常量查找。 哈希的常见实现包括一个链表数组和一个哈希函数,该函数将字符串映射到链表数组中的索引。有一种称为加载因子的东西,它是哈希映射中的元素数/链表数组的长度。因此,对于负载系数<1的情况,您将在最佳情况下实现常量查找,因为在最佳情况下,任何链表都不会包含多个元素


只有一种方法可以找出哪个更好——实现一个哈希映射,自己看看。您应该能够获得接近常量的查找:

在一些简单的情况下,您可以获得O1

假设您的值是8位,从0到255

如果将它们拆分为8个大小为2、2、4、8、16、32、64、128的存储箱,则存储箱值范围将为:0-1、2-3、4-7、8-15、16-31、32-63、64-127、128-255

在二进制文件中,这些范围如下所示:

0000000x (bin 0)
0000001x
000001xx
00001xxx
0001xxxx
001xxxxx
01xxxxxx
1xxxxxxx (bin 7)
所以,如果你能在O1中快速计算出值中有多少最重要的零位,你就能从中得到箱子号

在这种特殊情况下,您可以预先计算一个包含256个元素的查找表,其中包含bin编号,为值查找合适的bin只是一个表查找

实际上,对于8位值,您可以使用任意大小的容器,因为查找表很小


如果要使用2次方大小的容器,也可以将此查找表用于16位值。你需要两次检查。您可以将其扩展到更长的值。

在一些简单的情况下,您可以得到O1

假设您的值是8位,从0到255

如果将它们拆分为8个大小为2、2、4、8、16、32、64、128的存储箱,则存储箱值范围将为:0-1、2-3、4-7、8-15、16-31、32-63、64-127、128-255

在二进制文件中,这些范围如下所示:

0000000x (bin 0)
0000001x
000001xx
00001xxx
0001xxxx
001xxxxx
01xxxxxx
1xxxxxxx (bin 7)
所以,如果你能在O1中快速计算出值中有多少最重要的零位,你就能从中得到箱子号

在这种特殊情况下,您可以预先计算一个包含256个元素的查找表,其中包含bin编号,为值查找合适的bin只是一个表查找

实际上,对于8位值,您可以使用任意大小的容器,因为查找表很小


如果要使用2次方大小的容器,也可以将此查找表用于16位值。你需要两次检查。您可以将其扩展到更长的值。

普通哈希函数旨在将不同的值随机分散到某个范围内。参数中的一位差异可能导致结果中的几十位差异。因此,普通哈希函数不适合问题中描述的情况

另一种方法是使用索引到bin限制表B的条目构建数组p。给定某个值x,我们通过j=P找到它所属的料仓j,或者有时找到附近的料仓[⌊x·r⌋] 其中r是一个比率,它取决于P的大小和B中的最大值。该方法的有效性取决于B中的值和大小 P

函数p的行为[⌊x·r⌋] 可以通过下面显示的python代码看到。该方法在任何编程语言中都大致相同。但是,下面给出了python-to-C的提示。假设代码存储在文件histobins.py中,并使用命令import histobins作为hb加载到ipython解释器中。然后像hb.betterparts27、99、9、80155这样的命令生成输出我不喜欢

At  80 parts, steps = 20 =  7+13
At  81 parts, steps = 16 =  7+9
At  86 parts, steps = 14 =  6+8
At  97 parts, steps = 13 =  12+1
At 108 parts, steps = 12 =  3+9
At 109 parts, steps = 12 =  8+4
At 118 parts, steps = 12 =  6+6
At 119 parts, steps = 10 =  7+3
At 122 parts, steps = 10 =  3+7
At 141 parts, steps = 10 =  5+5
At 142 parts, steps = 10 =  4+6
At 143 parts, steps = 9 =  7+2
这些参数设置为nbins=27、topsize=99、seed=9、plo=80、phi=155,为0到99的值创建一个27个箱子的测试集,随机种子为9,p的大小为80到155-1。“步数”是测试部件中的两个while循环在10*nbins值从0到topsize的测试期间运行的次数。例如,“在143个部分,步数=9=7+2”意味着当P的大小为143时,在270次试验中,261次P[⌊x·r⌋] 立即生成正确的指数;指数必须减少7倍,增加两倍

该方法的总体思想是以空间换取时间。另一个折衷是准备时间与操作时间。如果你要进行数十亿次的查找,那么值得进行数千次尝试,以找到一个好的值| p |,即p的大小。如果你只进行几百万次查找,那么最好是o只需选取一些较大的| P |值并使用它运行,或者只在较窄的范围内运行更好的部分。如果我们从较大的| P |开始,则较少的测试可能会给出足够好的结果。例如,通过“hb.betterparts27、99、9、190200”进行的10次测试会产生

At 190 parts, steps = 11 =  5+6
At 191 parts, steps = 5 =  3+2
At 196 parts, steps = 5 =  4+1

只要p与其他相关数据一起放入某一级别的缓存中,使p变大将加快访问速度。因此,使p尽可能大是一个好主意。随着p变大,一个p值与下一个p值之间的性能差异会越来越小。因此,限制速度的因素包括乘以一个p值的时间d设置while循环所需的时间。更快乘法的一种方法可能是选择2的幂作为乘法器;计算| P |进行匹配;然后使用移位或对指数进行加法,而不是乘法。花费更少时间设置while循环的一种方法是在bin[bin]时移动语句普通散列函数旨在将不同的值随机分散在某个范围内。参数中的一个位差异可能导致结果中的几十个位不同。因此,普通散列函数不适用于问题中描述的情况

另一种方法是构建一个数组p,其中的条目索引到bin limits表B中。给定一些值x,我们通过j=p找到它所属的bin j,或者有时找到附近的bin[⌊x·r⌋] 其中r是一个比率,它取决于P的大小和B中的最大值。该方法的有效性取决于B中的值和P的大小

函数p的行为[⌊x·r⌋] 可以通过下面显示的python代码看到。该方法在任何编程语言中都大致相同。但是,下面给出了python-to-C的提示。假设代码存储在文件histobins.py中,并使用命令import histobins作为hb加载到ipython解释器中。然后像hb.betterparts27、99、9、80155这样的命令生成输出我不喜欢

At  80 parts, steps = 20 =  7+13
At  81 parts, steps = 16 =  7+9
At  86 parts, steps = 14 =  6+8
At  97 parts, steps = 13 =  12+1
At 108 parts, steps = 12 =  3+9
At 109 parts, steps = 12 =  8+4
At 118 parts, steps = 12 =  6+6
At 119 parts, steps = 10 =  7+3
At 122 parts, steps = 10 =  3+7
At 141 parts, steps = 10 =  5+5
At 142 parts, steps = 10 =  4+6
At 143 parts, steps = 9 =  7+2
这些参数设置为nbins=27、topsize=99、seed=9、plo=80、phi=155,为0到99的值创建一个27个箱子的测试集,随机种子为9,p的大小为80到155-1。“步数”是测试部件中的两个while循环在10*nbins值从0到topsize的测试期间运行的次数。例如,“在143个部分,步数=9=7+2”意味着当P的大小为143时,在270次试验中,261次P[⌊x·r⌋] 立即生成正确的指数;指数必须减少7倍,增加两倍

该方法的总体思想是以空间换取时间。另一个折衷是准备时间与操作时间。如果你要进行数十亿次的查找,那么值得进行数千次尝试,以找到一个好的值| p |,即p的大小。如果你只进行几百万次查找,那么最好是o只需选取一些较大的| P |值并使用它运行,或者只在较窄的范围内运行更好的部分。如果我们从较大的| P |开始,则较少的测试可能会给出足够好的结果。例如,通过“hb.betterparts27、99、9、190200”进行的10次测试会产生

At 190 parts, steps = 11 =  5+6
At 191 parts, steps = 5 =  3+2
At 196 parts, steps = 5 =  4+1
只要p与其他相关数据一起放入某一级别的缓存中,使p变大将加快访问速度。因此,使p尽可能大是一个好主意。随着p变大,一个p值与下一个p值之间的性能差异会越来越小。因此,限制速度的因素包括乘以一个p值的时间d设置while循环的时间。一种更快倍增的方法可能是

选择2的幂作为乘数;计算| P |以匹配;然后对指数使用移位或加法,而不是乘法。减少设置while循环时间的一种方法是,如果bins[bin]是您的朋友,则移动语句。这是一种乐观的、预测性的二进制搜索,它根据输入分布的线性假设来猜测箱子的位置,而不是在每一步将搜索空间一分为二。如果线性假设为真,则为O1,但如果假设为非真,则仍然有效,尽管速度较慢。由于它的预测准确,搜索速度很快。

是你的朋友。这是一种乐观的、预测性的二进制搜索,它根据输入分布的线性假设来猜测箱子的位置,而不是在每一步将搜索空间一分为二。如果线性假设为真,则为O1,但如果假设为非真,则仍然有效,尽管速度较慢。就其预测的准确性而言,搜索速度很快。

我实际上并不担心加载因子,因为我不需要哈希表,只需要哈希函数将我从值转换为二进制。我实际上不担心加载因子,因为我不需要哈希表,只需要哈希函数将我从值转换为二进制。在一般情况下,理论上不可能。如果可能的话,那么在排序列表中搜索一个数字就会变成O1。是的,我认为你是对的,散列fn的问题是你只想从一个键计算一个值,并希望它合理地唯一,我要求散列fn映射到一个特定的bin值-这需要一个复杂得可笑的散列fn来实现。所以总结一下,答案是否定的,你不能用散列fn来表示purpose@ElKamina一般来说,你可以对数字做更多的事情,而不仅仅是比较它们。@rrenaud只需告诉我如何使用O1对一般情况和你进行比较win@ElKaminaO1和Ologn之间存在复杂性。例如,Van Emde Boas树支持在Ologm时间内搜索m位整数,通常为m=logn。您可以比在log n上更快地对int进行排序。在一般情况下,这在理论上是不可能的。如果可能的话,那么在排序列表中搜索一个数字就会变成O1。是的,我认为你是对的,散列fn的问题是你只想从一个键计算一个值,并希望它合理地唯一,我要求散列fn映射到一个特定的bin值-这需要一个复杂得可笑的散列fn来实现。所以总结一下,答案是否定的,你不能用散列fn来表示purpose@ElKamina一般来说,你可以对数字做更多的事情,而不仅仅是比较它们。@rrenaud只需告诉我如何使用O1对一般情况和你进行比较win@ElKaminaO1和Ologn之间存在复杂性。例如,Van Emde Boas树支持在Ologm时间内搜索m位整数,通常为m=logn。可以比在log n上更快地对int进行排序。这是插值排序的实现吗?这一点,P[⌊x·r⌋], 看起来像一个插值排序顺便问一下,那些半方括号是什么?我甚至不能键入它们,更不用说知道它们的意思了。仔细阅读代码后,我会说这是一个线性插值“最佳猜测”,如果没有得到正确的话,接下来是线性搜索⌊x⌋ 代表floorx,以及⌈x⌉ 将代表ceilx。使用floor或ceil of x·r可能会消除其中一个while循环。人们可能会认为这是通过查表方法进行的插值。如果您取消对showparts的注释…调用testparts并运行例如hb.testparts11、99、17、1,您可以看到p[]了解它的工作原理。此外,线性搜索部分很少运行多个过程,如果NPart足够大,通常不会运行任何过程。这是插值排序的实现吗?这一位,P[⌊x·r⌋], 看起来像一个插值排序顺便问一下,那些半方括号是什么?我甚至不能键入它们,更不用说知道它们的意思了。仔细阅读代码后,我会说这是一个线性插值“最佳猜测”,如果没有得到正确的话,接下来是线性搜索⌊x⌋ 代表floorx,以及⌈x⌉ 将代表ceilx。使用floor或ceil of x·r可能会消除其中一个while循环。人们可能会认为这是通过查表方法进行的插值。如果您取消对showparts的注释…调用testparts并运行例如hb.testparts11、99、17、1,您可以看到p[]另外,线性搜索部分很少运行多个过程,如果NPart足够大,则通常不会运行任何过程。