能够创建所有组合及其组合群的算法

4
假设我有一组元素S = {1,2,3,4,5,6,7,8,9},我想创建三个元素的组合,并以一种方式将它们分组,以便每个数字只出现在一个组合中。
这是一个例子:{ {3, 7, 9}, {1, 2, 4}, {5, 6, 8} }。
数字在组内的顺序以及在整个示例中的组顺序均不重要。
简而言之,我想从原始集合中的每个可能组合中获取所有可能的组合,但要排除那些在多个组中出现数字的组合。
我的问题是:从运行时间和内存方面来看,这实际上可行吗? 我的样本量可能约为30-50个数字。
如果可行,创造这个算法的最佳方法是什么? 是创建所有可能的组合,仅在数字未曾出现时才选择组合吗?
我正在使用基于C ++的框架Qt 5.6编写此内容。

1
{4, 7, 9}{1, 2, 4}中都出现了数字4,这正常吗? - L. Meyer
你想把数字按3个一组分成n组,还是把它们分成n个3个一组的小组?不方便的是,你选择了一个由3²个元素组成的集合,所以从你的示例中并不清楚。 - Nelfeal
1
@Dillydill123:基于m69的正确算法,k组的公式为3k-13k-23k-43k-5...*1/2**k。换句话说,所有不是0 mod 3的1到3k数字的乘积除以2的k次方。我用Python计算了它。 - rici
2
简短公式:(3k)! / (k! * 6^k) - MBo
1
输出的大小是这个序列。对于30个元素来说,这完全是不可行的。 - n. m.
显示剩余14条评论
3个回答

13
如果在每次递归中保持第一个元素不变,并仅按顺序使用值创建3个组,则可以递归地执行此操作并避免重复,例如:
{1,2,3,4,5,6,7,8,9}
将最低的元素放在第一个位置(a),并保持它在那里:
{a,b,c} = {1, *, *}
对于第二个位置(b),从第二低到第二高的每个值进行迭代:
{a,b,c} = {1, 2~8, *}
对于第三个位置(c),迭代高于第二个值的每个值:
{1, 2~8, b+1~9}
然后用剩余的值进行递归。
{1,2,3} {4,5,6} {7,8,9} {1,2,3} {4,5,7} {6,8,9} {1,2,3} {4,5,8} {6,7,9} {1,2,3} {4,5,9} {6,7,8} {1,2,3} {4,6,7} {5,8,9} {1,2,3} {4,6,8} {5,7,9} {1,2,3} {4,6,9} {5,7,8} {1,2,3} {4,7,8} {5,6,9} {1,2,3} {4,7,9} {5,6,8} {1,2,3} {4,8,9} {5,6,7} {1,2,4} {3,5,6} {7,8,9} ... {1,8,9} {2,6,7} {3,4,5}
当我说“按顺序”时,不必是任何特定顺序(数字,字母表...),它可以只是输入的原始顺序。如果确保按接收到它们的顺序将其余值传递给下一个递归,则可以避免重新对每次递归的输入进行排序。
递归操作的说明:
假设你得到了输入{1,2,3,4,5,6,7,8,9}。作为组中的第一个元素,您从输入中取出第一个元素,并针对其他两个元素,您迭代其他值:

{1,2,3}
{1,2,4}
{1,2,5}
{1,2,6}
{1,2,7}
{1,2,8}
{1,2,9}
{1,3,4}
{1,3,5}
{1,3,6}
...
{1,8,9}

确保第三个元素始终在第二个元素之后,以避免出现重复项,例如:

{1,3,5} ⇆ {1,5,3}

假设在某个时刻,您选择了以下作为第一组:

{1,3,7}

然后将其余值传递给下一个递归:

{2,4,5,6,8,9}

在这个递归中,应用与第一组相同的规则:将第一个元素作为组中的第一个元素并保留在那里,然后迭代其他值以获得第二个和第三个元素:

{2,4,5}
{2,4,6}
{2,4,8}
{2,4,9}
{2,5,6}
{2,5,8}
{2,5,9}
{2,6,7}
...
{2,8,9}

现在,假设您选择以下作为第二组:

{2,5,6}

然后将其余值传递给下一个递归:

{4,8,9}

由于这是最后一组,只有一种可能性,因此该特定递归将以以下组合结束:

{1,3,7} {2,5,6} {4,8,9}

正如您所看到的,您不必在任何时候对值进行排序,只要按照接收到它们的顺序将它们传递到下一个递归即可。因此,如果您收到:

{q,w,e,r,t,y,u,i,o}

并从中选择以下组:

{q,r,u}

那么您应该继续传递:

{w,e,t,y,i,o}

这里是一个演示方法的JavaScript片段;它返回一个由元素组合组成的3D数组。
(过滤函数创建了输入数组的一个副本,并删除了元素0、i和j。)

function clone2D(array) {
    var clone = [];
    for (var i = 0; i < array.length; i++) clone.push(array[i].slice());
    return clone;
}

function groupThree(input) {
    var result = [], combination = [];
    group(input, 0);
    return result;

    function group(input, step) {
        combination[step] = [input[0]];
        for (var i = 1; i < input.length - 1; i++) {
            combination[step][1] = input[i];
            for (var j = i + 1; j < input.length; j++) {
                combination[step][2] = input[j];
                if (input.length > 3) {
                    var rest = input.filter(function(elem, index) {
                        return index && index != i && index != j;
                    });
                    group(rest, step + 1);
                }
                else result.push(clone2D(combination));
            }
        }
    }
}

var result = groupThree([1,2,3,4,5,6,7,8,9]);
for (var r in result) document.write(JSON.stringify(result[r]) + "<br>");


这看起来很有趣,也许正是我所需要的。我注意到在你提到“用剩下的值进行递归”之后的第二组中有一个错误。数字7出现在不止一个组中。当你谈到位置(a)(b)和(c)时,这些只针对前3个数字集吗?此外,你能解释一下如何使用其余的值进行递归吗?我还没有完全看清楚整个过程。 - Dillydill123
@Dillydill123 我在一台有工作键盘的电脑上扩展了答案 :-) - m69 ''snarky and unwelcoming''
1
非常感谢您深入的回答!!不幸的是,由于我的大样本量,创建所有组合将不可行。尽管如此,我仍然会接受您的答案! - Dillydill123
你能告诉我数组 g 的目的吗?我不熟悉 JavaScript,现在看起来它对我来说好像没有任何作用。 - Dillydill123
@Dillydill123 这是代码第一个版本的遗留问题,我忘记删除了,对此很抱歉。我已经稍微修复了代码示例。 - m69 ''snarky and unwelcoming''

0

我认为您可以使用动态规划的硬币找零问题来解决它,只需假设您正在寻找3的找零,并且数组中的每个索引都是一个硬币值1,然后输出已找到的硬币(您数组中的值)。

链接:https://www.youtube.com/watch?v=18NVyOI_690

0

对于从n个物品中取出3个的情况,您可以使用3个嵌套循环:

    for(k = 0; k < n-2; k++){
        for(j = k+1; j < n-1; j++){
            for(i = j+1; i < n  ; i++){
                ...  S[k] ... S[j] ... S[i]
            }
        }
    }

对于从n个物品中取k个的通用解决方案,您可以使用一个包含k个计数器的数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接