高效算法,可计算正整数间隔内以2为基数的位数(位数)的总和

时间:2018-11-03 23:06:23

标签: c algorithm digits

假设我得到了两个整数ab,其中a是一个正整数,并且小于b。我必须找到一种有效的算法,该算法将为我提供间隔[a, b]内以base2位数表示的位数(位数)的总和。例如,在间隔[0, 4]中,数字之和等于9,因为0 = 1个数字,1 = 1个数字,2 = 2个数字,3 = 2个数字和4 = 3个数字。

我的程序能够使用循环来计算此数字,但我正在寻找更有效的数字方法。以下是我的代码段,旨在给您一个想法:

int numberOfBits(int i) {
    if(i == 0) {
        return 1;
    }
    else {
        return (int) log2(i) + 1;
    }
 }

上面的函数用于计算间隔中一个数字的位数。

下面的代码向您展示了我如何在主要功能中使用它。

for(i = a; i <= b; i++) {
    l = l + numberOfBits(i);
}
printf("Digits: %d\n", l);

理想情况下,我应该能够使用间隔的两个值并使用一些特殊算法来获得位数。

8 个答案:

答案 0 :(得分:1)

尝试以下代码,我认为它可以满足您计算二进制文件的需求:

int bit(int x)
{
  if(!x) return 1;
  else
  {
    int i;
    for(i = 0; x; i++, x >>= 1);
    return i;
  }
}

答案 1 :(得分:0)

首先,我们可以提高log2的速度,但这只会使我们提高固定因子的速度,而不会改变缩放比例。

快速日志2改编自:https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup

  

查找表方法仅需要大约7个操作来查找日志   32位值。如果扩展为64位,则需要   大约9次作业。可以通过使用修剪其他操作   四个表格,每个表格都可能添加了其他内容。使用   根据您的体系结构,int表元素可能会更快。

第二,我们必须重新考虑算法。如果您知道N和M之间的数字具有相同的数字,是将它们一一加起来还是宁愿(M-N + 1)* numDigits个?

但是,如果我们有一个范围出现多个数字,该怎么办?让我们只找到相同数字的间隔,然后加上这些间隔的总和。在下面实现。我认为我的next可以通过查找表进一步优化。

代码

findEndLimit

输出

从0到UINT_MAX

#include <stdio.h>
#include <limits.h>
#include <time.h>

unsigned int fastLog2(unsigned int v)
{
    static const char LogTable256[256] = 
    {
    #define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
        -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
        LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
        LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
    };

    register unsigned int t, tt; // temporaries

    if (tt = v >> 16)
    {
      return (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
    }
    else 
    {
      return (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
    }
}

unsigned int numberOfBits(unsigned int i)
{
    if (i == 0) {
        return 1;
    }
    else {
        return fastLog2(i) + 1;
    }
}

unsigned int findEndLimit(unsigned int sx, unsigned int ex)
{
    unsigned int sy = numberOfBits(sx);
    unsigned int ey = numberOfBits(ex);
    unsigned int mx;
    unsigned int my;

    if (sy == ey) // this also means sx == ex
        return ex;

    // assumes sy < ey
    mx = (ex - sx) / 2 + sx; // will eq. sx for sx + 1 == ex
    my = numberOfBits(mx);
    while (ex - sx != 1) {
        mx = (ex - sx) / 2 + sx; // will eq. sx for sx + 1 == ex
        my = numberOfBits(mx);
        if (my == ey) {
            ex = mx;
            ey = numberOfBits(ex);
        }
        else {
            sx = mx;
            sy = numberOfBits(sx);
        }
    }
    return sx+1;
}

int main(void)
{
    unsigned int a, b, m;
    unsigned long l;
    clock_t start, end;
    l = 0;
    a = 0;
    b = UINT_MAX;

    start = clock();
    unsigned int i;
    for (i = a; i < b; ++i) {
        l += numberOfBits(i);
    }
    if (i == b) {
        l += numberOfBits(i);
    }
    end = clock();

    printf("Naive\n");
    printf("Digits: %ld; Time: %fs\n",l, ((double)(end-start))/CLOCKS_PER_SEC);

    l=0;
    start = clock();
    do {
        m = findEndLimit(a, b);
        l += (b-m + 1) * (unsigned long)numberOfBits(b);
        b = m-1;
    } while (b > a);
    l += (b-a+1) * (unsigned long)numberOfBits(b);
    end = clock();

    printf("Binary search\n");
    printf("Digits: %ld; Time: %fs\n",l, ((double)(end-start))/CLOCKS_PER_SEC);
}

在某些情况下,我的findEndLimit可能会花费很长时间:

从UINT_MAX / 16 + 1到UINT_MAX / 8

$ ./main 
Naive
Digits: 133143986178; Time: 25.722492s
Binary search
Digits: 133143986178; Time: 0.000025s

答案 2 :(得分:0)

算法

主要思想是找到四舍五入的n2 = log2(x)。那是x中的位数。让pow2 = 1 << n2n2 * (pow2 - x + 1)是值[x...pow2]中的位数。现在找到从1到n2-1

的2的幂的数字的太阳

代码

我敢肯定,可以进行各种简化。
未经测试的代码。待会儿再审核。

// Let us use unsigned for everything.

unsigned ulog2(unsigned value) {
  unsigned result = 0;
  if (0xFFFF0000u & value) {
    value >>= 16; result += 16;
  }
  if (0xFF00u & value) {
    value >>= 8; result += 8;
  }
  if (0xF0u & value) {
    value >>= 4; result += 4;
  }
  if (0xCu & value) {
    value >>= 2; result += 2;
  }
  if (0x2 & value) {
    value >>= 1; result += 1;
  }
  return result;
}

unsigned bit_count_helper(unsigned x) {
  if (x == 0) {
    return 1;
  }
  unsigned n2 = ulog2(x);
  unsigned pow2 = 1u << n;
  unsigned sum = n2 * (pow2 - x + 1u);  // value from pow2 to x
  while (n2 > 0) {
    // ... + 5*16 + 4*8 + 3*4 + 2*2 + 1*1
    pow2 /= 2;
    sum += n2 * pow2;
  }
  return sum;
}

unsigned bit_count(unsigned a, unsigned b) {
  assert(a < b);
  return bit_count_helper(b - 1) - bit_count_helper(a);
}

答案 3 :(得分:0)

从概念上讲,您需要将任务分为两个子问题- 1)从0..M和0..N找出数字的总和,然后减去。

2)找到底数(log2(x)),因为例如对于数字77,数字64,65,... 77都具有6位数字,接下来的32位具有5位数字,接下来的16位有4位数字,依此类推,这会导致几何级数。

因此:

 int digits(int a) {
   if (a == 0) return 1;   // should digits(0) be 0 or 1 ?
   int b=(int)floor(log2(a));   // use any all-integer calculation hack
   int sum = 1 + (b+1) * (a- (1<<b) +1);  // added 1, due to digits(0)==1
   while (--b)
     sum += (b + 1) << b;   // shortcut for (b + 1) * (1 << b);
   return sum;
 }
 int digits_range(int a, int b) {
      if (a <= 0 || b <= 0) return -1;   // formulas work for strictly positive numbers
      return digits(b)-digits(a-1);
 }

答案 4 :(得分:0)

由于效率取决于可用的工具,因此一种方法可以“模拟”完成:

NotificationCenter.default.addObserver(self, selector: #selector(disableMarcar), name: Notification.Name("isOnline"), object: nil)
NotificationCenter.default.post(name: Notification.Name("isOnline"), object: nil

:-)

这里的主要是迭代的数量和种类。

数字是

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

unsigned long long pow2sum_min(unsigned long long n, long long unsigned m)
{
  if (m >= n)
  {
    return 1;
  }

  --n;

  return (2ULL << n) + pow2sum_min(n, m);
}

#define LN(x) (log2(x)/log2(M_E))

int main(int argc, char** argv)
{
  if (2 >= argc)
  {
    fprintf(stderr, "%s a b\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  long a = atol(argv[1]), b = atol(argv[2]);

  if (0L >= a || 0L >= b || b < a)
  {
    puts("Na ...!");
    exit(EXIT_FAILURE);
  }

  /* Expand intevall to cover full dimensions: */
  unsigned long long a_c = pow(2, floor(log2(a)));
  unsigned long long b_c = pow(2, floor(log2(b+1)) + 1);

  double log2_a_c = log2(a_c);
  double log2_b_c = log2(b_c);

  unsigned long p2s = pow2sum_min(log2_b_c, log2_a_c) - 1;

  /* Integral log2(x) between a_c and b_c: */
  double A = ((b_c * (LN(b_c) - 1)) 
            - (a_c * (LN(a_c) - 1)))/LN(2)
            + (b+1 - a);

  /* "Integer"-integral - integral of log2(x)'s inverse function (2**x) between log(a_c) and log(b_c): */
  double D = p2s - (b_c - a_c)/LN(2);

  /* Corrective from a_c/b_c to a/b : */
  double C = (log2_b_c - 1)*(b_c - (b+1)) + log2_a_c*(a - a_c);

  printf("Total used digits: %lld\n", (long long) ((A - D - C) +.5));
}

做一个

log(floor(b_c)) - log(floor(a_c))

每次迭代。

答案 5 :(得分:0)

主要要理解的是,用于表示二进制数字的数字的位数以2的幂乘以1:

+--------------+---------------+
| number range | binary digits |
+==============+===============+
|    0 - 1     |       1       |
+--------------+---------------+
|    2 - 3     |       2       |
+--------------+---------------+
|    4 - 7     |       3       |
+--------------+---------------+
|    8 - 15    |       4       |
+--------------+---------------+
|   16 - 31    |       5       |
+--------------+---------------+
|   32 - 63    |       6       |
+--------------+---------------+
|     ...      |      ...      |

对蛮力算法的一个小改进是,找出传入的两个数字之间的数字位数增加了多少倍(以两个底数为底的对数给出),并通过乘以可以用给定数字位数(由2的幂表示)和数字位数表示的数字。

此算法的简单实现是:

int digits_sum_seq(int a, int b)
{
    int sum = 0;
    int i = 0;
    int log2b = b <= 0 ? 1 : floor(log2(b));
    int log2a = a <= 0 ? 1 : floor(log2(a)) + 1;

    sum += (pow(2, log2a) - a) * (log2a);

    for (i = log2b; i > log2a; i--)
        sum += pow(2, i - 1) * i;

    sum += (b - pow(2, log2b) + 1) * (log2b + 1);

    return sum;
}

然后可以通过在其他答案中看到的更有效的log和pow函数版本来改进它。

答案 6 :(得分:0)

这是一种完全基于查找的方法。您甚至不需要log2:)

算法

首先,我们预先计算间隔限制,在该限制下位数将发生变化并创建查找表。换句话说,我们创建了一个数组limits[2^n],其中limits[i]为我们提供了可以用(i+1)位表示的最大整数。那么我们的数组就是{1, 3, 7, ..., 2^n-1}

然后,当我们要确定范围的位总和时,必须首先将范围限制aba <= limits[i]和{{ 1}}成立,这将告诉我们需要b <= limits[j]位来表示(i+1),需要a位来表示(j+1)

如果索引相同,则结果为b,否则我们必须分别从值到相同位数间隔的边缘获取位数,并求和总数以及之间的每个间隔。无论如何,都是简单的算术。

代码

(b-a+1)*(i+1)

输出

#include <stdio.h>
#include <limits.h>
#include <time.h>

unsigned long bitsnumsum(unsigned int a, unsigned int b)
{
    // generate lookup table
    // limits[i] is the max. number we can represent with (i+1) bits
    static const unsigned int limits[32] =
    {
    #define LTN(n) n*2u-1, n*4u-1, n*8u-1, n*16u-1, n*32u-1, n*64u-1, n*128u-1, n*256u-1
        LTN(1),
        LTN(256),
        LTN(256*256),
        LTN(256*256*256)
    };

    // make it work for any order of arguments
    if (b < a) {
        unsigned int c = a;
        a = b;
        b = c;
    }

    // find interval of a
    unsigned int i = 0;
    while (a > limits[i]) {
            ++i;
    }
    // find interval of b
    unsigned int j = i;
    while (b > limits[j]) {
            ++j;
    }

    // add it all up
    unsigned long sum = 0;
    if (i == j) {
        // a and b in the same range
        // conveniently, this also deals with j == 0
        // so no danger to do [j-1] below
        return (i+1) * (unsigned long)(b - a + 1);
    }
    else {
        // add sum of digits in range [a, limits[i]]
        sum += (i+1) * (unsigned long)(limits[i] - a + 1);
        // add sum of digits in range [limits[j], b]
        sum += (j+1) * (unsigned long)(b - limits[j-1]);
        // add sum of digits in range [limits[i], limits[j]]
        for (++i; i<j; ++i) {
            sum += (i+1) * (unsigned long)(limits[i] - limits[i-1]);
        }
        return sum;
    }
}

int main(void)
{
    clock_t start, end;
    unsigned int a=0, b=UINT_MAX;

    start = clock();
    printf("Sum of binary digits for numbers in range "
    "[%u, %u]: %lu\n", a, b, bitsnumsum(a, b));
    end = clock();
    printf("Time: %fs\n", ((double)(end-start))/CLOCKS_PER_SEC);
}

答案 7 :(得分:0)

对于这个问题,您的解决方案是最简单的,称为“天真”的解决方案,您可以在该序列中或在案例间隔中查找每个元素以检查某项内容或执行操作。

天真算法

假定 a b 是正整数,并且 b 大于 a ,我们将其称为尺寸/尺寸 [a,b] 的时间间隔, n =(ba)

让元素数为n并使用一些算法表示法(例如big-O表示法link),最坏情况下的代价是O(n*(numberOfBits_cost))

由此我们可以看到,我们可以通过使用更快的算法来计算 numberOfBits()来加快算法的速度,或者我们需要找到一种方法,以不考虑花费时间间隔的每个元素我们进行操作。

直觉

现在查看可能的时间间隔 [6,14] ,您会发现对于 6 7 我们需要 3 < / strong>数字,其中 8,9,10,11,12,13,14 需要 4 。这样会为每个使用相同位数表示的数字调用 numberOfBits(),而以下乘法运算会更快:

(number_in_subinterval)*digitsForThisInterval
((14-8)+1)*4 = 28
((7-6)+1)*3 = 6

因此,我们通过9次操作将9个元素上的循环减少到仅2个。

因此编写使用此直觉的函数将使我们在时间上(而不是在内存中)算法更有效率。使用您的 numberOfBits()函数,我创建了以下解决方案:

   int intuitionSol(int a, int b){
    int digitsForA = numberOfBits(a);
    int digitsForB = numberOfBits(b);

    if(digitsForA != digitsForB){
        //because a or b can be that isn't the first or last element of the
        // interval that a specific number of digit can rappresent there is a need
        // to execute some correction operation before on a and b
        int tmp = pow(2,digitsForA)  - a;
        int result = tmp*digitsForA; //will containt the final result that will be returned

        int i;
        for(i = digitsForA + 1; i < digitsForB; i++){
            int interval_elements = pow(2,i) - pow(2,i-1);
            result = result + ((interval_elements) * i);
            //printf("NumOfElem: %i for %i digits; sum:= %i\n", interval_elements, i, result);
        }

        int tmp1 = ((b + 1) - pow(2,digitsForB-1));
        result = result + tmp1*digitsForB;
        return result;
    }
    else {
        int elements = (b - a) + 1;
        return elements * digitsForA; // or digitsForB
    }
}

让我们看一下成本,该算法的成本是对 a b 进行校正操作的成本,加上最昂贵的for循环的成本。但是,在我的解决方案中,我并没有遍历所有元素,而仅在numberOfBits(b)-numberOfBits(a)上循环,在最坏的情况下,当 [0,n] 变为 log(n)-1 等同于 O(log n)。 为了恢复,在最坏的情况下,我们从线性运算成本 O(n)转换为对数逻辑运算的 O(log n)。两者之间Look on this diagram the diferinces

注意

当我谈论间隔或子间隔时,我指的是使用相同数字位数表示二进制数的元素的间隔。 以下是我的一些测试输出,最后一个显示了差异:

Considered interval is [0,4]
YourSol: 9 in time: 0.000015s
IntuitionSol: 9 in time: 0.000007s

Considered interval is [0,0]
YourSol: 1 in time: 0.000005s
IntuitionSol: 1 in time: 0.000005s

Considered interval is [4,7]
YourSol: 12 in time: 0.000016s
IntuitionSol: 12 in time: 0.000005s

Considered interval is [2,123456]
YourSol: 1967697 in time: 0.005010s
IntuitionSol: 1967697 in time: 0.000015s