以递减的顺序生成笛卡尔积

Ayo*_*bai 5 sorting algorithm generator combinatorics cartesian-product

给定n个降序排序的列表A1,A2,...,An,是否有一种算法可以按元组和降序高效地生成其笛卡尔积的所有元素?

例如,n = 3

A1 = [9, 8, 0]
A2 = [4, 2]
A3 = [5, 1]

预期的输出将是A1xA2xA3的笛卡尔积,其顺序如下:
combination sum
9, 4, 5 18
8, 4, 5 17
9, 2, 5 16
8, 2, 5 15
9, 4, 1 14
8, 4, 1 13
9, 2, 1 12
8, 2, 1 11
0, 4, 5 9
0, 2, 5 7
0, 4, 1 5
0, 2, 1 3

Gen*_*ene 8

如果问题实例有 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) 
Run Code Online (Sandbox Code Playgroud)

接下来发出总和最大的其中之一。那是 [8,4,5]。与之相邻的是

[0,4,5], [8,2,5], [8,4,1]
Run Code Online (Sandbox Code Playgroud)

将这些添加到候选集,所以我们现在有

[9,2,5], [9,4,1], [0,4,5], [8,2,5], [8,4,1]
Run Code Online (Sandbox Code Playgroud)

再次选择最高的总和。那是 [9,2,5]。相邻的是

[8,2,5], [9,2,1]. 
Run Code Online (Sandbox Code Playgroud)

所以新的候选集是

[9,4,1], [0,4,5], [8,2,5], [8,4,1], [9,2,1]
Run Code Online (Sandbox Code Playgroud)

注意 [8,2,5] 再次出现。不要复制它。

这次最高的和是[8,2,5]。相邻的是

[0,2,5], [8,2,1]
Run Code Online (Sandbox Code Playgroud)

这时候你应该有想法了。

为候选集使用最大堆。然后找到具有最大和的元组需要 O(log |C|),其中 C 是候选集。

套装可以有多大?有趣的问题。我让你考虑一下。对于您的示例中的 3 个输入集,它是

|C| = O(|A1||A2| + |A2||A3| + |A1||A3|)
Run Code Online (Sandbox Code Playgroud)

所以发射每个元组的成本是

O(log(|A1||A2| + |A2||A3| + |A1||A3|))
Run Code Online (Sandbox Code Playgroud)

如果集合的大小最多为 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));
    }
  }
}
Run Code Online (Sandbox Code Playgroud)