【问题标题】:Create a sequence which is ordered by bits set创建一个按位集排序的序列
【发布时间】:2013-10-18 06:32:30
【问题描述】:

我正在寻找一个可逆函数unsigned f(unsigned),它在f(i) 中设置的位数随着i 而增加,或者至少不会减少。显然,f(0) 必须为 0,并且 f(~0) 必须排在最后。两者之间有更大的灵活性。在 f(0) 之后,接下来的 32* 值必须是 1U<<01U<<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.

【问题讨论】:

  • 我认为这些问题旨在找到二进制数的总顺序,以便每个数字将更少的位设置在一个具有更多位设置的数字之前。具有相同位数集的数字可以具有任意但确定的顺序。
  • 真正的目标是不同的东西in only a few bits?例如比如汉明距离?
  • stackoverflow.com/questions/13542794/… 这个可能很方便。
  • @hasan:“中设置的位数。在英语中,你经常可以省略这样一对词:“the people who are walk there ...” -> “the people walk there ...”
  • 您确定不能增量生成序列吗?这将相当容易。

标签: c++ math


【解决方案1】:

对于每个给定的数字k,我们知道有binom(n, k)n-bit 整数恰好具有k 位的值为1。我们现在可以生成n + 1 整数的查找表,为每个k 存储有多少个数字少了一位。然后可以使用该查找表查找f(i) 的一位数o

一旦我们知道了这个数字,我们就从i 中减去这个位数的查找表值,从而得到具有给定位数的数字的排列索引p。虽然我没有在这方面进行过研究,但我很确定存在一种方法可以找到 std::vector<bool> 的 pth 排列,它在最低位用零和 o 初始化。

反向函数

查找表再次派上用场。我们可以通过计算输入整数中的一位并读取查找表来直接计算前面少一位数的个数。然后你“只”需要确定排列索引并将其添加到查找的值中,你就完成了。

免责声明

当然,这只是一个粗略的大纲,某些部分(尤其是涉及排列的部分)可能需要比听起来更长的时间。

加法

你自己说的

我正在尝试使用它来标记一个大型数据集,并优先考虑我搜索它们的顺序。

在我看来,这听起来像是从低汉明距离到高汉明距离。在这种情况下,有一个增量版本就足够了,它从前一个数字生成下一个数字:

unsigned next(unsigned previous)
{
    if(std::next_permutation(previous))
        return previous;
    else
        return (1 << (1 + countOneBits(previous))) - 1;
}

当然std::next_permutation 排列不能以这种方式工作,但我认为我的意思很清楚。

【讨论】:

  • 在这些binom(n,k) 排列中,binom(n-1, k) 的第一位是零,binom(n-1, k-1) 的第一位是设置的。听起来我需要走帕斯卡的三角形。
  • Sounds like I'd need to walk Pascal's triangle. 什么时候?得到排列?
  • @MSalters:关于子集的大小,第一位是零/一:这些计算仅适用于k = 1..n-1,但其他两种情况(k = 0/n)当然是微不足道的.
【解决方案2】:

好吧,看来我有一个合理的答案。首先让我们将binom(n,k) 定义为我们可以在n 位中设置k 的方式数。这就是经典的帕斯卡三角形:

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&lt;&lt;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&lt;&lt;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 nn out of n

同样的结构可以用来快速告诉我们f(16) 必须是什么。我们已经确定它必须包含 2 个位集,因为它位于 f(10) - f(37) 范围内。特别是,它是 6 号,设置了 2 位(像往常一样从 0 开始)。将其定义为范围内的偏移量很有用,因为我们将尝试将此范围的长度从 28 缩小到 1。

我们现在将该范围细分为 21 个以 0 开头的值和 7 个以 1 开头的值。由于 6 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)

【讨论】:

猜你喜欢
  • 2018-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-24
  • 2012-09-24
  • 1970-01-01
  • 2022-08-23
  • 1970-01-01
相关资源
最近更新 更多