我正在尝试实现一种算法,以从一组n个元素中获取k个元素的所有组合,其中两个连续组合之间的差异被最大化(因此反向格雷码)。换句话说,应该对组合进行排序,以避免元素连续出现两次,从而不会对元素进行不必要的区分。
理想情况下,算法也不会预先计算所有组合并将其存储到内存中,而是按需提供组合。 我已经对此进行了广泛搜索,并找到了一些详细的答案,例如https://stackoverflow.com/a/127856/1226020,但我似乎无法应用它。此外,该答案中链接的许多文章都是付费内容。
说明我的意思:
从一组[0,1,2,3,4]中,找到两个元素的所有组合。 使用一个简单的算法尝试增加最右边的元素直到不再可能,然后向左移动,递增前一个数字等,我得到以下结果:
[0, 1]
[0, 2]
[0, 3]
[0, 4]
[1, 2]
[1, 3]
[1, 4]
[2, 3]
[2, 4]
[3, 4]
我使用以下Java代码生成此结果:
public class CombinationGenerator {
private final int mNrElements;
private final int[] mCurrentCombination;
public CombinationGenerator(int n, int k) {
mNrElements = n;
mCurrentCombination = new int[k];
initElements(0, 0);
// fake initial state in order not to miss first combination below
mCurrentCombination[mCurrentCombination.length - 1]--;
}
private void initElements(int startPos, int startValue) {
for (int i = startPos; i < mCurrentCombination.length; i++) {
mCurrentCombination[i] = i + startValue - startPos;
}
}
public int[] getNextCombination() {
for (int i = 0; i < mCurrentCombination.length; i++) {
int pos = mCurrentCombination.length - 1 - i;
if (mCurrentCombination[pos] < mNrElements - 1 - i) {
initElements(pos, mCurrentCombination[pos] + 1);
return mCurrentCombination;
}
}
return null;
}
public static void main(String[] args) {
CombinationGenerator cg = new CombinationGenerator(5, 2);
int[] c;
while ((c = cg.getNextCombination()) != null) {
System.out.println(Arrays.toString(c));
}
}
}
这不是我想要的,因为我希望每个连续组合尽可能与前一个组合不同。目前,元素“1”连续四次出现,然后再也不出现。对于这个特定的例子,一个解决方案是:
[0, 1]
[2, 3]
[0, 4]
[1, 2]
[3, 4]
[0, 2]
[1, 3]
[2, 4]
[0, 3]
[1, 4]
我确实设法通过在生成组合后应用排序算法来完成此特定情况的结果,但这不符合我对按需组合生成的要求,因为整个组合集合必须立即生成,然后排序并保存在内存中。我不确定它是否适用于任意k和n值。最后,我很确定这不是最有效的方法,因为排序算法基本上遍历了一组组合,试图找到一个与前一个组合没有共享元素的组合。我还考虑为每个元素保留一个“命中计数”表,并使用它来始终获得包含最低组合命中数的下一个组合。 我的一些实证结论是,如果n> 1,则可以避免元素完全出现在两个连续的组合中。 2K。否则,至少应该可以避免元素连续出现两次等。
你可以将这个问题与使用足球比赛等标准循环方案获得的k = 2进行比较,但我需要一个k的任意值的解决方案。我们可以想象这是一场某种比赛的比赛,我们有一些球员可以在一组比赛中对抗所有其他球员,每场比赛都有k个球员。玩家应该尽可能不必连续玩两场比赛,但也不必在两场比赛之间不必要地等待很长时间。
关于如何使用可靠的排序算法后期生成或者 - 最好是按需解决这个问题的任何指针都会很棒!
注意:通常假设n <= 50,k <= 5
由于
答案 0 :(得分:2)
快速&amp;处理@DavidEisenstat建议的脏工作代码:
public static void main(String[] args) {
ArrayList<int[]> all = new ArrayList<int[]>();
// output is 0 if distance(i, j) != max, and 1 otherwise
int[][] m = buildGraph(7, 4, all);
HamiltonianCycle hc = new HamiltonianCycle();
int path[] = hc.findHamiltonianCycle(m);
if (path != null) {
// I have no proof that such a path will always exist
for (int i : path) {
System.out.println(Arrays.toString(all.get(i)));
}
}
}
上述代码的输出(7,4);距离(长度 - size_of_intersection)总是3;尝试使用4会导致图表断开连接:
[0, 1, 2, 3]
[0, 4, 5, 6]
[1, 2, 3, 4]
[0, 1, 5, 6]
[0, 2, 3, 4]
[1, 2, 5, 6]
[0, 1, 3, 4]
[0, 2, 5, 6]
[1, 3, 4, 5]
[0, 1, 2, 6]
[0, 3, 4, 5]
[1, 2, 3, 6]
[0, 1, 4, 5]
[0, 2, 3, 6]
[1, 4, 5, 6]
[0, 2, 3, 5]
[1, 2, 4, 6]
[0, 3, 5, 6]
[1, 2, 4, 5]
[0, 3, 4, 6]
[1, 2, 3, 5]
[0, 2, 4, 6]
[1, 3, 5, 6]
[0, 2, 4, 5]
[1, 3, 4, 6]
[0, 1, 2, 5]
[2, 3, 4, 6]
[0, 1, 3, 5]
[2, 4, 5, 6]
[0, 1, 3, 6]
[2, 3, 4, 5]
[0, 1, 4, 6]
[2, 3, 5, 6]
[0, 1, 2, 4]
[3, 4, 5, 6]
缺少代码:
// uses JHH's code to build sequences, stores it in 'all'
public static int[][] buildGraph(int n, int k, ArrayList<int[]> all) {
SequenceGenerator sg = new SequenceGenerator(n, k);
int[] c;
while ((c = sg.getNextCombination()) != null) {
all.add(c.clone());
}
int best = Math.min(n-k, k);
System.out.println("Best is " + best);
int matrix[][] = new int[all.size()][];
for (int i=0; i<matrix.length; i++) {
matrix[i] = new int[all.size()];
for (int j=0; j<i; j++) {
int d = distance(all.get(j), all.get(i));
matrix[i][j] = matrix[j][i] = (d != best)? 0 : 1;
}
}
return matrix;
}
距离:(根本没有效率,但与哈密顿计算的成本相比相形见绌)
public static int distance(int[] a, int[] b) {
HashSet<Integer> ha = new HashSet<Integer>();
HashSet<Integer> hb = new HashSet<Integer>();
for (int i=0; i<a.length; i++) {
ha.add(a[i]);
hb.add(b[i]);
}
ha.retainAll(hb);
return a.length - ha.size();
}
为了找到汉密尔顿主义者,我修改了http://www.sanfoundry.com/java-program-find-hamiltonian-cycle-unweighted-graph/的代码:
import java.util.Arrays;
public class HamiltonianCycle {
private int V, pathCount;
private int[] path;
private int[] answer;
private int[][] graph;
public int[] findHamiltonianCycle(int[][] g) {
V = g.length;
path = new int[V];
Arrays.fill(path, -1);
graph = g;
path[0] = 0;
pathCount = 1;
if (solve(0)) {
return path;
} else {
return null;
}
}
public boolean solve(int vertex) {
if (graph[vertex][0] == 1 && pathCount == V) {
return true;
}
if (pathCount == V) {
return false;
}
for (int v = 0; v < V; v++) {
if (graph[vertex][v] == 1) {
path[pathCount++] = v;
graph[vertex][v] = 0;
graph[v][vertex] = 0;
if (!isPresent(v)) {
if (solve(v)) {
answer = path.clone();
return true;
}
}
graph[vertex][v] = 1;
graph[v][vertex] = 1;
path[--pathCount] = -1;
}
}
return false;
}
public boolean isPresent(int v) {
for (int i = 0; i < pathCount - 1; i++) {
if (path[i] == v) {
return true;
}
}
return false;
}
}
警告:对于大量组合来说,这将非常缓慢......
答案 1 :(得分:1)
虽然我非常感谢@tucuxi和@David Eisenstadt的努力,但我发现哈密顿方法存在一些问题,即它没有解决n和k的某些值,并且某些元素也被不必要地区分。
我决定将我的问题中列出的想法改为go(点击计数表),它似乎给出了相当不错的结果。然而,该解决方案还需要不满足按需奖励要求的后代分类。对于合理的n和k,这应该是可行的。
不可否认,我发现我的算法有时候似乎更喜欢导致一个元素连续出现的组合,这可能会被调整。但截至目前,我的算法可能不如特别为的tucuxi。然而,它确实为每个n,k提供了解决方案,并且似乎更少地区分元素。
我的代码列在下面。
再次感谢。
public class CombinationGenerator {
private final int N;
private final int K;
private final int[] mCurrentCombination;
public CombinationGenerator(int n, int k) {
N = n;
K = k;
mCurrentCombination = new int[k];
setElementSequence(0, 0);
mCurrentCombination[K - 1]--; // fool the first iteration
}
private void setElementSequence(int startPos, int startValue) {
for (int i = startPos; i < K; i++) {
mCurrentCombination[i] = i + startValue - startPos;
}
}
public int[] getNextCombination() {
for (int i = K - 1; i >= 0; i--) {
if (mCurrentCombination[i] < i + N - K) {
setElementSequence(i, mCurrentCombination[i] + 1);
return mCurrentCombination.clone();
}
}
return null;
}
}
public class CombinationSorter {
private final int N;
private final int K;
public CombinationSorter(int n, int k) {
N = n;
K = k;
}
public List<int[]> getSortedCombinations(List<int[]> combinations) {
List<int[]> sortedCombinations = new LinkedList<int[]>();
int[] combination = null;
int[] hitCounts = new int[N]; // how many times each element has been
// used so far
// Note that this modifies (empties) the input list
while (!combinations.isEmpty()) {
int index = findNextCombination(combinations, hitCounts, combination);
combination = combinations.remove(index);
sortedCombinations.add(combination);
addHitCounts(combination, hitCounts);
}
return sortedCombinations;
}
private int findNextCombination(List<int[]> combinations, int[] hitCounts,
int[] previousCombination) {
int lowestHitCount = Integer.MAX_VALUE;
int foundIndex = 0;
// From the remaining combinations, find the one with the least used
// elements
for (int i = 0; i < combinations.size(); i++) {
int[] comb = combinations.get(i);
int hitCount = getHitCount(comb, hitCounts);
if (hitCount < lowestHitCount) {
lowestHitCount = hitCount;
foundIndex = i;
} else if (hitCount == lowestHitCount
&& previousCombination != null
&& getNrCommonElements(comb, previousCombination) < getNrCommonElements(
combinations.get(foundIndex), previousCombination)) {
// prefer this combination if hit count is equal but it's more
// different from the previous combination in our sorted list
// than what's been found so far (avoids consecutive element
// appearances)
foundIndex = i;
}
}
return foundIndex;
}
private int getHitCount(int[] combination, int[] hitCounts) {
int hitCount = 0;
for (int i = 0; i < K; i++) {
hitCount += hitCounts[combination[i]];
}
return hitCount;
}
private void addHitCounts(int[] combination, int[] hitCounts) {
for (int i = 0; i < K; i++) {
hitCounts[combination[i]]++;
}
}
private int getNrCommonElements(int[] c1, int[] c2) {
int count = 0;
for (int i = 0; i < K; i++) {
for (int j = 0; j < K; j++) {
if (c1[i] == c2[j]) {
count++;
}
}
}
return count;
}
}
public class Test {
public static void main(String[] args) {
final int n = 7;
final int k = 3;
CombinationGenerator cg = new CombinationGenerator(n, k);
List<int[]> combinations = new LinkedList<int[]>();
int[] nc;
while ((nc = cg.getNextCombination()) != null) {
combinations.add(nc);
}
for (int[] c : combinations) {
System.out.println(Arrays.toString(c));
}
System.out.println("Sorting...");
CombinationSorter cs = new CombinationSorter(n, k);
List<int[]> sortedCombinations = cs.getSortedCombinations(combinations);
for (int[] sc : sortedCombinations) {
System.out.println(Arrays.toString(sc));
}
}
}
的结果:
[0, 1, 2, 3]
[0, 4, 5, 6]
[1, 2, 3, 4]
[0, 1, 5, 6]
[2, 3, 4, 5]
[0, 1, 2, 6]
[3, 4, 5, 6]
[0, 1, 2, 4]
[0, 3, 5, 6]
[1, 2, 4, 5]
[0, 1, 3, 6]
[2, 4, 5, 6]
[0, 1, 3, 4]
[2, 3, 5, 6]
[0, 1, 4, 5]
[0, 2, 3, 6]
[1, 3, 4, 5]
[0, 2, 4, 6]
[1, 2, 3, 5]
[0, 1, 4, 6]
[0, 2, 3, 5]
[1, 2, 4, 6]
[1, 3, 5, 6]
[0, 2, 3, 4]
[1, 2, 5, 6]
[0, 3, 4, 5]
[1, 2, 3, 6]
[0, 2, 4, 5]
[1, 3, 4, 6]
[0, 2, 5, 6]
[0, 1, 3, 5]
[2, 3, 4, 6]
[1, 4, 5, 6]
[0, 1, 2, 5]
[0, 3, 4, 6]
的结果:
[0, 1]
[2, 3]
[0, 4]
[1, 2]
[3, 4]
[0, 2]
[1, 3]
[2, 4]
[0, 3]
[1, 4]