多个列表的排列组合(笛卡儿积) | 迭代实现

西电开源社区逛论坛时候,发现下面的排列组合问题有一个高效的迭代方式实现。

如何从 ['ABC', '12'] 得到 A1 A2 B1 B2 C1 C2
然后推广到 ['abcd', '98h40ui', 'f', 'AY', ...] 这种一般情况
就是一个不定长的列表中包含多个项,每个项中只拿出来一个元素,然后列出所有可能的组合

容易得到,所有可能的组合方案总数为 \(len_1 \cdot len_2 \cdot ... \cdot len_k\)\(len_i\)为第i个字符串的长度)
如何不用dfs方式去枚举每个列表的选择呢?

 

进制转换问题

我们回想K进制的计数原理:K进制数的每一位数字为 0~K-1,如10进制 4321,数值大小表示从 00014321 之间编码的个数。
要分离 4321 每一位上的数字,则按如下操作不停取模获得余数(从低位到高位):

BASE = 10;
while(N) {
    bit = N % BASE;
    printf("%d", bit);
    N /= 10;
}

进制转化的问题也是如此,如将BASE改为2,则上述算法得到十进制数N的二进制表示。

 

变进制思想

对于该问题,每个列表的长度是不同的,可以设想我们使用一个变化进制的计数方式,将方案总数转化成该进制的数。依次从小到大遍历所有编码,分离出编码的每一位,即表示每个列表实际选取的下标。
变进制在全排列中也有运用,可以计算得到一个排列的字典序。全排列用到的阶乘数系的 BASE为 k!。

例:有排列 35241 ,我们从数字2开始看,2右侧有1个比它小的数字,数字3右侧有2个,数字4右侧有1个,数字5右侧有3个,我们将这些逆序数倒着写下来是:3,1,2,1,则该序列在我们这种排序方法中的位置序号是:
3x4!+1x3!+2x2!+1x1! = 83 注意,排序是从0开始计数的。

 

算法实现

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

// 迭代方式直接打印结果
void printCombination(const char *words[], int num) {
    int totCnt = 1;
    for (int i=0;i<num;i++) totCnt *= strlen(words[i]);
    
    for (int code=0;code<totCnt;code++) {
        int codeNow = code;
        for (int i=0;i<num;i++) {
            int base = strlen(words[i]);
            int bit = codeNow % base;
            codeNow /= base;
            printf("%c", words[i][bit]);
        }
        printf("\n");
    }
}

// 递归方式
void dfs(const char *words[], int num, int k, char now[]) {
    if (k>=num) {
          now[k] = '\0';
          printf("%s\n", now);
          return;
    }
	
    for(int i=0;i<strlen(words[k]);i++) {
          now[k] = words[k][i];
          dfs(words, num, k+1, now);
    }
}

int main() {
    const char *words[3] = {"ABC", "1234", "XY"};
    printCombination(words, 3);
	
    // char now[4];
    // dfs(strings, 3, 0, now);
    return 0;
}

 

Python实现

作为一门简洁、优雅的语言,对于这种繁杂的问题当然有更好的写法
Python标准库itertools为我们提供了非常方便的排列组合操作,itertools 模块提供的迭代器函数主要有三种类型

  • 无限迭代器:生成一个无限序列
  • 有限迭代器:接收一个或多个序列作为参数,进行组合、分组和过滤等
  • 组合生成器:序列的排列、组合,求序列的笛卡儿积等
    • product:笛卡尔积
    • permutations:排列
    • combinations:组合
    • combinations-with-replacement:生成的组合包含自身元素

代码实现:

from itertools import product

words = ["HOW", "ARE", "YOU"]
for item in product(*words):
    print("".join(item))

--End--

posted @ 2020-09-04 20:45  izcat  阅读(826)  评论(0编辑  收藏  举报