【问题标题】:Generate all combinations in bit version生成位版本的所有组合
【发布时间】:2015-07-29 08:18:10
【问题描述】:

我想以位表示生成所有可能的组合(不重复)。我不能使用任何库,如 boost 或 stl::next_combination - 它必须是我自己的代码(计算时间非常重要)。

这是我的代码(由 StackOverflow 用户修改):

    int combination  = (1 << k) - 1;
    int new_combination = 0;
    int change = 0;

    while (true)
    {
        // return next combination
        cout << combination << endl;

        // find first index to update
        int indexToUpdate = k;
        while (indexToUpdate > 0 && GetBitPositionByNr(combination, indexToUpdate)>= n - k + indexToUpdate)
            indexToUpdate--;

        if (indexToUpdate == 1) change = 1; // move all bites to the left by one position
        if (indexToUpdate <= 0) break; // done

         // update combination indices
        new_combination = 0;
        for (int combIndex = GetBitPositionByNr(combination, indexToUpdate) - 1; indexToUpdate <= k; indexToUpdate++, combIndex++)
        {
            if(change)
            {
                new_combination |= (1 << (combIndex + 1));
            }
            else
            {
                combination = combination & (~(1 << combIndex));
                combination |= (1 << (combIndex + 1));
            }
        }
        if(change) combination = new_combination;
        change = 0;
    }

其中n - 所有元素,k - 组合元素的数量。 GetBitPositionByNr - 返回第 k 位的位置。 GetBitPositionByNr(13,2) = 3 原因 13 是 1101,第二位在第三位。

它为n=4, k=2 提供了正确的输出,即:

0011 (3 - decimal representation - printed value)
0101 (5)
1001 (9)
0110 (6)
1010 (10)
1100 (12)

它还为k=1k=4 提供了正确的输出,但为k=3 提供了错误的输出,即:

0111 (7)
1011 (11)
1011 (9) - wrong, should be 13
1110 (14)

我猜问题出在内部 while 条件(第二个),但我不知道如何解决这个问题。

也许你们中的一些人知道我想要实现的更好(更快)算法?它不能使用额外的内存(数组)。

这是在 ideone 上运行的代码:IDEONE

【问题讨论】:

标签: c++ algorithm bit-manipulation combinations


【解决方案1】:

如有疑问,请使用蛮力。唉,生成所有重复的变体,然后过滤掉不必要的模式:

unsigned bit_count(unsigned n)
{
    unsigned i = 0;

    while (n) {
        i += n & 1;
        n >>= 1;
    }

    return i;
}

int main()
{
    std::vector<unsigned> combs;
    const unsigned N = 4;
    const unsigned K = 3;

    for (int i = 0; i < (1 << N); i++) {
        if (bit_count(i) == K) {
            combs.push_back(i);
        }
    }

    // and print 'combs' here
}

编辑:其他人已经指出了一个没有过滤和蛮力的解决方案,但我仍然会给你一些关于这个算法的提示:

  • 大多数编译器都提供某种固有的人口计数功能。我知道 GCC 和 Clang 有 __builtin_popcount()。使用这个内在函数,我能够将代码速度提高一倍。

  • 由于您似乎在使用 GPU,您可以并行化代码。我已经使用 C++11 的标准线程工具完成了它,并且我已经设法计算了所有 32在我的 8 核 Intel 机器上,在 7.1 秒内对任意选择的 popcounts 1、16 和 19 进行位重复。

这是我编写的最终代码:

#include <vector>
#include <cstdio>
#include <thread>
#include <utility>
#include <future>


unsigned popcount_range(unsigned popcount, unsigned long min, unsigned long max)
{
    unsigned n = 0;

    for (unsigned long i = min; i < max; i++) {
        n += __builtin_popcount(i) == popcount;
    }

    return n;
}

int main()
{
    const unsigned N = 32;
    const unsigned K = 16;

    const unsigned N_cores = 8;
    const unsigned long Max = 1ul << N;
    const unsigned long N_per_core = Max / N_cores;

    std::vector<std::future<unsigned>> v;

    for (unsigned core = 0; core < N_cores; core++) {
        unsigned long core_min = N_per_core * core;
        unsigned long core_max = core_min + N_per_core;

        auto fut = std::async(
            std::launch::async,
            popcount_range,
            K,
            core_min,
            core_max
        );

        v.push_back(std::move(fut));
    }

    unsigned final_count = 0;
    for (auto &fut : v) {
        final_count += fut.get();
    }

    printf("%u\n", final_count);

    return 0;
}

【讨论】:

  • 目标是性能,所以蛮力+过滤器可能不是要走的路。
  • @nwp 当然,生成 16 个数字是浪费时间,而您只需要其中的 4 个,对吧? (我的观点是,问题的复杂性本质上是组合的。当然,你可以通过提出一个非常聪明的算法来节省一些复杂性,但我必须看看那些讨厌的 real-world 常数因子。)
  • 事实上,这是一种清晰的计算方式,但不幸的是,计算时间对我来说非常重要,因为我有第一个 for 循环 i=0 to 2^n 然后嵌套第二个 for 循环 j=0 to 2^i 然后嵌套检查组合所以这不仅仅是一些不必要的计算 - 这是对时间的巨大浪费。
  • 顺便说一下,这里是 O(1) 时间复杂度的位计数:int uCount = n - ((n &gt;&gt; 1) &amp; 033333333333) - ((n &gt;&gt; 2) &amp; 011111111111); uCount = ((uCount + (uCount &gt;&gt; 3)) &amp; 030707070707) % 63;
  • @KamilZ 我的bit_count() 函数也是O(1),因为unsigned 是一个固定宽度的数据类型。但是如果使用 GCC 或 Clang,可以使用 __builtin_popcount(n),这样会更快(大约两倍,只是进行了基准测试)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-07
  • 1970-01-01
相关资源
最近更新 更多