【问题标题】:Enumerating binary sequences枚举二进制序列
【发布时间】:2016-03-25 11:40:01
【问题描述】:

假设我想枚举所有 4 位模式,即来自 0 (0000) to 15 (1111)。一种方法是“真值表”方法:

0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, ... 1111

这种方法相当于从十进制数从 0 到 15。

另一种方法是使用格雷码,其中一次只翻转一个位:

0000, 0001, 0011, 0010, 0110, ... 1000

如何以最小化位总和的顺序系统地枚举所有数字?例如,类似于以下内容的内容:

0000, 0001, 0010, 0100, 1000, 0011, 0101, 1001, 0110, 1010, 1001, 0111, 1011, 1101, 1110, 1111

对于 4 位示例。

最终,将其扩展到任何基础都会很好,但二进制情况似乎最容易实现。

编辑:我可能应该明确表示该方法必须是生成的,即我不能计算所有可能的序列然后对它们进行排序;解决方案必须以与指定顺序相似的顺序迭代生成序列。

【问题讨论】:

  • 这更像是一个组合问题而不是编程问题。
  • 这看起来像是一个“组合”问题。例如,2位数字的集合(共4个)是从4中取2个的组合数。选中的项目为1,其他为0。所以只需从0迭代c到n,并枚举从 n 项中取出 c 项的方法。
  • 您可以预先生成所选位数中可表示的所有数字,按位数排序,然后一一呈现。或者,您可以手动对用于生成 4 位变体的算法进行编码。你想要这个有什么特别的原因吗?

标签: c++ c binary sequence counting


【解决方案1】:

这个小技巧

unsigned next_combination(unsigned x)
{
  unsigned u = x & -x;
  unsigned v = u + x;
  x = v  + (((v ^ x) / u) >> 2);
  return x;
}

将允许您轻松枚举所有包含相同数量的 1 位的unsigned 位组合以递增顺序。 (见https://en.wikipedia.org/wiki/Combinatorial_number_system

您可以使用此算法顺序枚举所有具有一个 1 位、两个 1 位、三个 1 位等的组合。例如,对于最长 4 位的组合(如您的示例)

#define N 4u

int main()
{
  for (unsigned k = 1; k <= N; ++k)
  {
    for (unsigned subset = (1 << k) - 1; 
         subset < (1 << N); 
         subset = next_combination(subset))
      printf("%X ", subset);

    printf("\n");  
  }
}

http://coliru.stacked-crooked.com/a/0c8327c5e0611eaa

上面的 Wikipedia 链接还包含对更通用算法的描述,不依赖于任何位旋转。

【讨论】:

  • 这似乎对二进制序列非常有效,但所有这些位玩弄让我相信它不能很好地推广到任意基础。例如,我的应用程序需要 256 的基数,因此本例中的“子集”实际上是一个字节数组,而不是一个无符号整数。有没有办法概括这一点?
【解决方案2】:
int main()
{

    int n, p, j, i = 0, sum;
    cin >> n;

    bool *wsk = new bool[n];
    int hmany = static_cast<int> (pow(2, n));
    bool **sortarr = new bool *[hmany];
    for (int i = 0; i < hmany; i++) sortarr[i] = new bool[n];


    bool *wsk1 = wsk;

    for (int i = 0; i < n; i++) *wsk++ = 0;
    wsk = wsk1;

    int z = 0;
    int c = n - 1;
    do
    {

        sum = 0;
        c = n - 1;
        for (int i = 0; i < n; i++)
        {
            if (wsk[i] == 1) sum += static_cast<int>(pow(2,c));
            c--;

        }
        z = sum;

        for (int i = 0; i < n; i++)
        {
            sortarr[z][i] = wsk[i];

        }

        z++; wsk = wsk1;
        i++; p = 0; j = i;

        while (j % 2 == 0)
        {
            j /= 2; p++;

        }

        if (p <= n) wsk[p] = ((1 - wsk[p]) != 0); 


    } while (p < n);

这就是你想要的。

【讨论】:

    【解决方案3】:

    这会转储二进制值列表,按设置的二进制数字(总体)的计数升序排序。具有相同总体的数字按序数值排列。

    int 值的数组由每个唯一的 4 位整数填充。

    std::sort 对数组进行排序,使用 lambda 比较值。 lambda 调用函数population 来获取数字的设置位数。

    然后,该列表由函数dump_array 输出,该函数将整数值转换为std::bitset,以便在发送到std::cout 时获得二进制表示。

    #include <iostream>
    #include <array>
    #include <algorithm>
    #include <bitset>
    
    
    template <typename T>
    int population(T x) {  // x<0 => oo loop
        int count = 0;
        while(x) {
            count += x & 1;
            x >>= 1;
        }
        return count;
    }
    
    template <typename A>
    void dump_array(const A& a) {
        const char* delim = "";
        for(auto v : a) {
            std::bitset<4> bits(v);
            std::cout << delim << bits;
            delim = ", ";
        }
        std::cout << '\n';
    }
    
    int main() {
        std::array<int, 1<<4> vals;
        int i=0;
        for(auto& v : vals) { v = i++; }
        std::sort(vals.begin(), vals.end(), [](int a, int b) {
            const int pop_a = population(a);
            const int pop_b = population(b);
            return pop_a < pop_b || (pop_a == pop_b && a < b);
        });
        dump_array(vals);
    }
    

    【讨论】:

    • 我喜欢这种方法可以被推广到任意基数(通过修改总体),但我无法提前生成所有值;该方法必须以类似于我给出的顺序一次生成一个。
    • 是的,AnT 想出了一个很好的答案。您可以使用 std::bitset 将其调整为任意大小
    • 是的,但他的回答涉及到位旋转以获得二进制序列中的下一个元素,因此它不会像我需要的那样推广到任意基数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多