计算具有相同设定位数的下一个更高的数字?

时间:2015-06-19 21:11:45

标签: c++ algorithm

geeksforgeeks网站上提供了此问题的解决方案。

我想知道是否存在更好,更简单的解决方案?理解这有点复杂。只需一个算法就可以了。

3 个答案:

答案 0 :(得分:2)

有一个更简单但效率更低的一个。它如下:

  • 计算您的号码中的位数(将您的号码右移至零,并计算最右侧位数为1的次数。)
  • 增加数字,直到得到相同的结果。

当然效率极低。考虑一个2的幂(设置为1位)的数字。您必须将此数字加倍才能得到答案,在每次迭代中将数字增加1。当然它不会起作用。

如果您想要一个更简单的高效算法,我不认为有一个。事实上,对我来说,它看起来非常简单明了。

编辑:通过"更简单",我的意思是它可以直接实现,并且代码行可能会少一些。

答案 1 :(得分:2)

我非常确定这种算法与链接算法一样高效且易于理解。

这里的策略是要明白,在不增加1的数量的情况下使数字更大的唯一方法是携带1,但如果你携带多个1,那么你必须将它们添加回来英寸

  • 给定一个数字1001 1100

  • 右移它直到值为奇数0010 0111。请记住班次数:shifts = 2;

  • 右移它直到值为偶数0000 0100。记住执行的班次次数和消耗的比特数。 shifts += 3; bits = 3;

  • 到目前为止,我们已经从算法中取了5个移位和3个比特来携带最低的数字。现在我们还钱了。

  • 最右边的位1. 0000 0101。我们现在欠它2位。 bits -= 1

  • 左移3次以添加0' s。 0010 1000。我们这样做了三次,因为shifts - bits == 3 shifts -= 3

  • 现在我们欠了两位和两位。所以将它向左移动两次,每次将最左边的位设置为1。 1010 0011。我们已经偿还了所有的比特和所有轮班。 bits -= 2; shifts -= 2; bits == 0; shifts == 0

以下是其他一些示例......每个步骤都显示为current_val, shifts_owed, bits_owed

0000 0110
0000 0110, 0, 0 # Start
0000 0011, 1, 0 # Shift right till odd
0000 0000, 3, 2 # Shift right till even
0000 0001, 3, 1 # Set LSB
0000 0100, 1, 1 # Shift left 0's
0000 1001, 0, 0 # Shift left 1's
0011 0011
0011 0011, 0, 0 # Start
0011 0011, 0, 0 # Shift right till odd
0000 1100, 2, 2 # Shift right till even
0000 1101, 2, 1 # Set LSB
0001 1010, 1, 1 # Shift left 0's
0011 0101, 0, 0 # Shift left 1's

答案 2 :(得分:2)

基于一些代码,我碰巧开始使用,这与geeksforgeeks解决方案非常相似(请参阅此答案:https://stackoverflow.com/a/14717440/1566221)和高度优化的@QuestionC's answer版本,避免了一些转变,我得出的结论是,某些CPU(即我的英特尔i5笔记本电脑上)的分区速度足够慢,但循环实际上胜出了。

但是,可以用移位循环替换g-for-g解决方案中的划分,结果证明这是最快的算法,仅在我的机器上。我在这里为任何想要测试它的人粘贴代码。

对于任何实现,有两个恼人的极端情况:一个是给定整数为0的位置;另一个是整数是最大可能值的位置。以下函数都具有相同的行为:如果给定具有k位的最大整数,则它们返回具有k位的最小整数,从而重新启动循环。 (也适用于0:它表示给定0,函数返回0。)

分裂的比特破解解决方案:

template<typename UnsignedInteger>
UnsignedInteger next_combination_1(UnsignedInteger comb) {
  UnsignedInteger last_one = comb & -comb;
  UnsignedInteger last_zero = (comb + last_one) &~ comb;
  if (last_zero)
    return comb + last_one + ((last_zero / last_one) >> 1) - 1;
  else if (last_one)
    return UnsignedInteger(-1) / last_one;
  else
    return 0;
}

将分区替换为移位循环的位错解决方案

template<typename UnsignedInteger>
UnsignedInteger next_combination_2(UnsignedInteger comb) {
  UnsignedInteger last_one = comb & -comb;
  UnsignedInteger last_zero = (comb + last_one) &~ comb;
  UnsignedInteger ones = (last_zero - 1) & ~(last_one - 1);
  if (ones) while (!(ones & 1)) ones >>= 1;
  comb += last_one;
  if (comb) comb += ones >> 1; else comb = ones;
  return comb;
}

优化的换档解决方案

template<typename UnsignedInteger>
UnsignedInteger next_combination_3(UnsignedInteger comb) {
  if (comb) {
    // Shift the trailing zeros, keeping a count.
    int zeros = 0; for (; !(comb & 1); comb >>= 1, ++zeros);
    // Adding one at this point turns all the trailing ones into
    // trailing zeros, and also changes the 0 before them into a 1.
    // In effect, this is steps 3, 4 and 5 of QuestionC's solution,
    // without actually shifting the 1s.
    UnsignedInteger res = comb + 1U;
    // We need to put some ones back on the end of the value.
    // The ones to put back are precisely the ones which were at
    // the end of the value before we added 1, except we want to
    // put back one less (because the 1 we added counts). We get
    // the old trailing ones with a bit-hack.
    UnsignedInteger ones = comb &~ res;
    // Now, we finish shifting the result back to the left
    res <<= zeros;
    // And we add the trailing ones. If res is 0 at this point,
    // we started with the largest value, and ones is the smallest
    // value.
    if (res) res += ones >> 1;
    else res = ones;
    comb = res;
  }
  return comb;
}

(有人会说上面是另一个有点黑客,而且我不会争辩。)

极不具代表性的基准

我通过遍历所有32位数字来测试它。 (也就是说,我使用i创建最小的模式,然后遍历所有可能性,对于i的每个值,从0到32。):

#include <iostream>
int main(int argc, char** argv) {
  uint64_t count = 0;
  for (int i = 0; i <= 32; ++i) {
    unsigned comb = (1ULL << i) - 1;
    unsigned start = comb;
    do {
      comb = next_combination_x(comb);
      ++count;
    } while (comb != start);
  }
  std::cout << "Found " << count << " combinations; expected " << (1ULL << 32) << '\n';
  return 0;
}

结果:

1. Bit-hack with division: 43.6 seconds
2. Bit-hack with shifting: 15.5 seconds 
3. Shifting algorithm:     19.0 seconds