【问题标题】:Generate All Permutations of Several Lists in Permutation-Value Order以排列值顺序生成多个列表的所有排列
【发布时间】:2015-01-21 22:14:25
【问题描述】:

我查看了其他几个关于排列的问题,但似乎没有出现这种变体。我正在寻找一种生成有偏排列的简单方法。这就是我的意思。

假设我有两个列表(尽管我需要解决 N 个列表):

l1 = [a, b, c]
l2 = [d, e, f]

这些列表的排列看起来像 [(a,d), (a,e), (a,f), (b,d), (b,e), (b,f) ...] .然而,在我的世界里,排列的元素被评分和相加以评估排列。假设 ade 值 2 分,而 bcf 为 1 分。那么一些样本排列的值是:

(a,d) = 4
(a,e) = 4 
(a,f) = 3
(c,f) = 2

我需要生成所有排列,但我想在较低值排列之前生成高值排列。

假设每个列表的元素按值降序排序,有没有一种好的方法可以按值顺序生成排列?

(显然我可以生成所有排列并对它们进行排序,但我更喜欢编写一个生成器,因为排列的数量可能很大。)

【问题讨论】:

  • 为什么 b 在 d 之前?您还想要产品或排列吗?
  • (b, c) 真的应该在列表中吗?我的理解是你把两者混在一起了。
  • 投票结束,因为您对输出的期望不是很清楚
  • @PadraicCunningham 我正在寻找排列。我修复了这个例子,现在看起来很清楚了。
  • @SlaterTyranus 这是一个错字,应该是 c,f。这些列表是独立的,不应混用。

标签: python algorithm


【解决方案1】:

例如,假设 l1={6,4,3,1} 和 l2={5,4,1}。将它们绘制为 2D 平面上的水平线和垂直线。

那么兴趣点都是交叉点。我们应该报告这些交叉点,以便从 (inf, inf) 移动到 (0, 0) 的假想扫描线接触它们。请注意,位于水平线上的点不能比同一线上的另一个点更早报告。所以对于每条水平线,我们必须只检查最右边的点。从所有这些点中,我们必须选择一个坐标和最大的点。这可以通过堆数据结构来完成。

最初,我们将位于最右边垂直线上的所有点放入堆中。然后我们从堆中提取顶部点,让出它,最后将它的左邻居放入堆中(如果有的话)。所以堆总是最多包含 len(l1) 个元素,并且每个新生成的点都花费我们 O(log(len(l1)))。如果我们从两个给定的列表中选择 l1 作为最小的列表,则可以改进解决方案。

这里是示例解决方案:

import heapq

a = [("a", 6), ("b", 4), ("c", 3), ("d", 1)]
b = [("e", 5), ("f", 5), ("g", 4), ("h", 2)]

class Pair:
    def __init__(self, i, j, value):
        self.i = i
        self.j = j
        self.value = value
    def __cmp__(self, other):
        return other.value - self.value

def solution(a, b):
    heap = []
    for i in range(len(a)):
        heapq.heappush(heap, Pair(i, 0, a[i][1] + b[0][1]))
    while len(heap) > 0:
        pair = heapq.heappop(heap)
        yield (a[pair.i], b[pair.j], pair.value)
        if pair.j + 1 < len(b):
            heapq.heappush(heap, Pair(pair.i, pair.j + 1, a[pair.i][1] + b[pair.j + 1][1]))

for (a, b, value) in solution(a, b):
    print ("%s %s -> %d" % (a, b, value))

当我们进入更高维度(要组合超过 2 个列表)时,情况会变得更糟。它可以在 2D 解决方案之上通过记忆化解决,因此我们首先将 l1,l2 构建为类似惰性列表的数据结构的答案,然后再次应用相同的算法,将此记忆化列表和 l3 作为参数,等等。必须采取的最后一步 - 我们应该始终使用长度较小的数组作为 l1,或者在开始时摆脱将 l1 的所有元素推入堆中。

N 个列表 here 的完整代码示例,因为它太长了。

【讨论】:

  • 不错的分析和解决方案!我试图将问题定义为自定义树的复杂 DFS,但我会先仔细研究一下。
【解决方案2】:

这应该很容易通过简单的贪心算法实现。也就是说,我假设您可以访问特定值,而不仅仅是值的排序列表。还假设它是排序的。

l1 = [(a, 2), (b, 1), (c, 1)]
l2 = [(d, 2), (e, 2), (f, 1)]

事实是它非常重要,但这是解决问题的方法(代码可能稍后会出现,因为正如我所说,它实际上并不重要。)

假设您在任何给定点都有三个可能的操作:

  1. (next_values 中的最高值(见下文),l1 的 l2 的最高剩余条目)
  2. (l1 的当前条目,l2 的下一个条目)# 假设您正在迭代 l2
  3. (l1 的下一个条目,l2 的最高剩余条目)

然后你必须简单地跟踪这三个中每一个的值,随着时间的推移,并在每个时间步选择最优值。它并不完全是惰性的,因为您必须在每个时间步更新这三个值,它非常接近。

要真正实现这一点,我们必须保留一个数据结构:

next_entries: {*l1: last entry of l2 explored}
next_values: {*l1: l1 entry + next l2 entry}

此时可以完成上述三个可能点的计算。同样,可以生成代码,可能会这样做,但要以良好的可读性方式编写大约 20 行密集的代码。

【讨论】:

  • 这听起来对我来说也是正确的方向。如果由每个列表和副本组成的副本仅包含该特定列表的最高未用尽值集(即本示例中的所有 2),该怎么办。在这种情况下,这意味着 l1 = [a] 和 l2 = [d,e]。将为这些列表生成所有排列,然后将弹出并填充具有下一个最低非空值集的列表。 (在这种情况下任意选择 l1 而不是 l2,给出 l1=[b,c] 和 l2 = [d,e]。然后将为新的 l1 和 l2 等生成排列。
  • 我真的不明白这是如何工作的......如果我们迭代l2,“l1 的当前条目”是什么?另外,为什么我们在每个点上只有 3 个可能的操作?
  • @quasiverse 我认为解决方案甚至比这更复杂一些。无论如何,我的理解是l1的当前条目是递归/当前稳定成员的头部。
  • @Colin 该复制技术与我提出的技术相对等效,但存储密集程度更高。我想如果值的传播与值的数量相比相对较小,那么这种方法会更好,但我认为如果传播更大,这种方法会更有效。您的理解是准确的。
  • @quasiverse 只有三种可能的操作,因为我们假设列表已排序。这三个选项基本上是:增加l1,增加l2,返回头部。如果列表在开始时没有排序,这是不可能的。
猜你喜欢
  • 2013-04-22
  • 2013-06-28
  • 1970-01-01
  • 1970-01-01
  • 2010-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多