如何得到最小的n,那个2 ^ n> = x对于给定的整数x在O(1)中?

时间:2011-05-11 09:48:49

标签: c++ algorithm

给定无符号整数x如何找到最小的n,O(1)中2 ^ nx?换句话说,我希望在O(1)中以x的二进制格式(加上1,如果x不是2的幂)找到更高设置位的索引(不依赖于整数的大小和字节大小。)

11 个答案:

答案 0 :(得分:7)

如果没有内存限制,那么您可以使用查找表(x的每个可能值的一个条目)来实现 O(1)时间。

如果你想要一个实用的解决方案,大多数处理器都会有某种“找到最高位设置”的操作码。例如,在x86上,它是BSR。大多数编译器都有一个编写原始汇编程序的机制。

答案 1 :(得分:4)

好的,到目前为止还没有人发布编译时解决方案,这是我的。前提条件是您的输入值是编译时常量。如果你有,那么这一切都是在编译时完成的。

#include <iostream>
#include <iomanip>

// This should really come from a template meta lib, no need to reinvent it here, 
// but I wanted this to compile as is. 
namespace templ_meta {
    // A run-of-the-mill compile-time if. 
    template<bool Cond, typename T, typename E> struct if_;
    template<           typename T, typename E> struct if_<true , T, E> {typedef T result_t;};
    template<           typename T, typename E> struct if_<false, T, E> {typedef E result_t;};

    // This so we can use a compile-time if tailored for types, rather than integers. 
    template<int I>
    struct int2type {
        static const int result = I;
    };
}

// This does the actual work.
template< int I, unsigned int Idx = 0>
struct index_of_high_bit {
    static const unsigned int result = 
        templ_meta::if_< I==0
           , templ_meta::int2type<Idx>
           , index_of_high_bit<(I>>1),Idx+1> 
        >::result_t::result;
};

// just some testing
namespace {
    template< int I >
    void test() 
    {
        const unsigned int result = index_of_high_bit<I>::result;
        std::cout << std::setfill('0') 
                  << std::hex << std::setw(2) << std::uppercase << I << ": " 
                  << std::dec << std::setw(2) << result  
                  << '\n';
    }
}

int main()
{
    test<0>();
    test<1>();
    test<2>();
    test<3>();
    test<4>();
    test<5>();
    test<7>();
    test<8>();
    test<9>();
    test<14>();
    test<15>();
    test<16>();
    test<42>();
    return 0;
}
这样做很有趣。

答案 2 :(得分:1)

<cmath>中,有对数函数可以为你执行此计算。

ceil(log(x) / log(2));

答案 3 :(得分:1)

转换表达式的一些数学运算:

 int n = ceil(log(x)/log(2));

这显然是O(1)。

答案 4 :(得分:1)

这是一个关于找到最高位集的问题(正如lshtar和Oli Charlesworth指出的那样)。 Bit Twiddling Hacks提供了一个解决方案,对32位整数进行大约7次操作,对64位整数进行大约9次操作。

答案 5 :(得分:0)

您可以使用预先计算的表格。

如果您的号码在[0,255]间隔内,则可以查看简单的表格。

如果它更大,那么你可以按字节分割并从高到低检查它们。

答案 6 :(得分:0)

也许这link会有所帮助。

警告:代码不是很简单,而且看起来不太可维护。

  uint64_t v;          // Input value to find position with rank r.
  unsigned int r;      // Input: bit's desired rank [1-64].
  unsigned int s;      // Output: Resulting position of bit with rank r [1-64]
  uint64_t a, b, c, d; // Intermediate temporaries for bit count.
  unsigned int t;      // Bit count temporary.

  // Do a normal parallel bit count for a 64-bit integer,                     
  // but store all intermediate steps.                                        
  // a = (v & 0x5555...) + ((v >> 1) & 0x5555...);
  a =  v - ((v >> 1) & ~0UL/3);
  // b = (a & 0x3333...) + ((a >> 2) & 0x3333...);
  b = (a & ~0UL/5) + ((a >> 2) & ~0UL/5);
  // c = (b & 0x0f0f...) + ((b >> 4) & 0x0f0f...);
  c = (b + (b >> 4)) & ~0UL/0x11;
  // d = (c & 0x00ff...) + ((c >> 8) & 0x00ff...);
  d = (c + (c >> 8)) & ~0UL/0x101;
  t = (d >> 32) + (d >> 48);
  // Now do branchless select!                                                
  s  = 64;
  // if (r > t) {s -= 32; r -= t;}
  s -= ((t - r) & 256) >> 3; r -= (t & ((t - r) >> 8));
  t  = (d >> (s - 16)) & 0xff;
  // if (r > t) {s -= 16; r -= t;}
  s -= ((t - r) & 256) >> 4; r -= (t & ((t - r) >> 8));
  t  = (c >> (s - 8)) & 0xf;
  // if (r > t) {s -= 8; r -= t;}
  s -= ((t - r) & 256) >> 5; r -= (t & ((t - r) >> 8));
  t  = (b >> (s - 4)) & 0x7;
  // if (r > t) {s -= 4; r -= t;}
  s -= ((t - r) & 256) >> 6; r -= (t & ((t - r) >> 8));
  t  = (a >> (s - 2)) & 0x3;
  // if (r > t) {s -= 2; r -= t;}
  s -= ((t - r) & 256) >> 7; r -= (t & ((t - r) >> 8));
  t  = (v >> (s - 1)) & 0x1;
  // if (r > t) s--;
  s -= ((t - r) & 256) >> 8;
  s = 65 - s;

答案 7 :(得分:0)

正如已经提到的,x + 1的二进制表示的长度是你正在寻找的n(除非x本身是2的幂,意味着10 ...... 0在二进制表示中) 。 我严重怀疑在O(1)中存在一个真正的解决方案,除非你认为二进制表示的翻译是O(1)。

答案 8 :(得分:0)

对于32位int,以下伪代码将为O(1)。

highestBit(x)
  bit = 1
  highest = 0
  for i 1 to 32
    if x & bit == 1
      highest = i
    bit = bit * 2
  return highest + 1

x有多大并不重要,它总是检查所有32位。因此,时间不变。

如果输入可以是任何整数,则输入为n位数。然后读取输入的任何解决方案将读取n个数字,并且必须至少为O(n)。除非有人在没有读取输入的情况下提出解决方案,否则无法找到O(1)解决方案。

答案 9 :(得分:0)

在互联网上进行一些搜索之后,我发现了这2个版本的32位无符号整数。我测试过他们的工作。很明显,为什么第二个有效,但现在我仍在考虑第一个......

1

unsigned int RoundUpToNextPowOf2(unsigned int v)
{
    unsigned int r = 1;
    if (v > 1) 
    {
      float f = (float)v;
      unsigned int const t = 1U << ((*(unsigned int *)&f >> 23) - 0x7f);
      r = t << (t < v);
    }
    return r;
}

2

unsigned int RoundUpToNextPowOf2(unsigned int v)
{
    v--;
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v++;
    return v;
}

编辑:第一个也是明确的。

答案 10 :(得分:0)

一个有趣的问题。你的意思是不依赖于大小 int或一个字节中的位数?遇到不同的号码 在一个字节中的位,你将不得不使用另一台机器 一组不同的机器指令,可能会或可能不会影响 答案。

无论如何,基于Mihran提出的第一个解决方案,含糊不清, 我明白了:

int
topBit( unsigned x )
{
    int r = 1;
    if ( x > 1 ) {
        if ( frexp( static_cast<double>( x ), &r ) != 0.5 ) {
            ++ r;
        }
    }
    return r - 1;
}

这适用于输入值必须精确的约束 代表double;如果输入为unsigned long long,则为此 可能不是这样,在一些更具异国情调的平台上,它 甚至可能不是unsigned的情况。

我可以唯一的其他常数时间(相对于位数) 想到的是:

int
topBit( unsigned x )
{
    return x == 0 ? 0.0 : ceil( log2( static_cast<double>( x ) ) );
}

,它与x完全相同 在double中可表示,并且还可能遭受舍入错误 浮点运算中固有的(尽管如果log2是 正确实施,我不认为应该是这种情况)。如果 您的编译器不支持log2(C ++ 11功能,但也存在 在C90中,我希望大多数编译器已经实现了 当然,log( x ) / log( 2 )可以使用,但我怀疑 这将增加导致舍入错误的风险 错误的结果。

FWIW,我发现O(1)的位数有点不合逻辑 我在上面指定的原因:位数只是其中之一 “常数因素”取决于您运行的机器。 无论如何,我提出了以下纯粹的整数解决方案,即 O(lg 1)表示位数,O(1)表示其他所有内容:

template< int k >
struct TopBitImpl
{
    static int const k2 = k / 2;
    static unsigned const m = ~0U << k2;
    int operator()( unsigned x ) const
    {
        unsigned r = ((x & m) != 0) ? k2 : 0;
        return r + TopBitImpl<k2>()(r == 0 ? x : x >> k2);
    }
};

template<>
struct TopBitImpl<1>
{
    int operator()( unsigned x ) const
    {
        return 0;
    }
};

int
topBit( unsigned x )
{
    return TopBitImpl<std::numeric_limits<unsigned>::digits>()(x)
                + (((x & (x - 1)) != 0) ? 1 : 0);
}

一个好的编译器应该能够内联递归调用 接近最佳代码。