如何在剔除坏序列时生成排列?

Posted

技术标签:

【中文标题】如何在剔除坏序列时生成排列?【英文标题】:How to generate permutations while culling bad sequences? 【发布时间】:2017-07-31 21:13:23 【问题描述】:

问题陈述:我有 150 个带有权重和值的对象。对象的权重可能会根据它们被选择的顺序而改变,通常选择大约 70-80 个项目。我最多只能选择一个最大权重,因此一旦我找到具有该序列的子解决方案,就需要跳过所有以相同序列开头的排列。目标是实现价值最大化。

我可以轻松地创建所有排列:

from itertools import permutations
for i in permutations(list(range(150))):
    # do something with i

但是,这会创建许多我不需要检查的序列。我也可以用 r 限制排列长度,这样

from itertools import permutations
for i in permutations(list(range(150)), r=80):
    # do something with i

但是对于非常糟糕的序列,仍然会有很多冗余检查。此外,这可能会在“最佳”解决方案之前停止。

我可以做类似的事情

from itertools import permutations
v = []
for i in permutations(list(range(150)), r=80):
    if v and v == i[:len(v)]:
        continue
    # do something with i
    v = i # would be some optimal subset of i

但是,这仍然需要很长时间才能运行,因为 Python 仍在生成和检查序列。关于我应该如何处理这个问题的任何想法?理想情况下,我将能够并行运行检查。

更多背景:我正在尝试为一款名为 Black Desert Online 的游戏创建优化的资源图(位于 somethinglovely.net/bdo/ 的图示例)。该图有大约 150 个资源节点,每个资源节点可以连接到 14 个根节点的子集。图上的中间节点具有与之关联的权重。每个城市都有可以连接的最大节点数量,并且将资源节点连接到城市需要额外的权重成本。我在随机图生成以及用于“寻找”最佳解决方案的遗传算法方面没有取得成功。此外,仅仅做出贪婪的选择会导致一个次优的解决方案。我目前对如何生成一个蛮力+综合解决方案感到困惑,该解决方案将在合理的时间内运行(合理的在一天之内在合理的台式计算机上运行。

【问题讨论】:

如果你使用通常的递归算法,你可以很容易地从递归中的任何地方返回来修剪整个子树。 之前尝试过,但遇到了递归深度错误。但是在过度设计问题时,我忘记了 setrecursionlimit。虽然我认为这仍然需要比我愿意运行程序更长的时间才能完成,但它确实回答了我提出的问题。 如果对象的权重根据它们被选择的顺序而变化,那么这会使问题变得更加困难——因为同一组对象的权重可能会根据它们的顺序而有所不同选择。这似乎意味着您必须在每个排列中尝试所有对象组合。 您能否更详细地描述问题的工作原理?可能会有更详细的答案 是增加连接的成本只是额外边的权重加上“连接惩罚”的总和(或者无论如何你称之为,将更多节点链接到根的成本会上升)?因为那样看起来您不必关注顺序(只是边缘是否正在使用的二进制事实,以及连接到根的事物的数量)并且不优化顺序,您应该有很多较小的问题。也许您也可以将其编写为线性程序,以获得良好的剪枝启发式。 【参考方案1】:

浏览库存清单,一次一件,并尝试包装有和没有该物品(两次递归)。当我们达到以下两个点之一时报告解决方案:

    没有更多需要考虑的事项 剩余的列表符合我们的体重预算。

这会通过主动构建来处理剔除。

代码:

items = [
    # Description, weight
    ("petrol", 10),
    ("clothes", 8),
    ("tents", 7),
    ("beer", 16),
    ("food", 20),
    ("teddy bear", 3),
    ("tank", 25),
    ("skin lotion", 2),
    ("library", 17),
    ("mortar", 9),
    ("cut lumber", 12),
    ("sports gear", 14),
]

limit = 20

def load(inventory, max_weight, current):

    still_okay = [item for item in inventory if item[1] <= max_weight]
    if len(still_okay) == 0:
        # Can't add any more; emit solution and back up
        print "RESULT", current
    else:
        # If the rest of the list fits in our weight budget,
        #   take everything.
        if sum([item[1] for item in still_okay]) <= max_weight:
            print "RESULT", current + still_okay
        else:
            item = still_okay.pop()
            # recur on two branches: one each with and without this item
            load(still_okay, max_weight - item[1], current + [item])
            load(still_okay, max_weight, current)

load(items, limit, [])

输出:

RESULT [('sports gear', 14), ('teddy bear', 3), ('skin lotion', 2)]
RESULT [('cut lumber', 12), ('skin lotion', 2), ('teddy bear', 3)]
RESULT [('cut lumber', 12), ('teddy bear', 3)]
RESULT [('cut lumber', 12), ('tents', 7)]
RESULT [('cut lumber', 12), ('clothes', 8)]
RESULT [('mortar', 9), ('skin lotion', 2), ('teddy bear', 3)]
RESULT [('mortar', 9), ('skin lotion', 2), ('tents', 7)]
RESULT [('mortar', 9), ('skin lotion', 2), ('clothes', 8)]
RESULT [('mortar', 9), ('teddy bear', 3), ('tents', 7)]
RESULT [('mortar', 9), ('teddy bear', 3), ('clothes', 8)]
RESULT [('mortar', 9), ('tents', 7)]
RESULT [('mortar', 9), ('clothes', 8)]
RESULT [('mortar', 9), ('petrol', 10)]
RESULT [('library', 17), ('skin lotion', 2)]
RESULT [('library', 17), ('teddy bear', 3)]
RESULT [('skin lotion', 2), ('teddy bear', 3), ('tents', 7), ('clothes', 8)]
RESULT [('skin lotion', 2), ('teddy bear', 3), ('clothes', 8)]
RESULT [('skin lotion', 2), ('teddy bear', 3), ('petrol', 10)]
RESULT [('skin lotion', 2), ('beer', 16)]
RESULT [('skin lotion', 2), ('tents', 7), ('clothes', 8)]
RESULT [('skin lotion', 2), ('tents', 7), ('petrol', 10)]
RESULT [('skin lotion', 2), ('petrol', 10), ('clothes', 8)]
RESULT [('teddy bear', 3), ('beer', 16)]
RESULT [('teddy bear', 3), ('tents', 7), ('clothes', 8)]
RESULT [('teddy bear', 3), ('tents', 7), ('petrol', 10)]
RESULT [('teddy bear', 3), ('clothes', 8)]
RESULT [('teddy bear', 3), ('petrol', 10)]
RESULT [('food', 20)]
RESULT [('beer', 16)]
RESULT [('tents', 7), ('clothes', 8)]
RESULT [('tents', 7), ('petrol', 10)]
RESULT [('petrol', 10), ('clothes', 8)]

【讨论】:

这回答了我的问题的一个更简单的版本。您的解决方案采用静态权重,而我的版本顺序很重要,因为它可以更改权重。然而,这应该给我一个比我最初的贪婪算法更好的近似值。我只需要在运行模拟现实的选择顺序(以及静态权重)之前弄清楚如何对项目进行排序。 是的,我认为这会奏效。您需要在第一个递归步骤中插入权重更新。也许颠倒顺序,所以你做“没有”递归,然后改变权重,最后进行“有”递归调用。

以上是关于如何在剔除坏序列时生成排列?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 IE 中动态生成的剔除模板中自动聚焦到输入元素

如何迭代 Rust 中序列的所有唯一排列?

MATLAB 大数据剔除坏值

n个数排序,最坏情况下的最小交换次数是多少

如何在 R 中生成对象的排列或组合?

序列生成器中止时的行为如何?