如何有效地生成组合而不重复,它们之间有特定的数字
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 < 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;
【讨论】:
以上是关于如何有效地生成组合而不重复,它们之间有特定的数字的主要内容,如果未能解决你的问题,请参考以下文章