生成分为k组的n个元素的每个组合的算法

时间:2014-10-21 19:50:06

标签: algorithm grouping combinations

我想生成将小写字母分成两组六个字母和两组七个字母的每个不同组合。集合中字母的顺序并不重要,即如果两个解决方案仅按集合中字母的顺序不同,那么这些解决方案是相同的。

即。这两个解决方案完全相同:

[a,b,c,d,e,f][g,h,i,j,k,l][m,n,o,p,q,r,s][t,u,v,w,x,y,z]
[f,b,c,d,e,a][l,h,i,j,k,g][s,n,o,p,q,r,m][z,u,v,w,x,y,t]

一种天真的方法可能是生成26个字母加上2个假人的每个排列,将它们平均分成四组并丢弃重复解决方案(当我使用数据时忽略假人)。但这似乎效率低下。我确定那里有一个众所周知的算法可以做到这一点,但是由于那里存在大量相似但不同的排列/组合问题,我正在努力寻找这个。

是否存在可以将 nk 元素拆分为 n k 元素集的现有命名算法,从而生成这些集合的每个组合?如果没有,我将不得不自己一起破解。但这感觉就像一个问题已经解决了。

2 个答案:

答案 0 :(得分:4)

我不知道有任何算法名称(尽管可能存在一个),但我在评论中提到的方法避免了处理重复,并且像我想象的那样有效。

  

似乎你可以通过解决问题来改善一些问题:每个字母必须进入四个桶中的一个,并且桶的空间有限,所以递归尝试将每个字母放入每个有空间的桶中它。这样你只会产生组合而不是排列。

这是一个C#实现。它可以在30秒内生成10,000,000个组合,并且只有2/3的时间用于构建字符串输出:

void Main()
{
    // Tweak these starting values to create smaller subsets if you want.
    var letters = Enumerable.Range(0, 26).Select(i => (char)('a' + i)).ToList();
    var buckets = new[]{new Bucket(6), new Bucket(6), new Bucket(7), new Bucket(7)};
    // I'm only taking 100 values because otherwise this would take a really long time.
    var combos = Combos(letters, 0, buckets).Take(100);
    foreach (var combo in combos)
    {
        Console.WriteLine(combo);
    }
}

public class Bucket : List<char>
{
    public int MaxLoad {get; private set;}
    public Bucket(int capacity) : base(capacity)
    {
        MaxLoad = capacity;
    }
}

// Define other methods and classes here
IEnumerable<string> Combos(IList<char> letters, int currentIndex, Bucket[] buckets)
{
    if(currentIndex == letters.Count){
        yield return string.Join("|", buckets.Select(b => string.Join(",", b)));
        yield break;
    }
    var currentLetter = letters[currentIndex];
    foreach (var bucket in buckets)
    {
        if(bucket.Count < bucket.Capacity)
        {
            bucket.Add(currentLetter);
            foreach (var possibility in Combos(letters, currentIndex + 1, buckets))
            {
                yield return possibility;
            }
            bucket.Remove(currentLetter);
        }
    }
}

示例输出:

a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,s|t,u,v,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,t|s,u,v,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,u|s,t,v,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,v|s,t,u,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,w|s,t,u,v,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,x|s,t,u,v,w,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,y|s,t,u,v,w,x,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,r,z|s,t,u,v,w,x,y
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,t|r,u,v,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,u|r,t,v,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,v|r,t,u,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,w|r,t,u,v,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,x|r,t,u,v,w,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,y|r,t,u,v,w,x,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,s,z|r,t,u,v,w,x,y
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,t,u|r,s,v,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,t,v|r,s,u,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,t,w|r,s,u,v,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,t,x|r,s,u,v,w,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,t,y|r,s,u,v,w,x,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,t,z|r,s,u,v,w,x,y
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,u,v|r,s,t,w,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,u,w|r,s,t,v,x,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,u,x|r,s,t,v,w,y,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,u,y|r,s,t,v,w,x,z
a,b,c,d,e,f|g,h,i,j,k,l|m,n,o,p,q,u,z|r,s,t,v,w,x,y
...

我给出的方法的一个很好的功能是,您可以在生成结果时处理结果 - 您不必等待生成整个列表,并且您不需要全部内存中的组合同时存在。

但要注意,你最终会得到很多的组合 - 很可能比计算机在任何合理的时间内都能生成的更多,而不管算法的效率如何。例如,如果文森特对10 ^ 12的估计是正确的,那么使用上面的代码需要大约一年的时间。您可以将其优化到一个月左右。并行化可能会在一台非常强大的计算机上将其缩短到一周。

答案 1 :(得分:0)

这是一个递归问题。

如果我想找到包含一些字母的所有长度为n的列表,最简单的思考方法是列出所有长度为n-1且不包含此字母的字母与[letter]字母串联]对于每一个,为了避免重复,你丢弃你以前做过的所有元素

例如,如果我想在集合[A-F]中找到两个字母组合的数量,答案是获取每个元素并找到它的组合。所以说我想找到包含A的所有组合然后是[A] [BF]然后说我想找到包含B而不是A的所有组合继续这可能是[B] [CF]这样做对于所有字母af将为您提供两个字母AF的所有可能组合,因此现在该组合成为三个字母组合的列表尾部。

您可以在不包含A的所有两个字母组合中添加一个,然后将b添加到不包含a或b的所有两个字母组合中,并继续此操作以获取所有三个字母组合。< / p>

你可以继续使用这个算法来拥有你想要的多个级别,它会找到一组给定长度的所有元素的组合

我知道你不是在寻找代码,但这是一个c#实现

public IList<string> Combos(IList<string> elements, int level)
    {
        if (level == 1)
        {
            return elements;
        }
        var combinations = new List<string>();
        var previousCombos = Combos(elements, level - 1);
        for (var i = 0; i < elements.Count; i++)
        {
            previousCombos.ToList().ForEach(item =>
            {
                if (!elements.Take(i+1).Any(item.Contains))
                {
                    combinations.Add(item + elements[i]);
                }
            });
        }
        return combinations;
    }

只是一句警告,这是非常低效的,事实上我认为它是一种指数算法,所以不要在大型​​数据集或大小上使用它,否则你的计算将需要永远。