【问题标题】: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)。我感觉行数就是不定数的个数,但我还不能证明。
  • @FabianPijcke :这 3 个参数将始终存在,我无法修复该数字,因为它会影响它可能产生的最佳组合。也许我可以把它放到像这样的现实生活中..
  • @FabianPijcke : 抱歉在未完成时被点击.. 这是示例案例:"您是一位老师,即将给您的学生考试,如果您有 8 个问题并且你的每个学生应该得到 5 个问题,并且你想给学生之间 40% 的差异,那么这些问题的组合可以涵盖多少学生?” ..当然不是关于数字,而是组合本身。 (对不起,如果这种情况只会使情况更加混乱).
  • 不,我明白了,尽管在您的真实示例中,教授不会介意某些学生之间的差异是否高于 40%,这只是恕我直言的下限。我说的不是参数之一,而是所有子集中存在的数字之一(0 从未出现在您的独特数字表中)...

标签: c# algorithm combinations


【解决方案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 上提问,可能会很有用,可能会同时链接这个问题和我链接的问题。

算法建议

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

  1. 生成一个由k 后跟(numbers.Length - nD) 零组成的二进制字并将其存储在一个列表中
  2. 迭代生成与原始单词完全不同的2*nD 位的每个单词。
  3. 对于每个生成的单词,只有当它与列表中的其他单词有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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 2021-01-24
      相关资源
      最近更新 更多