找到没有循环的32位数字的最高位集的索引

时间:2012-01-28 00:23:41

标签: algorithm bit-manipulation

这是一个艰难的(至少我很难:P):

在不使用任何循环的情况下找到32位数字的最高位集的索引。

11 个答案:

答案 0 :(得分:5)

使用递归:

int firstset(int bits) {        
     return (bits & 0x80000000) ? 31 : firstset((bits << 1) | 1) - 1;
}
  • 假设[31,..,0]索引
  • 如果没有设置位
  • ,则返回-1
  • | 1通过限制轮班次数来阻止堆栈溢出,直到达到1为止(32)
  • not tail recursive:)

答案 1 :(得分:3)

对数基数二的底板应该可以解决问题(尽管你必须使用特殊情况0)。

Floor of log base 2 of 0001 is 0 (bit with index 0 is set).
 "           "      of 0010 is 1 (bit with index 1 is set).
 "           "      of 0011 is 1 (bit with index 1 is set).
 "           "      of 0100 is 2 (bit with index 2 is set).
and so on.

在一个不相关的说明中,这实际上是一个非常可怕的面试问题(我说这是对潜在候选人进行技术采访的人),因为它实际上与你在实际编程中所做的任何事情都不相符。

你的老板有一天不会来找你并说“嘿,所以我们急需这个最新功能,而且需要在没有循环的情况下实施!”

答案 2 :(得分:2)

你可以这样做(未优化):

int index = 0;
uint32_t temp = number;

if ((temp >> 16) != 0) {
    temp >>= 16;
    index += 16;
}

if ((temp >> 8) != 0) {
    temp >>= 8
    index += 8;
}

...

答案 3 :(得分:1)

这可以作为二进制搜索来完成,将O(N)(对于N位字)的复杂度降低到O(log(N))。可能的实现是:

int highest_bit_index(uint32_t value)
{ 
  if(value == 0) return 0;
  int depth = 0;
  int exponent = 16;

  while(exponent > 0)
  {
    int shifted = value >> (exponent);
    if(shifted > 0)
    {
      depth += exponent;
      if(shifted == 1) return depth + 1;
      value >>= exponent;
    }
    exponent /= 2;
  }

  return depth + 1;
}

输入是32位无符号整数。 它有一个循环,可以转换为5级if语句,因此产生32个左右的if语句。你也可以使用递归摆脱循环,或绝对邪恶的“goto”;)

答案 4 :(得分:1)

抱歉碰到一个旧线程,但这个怎么样

inline int ilog2(unsigned long long i) {
  union { float f; int i; } = { i }; 
  return (u.i>>23)-27;
}
...
int highest=ilog2(x); highest+=(x>>highest)-1;
// and in case you need it
int lowest = ilog2((x^x-1)+1)-1;

答案 5 :(得分:0)

让 n - 要识别的位位置的十进制数 start - 表示(1&lt;&lt;&lt; 32) - 2147483648的十进制值 bitLocation - 表示设置为1的位位置

public int highestBitSet(int n, long start, int bitLocation)
{
    if (start == 0)
    {
        return 0;
    }
    if ((start & n) > 0)
    {
        return bitLocation;
    }
    else
    {
        return highestBitSet(n, (start >> 1), --bitLocation);
    }
}

    long i = 1;
    long startIndex = (i << 31);
    int bitLocation = 32;
    int value = highestBitSet(64, startIndex, bitLocation);
    System.out.println(value);

答案 6 :(得分:0)

int high_bit_set(int n, int pos)
{
if(pos<0) 
return -1;
else
return (0x80000000 & n)?pos:high_bit_set((n<<1),--pos);
}

main()
{
int n=0x23;
int high_pos = high_bit_set(n,31);
printf("highest index = %d",high_pos);
}

来自主调用函数high_bit_set(int n , int pos),输入值为n,默认31为最高位置。功能如上所述。

答案 7 :(得分:0)

Paislee's solution实际上很容易做出尾递归,但它比建议的楼层(log2(n))慢得多;

int firstset_tr(int bits, int final_dec) {

     // pass in 0 for final_dec on first call, or use a helper function

     if (bits & 0x80000000) {
      return 31-final_dec;
     } else {
      return firstset_tr( ((bits << 1) | 1), final_dec+1 );
     }
}

此功能也适用于其他位尺寸,只需更改支票, e.g。

if (bits & 0x80) {   // for 8-bit
  return 7-final_dec;
}

答案 8 :(得分:0)

请注意,您要做的是计算整数的整数log2,

#include <stdio.h>
#include <stdlib.h>

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

注意您可以尝试一次搜索多于1位。

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

此方法使用二进制搜索

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

另一种二元搜索方法,可能更具可读性

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

因为你想测试这些,

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}

答案 9 :(得分:0)

非常有趣的问题,我将为您提供基准测试答案


使用循环的解决方案

uint8_t highestBitIndex( uint32_t n )
{
    uint8_t r = 0;
    while ( n >>= 1 )
        r++;
    return r;
}

这有助于更好地理解问题,但效率很低。


使用log的解决方案

这种方法也可以通过log方法来总结

uint8_t highestSetBitIndex2(uint32_t n) {
    return (uint8_t)(log(n) / log(2));
}

但是它还是低效的(甚至超过一个,请参见基准)


使用内置指令的解决方案

uint8_t highestBitIndex3( uint32_t n )
{
    return 31 - __builtin_clz(n);
}

此解决方案虽然非常有效,但它只能在特定的编译器(gcc和clang可以使用)和特定的平台上运行。

NB:如果我们想要索引,它是31,而不是32


内部解决方案

#include <x86intrin.h> 

uint8_t highestSetBitIndex5(uint32_t n)
{
    return _bit_scan_reverse(n); // undefined behavior if n == 0
}

这将在汇编级调用bsr指令


使用内联汇编的解决方案

LZCNT和BSR可以通过以下功能汇总在一起:

uint8_t highestSetBitIndex4(uint32_t n) // undefined behavior if n == 0
{
    __asm__ __volatile__ (R"(
        .intel_syntax noprefix
            bsr eax, edi
        .att_syntax noprefix
        )"
            );
}

uint8_t highestSetBitIndex7(uint32_t n) // undefined behavior if n == 0
{
    __asm__ __volatile__ (R"(.intel_syntax noprefix
        lzcnt ecx, edi
        mov eax, 31
        sub eax, ecx
        .att_syntax noprefix
    )");
}

注意:除非您知道自己在做什么,否则请勿使用


使用查找表和幻数乘法(可能是最好的AFAIK)的解决方案

首先,您使用以下功能清除除最高位以外的所有位:

uint32_t keepHighestBit( uint32_t n )
{
    n |= (n >>  1);
    n |= (n >>  2);
    n |= (n >>  4);
    n |= (n >>  8);
    n |= (n >> 16);
    return n - (n >> 1);
}

信用:这个想法来自小亨利·沃伦(Henry S. Warren),在他的《骇客的喜悦》一书中

然后,我们使用基于DeBruijn's Sequence的算法来执行一种二进制搜索:

uint8_t highestBitIndex8( uint32_t b )
{
    static const uint32_t deBruijnMagic = 0x06EB14F9; // equivalent to 0b111(0xff ^ 3)
    static const uint8_t deBruijnTable[64] = {
         0,  0,  0,  1,  0, 16,  2,  0, 29,  0, 17,  0,  0,  3,  0, 22,
        30,  0,  0, 20, 18,  0, 11,  0, 13,  0,  0,  4,  0,  7,  0, 23,
        31,  0, 15,  0, 28,  0,  0, 21,  0, 19,  0, 10, 12,  0,  6,  0,
         0, 14, 27,  0,  0,  9,  0,  5,  0, 26,  8,  0, 25,  0, 24,  0,
     };
    return deBruijnTable[(keepHighestBit(b) * deBruijnMagic) >> 26];
}

另一个版本:

void propagateBits(uint32_t *n) {
    *n |= *n >> 1;
    *n |= *n >> 2;
    *n |= *n >> 4;
    *n |= *n >> 8;
    *n |= *n >> 16;
}

uint8_t highestSetBitIndex8(uint32_t b)
{
  static const uint32_t Magic = (uint32_t) 0x07C4ACDD;

  static const int BitTable[32] = {
     0,  9,  1, 10, 13, 21,  2, 29,
    11, 14, 16, 18, 22, 25,  3, 30,
     8, 12, 20, 28, 15, 17, 24,  7,
    19, 27, 23,  6, 26,  5,  4, 31,
  };
  propagateBits(&b);

  return BitTable[(b * Magic) >> 27];
}

拥有1亿次通话的基准

使用g++ -std=c++17 highestSetBit.cpp -O3 && ./a.out

进行编译
highestBitIndex1  136.8 ms (loop)  
highestBitIndex2  183.8 ms (log(n) / log(2)) 
highestBitIndex3   10.6 ms (de Bruijn lookup Table with power of two, 64 entries)
highestBitIndex4   4.5 ms (inline assembly bsr)
highestBitIndex5   6.7 ms (intrinsic bsr)
highestBitIndex6   4.7 ms (gcc lzcnt)
highestBitIndex7   7.1 ms (inline assembly lzcnt)
highestBitIndex8  10.2 ms (de Bruijn lookup Table, 32 entries)

如果您将重点放在可移植性上,那么我个人会选择highestBitIndex8,否则内置的gcc很不错。

答案 10 :(得分:-3)

根据我所知,函数Log在大多数编程语言中都非常有效地实现,即使它确实包含循环,它们可能很少,内部 所以我想说在大多数情况下使用日志会更快,更直接。 你必须检查0,并避免记录0,因为这会导致程序崩溃。