【问题标题】:Find rank of a decimal number based on function F( N ) = rank根据函数 F( N ) = rank 求十进制数的秩
【发布时间】:2012-09-03 18:20:41
【问题描述】:

我发现了这个问题here,但这不是我要找的答案。因此,再次发布。

一个函数的形式:

F( N ) = rank

表示给定一个十进制表示的数字N,其排名为:

Starting from 0 to N, how many numbers are there with same number of set bits in its
binary representation.

我将通过一个例子来更清楚地说明。

N = 6 ( 0110 )
Its rank is 3.
1. 3 ( 0011 )
2. 5 ( 0101 )
3. 6 ( 0110 )

现在,给定一个数字,找出它的等级。

显而易见的方法是从 0 开始并检查每个数字的设置位数,直到 N-1

问题是:

有没有logN解决方案?

【问题讨论】:

    标签: c performance algorithm bit-manipulation time-complexity


    【解决方案1】:

    是的,有一个log n 解决方案。

    n 设置k 位,最高有效位位于p 位置(从0 开始计数位置,因此2^p <= n < 2^(p+1))。然后有pCk(二项式系数,也称为choose(p,k))方法将k 位放置在0, ..., p-1 的位置,所有这些都给出了恰好k 设置位小于n 的数字。如果k == 1,就是这样,否则还有k 设置位和p-th 位设置小于n 的数字需要考虑。这些可以通过确定n - 2^p的排名来计算。

    代码(不是最优的,会进行一些不必要的重新计算,并且不能尽其所能避免溢出):

    unsigned long long binom(unsigned n, unsigned k) {
        if (k == 0 || n == k) return 1;
        if (n < k) return 0;
        if (k > (n >> 1)) k = n-k;
        unsigned long long res = n, j;
        // We're not doing all we can to avoid overflow, as this is a proof of concept,
        // not production code.
        for(j = 2; j <= k; ++j) {
            res *= (n+1-j);
            res /= j;
        }
        return res;
    }
    
    unsigned popcount(unsigned long long n) {
        unsigned k = 0;
        while(n) {
            ++k;
            n &= (n-1);
        }
        return k;
    }
    
    unsigned long long rank(unsigned long long n) {
        if (n == 0) return 1;
        unsigned p = 0, k = popcount(n);
        unsigned long long mask = 1,h = n >> 1;
        while(h > 0) {
            ++p;
            h >>= 1;
        }
        mask <<= p;
        unsigned long long r = binom(p,k);
        r += rank(n-mask);
        return r;
    }
    

    0 &lt;= n &lt; 10^8 的循环中进行测试,以检查错误,没有发现任何不匹配。

    检查输出here

    【讨论】:

    • 供参考,pCk 指的是binomial coefficient
    • 该方法看起来不错。但是,您检查过 N = 5 和 N = 6 吗?在这里失败了。
    • @Aashish 我还没有完成所有细节,给我几分钟时间写一些代码;)
    • @DanielFischer:[戴上 OEIS 编辑帽] 如果您确实了解了细节,您可以将其作为评论添加到 A068076;我会自己审核。
    • @DSM 详细信息已确定,A068076 为 rank(n)-1。但是我如何在 OEIS 上添加评论?我需要在那里注册吗?
    【解决方案2】:

    这是一个相当高效的 O(logN) 实现,每一步并行执行多个加法:

    unsigned int countBits( unsigned int bits )
    {
        bits = ( bits & 0x55555555 ) + ( ( bits >> 1 ) & 0x55555555 );
        bits = ( bits & 0x33333333 ) + ( ( bits >> 2 ) & 0x33333333 );
        bits = ( bits + ( bits >>  4 ) ) & 0x0f0f0f0f;
        bits += bits >>  8;
        return ( bits + ( bits >> 16 ) ) & 63;
    }
    

    它以 16 个 2 位加法开始,然后进行 8 个 4 位加法,依此类推。通过使用更长的掩码和一个额外的步骤,它可以扩展为使用 64 位整数:

    unsigned int countBits( unsigned long long bits )
    {
        bits = ( bits & 0x5555555555555555L ) + ( ( bits >> 1 ) & 0x5555555555555555LL );
        bits = ( bits & 0x3333333333333333L ) + ( ( bits >> 2 ) & 0x3333333333333333LL );
        bits = ( bits + ( bits >>  4 ) ) & 0x0f0f0f0f0f0f0f0fLL;
        bits += bits >>  8;
        bits += bits >> 16;
        return (unsigned int) ( bits + ( bits >> 32 ) ) & 127;
    }
    

    【讨论】:

      【解决方案3】:

      可以通过组合和排列技术来解决。

      F(N) = 排名

      首先找到表示N所需的位数。在二进制表示中,数字可以构造如下:

      N = cn 2^n + .... + c3 2^2 + c2 2^1 + c1 2^0
      

      现在,为了在上述等式中找到n(或二进制表示的位数),我们可以假设它将是floor(log2(N)) + 1。例如,考虑任何数字,假设118 那么它可以表示为 floor(log2(118)) + 1 = 7 位。

      n = floor(log2(118)) + 1;
      

      以上公式仅为O(1)。现在,我们需要找出一个数字的二进制表示中有多少个 1。考虑使用伪代码来完成这项工作:

      function count1(int N) {
          int c = 0;
          int N' = N;
      
          while(N' > 0) {
             N' -= 2^(floor(log2(N')));
             c++;
          }
      }
      

      上面的伪代码是O(logN)。我在 MATLAB 中编写了一个小脚本来测试我上面的伪代码,结果如下。

      count1(6)   = 2
      count1(3)   = 2
      count1(118) = 5
      

      完美,现在我们有了这些位的位数和 1 的数量。现在,可以应用简单的组合和排列来找到数字的秩。首先让我们假设,n 是表示数字所需的位数,c 是数字的位表示中的 1 的数量。因此,排名将由以下方式给出:

      r = n ! / c ! * (n - c) ! 
      

      编辑:根据 DSM 的建议,我已更正逻辑以找到正确的 RANK。想法是从排列中删除所有不需要的数字。所以添加了这段代码:

      for i = N + 1 : 2^n - 1
          if count(i) == c
             r = r - 1;
          end
      end
      

      我已经编写了一个 MATLAb 脚本来使用上述方法查找数字的排名:

      function r = rankN(N)
      
      n = floor(log2(N)) + 1;
      c = count(N);
      r = factorial(n) / (factorial(c) * factorial(n - c));
      % rejecting all the numbers may have included in the permutations 
      % but are unnecessary.
      for i = N + 1 : 2^n - 1
          if count(i) == c
             r = r - 1;
          end
      end
      
      function c = count(n)
      
      c = 0;
      N = n;
      while N > 0
          N = N - 2^(floor(log2(N)));
          c = c + 1;
      end
      

      上面的算法是O(1) + O(logN) + O(1) = O(logN)。输出是:

      >> rankN(3)
      
      ans =
      
           1
      
      >> rankN(4)
      
      ans =
      
           3
      
      >> rankN(7)
      
      ans =
      
           1
      
      >> rankN(118)
      
      ans =
      
          18
      
      >> rankN(6)
      
      ans =
      
           3
      

      注意:0 的等级始终为1,因为0 的上述方法将失败,因为log2(0) 未定义。

      【讨论】:

      • rankN(3) 怎么可能是 2?它是二进制的11;唯一具有 2 位的数字
      • 是的,我的脚本中有一些错误,但现在输出为 [1, 3, 1, 21]
      • 我很确定 rank(118) 只有 18 位。我认为您也在计算 119-128 中位数与 118 相同的数字。
      • 118 的二进制表示是 1110110 7 位,它包含 5 - 1 和 2 - 0,所以 7 ! / 5 ! * 2 ! = 21 与我的脚本匹配。现在,问题缩小到我的排列公式是否正确?你怎么看?
      • 您忘记了 111100111110101111100 是有效的排列,但是是无效的数字,因为它们都大于 118。这就是为什么您在回答时得到 21是 18 岁。
      猜你喜欢
      • 2010-10-18
      • 1970-01-01
      • 1970-01-01
      • 2019-03-02
      • 1970-01-01
      • 2013-06-24
      • 2023-01-10
      • 2021-11-21
      • 1970-01-01
      相关资源
      最近更新 更多