根据函数F(N)= rank找到十进制数的等级

时间:2012-09-03 18:20:41

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

我发现了这个问题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解决方案?

3 个答案:

答案 0 :(得分:7)

是的,有一个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 - 位设置小于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 <= n < 10^8的循环中测试,以检查错误,但未发现任何不匹配。

检查输出here

答案 1 :(得分:0)

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

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)未定义。

答案 2 :(得分:0)

这是一个相当高效的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;
}