【问题标题】:algorithm: How to generate numbers sorted by the number of 1 in its binary representation?算法:如何生成按其二进制表示中 1 的数量排序的数字?
【发布时间】:2023-03-14 19:15:02
【问题描述】:

我正在寻找一个运算符来生成低于 2^n 的所有数字,其二进制表示形式为 1 的数量。 例如:

int next(int cur, int max) { /* ??? */ }

for (int i=0; i< 1<<6; i = next(i, 1<<6)) printf("%d ", i);

应该给出类似的东西:

0  1  2  4  8  16  32  3  5  6  9  10  12  17  18  20  24  33  34  36  40  48  7  11  13  14  19  21  22  25  26  28  35  37  38  41  42  44  49  50  52  56  15  23  27  29  30  39  43  45  46  51  53  54  57  58  60  31  47  55  59  61  62  63

(与1相同的数字顺序无关)

是否可以编写一个纯next() 函数而不记住任何状态?


这可能看起来像 Encoding system sorted by the number of 1 的重复,但这个问题是询问如何生成下一个数字而不是按索引返回

【问题讨论】:

  • (1) 编写一个函数,给定 int,返回 1 的位数。 (2) 用整数 1 到 N 填充数组。 (3) 调用 qsort,将指针传递给 (4) 比较函数,该函数使用 (1) 中的位计数函数。最后,(5)打印出排序后的数组。如果您需要帮助计算位数或致电qsort,您可以提出,但它们都是非常常见的问题,所以请先搜索。
  • next 需要是纯函数吗?记住调用之间的更多状态或递归枚举数字可能更容易。
  • 生成包含相同1s的有序序列是没有问题的。问题是过渡到下一个序列。我想一旦你做了第一个,你就可以知道哪个数字是该序列中的最后一个,并编写逻辑来检测它。
  • 假装数字是0和1的数组。找到最右边的 01 组合。交换这两个元素,并将其右侧的所有 1 移到最右端。如果不存在 01 组合,但仍有 0,则将其替换为 1,并将所有 1 移到最右边。
  • 请注意,当您从 6 中设置 2 位的权限时,您还可以从 6 中设置 4 位的权限(通过反转),类似地,6 中的 1 和 6 中的 5 等。

标签: algorithm bit-manipulation


【解决方案1】:

终于从heren-m's comment找到了答案。

基本思想是将数字视为01s 的排列。如果cur不是与1s相同数量的最大数字,我们可以简单地使用next permutation algorithm得到下一个(通过位技巧的实现),否则我们可以返回(1 &lt;&lt; (number_of_1(cur) + 1)) - 1

这是一个简单的实现。我想有可能通过一些魔法完全消除条件跳转和迭代:

// https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation
static int next_perm(unsigned int v)
{
    unsigned int t = v | (v - 1); // t gets v's least significant 0 bits set to 1
    // Next set to 1 the most significant bit to change, 
    // set to 0 the least significant ones, and add the necessary 1 bits.
    return (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));  
}

static int next(unsigned int cur, unsigned int max) {
    if (cur + 1 == max) return max;
    if (((cur - 1) | cur) >= max - 1) return (1 << (__builtin_popcount(cur) + 1)) - 1;
    return next_perm(cur);
}

int main() {
    for (volatile int i=0; i< 1<< 30; i = next(i, 1<<30));
}

The link given by @rici 提供了另一种解决方案:

// end with zero
template<typename UnsignedInteger>
UnsignedInteger next_combination(UnsignedInteger comb, UnsignedInteger mask) {
  UnsignedInteger last_one = comb & -comb;
  UnsignedInteger last_zero = (comb + last_one) &~ comb & mask;
  if (last_zero) return comb + last_one + (last_zero / (last_one * 2)) - 1;
  else if (last_one > 1) return mask / (last_one / 2);
  else return ~comb & 1;
}

int main() {
    for (volatile int i = 1; i; i = next_combination(i, (1 << 30) -1));
}

根据我使用 gcc-8.1.1 -O3 -flto 的微基准测试:

➜  bitmagic git:(master) ✗ time ./next-1
./next-1  4.27s user 0.01s system 99% cpu 4.313 total
➜  bitmagic git:(master) ✗ time ./next-2
./next-2  12.42s user 0.00s system 99% cpu 12.436 total

【讨论】:

  • @rici 谢谢!我添加了该解决方案并附上了我的微基准测试结果。
  • 是的,除法让它真的很慢。您可以使用 CTZ 和 shift 来加快速度。 en.m.wikipedia.org/wiki/Find_first_set
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-01
  • 2020-02-28
  • 1970-01-01
相关资源
最近更新 更多