【问题标题】:Generate all binary strings of length n with k bits set生成所有长度为 n 且设置了 k 位的二进制字符串
【发布时间】:2010-12-23 11:53:11
【问题描述】:

找到所有包含 k 位集合的长度为 n 的二进制字符串的最佳算法是什么?例如,如果 n=4 和 k=3,则有...

0111
1011
1101
1110

在给定任何 n 和任何 k 的情况下,我需要一种生成这些的好方法,所以我希望它使用字符串来完成。

【问题讨论】:

  • 用于研究。对某些图的匹配排除数进行一些分析,我需要一些方法来测试所有可能的 k 边删除。
  • 如果您关心性能(即较大的 n 和 k),您可能需要考虑动态编程方法。
  • ... 特别是如果可以在图形的子集上执行和缓存(记忆)部分边删除的结果,而不是首先生成所有字符串和 then 用它们做事。这将大大提高您的表现。

标签: algorithm binary permutation combinations bits


【解决方案1】:

此方法将生成所有正好有 N 个“1”位的整数。

来自https://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation

按字典顺序计算下一位排列

假设我们在一个整数中有一个 N 位设置为 1 的模式,并且我们想要 N 1 位在字典上的下一个排列。为了 例如,如果 N 为 3 且位模式为 00010011,则下一个模式 将是000101010001011000011001000110100001110000100011、 等等。以下是计算下一个的快速方法 排列。

unsigned int v; // current permutation of bits
unsigned int w; // next permutation of bits

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.
w = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(v) + 1));

x86 CPU 固有的__builtin_ctz(v) GNU C 编译器返回尾随零的数量。如果您使用 Microsoft 编译器 x86,内在是_BitScanForward。这些都发出bsf 指令,但等效物可能可用于其他体系结构。 如果不是,则考虑使用其中一种方法来计算 前面提到的连续零位。这是另一个版本 由于它的除法运算符,它往往会变慢,但它不会 需要计算尾随零。

unsigned int t = (v | (v - 1)) + 1;
w = t | ((((t & -t) / (v & -v)) >> 1) - 1);

感谢阿根廷的 Dario Sneidermanis,他于 2009 年 11 月 28 日提供了此信息。

【讨论】:

  • w = v==0 ? 0 : t | ((((t & -t) / (v & -v)) >> 1) - 1); 防止被零除异常!
【解决方案2】:

Python

import itertools

def kbits(n, k):
    result = []
    for bits in itertools.combinations(range(n), k):
        s = ['0'] * n
        for bit in bits:
            s[bit] = '1'
        result.append(''.join(s))
    return result

print kbits(4, 3)

Output: ['1110', '1101', '1011', '0111']

解释:

本质上我们需要选择 1 位的位置。在 n 个总比特中,有 n 种选择 k 种方式来选择 k 个比特。 itertools 是一个很好的模块,可以为我们做这件事。 itertools.combinations(range(n), k) 将从 [0, 1, 2 ... n-1] 中选择 k 位,然后只需在给定这些位索引的情况下构建字符串即可。

由于您没有使用 Python,请在此处查看 itertools.combinations 的伪代码:

http://docs.python.org/library/itertools.html#itertools.combinations

应该很容易用任何语言实现。

【讨论】:

  • 您知道独立于语言的解决方案吗?这取决于python的itertools,但我的程序不是用python编写的。
  • 查看我的编辑。文档显示了 itertools.combinations 是如何实现的。您可以轻松地将其移植到您使用的任何语言。
  • 我添加了一个指向 Java 组合生成器的链接。
  • Java组合生成器链接无效
【解决方案3】:

忘记实现(“用字符串来完成”显然是一个实现问题!)——考虑一下算法,看在Pete的份上...就像在,你的第一个 TAG,伙计!

您要查找的是一组 N 中 K 项的所有组合(设置位的索引 0 到 N-1 )。这显然是最简单的递归表达,例如,伪代码:

combinations(K, setN):
  if k > length(setN): return "no combinations possible"
  if k == 0: return "empty combination"
  # combinations including the first item:
  return ((first-item-of setN) combined combinations(K-1, all-but-first-of setN))
   union combinations(K, all-but-first-of setN)

即,第一个项目要么存在要么不存在:如果存在,你还有 K-1 离开(从尾部也就是除了冷杉),如果不存在,还有 K 离开。

模式匹配的函数式语言,如 SML 或 Haskell 可能是表达这种伪代码的最佳方式(过程式语言,如我最爱的 Python,实际上可能通过包含过于丰富的功能,例如 itertools.combinations,而将问题掩盖得太深为您完成所有的艰苦工作,因此对您隐藏!)。

为此,您最熟悉什么——Scheme、SML、Haskell……?我很乐意为您翻译上述伪代码。当然,我也可以用 Python 之类的语言来做——但因为重点是让你了解这个家庭作业的机制,所以我不会使用过于丰富的功能,比如 itertools.combinations,而是使用递归(和递归消除,如果需要的话)在更明显的原语(如头、尾和连接)上。但是请务必让我们知道您最熟悉的类似伪代码的语言! (您确实明白,您所说的问题与“将 K 项的所有组合排除或范围(N)”相同,对吧?)。

【讨论】:

  • @Chip,“激烈”?!你还没见过 nuttin ——记住,我是从设计(数字)芯片开始的,所以这类问题真的激起了我的意大利血统!-)
  • 你喜欢 itertools,你知道的。
  • 呃,首先,这不是家庭作业。其次,我使用的是 Java,但这并不重要。虽然 itertools.combinations 是一个特定于 python 的解决方案,但我想我可以在 Java 中实现它,但这是另一个潜在的冗余来源,它已经运行得比我预期的要慢。该程序的执行时间已经在几天的范围内,但我能够找到计算能力来暴力破解它,因为这是一个 NP 完全问题。我只是不再需要它了。
  • 我所说的 NP 完全问题不是这个二进制字符串问题,而是我试图解决的匹配排除问题需要这个算法。对不起。
【解决方案4】:

此 C# 方法返回一个创建所有组合的枚举器。由于它会在您枚举组合时创建组合,因此它只使用堆栈空间,因此它可以创建的组合数量不受内存空间的限制。

这是我想出的第一个版本。它受到堆栈空间的限制,长度约为 2700:

static IEnumerable<string> BinStrings(int length, int bits) {
  if (length == 1) {
    yield return bits.ToString();
  } else {
    if (length > bits) {
      foreach (string s in BinStrings(length - 1, bits)) {
        yield return "0" + s;
      }
    }
    if (bits > 0) {
      foreach (string s in BinStrings(length - 1, bits - 1)) {
        yield return "1" + s;
      }
    }
  }
}

这是第二个版本,它使用二进制拆分而不是拆分第一个字符,因此它更有效地使用堆栈。它仅受它在每次迭代中创建的字符串的内存空间的限制,我已经测试了它的长度为 10000000:

static IEnumerable<string> BinStrings(int length, int bits) {
  if (length == 1) {
    yield return bits.ToString();
  } else {
    int first = length / 2;
    int last = length - first;
    int low = Math.Max(0, bits - last);
    int high = Math.Min(bits, first);
    for (int i = low; i <= high; i++) {
      foreach (string f in BinStrings(first, i)) {
        foreach (string l in BinStrings(last, bits - i)) {
          yield return f + l;
        }
      }
    }
  }
}

【讨论】:

    【解决方案5】:

    这个问题的许多标准解决方案的一个问题是生成整个字符串集,然后迭代这些字符串,这可能会耗尽堆栈。除了最小的集合之外,它很快就会变得笨拙。此外,在许多情况下,只需要部分采样,但标准(递归)解决方案通常将问题分成严重偏向一个方向的部分(例如,考虑所有起始位为零的解决方案,然后将所有以一个开头的解决方案)。

    在许多情况下,更希望能够将一个位串(指定元素选择)传递给一个函数,并让它返回下一个位串以使变化最小(这是已知的)作为格雷码)并具有所有元素的表示形式。

    Donald Knuth 在他的 Fascicle 3A 第 7.2.1.3 节:生成所有组合中涵盖了很多算法。

    http://answers.yahoo.com/question/index?qid=20081208224633AA0gdMl,有一种方法可以解决从 n 中选择 k 个元素的所有方法的迭代格雷码算法 在页面底部的注释(单击以展开它)中列出了指向最终 PHP 代码的链接。

    【讨论】:

      【解决方案6】:

      一种可能的 1.5 线:

      $ python -c 'import itertools; \
                   print set([ n for n in itertools.permutations("0111", 4)])'
      
      set([('1', '1', '1', '0'), ('0', '1', '1', '1'), ..., ('1', '0', '1', '1')])
      

      .. 其中k"0111"1s 的数量。

      itertools 模块解释了其方法的等价物;请参阅 permutation method 的等效项。

      【讨论】:

      • 很好,但也不能扩展,尤其是当 n 很大而 k 很小时。
      • 效率极低,考虑改用itertools.combinations
      【解决方案7】:

      一种应该有效的算法:

      generate-strings(prefix, len, numBits) -> String:
          if (len == 0):
              print prefix
              return
          if (len == numBits):
              print prefix + (len x "1")
          generate-strings(prefix + "0", len-1, numBits)
          generate-strings(prefix + "1", len-1, numBits)
      

      祝你好运!

      【讨论】:

      • 啊,稍加修改,这个算法就可以工作了。谢谢!我将在原始问题中发布修改。
      • 虽然经过考虑,这确实会在树上产生很多枯枝。我将不得不用更大的 n 值对此进行测试。
      • 啊,是的,对于我需要测试的数据集,这个算法的运行时间似乎太长了。我正在专门研究 n=32 和 k=7,但我需要为未来的测试提供规模的灵活性。
      • FWIW,我的算法在大约 5 秒内运行 (32, 7)... 330 万个组合。那就是 Python,Java 会更快。
      【解决方案8】:

      以更通用的方式,以下函数将为您提供 N 选择 K 问题的所有可能索引组合,然后您可以将其应用于字符串或其他任何内容:

      def generate_index_combinations(n, k):
      
          possible_combinations = []
      
          def walk(current_index, indexes_so_far=None):
              indexes_so_far = indexes_so_far or []
              if len(indexes_so_far) == k:
                  indexes_so_far = tuple(indexes_so_far)
                  possible_combinations.append(indexes_so_far)
                  return
              if current_index == n:
                  return
              walk(current_index + 1, indexes_so_far + [current_index])
              walk(current_index + 1, indexes_so_far)
      
          if k == 0:
              return []
          walk(0)
          return possible_combinations
      

      【讨论】:

        【解决方案9】:

        我会尝试递归。

        有 n 个数字,其中 k 个是 1。另一种查看方式是 k+1 个时隙序列,其中分布有 n-k 个 0。也就是说,(一个 0 后跟一个 1)k 次,然后是另一个 0 的运行。这些运行中的任何一个都可以长度为零,但总长度需要为 n-k。

        将其表示为 k+1 个整数的数组。在递归的底部转换为字符串。

        递归调用深度 n-k,一种在递归调用之前递增数组的一个元素然后递减它的方法,k+1 次。

        在n-k的深度,输出字符串。

        int[] run = new int[k+1];
        
        void recur(int depth) {
            if(depth == 0){
                output();
                return;
            }
        
            for(int i = 0; i < k + 1; ++i){
                ++run[i];
                recur(depth - 1);
                --run[i];
            }
        
        public static void main(string[] arrrgghhs) {
            recur(n - k);
        }
        

        我做Java已经有一段时间了,所以这段代码可能有一些错误,但这个想法应该可行。

        【讨论】:

          【解决方案10】:

          字符串比整数数组快吗?字符串前面的所有解决方案可能会在每次迭代时生成字符串的副本。

          因此,最有效的方法可能是您附加到的 int 或 char 数组。 Java 有高效的可增长容器,对吧?如果它比字符串快,请使用它。或者如果 BigInteger 是高效的,它肯定是紧凑的,因为每个位只需要一点,而不是整个字节或整数。但是然后迭代您需要屏蔽的位,并将掩码移位到下一个位位置。所以可能会更慢,除非 JIT 编译器现在很擅长。

          我会在原始问题上发表评论,但我的业力还不够高。对不起。

          【讨论】:

            【解决方案11】:

            Python(函数式)

            使用pythonitertools.combinations,您可以生成kn的所有选项,并将这些选项映射到带有reduce的二进制数组

            from itertools import combinations
            from functools import reduce # not necessary in python 2.x
            
            def k_bits_on(k,n):
                   one_at = lambda v,i:v[:i]+[1]+v[i+1:]
                   return [tuple(reduce(one_at,c,[0]*n)) for c in combinations(range(n),k)]
            

            示例用法:

            In [4]: k_bits_on(2,5)
            Out[4]:
            [(0, 0, 0, 1, 1),
             (0, 0, 1, 0, 1),
             (0, 0, 1, 1, 0),
             (0, 1, 0, 0, 1),
             (0, 1, 0, 1, 0),
             (0, 1, 1, 0, 0),
             (1, 0, 0, 0, 1),
             (1, 0, 0, 1, 0),
             (1, 0, 1, 0, 0),
             (1, 1, 0, 0, 0)]
            

            【讨论】:

            • 有 numpy 等价物吗?
            【解决方案12】:

            对于this 问题(您需要按照设置位数量的递增顺序迭代所有子掩码),这已被标记为重复。

            我们可以简单地遍历所有子掩码,将它们添加到一个向量中,并根据设置的位数对其进行排序。

            vector<int> v;
            for(ll i=mask;i>0;i=(i-1)&mask)
                v.push_back(i);
            auto cmp = [](const auto &a, const auto &b){
                return __builtin_popcountll(a) < __builtin_popcountll(b);
            }
            v.sort(v.begin(), v.end(), cmp);
            

            另一种方法是遍历所有子掩码 N 次,如果在第 i 次迭代中设置的位数等于 i,则向向量添加一个数字。

            两种方式的复杂度都是 O(n*2^n)

            【讨论】:

              猜你喜欢
              • 2013-11-08
              • 2018-02-12
              • 2011-12-04
              • 2015-06-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多