【问题标题】:How get smallest n, that 2 ^ n >= x for given integer x in O(1)?对于 O(1) 中的给定整数 x,如何获得最小的 n,即 2 ^ n >= x?
【发布时间】:2011-05-11 09:48:49
【问题描述】:

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

【问题讨论】:

标签: c++ algorithm


【解决方案1】:

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

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

【讨论】:

  • 没有内存限制......这说明了一切,这不是一个相当理论的事情,因为总会有内存限制吗?
  • @Tony:如果这是一个计算机科学问题,而不是一个实际问题,那么我们经常会忽略诸如“内存限制”之类的小细节!
  • 有时在 CS 中存在内存限制:图灵机必须能够访问无限磁带。那是一个约束。 :P
【解决方案2】:

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

#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;
}

这样做很有趣。

【讨论】:

  • 不是我的反对票,但这不是 O(1)。我敢打赌这很有趣,但它没有回答问题。
  • "Evaluated at compile-time" 与 O(1) 不同,这甚至没有假设输入可作为编译时常量使用是一个很大的延伸,当问题没有说明。
  • 事先针对给定问题进行工作(针对特定的一组输入,而不是生成通用查找表),保存结果,然后说“我可以在零时间内找到它”至少可以说是误导。
  • @sbi:Oli 的解决方案需要一个通用查找表,该查找表可以重复用于任何输入。您的解决方案需要事先知道确切的输入来预先计算答案。这些不是一回事。
  • @sbi 由于可以通过模板元编程实现“图灵机”,所以理论上我们可以解决每个问题的编译时间。你已经为这个问题做了。谢谢!但实际上我可能会计算n runtime :)
【解决方案3】:

&lt;cmath&gt; 中有对数函数可以为您执行此计算。

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

【讨论】:

  • 不要认为这会比仅在整数位上运行更快。
【解决方案4】:

一些数学来转换表达式:

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

这显然是 O(1)。

【讨论】:

  • 仅当您将log(x) 视为一项操作时,这不是
  • @davka:为什么 log(x) 不是 O(1)。
  • @Martin - 取决于您认为什么是衡量算法复杂性的原子操作。通常使用非常基本的操作(例如算术和内存访问),以便复杂性分析独立于特定硬件。但是即使您将所有处理器的指令集作为基本操作,log(x) 仍然需要不是O(1) 的计算。它被编写为一个库函数调用这一事实并不能使其具有原子性。
  • log()的指令数是否依赖于x的大小? (显然不是 x 的值)。
  • @Martin 不在我见过的实现中。
【解决方案5】:

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

【讨论】:

    【解决方案6】:

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

    如果您的号码在 [0,255] 区间内,则可以进行简单的表格查找。

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

    【讨论】:

    • 这在 O(log N) 时间内运行,其中 Nx 的最大大小。
    【解决方案7】:

    也许这个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;
    

    【讨论】:

      【解决方案8】:

      如前所述,x + 1 的二进制表示的长度是您要查找的 n(除非 x 本身是 2 的幂,即二进制表示中的 10.....0) . 我严重怀疑 O(1) 中是否存在真正的解决方案,除非您将二进制表示的转换视为 O(1)。

      【讨论】:

      • 我们不需要“翻译”为二进制表示。它已经以二进制格式存储在内存中。
      • @Mihran Hovsepyan 当然是,但是如何在 O(1) 中获得其二进制表示的长度?也许这里有某种二进制运算符技巧来找到最后一个(最左边的)'1'......无论如何,如果你可以假设你知道它的二进制表示,那么正如提到的,它是长度 + 1(或长度本身,如果x = 2^k 对于一些自然 k)
      • 我可以在 O(1) 中获得unsigned int 中的位数,但不能获得整数 x 的二进制表示的位数。
      • 是的,忘记了 2 的补码表示 :) 那么,如果数字为负数或为 0,则所需的 n 等于 0(或负无穷大,取决于 n 的要求)。如果数字是正数,只需将其转换为无符号整数。
      【解决方案9】:

      对于 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) 解决方案。

      【讨论】:

      • @lshtar 这不是 O(1)。仔细阅读问题O(1) (not depended on size of integer and size of byte)
      • @Mihran - 这是令人困惑的部分,您不希望它依赖于整数的大小,但是,您也意味着一个小于或等于 unsigned int 的整数。伪代码实际上不依赖于整数的大小,假设它最多为 32 位。如果我做highestBit(1)highestBit(MAX_INT) 运行时间是一样的。
      • 这是实现 O(1) 算法的有趣方式 - 总是在最坏的情况下运行步数:)
      • @Oli Charlesworth:实际上是这样。但我懒得争论。那是我们我删除了我的评论。但我的评论实际上是基于它不依赖于 X 的值。但后来我重新阅读了这个问题,它是基于 sizeof(x) 的。从技术上讲,它滥用了 O() 表示法,因为它并不是为此而设计的。技术上大的 O() 用于衡量算法相对于数据量的变化。这里的数据量总是固定的 sizeof(n) 是恒定的。
      【解决方案10】:

      在互联网上进行一些搜索后,我找到了 32 位无符号整数的这 2 个版本。我已经测试过它们并且它们有效。我很清楚为什么第二个有效,但现在我仍在考虑第一个...

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

      编辑:第一个也是清晰的。

      【讨论】:

      • 我不完全理解它是如何工作的,但显然它不能满足您对not depended on size of integer and size of byte的要求
      • @davka 实际上这会对我有所帮助,因为可以为 64 位整数编写相同的算法并在检查整数大小后调用需要的算法。
      • 仍然需要假设 x 的上限不再是 not depended on size of integer
      • 第一个实际上需要 IEEE 浮点和 32 位整数。
      • 它也给出了与所要求的完全不同的结果。
      【解决方案11】:

      一个有趣的问题。不取决于大小是什么意思 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);
      }
      

      一个好的编译器应该能够内联递归调用,结果是 接近最优代码。

      【讨论】:

      • 我发现x&lt;y&gt;()(z) 语法很尴尬。将operator() 设为static 成员函数会将其变成IMO 更惯用的x&lt;y&gt;::f(z)。哦,让x 成为编译时常量允许纯编译时解决方案。
      • 我发现语法也很尴尬,但是将operator() 设为静态不是一种选择,因为标准要求operator() 是非静态的。给函数起一个名字并将其设为静态可能会比我写的更干净,但我懒得想一个好名字,而且当你没有好名字时使用operator() 有很长的传统(有时甚至在你这样做的时候)。
      猜你喜欢
      • 2021-11-04
      • 1970-01-01
      • 2022-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多