【问题标题】:Generate cartesian product in decreasing sum order以递减和顺序生成笛卡尔积
【发布时间】:2018-08-31 05:40:56
【问题描述】:

给定 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

【问题讨论】:

    标签: algorithm sorting generator combinatorics cartesian-product


    【解决方案1】:

    如果问题实例有 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));
        }
      }
    }
    

    【讨论】:

    • 生成方法的主要优点是您不会存储所有可能的组合,这在实际应用中可能甚至无法存储。但是您仍然可以从所有组合的完整结果集中迭代前 N 个元素,其中“top”实际上可以是用户定义的。
    【解决方案2】:

    这里有一些 Python 代码。 (效率不高 - 最好只生成整个列表然后对其进行排序。)

    #! /usr/bin/env python
    import heapq
    
    def decreasing_tuple_order(*lists):
        # Each priority queue element will be:
        #    (-sum, indices, incrementing_index, sliced)
        # The top element will have the largest sum.
        if 0 < min((len(l) for l in lists)):
            indices = [0 for l in lists]
            sliced = [lists[i][indices[i]] for i in range(len(indices))]
            queue = [(-sum(sliced), indices, 0, sliced)]
            while 0 < len(queue):
                #print(queue)
                (_, indices, indexable, sliced) = heapq.heappop(queue)
                yield sliced
    
                # Can we increment this index?
                if indices[indexable] + 1 < len(lists[indexable]):
                    new_indices = indices[:]
                    new_indices[indexable] = indices[indexable] + 1
                    sliced = [lists[i][new_indices[i]] for i in range(len(indices))]
                    heapq.heappush(queue, (-sum(sliced), new_indices, indexable, sliced))
    
                # Start indexing the next index?
                while indexable + 1 < len(lists):
                    indexable = indexable + 1
                    if 1 < len(lists[indexable]):
                        # Start incrementing here.
                        indices[indexable] = 1
                        sliced = [lists[i][indices[i]] for i in range(len(indices))]
                        heapq.heappush(queue, (-sum(sliced), indices, indexable, sliced))
    
    
    
    a1 = [9, 8, 0]
    a2 = [4, 2]
    a3 = [5, 1]
    
    for x in decreasing_tuple_order(a1, a2, a3):
        print((x,sum(x)))
    

    【讨论】:

      猜你喜欢
      • 2016-10-08
      • 1970-01-01
      • 2021-05-06
      • 2011-04-06
      • 2016-05-07
      • 2021-09-05
      • 2020-02-19
      • 2012-11-18
      • 2021-11-20
      相关资源
      最近更新 更多