将所有大小为<= N的连续0位的组翻转为1

时间:2012-12-11 05:58:11

标签: c++ c bit-manipulation

假设我有一个无符号的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可以在运行时变化。

4 个答案:

答案 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;
}

希望它有所帮助!!!!!!