有没有高效的位运算方法可以得到一个整数末尾具有的设置位数?例如,1110 = 10112 将是两个尾随的1位。810 = 10002 将是0个尾随的1位。
是否有比线性搜索更好的算法?我正在实现一个随机跳表,并使用随机数确定插入元素时的最大级别。我在 C++ 中处理32位整数。
编辑:汇编语言不行,请提供纯C++解决方案。
有没有高效的位运算方法可以得到一个整数末尾具有的设置位数?例如,1110 = 10112 将是两个尾随的1位。810 = 10002 将是0个尾随的1位。
是否有比线性搜索更好的算法?我正在实现一个随机跳表,并使用随机数确定插入元素时的最大级别。我在 C++ 中处理32位整数。
编辑:汇编语言不行,请提供纯C++解决方案。
计算~i & (i + 1)
,并将结果用作具有32个条目的表的查找。 1
表示零个1,2
表示一个1,4
表示两个1,依此类推,但0
表示32个1。
Bit Twiddling Hacks页面提供了许多用于计算末尾零的算法。只需首先对您的数字取反,即可适应其中任何一种算法。还有可能有巧妙的方法可以在不这样做的情况下改变算法。在具有廉价浮点运算的现代CPU上,最好的算法可能是:
unsigned int v=~input; // find the number of trailing ones in input
int r; // the result goes here
float f = (float)(v & -v); // cast the least significant bit in v to a float
r = (*(uint32_t *)&f >> 23) - 0x7f;
if(r==-127) r=32;
b = ~i & (i+1); // 这给出了一个1,位于末尾的1的左边 b--; // 这使我们只得到需要计数的末尾1 b = (b & 0x55555555) + ((b>>1) & 0x55555555); // 1位数字的2位和 b = (b & 0x33333333) + ((b>>2) & 0x33333333); // 2位数字的4位和 b = (b & 0x0f0f0f0f) + ((b>>4) & 0x0f0f0f0f); // 4位数字的8位和 b = (b & 0x00ff00ff) + ((b>>8) & 0x00ff00ff); // 8位数字的16位和 b = (b & 0x0000ffff) + ((b>>16) & 0x0000ffff); // 16位数字的和
最终,b将包含1的计数(掩码、添加和移位计算1)。 除非我犯了错误。使用前请测试。
GCC有__builtin_ctz
,其他编译器也有自己的内部函数。只需用#ifdef
来保护它:
#ifdef __GNUC__
int trailingones( uint32_t in ) {
return ~ in == 0? 32 : __builtin_ctz( ~ in );
}
#else
// portable implementation
#endif
实现Steven Sudit的想法...
uint32_t n; // input value
uint8_t o; // number of trailing one bits in n
uint8_t trailing_ones[256] = {
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,
0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 8};
uint8_t t;
do {
t=trailing_ones[n&255];
o+=t;
} while(t==8 && (n>>=8))
1(最佳)到4(最差)(平均1.004)倍(1次查找+1次比较+3次算术运算)减去一次算术运算。
可能有更好的答案,特别是如果汇编语言不是问题的话,但一个可行的解决方案是使用查找表。它将有256个条目,每个条目返回连续尾随1位的数量。将其应用于最低字节。如果是8,则应用到下一个并计数。
#define trailing_ones(x) __builtin_ctz(~x & (x + 1))
当你得到一个比之前所有数字都大的数字时,你可以简单地计算末尾的零。
这段代码计算末尾零位的数量,取自这里(也有一个依赖于IEEE 32位浮点表示的版本,但我不会相信它,而模数/除法方法看起来非常流畅 - 也值得一试):
int CountTrailingZeroBits(unsigned int v) // 32 bit
{
unsigned int c = 32; // c will be the number of zero bits on the right
static const unsigned int B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF};
static const unsigned int S[] = {1, 2, 4, 8, 16}; // Our Magic Binary Numbers
for (int i = 4; i >= 0; --i) // unroll for more speed
{
if (v & B[i])
{
v <<= S[i];
c -= S[i];
}
}
if (v)
{
c--;
}
return c;
}
然后计算末尾的1:
int CountTrailingOneBits(unsigned int v)
{
return CountTrailingZeroBits(~v);
}
基于Ignacio Vazquez-Abrams的答案实现
uint8_t trailing_ones(uint32_t i) {
return log2(~i & (i + 1));
}
log2() 的实现留给读者作为练习 (请参见这里)
i
,我们得到了在末尾设置为1的1位序列后面的0位。通过将1添加到i
,我们得到相同的位设置为1。通过and操作两者,我们得到2的幂次方,因为其他所有位都是0,对吗?然而,即使我保留lookup[i] = ~i & (i + 1)
,这如何帮助我获得我感兴趣的实际计数?对于一个数字n
?使用查找表只能得到2的幂次方,我仍然需要一种方法来获取它的对数,不是吗?所以我不确定这是否比其他解决方案更好。 - IVlad