【问题标题】:Solve a simple packing combination with dependencies解决一个简单的有依赖的打包组合
【发布时间】:2018-10-01 13:28:33
【问题描述】:

这不是家庭作业问题,而是我正在从事的项目中提出的问题。上图是一组盒子的包装配置,其中A,B,C,D在第一层,E,F,G在第二层。问题是,如果这些盒子是按随机顺序给出的,那么这些盒子可以放在给定配置中的概率是多少?

放置的唯一条件是所有的盒子都需要自上而下放置,一旦放置就不能移动。因此,不允许在现有盒子下滑动或浮动放置,但可以为同一层的盒子节省空间。例如,只有在 A 和 B 已经就位时才能放置 E。如果出库顺序为AEB...则不能放入指定配置,如果出库顺序为ACBE...则E可以正确下放。

更正式的描述是将打包配置转换为一组依赖项或先决条件。第一层ABCD有0个依赖,而E的依赖是{A和B},F是{B和C},G是{C和D},对应的依赖必须在E或F或G发生之前发生。此外,虽然它不适用于这个问题,但在某些问题中,依赖关系也可能是“或”关系而不是“和”。

我想知道解决这个或这类问题的一般确定性算法是什么?我能想到的一种方法是以随机顺序生成 A、B、C、D、E、F、G 10,000 次,并为每个顺序检查在调用每个元素时是否发生了相应的先决条件。然而,这个幼稚的想法很耗时,并且无法产生确切的概率(我相信这个问题的答案是~1/18,基于我实现的这个幼稚算法)。

非常感谢您的建议!

【问题讨论】:

  • 你试过什么?
  • @user4343502 我在上一段提到了我目前的想法
  • 这是一个很酷的问题,但可能更适合Mathematics
  • 嗨@juanpa.arrivillaga 我同意这是一个数学问题,尽管我更好奇是否已经存在解决此类问题的通用算法。一旦问题扩大,这将在数学上变得相当混乱。

标签: python algorithm math random combinations


【解决方案1】:
 E F G
A B C D

在您发布的这个特定实例中,ABECDG 有两种排列方式,这两个组可以以任何方式交错。

4 * (3 + 4 - 1) choose 3 = 80

现在我们可以将F 放在BC 之后的任何位置。分析F的索引分布,我们得到:

{2: 12, 3: 36, 4: 64, 5: 80, 6: 80}

正如您所说,试图为这组特定的依赖关系制定一个公式是“混乱的”。在这种情况下,我可能会生成交错的前两个金字塔,然后计算在每个金字塔中放置 F 的方法,因为组合解决方案似乎同样复杂。

一般来说,要扩展这样的问题,可以通过图形进行搜索以及利用对称性。在这种情况下,以A 开头将类似于以D 开头; BC

Python 示例:

nodes = {
  'A': {
    'neighbours': ['B','C','D','E','F','G'], 'dependency': set()},
  'B': {
    'neighbours': ['A','C','D','E','F','G'], 'dependency': set()},
  'C': {
    'neighbours': ['A','B','D','E','F','G'], 'dependency': set()},
  'D': {
    'neighbours': ['A','B','C','E','F','G'], 'dependency': set()},
  'E': {
    'neighbours': ['C','D','F','G'], 'dependency': set(['A','B'])},
  'F': {
    'neighbours': ['A','D','E','G'], 'dependency': set(['B','C'])},
  'G': {
    'neighbours': ['A','B','E','F'], 'dependency': set(['C','D'])}
}

def f(key, visited):
  if len(visited) + 1 == len(nodes):
    return 1

  if nodes[key]['dependency'] and not nodes[key]['dependency'].issubset(visited):
    return 0

  result = 0

  for neighbour in nodes[key]['neighbours']:
    if neighbour not in visited:
      _visited = visited.copy()
      _visited.add(key)
      result += f(neighbour, _visited)

  return result

print 2 * f('A', set()) + 2 * f('B', set()) # 272

# Probability = 272 / 7! = 17 / 315 = 0.05396825396825397

【讨论】:

  • 这是一个深思熟虑的答案。您手动计算概率给我留下了特别深刻的印象......尽管通用算法已经比通过所有排列快得多,但可以进一步加快速度。例如,如果没有一个节点具有依赖关系(例如同一层上的所有框),则概率应该为 1,无需进一步计算,而使用此算法,所有图都需要在返回 1 之前进行完全探索。你有任何进一步优化该算法的见解,可能是通过根据节点的依赖关系对节点进行预分组?
  • @Susie 关于如何进一步优化的有趣问题。在我看来,每种情况都可能提供不同的优化线索,因此很难一概而论。直观地说,似乎依赖关系越复杂,概率越低,并且越有可能足够快地遍历图。
  • 我从你的回答中得到了一些进一步优化问题的线索,即解决依赖图并将立方体分成独立于其他存在的组。这个具体案例中的7个block不能这样解决,但是如果F不存在,就可以进行分组。根据您的经验,您能否建议一些快速的方法将立方体分成这些独立的组?
  • @Susie 好问题。也许发布它以获得更多不同的反馈?
  • 好建议,会这样做!
【解决方案2】:

这可以通过计数方法来解决。有两种类型的框:小 (S) 和大 (L)。有N 不同的box 排列,每一个都与一个位串相关(例如:ABCDEFGSSSSLLL0000111)。

您可以找到排列数,其中小数直到并包括一些L 字母严格大于大数。

例如,在SSLSSLL 中,当您到达第一个L 时,到那时为止有两个S 和一个L(所以S > L),对于最后两个,S的数量大于L的数量。

【讨论】:

    【解决方案3】:

    有 5040 (7!) 种可能的排列。排列的数量足够小,以便实现一个脚本来检查每个排列是否是“有效”排列(即:可以达到给定的配置)。并且可以推导出概率:valid_permutations/all_permutations

    现在,问题变成了如何检查排列是否有效?如果我理解正确,当且仅当:

    • E 在 A 和 B 之后
    • F 在 B 和 C 之后
    • G 在 C 和 D 之后

    所以代码变成了(在0..6中转换A...B之后):

    valids = 0
    range7 = range(7)
    for perm in itertools.permutations(range7):
        indexes = [perm.index(x) for x in range7]
        if (indexes[4] < indexes[0] or indexes[4] < indexes[1]):
            continue
        if (indexes[5] < indexes[1] or indexes[5] < indexes[2]):
            continue
        if (indexes[6] < indexes[2] or indexes[6] < indexes[3]):
            continue
        valids += 1
    print(valids / 5040.)
    # 5,39 %!
    

    【讨论】:

    • 这绝对正确且易于理解,它比接受的答案慢(对于这种特定情况慢约 30 倍),但对于像这样的小问题并不算太慢。
    猜你喜欢
    • 2020-12-11
    • 2011-04-05
    • 2020-11-08
    • 2020-01-16
    • 1970-01-01
    • 2013-03-20
    • 1970-01-01
    • 2022-01-14
    • 2021-03-07
    相关资源
    最近更新 更多