如何有效地生成组合而不重复,它们之间有特定的数字

Posted

技术标签:

【中文标题】如何有效地生成组合而不重复,它们之间有特定的数字【英文标题】:How to efficiently generate combination without repetition with certain distinctive number between them 【发布时间】:2017-05-27 05:33:29 【问题描述】:

如何有效地生成无重复的数字组合的集合,其中所有集合之间都有一定的独特数字。 *注意 : 范围编号总是从 0 开始。


示例:

范围编号(numbers[ ]) = 0,1,2,3,4,5,6,7 ==> 共 8 个数字 (n)。 组合 (k) = 5 个数字。 独特的数字 (nD) = 2 个数字。

结果: 0 1 2 3 4 0 1 2 5 6 0 1 3 5 7 0 1 4 6 7 0 2 3 6 7 0 2 4 5 7 0 3 4 5 6 有 7 种有效组合


如何组装:

由于我不擅长文字,所以让我将它们想象成这样:

解释它们的独特编号:

我们可以将它们汇总到这张表中:


到目前为止我取得了什么成就

我目前的解决方案效率很低(或者你可以称之为蛮力)。 * 首先 i 循环每个组合。 ==> k C n * 然后我为有效组合创建一个临时文件。 * 然后,对于每个组合,我都会验证我的 temp,如果它有效,则将其存储在 temp 中,否则忽略它。 就是这样。

这是我在控制台应用程序中的代码:

class Program

    static List<int[]> ValidCombinations;

    static void Main()
    
        ValidCombinations = new List<int[]>();

        int[] numbers = Enumerable.Range(0, 8).ToArray();
        int n = numbers.Length;
        const int k = 5;
        const int nD = 2;

        int maxIntersect = k - nD;

        int iCombination = 0;
        int iValidCombination = 0;
        int[] _temp = new int[k];
        foreach (int[] c in FindCombinations(k, n))
        
            // #Print out
            for (int i = 0; i < n; i++)
            
                if (c.Contains(i))
                    Console.Write(c[Array.IndexOf(c, i)] + " ");
                else
                    Console.Write("_ ");
            

            // Save to List
            if (IsValidSet(c, maxIntersect))
            
                _temp = new int[k];
                for (int i = 0; i < c.Length; i++)
                
                    _temp[i] = c[i];
                
                ValidCombinations.Add(_temp);
                iValidCombination++;
                Console.Write(" ### --> 0", string.Join(" ", c));
            
            Console.WriteLine();

            iCombination++;
        
        Console.WriteLine("\nTotal Combination = 0", iCombination);
        Console.WriteLine("Valid Combination Found = 0", iValidCombination);
    

    public static IEnumerable<int[]> FindCombosRec(int[] buffer, int done, int begin, int end)
    
        for (int i = begin; i < end; i++)
        
            buffer[done] = i;

            if (done == buffer.Length - 1)
                yield return buffer;
            else
                foreach (int[] child in FindCombosRec(buffer, done + 1, i + 1, end))
                    yield return child;
        
    

    public static IEnumerable<int[]> FindCombinations(int m, int n)
    
        return FindCombosRec(new int[m], 0, 0, n);
    

    private static bool IsValidSet(int[] set, int maxIntersect)
    
        foreach (var item in ValidCombinations)
        
            if (set.Intersect(item).Count() > maxIntersect)
                return false;
        

        return true;
    

我得到了从here 生成组合的基本代码。


问题

这是可行的,但对于更大范围的数字,此解决方案将需要很长时间才能完成。我知道,因为涉及到组合算法,但必须有某种捷径或模式来简化它(我的小脑袋无法弄清楚)

非常感谢。

【问题讨论】:

优化的第一步是将 n - k - nD 的第一个数字固定为始终存在(如果 n - k - nD 我上面的评论是错误的,让我重新表述为:优化的第一步是确定始终存在多少数字。在您的示例中,始终存在 0;在 n = 8、k = 5 和 nD = 1 的情况下,我们可以将 0、1、2 和 3 固定为始终存在。此外,确定行数肯定会很有用(在你的例子中是 7,在我的例子中是 4)。我感觉行数就是不定数的个数,但我还不能证明。 @F***Pijcke :这 3 个参数将始终存在,我无法修复该数字,因为它会影响它可能产生的最佳组合。也许我可以把它放到像这样的现实生活中.. @F***Pijcke : 抱歉在未完成时被点击.. 这是示例案例:"您是一位老师,即将给您的学生考试,如果您有 8 个问题并且你的每个学生应该得到 5 个问题,并且你想给学生之间 40% 的差异,那么这些问题的组合可以涵盖多少学生?” ..当然不是关于数字,而是组合本身。 (对不起,如果这种情况只会使情况更加混乱). 不,我明白了,尽管在您的真实示例中,教授不会介意某些学生之间的差异是否高于 40%,这只是恕我直言的下限。我说的不是参数之一,而是所有子集中存在的数字之一(0 从未出现在您的独特数字表中)... 【参考方案1】:

您的矩阵表示表明这个问题是同源的,或者至少非常类似于找到一组不同的固定大小的二进制字,常数Hamming weight,并且在它们之间的任何一对之间都有一个常数Hamming distance。

图形化

正如this question 中所述,这个问题不一定是微不足道的。特别是,建议的解决方案解释了如何构造Hadamard matrix,哪些行是您要查找的二进制字。

这看起来与您的矩阵非常相似。无论如何,你需要的是更通用一点。与这种情况不同,您不希望每对行的距离正好为 n/2,而是恒定距离为 d &lt; n/2

底线

轻松生成具有恒定大小(由您的numbers 数组的长度确定)、恒定权重(由您的k 确定)和恒定距离(由您的nD 确定)的二进制字集的可能性在很大程度上取决于这些参数。鉴于some techniques for generating those sets 依赖于对这些参数的一些假设,我的猜测是对于一般情况没有有效的算法。

无论如何,如果您改写您的问题并在MathOverflow 上提问,可能会很有用,可能会同时链接这个问题和我链接的问题。

算法建议

至于算法(就像您的算法一样,不适用于大数字),您可以尝试以下方法:

    生成一个由k 后跟(numbers.Length - nD) 零组成的二进制字并将其存储在一个列表中 迭代生成与原始单词完全不同的2*nD 位的每个单词。 对于每个生成的单词,只有当它与列表中的其他单词有2*nD 距离时,才尝试将其存储在列表中。

与您的方法没有太大不同,但我认为这可能会更好一些。

【讨论】:

【参考方案2】:
#include<iostream>
#include<vector>
#define N 8
#define K 5
#define D 2
using namespace std;

vector<vector<int>> vv;
vector<int> v;
int intersection(const vector<int>& a, const vector<int>& b) 
//count elements of intersection of two sorted vectors
    int count = 0;
    auto a_it = a.begin();
    auto b_it = b.begin();
    while(a_it != a.end() && b_it != b.end()) 
        if(*a_it == *b_it) count++, a_it++, b_it++; 
        else if(*a_it < *b_it) a_it++;
        else b_it++;
    
    return count;


void select_num(int n)
//might reduce some unnecessary iteration of nCk combination 
    for(auto& a : vv) if(intersection(a, v) > K - D) return;
    //above line will cut off the chain when the intersection is already over 
    //limit. You can add some more conditions to cut off unnecessary calculation.
    if(v.size() == K) 
        bool ok = true;
        for(auto& a : vv) 
            if(intersection(a, v) != K - D) 
                ok = false;
                break;
            
        
        if(ok) vv.push_back(v);
        return;
    
    if(n == N) return;

    //case : select n
    v.push_back(n);
    select_num(n+1);
    v.pop_back();

    //case : do not select n
    select_num(n+1);


int main()

    select_num(0);
    for(auto& a : vv) 
        for(auto& b : a) cout << b << ' ';
        cout << endl;
    
    cout << endl << vv.size() << endl;

【讨论】:

以上是关于如何有效地生成组合而不重复,它们之间有特定的数字的主要内容,如果未能解决你的问题,请参考以下文章

有效地将相似的数字组合在一起[重复]

您如何有效地生成介于 0 和上限 N 之间的 K 个非重复整数列表 [重复]

Python:解决找到满足特定条件的组合的问题

实体框架 - 有效地删除所有子实体而不加载它们

表中数组的组合[重复]

VBA - 复制粘贴单词表而不合并