在位数组中找到N个1位的字符串

时间:2010-02-11 20:40:20

标签: c++ c bit-manipulation

作为标题sais,我想在可变大小(M)的位数组中找到n个一位的连续运行。

通常的用例是N <= 8且M <= 128

我在嵌入式设备的内环中做了很多这样的操作。编写一个简单的实现很容易但不够快我的口味(例如在找到解决方案之前进行强力搜索)。

我想知道是否有人在他的技巧包中有更优雅的解决方案。

8 个答案:

答案 0 :(得分:4)

int nr = 0;    
for ( int i = 0; i < M; ++i )
{
  if ( bits[i] )
    ++nr;
  else
  {
    nr = 0; continue;
  }
  if ( nr == n ) return i - nr + 1; // start position    
}

蛮力是什么意思? O(M * N)或这个O(M)溶液?如果你的意思是这样,那么我不确定你能做多少优化。

我们可以通过遍历每个字节而不是每一个字来实现持续改进。想到这一点: 当我说字节时,我的意思是这次是N位的序列。

for ( int i = 0; i < M; i += N )
  if ( bits[i] == 0 ) // if the first bit of a byte is 0, that byte alone cannot be a solution. Neither can it be a solution in conjunction with the previous byte, so skip it.
    continue;
  else // if the first bit is 1, then either the current byte is a solution on its own or it is a solution in conjunction with the previous byte
  {
    // search the bits in the previous byte.
    int nrprev = 0;
    while ( i - nrprev >= 0 && bits[i - nrprev] ) ++nrprev;

    // search the bits in the current byte;
    int nrcurr = 0;
    while ( bits[i + nrcurr + 1] && nrcurr + nrprev <= N ) ++nrcurr;

    if ( nrcurr + nrprev >= N ) // solution starting at i - nrprev + 1.
      return i - nrprev + 1;   
  }

未经测试。可能需要一些额外的条件来确保正确性,但这个想法似乎很合理。

答案 1 :(得分:2)

答案 2 :(得分:1)

使用查找表展开内部循环。

有四类字节:

00000001 - // Bytes ending with one or more 1's.  These start a run.
11111111 - // All 1's.  These continue a run.
10000000 - // Bytes starting with 1's but ending with 0's.  These end a run.
10111000 - // All the rest.  These can be enders or short runs.

创建一个查找表,让您区分这些。然后一次处理一个字节的位数组。

修改

我想对查找表的内容稍微模糊一些。具体来说,我建议您需要三个表,每个表有256个条目,具有以下特征:

Number of bits set.
Number of bits set before first zero.
Number of bits set after last zero.

根据您的操作方式,您可能不需要第一个。

答案 3 :(得分:1)

我在MIPS核心上运行的嵌入式设备上做了类似的事情。 MIPS架构包括CLZ指令(“计数前导零”),它将返回指定寄存器的前导零位数。如果需要计算前导的一位,只需在调用CLZ之前反转数据。

示例,假设您有一个C语言函数CLZ作为汇编指令的别名:

unsigned numbits = 0, totalbits = 0;
while (data != 0 && numbits != N) {
    numbits = CLZ(data);  // count leading zeroes
    data <<= numbits;     // shift off leading zeroes
    totalbits += numbits; // keep track of how many bits we've shifted off
    numbits = CLZ(~data); // count leading ones
    data <<= numbits;     // shift off leading ones
    totalbits += numbits; // keep track of how many bits we've shifted off
}

在此循环结束时,totalbits将指示第一次运行N个连续一位的偏移量(以左为单位)。循环内的每一行都可以用单个汇编指令表示(第四行除外,它需要一秒钟用于反转操作)。

其他非MIPS架构可能有类似的指令。

答案 4 :(得分:1)

简单SWAR回答:

考虑到您正在检查的值V,请使用N M位宽寄存器。对于n中的所有N,请将n注册为V >> n

bitwise AND(all N)转储到另一个M-wide寄存器中。然后只需找到该寄存器中设置的位,这将是全位运行的开始。

我确定如果你没有M位宽的寄存器,你可以将它调整为更小的寄存器大小。

答案 5 :(得分:1)

这很容易解决,而且你不需要count-zeroo指令。

y = x ^ x-1

为您提供1到1 {1}}中最不重要的1位字符串。

x

是下一个可能是1或0的个别位,

y + 1

从该位给出一个1的字符串,直到下一个1位。

然后你可以将搜索模式乘以(y + 1)并递归...

我正在研究一种算法来获取字符串......等等......

是的......很容易解决......当我正在努力的时候,注意还有另外一个技巧。如果将一个单词划分为x ^ x-(y+1) 位的子串,则一系列n 1必须至少包含一个子串。为简单起见,假设子串是4位,字是32位。您可以同时检查子字符串以快速过滤输入:

≥2n-1

这是有效的,因为在加法运算之后,const unsigned int word_starts = 0x11111111; unsigned int word = whatever; unsigned int flips = word + word_starts; if ( carry bit from previous addition ) return true; return ~ ( word ^ flips ) & word_starts; 中与flips中的1位对应的每个位(除了第一位)等于(通过二进制加法的定义)

word_starts

你可以通过word ^ carry_from_right ^ 1 再次提取进位,取消和取消。如果没有设置进位,则不存在1字符串。

不幸的是,你必须检查最后的进位,C不能做,但大多数处理器都可以。

答案 6 :(得分:1)

如果您使用的是英特尔兼容平台,BSF(位扫描转发)和BSR(位扫描反转)asm指令可以帮助您丢弃第一个和最后一个零位。这比蛮力方法更有效。

答案 7 :(得分:1)

对于你正在做的事情,这可能有点过头但是我需要一些重量级的自定义文件系统块分配。如果N < 32然后你可以删除代码的后半部分。

为了向后兼容,第一个字的最高有效位被视为位0。

请注意,该算法在末尾使用一个标记字(全零)来停止任何搜索,而不是不断检查数组的结尾。还要注意,该算法允许搜索从位数组中的任何位置开始(通常是最后一次成功分配的结束),而不是始终从位数组的开头开始。

提供您自己的编译器特定的msbit32()函数。

#define leftMask(x) (((int32_t)(0x80000000)) >> ((x) - 1))     // cast so that sign extended (arithmetic) shift used
#define rightMask(x) (1 << ((x) - 1))


/* Given a multi-word bitmap array find a run of consecutive set bits and clear them.
 *
 * Returns 0 if bitrun not found.
 *         1  if bitrun found, foundIndex contains the bit index of the first bit in the run (bit index 0 is the most significant bit of the word at lowest address).
 */

static int findBitRun(int runLen, uint32_t *pBegin, uint32_t *pStartMap, uint32_t *pEndMap, uint32_t *foundIndex)
{
    uint32_t *p = pBegin;
    unsigned int bit;

    if (runLen == 1)
    {    // optimise the simple & hopefully common case
        do {
            if (*p)
            {
                bit = msbit32(*p);
                *p &= ~(1 << bit);
                *foundIndex = ((p - pStartMap) * 32ul) + (31 - bit);
                return 1;
            }
            if (++p > pEndMap)
            {
                p = pStartMap;
            }
        } while (p != pBegin);
    }

    else if (runLen < 32)
    {
        uint32_t rmask = (1 << runLen) - 1;
        do {
            uint32_t map = *p;
            if (map)
            {
                // We want to find a run of at least runLen consecutive ones within the word.
                // We do this by ANDing each bit with the runLen-1 bits to the right
                // if there are any ones remaining then this word must have a suitable run.

                // The single bit case is handled above so can assume a minimum run of 2 required

                uint32_t w = map & (map << 1); // clobber any 1 bit followed by 0 bit
                int todo = runLen - 2;  // -2 as clobbered 1 bit and want to leave 1 bit

                if (todo > 2)
                {
                    w &= w << 2;      // clobber 2 bits
                    todo -= 2;

                    if (todo > 4)
                    {
                        w &= w << 4;      // clobber 4 bits
                        todo -= 4;
                        if (todo > 8)
                        {
                            w &= w << 8;      // clobber 8 bits
                            todo -= 8;
                        }
                    }
                }

                w &= w << todo;     // clobber any not accounted for

                if (w)              // had run >= runLen within word
                {
                    bit = msbit32(w); // must be start of left most run
                    *p &= ~(rmask << ((bit + 1) - runLen));
                    *foundIndex = ((p - pStartMap) * 32ul) + (31 - bit);
                    return 1;
                }
                else if ((map & 1) && (p[1] & 0x80000000ul))    // assumes sentinel at end of map
                {
                    // possibly have a run overlapping two words
                    // calculate number of bits at right of current word
                    int rbits = msbit32((map + 1) ^ map);
                    int lmask = rmask << ((32 + rbits) - runLen);
                    if ((p[1] | lmask) == p[1])
                    {
                        p[0] &= ~((1 << rbits) - 1);
                        p[1] &= ~lmask;
                        *foundIndex = ((p - pStartMap) * 32ul) + (32 - rbits);
                        return 1;
                    }
                }
            }
            if (++p > pEndMap)
            {
                p = pStartMap;
            }
        } while (p != pBegin);
    }
    else    // bit run spans multiple words
    {
        pEndMap -= (runLen - 1)/32;    // don't run off end
        if (pBegin > pEndMap)
        {
            pBegin = pStartMap;
        }

        do {
            if ((p[0] & 1) && ((p[0] | p[1]) == 0xfffffffful))   // may be first word of run
            {
                uint32_t map = *p;
                uint32_t *ps = p;      // set an anchor
                uint32_t bitsNeeded;
                int sbits;

                if (map == 0xfffffffful)
                {
                    if (runLen == 32)        // easy case
                    {
                        *ps = 0;
                        *foundIndex = (ps - pStartMap) * 32ul;
                        return 1;
                    }
                    sbits = 32;
                }
                else
                {
                    sbits = msbit32((map + 1) ^ map);
                }

                bitsNeeded = runLen - sbits;

                while (p[1] == 0xfffffffful)
                {
                    if (bitsNeeded <= 32)
                    {
                        p[1] = ~(0xfffffffful << (32 - bitsNeeded));
                        while (p != ps)
                        {
                            *p = 0;
                            --p;
                        }
                        *ps &= ~rightMask(sbits);
                        *foundIndex = ((p - pStartMap) * 32ul) + (32 - sbits);
                        return 1;
                    }
                    bitsNeeded -= 32;
                    if (++p == pBegin) 
                    {
                        ++pBegin;   // ensure we terminate
                    }
                }

                if ((bitsNeeded < 32) & (p[1] & 0x80000000ul))
                {
                    uint32_t lmask = leftMask(bitsNeeded);

                    if ((p[1] | lmask) == p[1])
                    {
                        p[1] &= ~lmask;
                        while (p != ps)
                        {
                            *p = 0;
                            --p;
                        }
                        *ps &= ~rightMask(sbits);
                        *foundIndex = ((p - pStartMap) * 32ul) + (32 - sbits);
                        return 1;
                    }
                }
            }

            if (++p > pEndMap)
            {
                p = pStartMap;
            }
        } while (p != pBegin);
    }

    return 0;
}