如果问题实例有 N 个要交叉的集合,那么您可以将产品中的元组视为 N 维“矩形”网格,其中每个元组对应一个网格元素。您将首先发出最大和元组 [9,4,5],它位于网格的一个角落。
您将跟踪未发出元组的“候选集”,这些元组在每个维度上至少比已发出的元组小一个。如果有帮助,您可以将已经发出的元组可视化为网格中的“实体”。候选集是所有接触实体表面的元组。
您将反复从候选集中选择下一个要发出的元组,然后用新发出的元组的邻居更新该组。当集合为空时,您就完成了。
发出[9,4,5]后,候选集为
[8,4,5] (one smaller on first dimension)
[9,2,5] (one smaller on second dimension)
[9,4,1] (one smaller on third dimension)
接下来发出总和最大的一个。那是[8,4,5]。与之相邻的是
[0,4,5], [8,2,5], [8,4,1]
将这些添加到候选集,所以我们现在有
[9,2,5], [9,4,1], [0,4,5], [8,2,5], [8,4,1]
再次选择最高的总和。那是[9,2,5]。相邻的是
[8,2,5], [9,2,1].
所以新的候选集是
[9,4,1], [0,4,5], [8,2,5], [8,4,1], [9,2,1]
Note [8,2,5] 再次出现。不要复制它。
这次最大的和是[8,2,5]。相邻的是
[0,2,5], [8,2,1]
此时你应该有这个想法了。
为候选集使用最大堆。然后找到最大和的元组需要 O(log |C|),其中 C 是候选集。
集合可以有多大?有趣的问题。我让你考虑一下。对于您的示例中的 3 个输入集,它是
|C| = O(|A1||A2| + |A2||A3| + |A1||A3|)
所以发出每个元组的成本是
O(log(|A1||A2| + |A2||A3| + |A1||A3|))
如果集合的大小最多为 N,那么这是 O(log 3 N^2) = O(log 3 + 2 log N) = O(log N)。
有|A1||A2||A3|要发出的元组,即 O(N^3)。
生成所有元组并排序的更简单算法是O(log N^3) = O(3 log N) = O(log N)。它大约只慢了 50%,这是渐近相同的。更复杂算法的主要优点是它节省了 O(N) 空间。堆/优先级队列大小仅为 O(N^2)。
这是一个快速的 Java 实现,旨在保持代码量较小。
import java.util.Arrays;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;
public class SortedProduct {
final SortedTuple [] tuples;
final NoDupHeap candidates = new NoDupHeap();
SortedProduct(SortedTuple [] tuple) {
this.tuples = Arrays.copyOf(tuple, tuple.length);
reset();
}
static class SortedTuple {
final int [] elts;
SortedTuple(int... elts) {
this.elts = Arrays.copyOf(elts, elts.length);
Arrays.sort(this.elts);
}
@Override
public String toString() {
return Arrays.toString(elts);
}
}
class RefTuple {
final int [] refs;
final int sum;
RefTuple(int [] index, int sum) {
this.refs = index;
this.sum = sum;
}
RefTuple getSuccessor(int i) {
if (refs[i] == 0) return null;
int [] newRefs = Arrays.copyOf(this.refs, this.refs.length);
int j = newRefs[i]--;
return new RefTuple(newRefs, sum - tuples[i].elts[j] + tuples[i].elts[j - 1]);
}
int [] getTuple() {
int [] val = new int[refs.length];
for (int i = 0; i < refs.length; ++i)
val[i] = tuples[i].elts[refs[i]];
return val;
}
@Override
public int hashCode() {
return Arrays.hashCode(refs);
}
@Override
public boolean equals(Object o) {
if (o instanceof RefTuple) {
RefTuple t = (RefTuple) o;
return Arrays.equals(refs, t.refs);
}
return false;
}
}
RefTuple getInitialCandidate() {
int [] index = new int[tuples.length];
int sum = 0;
for (int j = 0; j < index.length; ++j)
sum += tuples[j].elts[index[j] = tuples[j].elts.length - 1];
return new RefTuple(index, sum);
}
final void reset() {
candidates.clear();
candidates.add(getInitialCandidate());
}
int [] getNext() {
if (candidates.isEmpty()) return null;
RefTuple next = candidates.poll();
for (int i = 0; i < tuples.length; ++i) {
RefTuple successor = next.getSuccessor(i);
if (successor != null) candidates.add(successor);
}
return next.getTuple();
}
/** A max heap of indirect ref tuples that ignores addition of duplicates. */
static class NoDupHeap {
final PriorityQueue<RefTuple> heap =
new PriorityQueue<>((a, b) -> Integer.compare(b.sum, a.sum));
final Set<RefTuple> set = new HashSet<>();
void add(RefTuple t) {
if (set.contains(t)) return;
heap.add(t);
set.add(t);
}
RefTuple poll() {
RefTuple t = heap.poll();
set.remove(t);
return t;
}
boolean isEmpty() {
return heap.isEmpty();
}
void clear() {
heap.clear();
set.clear();
}
}
public static void main(String [] args) {
SortedTuple [] tuples = {
new SortedTuple(9, 8, 0),
new SortedTuple(4, 2),
new SortedTuple(5, 1),
};
SortedProduct product = new SortedProduct(tuples);
for (;;) {
int[] next = product.getNext();
if (next == null) break;
System.out.println(Arrays.toString(next));
}
}
}