【问题标题】:Finding an optimal solution that minimizes a constraint?找到最小化约束的最佳解决方案?
【发布时间】:2011-12-17 04:28:42
【问题描述】:

让我们将此问题称为 Slinger-Bird 问题(实际上,Slinger 类似于服务器,而鸟类似于请求,但我一想到它就精神崩溃,所以我改变了他们希望获得不同的观点!) .

  • 有 S 掷石者(投石者)和 B 鸟。
  • 弹射器不在彼此的范围内。
  • 投掷一次可以杀死投掷者视线内的所有鸟类,并且会消耗一枪和一个时间单位

我正在尝试找出最佳解决方案,以在特定鸟类到达模式的情况下最大限度地减少杀死鸟类所需的时间和射击次数。让我举一个绝对数字的例子:3 Slingers 和 4 Birds。

        Time        1            2            3           4             5
Slinger
S1                B1, B2     B1, B2, B3       B4
S2                               B1         B1, B2      B3,B4     
S3                  B1         B3, B4                 B1,B2,B3,B4

我的数据如下所示:

>> print t
[
  {
    1: {S1: [B1, B2], S2: [], S3: [B1]}, 
    2: {S1: [B1, B2, B3], S2: [B1], S3: [B3, B4]},
    3: {S1: [B4], S2: [B1,B2], S3: []},
    4: {S1: [], S2: [B3, B4], S3: [B1, B2, B3, B4]}
  }
]

我能想到的解决方案有很多(Sx at t=k 意味着 slinger Sx 在时间 k 出手):

  1. S1 在 t=1,S1 在 t=2,S1 在 t=3 成本:3 次射击 + 3 个时间单位 = 6
  2. S1 在 t=2,S1 在 t=3 成本:2 次射击 + 3 个时间单位 = 5
  3. S1 在 t=1,S3 在 t=2 成本:2 次射击 + 2 个时间单位 = 4
  4. S3 在 t=4 成本:1 次射击 + 4 个时间单位 = 5

在我看来,解决方案 3 是其中的最佳解决方案。当然,我是手工完成的(所以我可能遗漏了一些东西),但我想不出一种可扩展的方式来做到这一点。另外,我担心会出现一些极端情况,因为一个射手的决定可能会改变其他人的决定,但因为我有全局观,可能没关系。

在python中解决这个问题的快速而好的方法是什么?我很难想出一个好的数据结构来做到这一点,而不管算法是什么。我正在考虑使用动态编程,因为这似乎涉及状态空间探索,但对如何进行有点困惑。有什么建议吗?

【问题讨论】:

  • 供您参考:只有一个时间段,您的问题相当于Minimum Set Cover
  • @mhum:仔细想想,我认为这个问题实际上可以通过去除时间概念并将成本函数附加到每个集合来编码为最小集合覆盖。当然,我不确定如何将 slinger 编码到问题中。您对这种方法有什么建议吗?

标签: python algorithm dynamic-programming


【解决方案1】:

我建议对弹弓和小鸟使用位图,即

S1 = B1 = 1, S2 = B2 = 2, S3 = B3 = 4, B4 = 8

那么输入数据可以写成

bird_data = [[3, 0, 1], [7, 1, 12], [8, 3, 0], [0, 12, 15]]

成本函数现在可以这样写:

def cost(shots):
    hit = units = 0
    for show, shot in zip(bird_data, shots):
        units += 1
        for n, birds in enumerate(show):
            if shot & 1:
                units += 1
                hit |= birds
                if hit == 15: # all are hit
                    return units
            shot >>= 1
    return 99 # penalty when not all are hit

现在通过计算成本函数的最小值很容易找到最佳镜头:

from itertools import product

shot_sequences = product(*([range(7)]*len(bird_data)))

print min((cost(shots), shots) for shots in shot_sequences)

打印出来

(4, (0, 5, 0, 0))

这意味着当 S1 和 S3 (5 = 1 + 4) 在 t=2 时开火时,最佳值为 4 个单位。当然,您的解决方案也是可行的,其中 S1 在 t=1 时触发,S3 在 t=2 时触发,两者的成本相同。

但是,由于该算法使用蛮力,遍历所有可能的镜头序列,因此只有在数据集非常小时(如您的示例中)时,它才快速且可行。

【讨论】:

  • +1 谢谢。我不太确定我是否理解编码,但我会再试一次。乍一看,这可能是不可行的,因为在我的实际问题中,时间长度约为 10000,我认为这会延长运行时间。
【解决方案2】:

我假设您在开始算法时知道示例中给出的所有数字,并且在完成 t1 等后没有得到 t2。

我还假设两个投石手可以同时开火,尽管这并不重要。

在第一个选择中,您可以为每个单元格分配一个值,即 amountOfBirdsInCell-time。

这为您提供了两个值为 1 的单元格,分别是 S1t1、S1t2,其余的较低。

只有最后一个单元格的时间才会计入您的分数,因此选择最早的单元格将删除下一轮的时间,使其成为最宝贵的时间。这是第一个选择。

现在,从所有单元格中移除在第一个选择中杀死的鸟。

对剩余的单元格重复值确定过程。在您的示例中,单元格 S3t2 将给出最高的结果,为 0。

重复此过程,您可以尽早获得最有价值的细胞。

您的示例未涵盖的一个重要部分:如果您的第一个最有价值的选择是在 t2 时,那么下一个最有价值的选择可能是在 t1 或 t2,因此您应该将这些考虑在内。但是,由于 t2 已经确定,因此您不应将其时间考虑在内。

我从来没有用python写过,我只是因为算法标签,所以这里有一些类似java/c的伪代码:

highestCellTime = 0;
while(birdsRemain)
{
    bestCell;

    for(every cell that has not been picked yet)
    {
        currentCellValue = amountOfBirds;
        if(currentCellTime > highestCellTime)
        {
            currentCellValue = currentCellValue - currentCellTime;
        }

        if(currentCellValue < bestCellValue)
        {
            bestCell = thisCell;
        }
        else if(currentCellValue == bestCellValue && currentCellTime < bestCellTime)
        {
            bestCell = thisCell;
        }
    }
    addCellToPicks(bestCell);
    removeBirdsFromOtherCells(bestCellBirds);
}

除非我忘记了什么,否则你现在在你的精选集合中拥有了一个最佳的单元格组合。

我希望这段代码对 Python 程序员有意义。如果有人可以翻译它,请做!并且请删除这段文字以及之前提到的 java/c-pseudocode。

由 OP 编辑​​:第一个版本并没有以最好的单元格结束。我猜这一定是我的代码中的一个错误,但我还是在这里发布。

import math

cellsNotPicked = range(0,12)
cellToBird = {
    0: [1, 2],
    1: [],
    2: [1],
    3: [1,2,3],
    4: [1],
    5: [3,4],
    6: [4],
    7: [1,2],
    8: [],
    9: [],
    10: [3,4],
    11: [1,2,3,4]
}

picks = []

def getCellValue(cell):
    return len(cellToBird[cell])

def getCellTime(cell):
    return int(math.floor(cell / 3)) + 1

birdsRemain = 4

while(birdsRemain > 0):

    bestCell = 0;

    for thisCell in cellsNotPicked:

        currentCellValue = getCellValue(thisCell);

        currentCellTime = getCellTime(thisCell)
        highestCellTime = getCellTime(bestCell)

        if(currentCellTime > highestCellTime):
            currentCellValue = currentCellValue - currentCellTime;

        if(currentCellValue < getCellValue(bestCell)):
            bestCell = thisCell
        elif (currentCellValue == getCellValue(bestCell)) and (currentCellTime < getCellTime(bestCell)):
            bestCell = thisCell

    picks.append(bestCell)
    cellsNotPicked.remove(bestCell)

    birdsToRemove = cellToBird[bestCell]

    for key in cellToBird:
        for bird in birdsToRemove:
            try:
                cellToBird[key].remove(bird)
                birdsRemain -= 1
            except:
                pass

print picks

【讨论】:

  • +1 首先,感谢您抽出宝贵时间。我将算法转换为实际代码,但当然,我的代码中可能存在错误,因为它给我的答案是 [9,10] 作为要选择的单元格。我目前正在尝试调试它,如果我发现它有什么问题,我会回复。代码本身很容易理解,我尝试使用与算法中使用的相同的词,以便我们可以使用通用语言 :)
  • @Legend 不客气,谢谢你的拼图!这很有趣。无论如何,我多次在代码中上下运行,我看不出单元 9 有什么特别之处,以至于它会选择它。它先选哪个?顺便说一句,对于您删除的每只重复的鸟,您似乎将 BirdRemain 降低 1。这让我怀疑它首先选择了 9,没有移除任何鸟类,然后选择了 10,这使得 amountOfBirds 变为 -2。但也许我只是对python代码还不够了解。 ^^'
【解决方案3】:

这不是一个最佳分配问题,因为投石手会杀死视野中的所有鸟类。

您有一个二维目标函数,因此可以在投篮次数和时间之间进行许多权衡。确定特定时间限制的最小拍摄次数正是设置掩护问题(如 mhum 建议的那样)。集合覆盖问题是 NP-hard 且难以近似,但在实践中,使用线性规划公式的对偶进行分支定界对于找到最优值非常有效。

【讨论】:

  • +1 感谢您的提示。我不是 LP 专家,但我会尝试对问题进行编码。我将其写为顶部的评论,但我正在考虑删除时间的概念并将成本函数附加到每个单元格。但是,正如我所提到的,我并不完全清楚如何制定它。
猜你喜欢
  • 2018-04-06
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 2021-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多