假设我有一个无符号的M位整数(其中M是8,16,32,64中的一个),其中有不同的0位运行:
... 111 000 11 00 1 0000 1 0 1 00000 111 ...
给定数量N,其中0 <= N <= M,我想要填充整数中大于&lt; = N的所有0组。因此,如果对于上述整数,我们给出N = 3,结果将是:
... 111 111 11 11 1 0000 1 1 1 00000 111 ...
请注意,4组和5组零没有被翻转,因为它们的大小是> 0。 3。
我如何在C / C ++中编写高效的实现?我假设有一些聪明的小事我能做,但我不知道从哪里开始。我已经看到乘法用于传播一点模式,但不是这种变长检查。由于同样的原因,查找表似乎很痛苦。一个位置较好的减法可以翻转一个比特,但找出减去的内容看起来很棘手。
编辑:要清楚,虽然M在编译时是固定的,但N可以在运行时变化。
答案 0 :(得分:10)
尝试这样的事情:
x = ~x;
for (i=0; i<N; i++) x&=x/2;
for (i=0; i<N; i++) x|=x*2;
x = ~x;
不操作是为了说明零在顶部而不是从“移入”的事实。您可以通过手动引入顶部的一个来避免它;那么&=
和|=
步骤也会反转。
顺便说一下,如果这确实有效,你可能想为每个N编写一个展开版本,而不是使用这些循环。
答案 1 :(得分:5)
x = ~x;
for (j = 1; j <= N/2; j *= 2) x &= (x >> j);
x &= (x >> (N - j + 1));
for (j = 1; j <= N/2; j *= 2) x |= (x << j);
x |= (x << (N - j + 1));
x = ~x;
与R ..的解决方案相同,但有点优化。
为了优化更多,可以消除第二个循环:
t = ~x;
m = x & (t << 1);
for (j = 1; j <= N/2; j *= 2) t &= (t >> j);
t &= (t >> (N - j + 1));
t |= ((m - t) & ~m);
x = ~t;
这里唯一剩下的循环移位了位组(与前一个变量完全相同),但是使用简单的逐位技巧代替第二个循环,恢复长于N组。
示例(N = 4):
input string 110000100000011
inverted one 001111011111100
loop iter. 1 000111001111100
loop iter. 2 000001000011100
one more iter 000000000001100
第一次循环迭代正常工作,因为每个位组前面至少有一个零位。结果,我们在每个位组之前至少有两个零位。因此可以在第二次循环迭代中一次移位两位。出于同样的原因,第三次循环迭代可以一次移位4位,等等。但是这个例子不需要大于2位的移位。由于循环已经将位组移位了3位,我们必须将它们移位N-3 = 1位以上(这是在循环之后的下一行)。
现在较小的位组消失了,但较大的位组由一对位表示。为了重建剩余的组,可以使用第二循环:
starting with 000000000001100
loop iter. 1 000000000011100
loop iter. 2 000000001111100
one more iter 000000011111100
result 111111100000011
或者代替第二个循环,我们可以使用按位技巧:
m 010000100000000
t 000000000001100
m-t 010000011110100
(m-t) & ~m 000000011110100
t|((m-t)&~m) 000000011111100
result 111111100000011
m
标志着每个小组的开始。 m-t
恢复所有移出的位。下一个操作清除m
的未使用位。需要再做一次操作,将恢复的位与移位循环后剩余的位组合起来。
基准测试结果(AMD K8,GCC 4.6.3 -O2),秒:
N one_loop two_loops unoptimized
1 3.9 4.2 3.3
2 4.6 6.2 5.2
3 4.6 6.2 7.1
4 5.6 7.9 8.9
5 5.6 7.9 11.3
6 5.6 7.9 13.3
15 6.7 10.0 46.6
答案 2 :(得分:3)
修改:对于此问题,R..
的解决方案更好,除非N
相对较大(在我的机器上,M = 32
的交叉点是大约N = 18
,但是正在测试所有可能的32位数字,这可能不代表预期的用途)。所以我赞成了这个解决方案,但是我要离开我的,因为它显示了如何在一个单词中迭代0
s(或者,带有一个小的修改1
s)的跨度,这可能是对其他目的有用。
这是你的例子,范围和前面的1标记为:
111 000 11 00 1 0000 1 0 1 00000 111
1 000 1 00 1 0
假设我们从每个范围中减去“1”:(相对于范围,而不是实数1)
0 111 0 11 0 1
然后将其添加回原始版本:
111 111 11 11 1 0000 1 1 1 00000 111
好的,那看起来不错。因此,我们需要计算出要减去的所有1
s,这将是每个大小≤≤N的最后0
的位置。并且要计算出范围的大小,我们还需要找到前面的1
。
所以,基本上我们需要通过交替搜索最后0
和最后0
来查找1
s的所有跨度,注意将尾随位设置为全部1
个或全部0
个。如果范围不是太大,那么我们可以减去最后一个0
位置的位,并将1
加回到最后1的位置,这将使所有{{1}在0
s的范围内。我担心这不是很清楚,所以让我们尝试一些代码。
首先是以下小功能:
1
尝试一下,直到你确信它有效。请注意,它们都返回一位,如果没有任何unsigned int last_one(unsigned int k) { return k & -k; }
unsigned int last_zero(unsigned int k) { return (k + 1) & ~k; }
接下来,跨度有多大?计算last_{one,zero}
s会有点痛苦,因为这涉及找到位置(不是那么困难,但不必要)。但我们并不关心实际数字是多少;我们只需要知道它与0
的比较。但是,如果最后N
与最后N
(标记)的比率最多为2 N <,我们可以看到跨度的大小最多为1
位/ SUP>
所以我们把所有这些放在一起。
0
答案 3 :(得分:1)
我相信下面的代码可以满足您的需求,您仍然可以优化它,我将其留给您...
#define BIT_GROUP 4
int main()
{
uint32_t i, j, n;
uint8_t bit_pos, zero_bit_pos, count;
i = n = 0xaa05;
printf("%x\n", i);
while (n)
{
if (n & 1)
printf("1");
else
printf("0");
n >>= 1;
}
printf("\n");
n = i;
bit_pos = count = 0;
while(n)
{
if(!(n & 1))
{
if(count <= BIT_GROUP)
{
zero_bit_pos = bit_pos;
count++;
}
else
{
zero_bit_pos = 0;
count = 0;
j = 0;
}
if(count <= BIT_GROUP)
{
j = ((1 << count) - 1);
}
}
else
{
j <<= ((zero_bit_pos + 1) - count);
i |= j;
j = 0;
zero_bit_pos = 0;
count = 0;
}
bit_pos++;
n >>= 1;
}
n = i;
while (n)
{
if (n & 1)
printf("1");
else
printf("0");
n >>= 1;
}
printf("\n");
printf("%x\n", i);
return 0;
}
希望它有所帮助!!!!!!