我正在寻找一个可逆函数unsigned f(unsigned)
,f(i)
中设置的位数随i
增加,或至少不减少。显然,f(0)
必须为0,而f(〜0)必须是最后的。在两者之间有更多的灵活性。在f(0)之后,接下来的32 *值必须是1U<<0
到1U<<31
,但我并不关心顺序(它们都有1位设置)。
我想要一个算法,不需要计算f(0)...f(i-1)
来计算f(i)
,而完整的表格也不可行。
这类似于格雷码,但我看不到重用该算法的方法。我正在尝试使用它来标记大型数据集,并优先考虑我搜索它们的顺序。我的想法是,我有一个密钥C
,我会检查标签C ^ f(i)
。 i
的低值应该给我类似于C
的标签,即只有几位不同。
[*]奖励点不假设unsigned
有32位。
[示例] 有效的初始序列:
0, 1, 2, 4, 16, 8 ... // 16 and 8 both have one bit set, so they compare equal
无效的初始序列:
0, 1, 2, 3, 4 ... // 3 has two bits set, so it cannot precede 4 or 2147483648.
答案 0 :(得分:2)
binom(n,k)
定义为我们可以从k
位中设置n
的方式。那是经典的Pascal三角形:
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
...
轻松计算和缓存。请注意,每行的总和为1<<lineNumber
。
接下来我们需要的是该三角形的partial_sum
:
1 2
1 3 4
1 4 7 8
1 5 11 15 16
1 6 16 26 31 32
1 7 22 42 57 63 64
1 8 29 64 99 120 127 128
1 9 37 93 163 219 247 255 256
...
同样,可以通过对上一行中的两个值求和来创建此表,但每行上的新条目现在为1<<line
而不是1
。
让我们使用上面的表格为8位数字构造f(x)
(它可以简单地推广到任意数量的位)。 f(0)
仍然必须为0.查看第一个三角形中的第8行,我们会看到接下来的8个条目为f(1)
到f(9)
,所有条目都设置为一位。接下来的28个条目(7 + 6 + 5 + 4 + 3 + 2 + 1)都设置了2位,因此f(10)到f(37)。接下来的56个条目f(38)到f(93)有3个比特,并且有70个条目设置了4个比特。从对称性我们可以看出它们以f(128)为中心,特别是它们是f(94)到f(163)。很明显,8位设置的唯一数字最后排序,如f(255)。
因此,通过这些表,我们可以快速确定必须在f(i)中设置多少位。只需在表格的最后一行进行二分查找即可。但这并没有完全回答设置的位。为此我们需要前面的行。
可以从上一行创建表中的每个值的原因很简单。 binom(n,k)== binom(k,n-1)+ binom(k-1,n-1)。设置k位的数字有两种:以0...
开头的数字和以1...
开头的数字。在第一种情况下,下一个n-1
位必须包含那些k
位,在第二种情况下,下一个n-1
位必须仅包含k-1
位。特殊情况当然是0 out of n
和n out of n
。
这个结构可以用来快速告诉我们f(16)
必须是什么。我们已经确定它必须包含2位设置,因为它位于f(10) - f(37)
范围内。特别是,设置为2位的是6号(通常以0开始)。将此值定义为范围内的偏移量非常有用,因为我们会尝试将此范围的长度从28缩小到1。
我们现在将该范围细分为21个值,以0开始,7开始为1。从6&lt; 21,我们知道第一个数字是零。在剩下的7位中,仍然需要设置2,所以我们向上移动三角形中的一条线,看到15个值以两个零开始,6个以01开始。在图15中,f(16)以00开始。继续向上,7 <= 10,因此它以000
开始。但是6 == 6,所以它不是以0000
开头,而是以0001
开头。此时我们更改范围的开始,因此新偏移变为0(6-6)
我们知道需要只关注以0001
开头并且有一个额外位的数字f(16)...f(19)
。应该明白,范围是f(16)=00010001, f(17)=00010010, f(18)=00010100, f(19)=00011000
。
所以,为了计算每一位,我们在三角形中向上移动一行,比较我们的“余数”,根据比较加一个零或一个可能左一列。这意味着f(x)
的计算复杂度为O(bits)
或O(log N)
,所需的存储空间为O(bits*bits)
。
答案 1 :(得分:1)
对于每个给定的数字k
,我们知道有binom(n, k)
n
位正整数,其值为k
位。我们现在可以生成一个n + 1
整数的查找表,为每个k
存储多少个数字少于一位的整数。然后,可以使用此查找表来查找o
的一位的数字f(i)
。
一旦我们知道了这个数字,我们就从i
中减去这个位数的查找表值,这给我们留下了具有给定1位数的数字的置换索引p
。尽管我还没有在这个领域做过研究,但我确信存在一种方法可以找到std::vector<bool>
的第p个排列,它用最低位的零和o
初始化。
再次查找表就派上用场了。我们可以通过计算输入整数中的一位并在查找表中读取来直接计算少于一位的前面数字的数量。然后你“只”需要确定排列索引并将其添加到查找值中,然后就完成了。
当然这只是一个粗略的轮廓,某些部分(特别是涉及排列)可能需要比听起来更长的时间。
你说了自己
我正在尝试使用它来标记大型数据集,并优先考虑我搜索它们的顺序。
这对我来说听起来好像你会从低汉明距离到高汉明距离。在这种情况下,拥有一个增量版本就足够了,它可以生成前一个的下一个数字:
unsigned next(unsigned previous)
{
if(std::next_permutation(previous))
return previous;
else
return (1 << (1 + countOneBits(previous))) - 1;
}
当然std::next_permutation
排列不起作用,但我认为很明显我的意思是使用它。