具有冷却时间的操作顺序优化算法

5
我可以从一个“动作”列表中选择一个来执行,每秒钟执行一次。列表中的每个动作都有一个数字值,表示它的价值,还有一个表示“冷却时间”的值——我必须等待多少秒才能再次使用该动作。列表可能看起来像这样:
  • 动作A的价值为1,冷却时间为2秒
  • 动作B的价值为1.5,冷却时间为3秒
  • 动作C的价值为2,冷却时间为5秒
  • 动作D的价值为3,冷却时间为10秒
因此,在这种情况下,顺序ABA的总价值为(1 + 1.5 + 1)= 3.5,并且是可接受的,因为A的第一次使用发生在1秒钟处,而A的最后一次使用发生在3秒钟处,两者之间的差异大于或等于A的冷却时间2秒。顺序AAB将不起作用,因为您只会相隔一秒钟地执行A,小于冷却时间。
我的问题是尝试优化使用动作的顺序,以最大化一定数量的动作的总价值。显然,如果您只使用一种动作,则最佳顺序是执行动作D,结果总价值为3。使用两个动作的最大价值来自执行CD或DC,结果总价值为5。当您执行10或20或100个动作时,情况会变得更加复杂。我无法找到一种在不进行暴力破解的情况下优化行动顺序的方法,这将使其复杂度指数级地增长,取决于您要优化的行动总数。过去大约15个行动就变得不可能了。
因此,有没有一种以较少的复杂性找到最佳时间的方法?是否曾经研究过这个问题?我想象中可能有一种基于加权图类型的算法可以解决这个问题,但我不知道它是如何工作的,更不用说如何实现它了。
如果这很令人困惑,请原谅——它在概念上有点奇怪,我找不到更好的表述方式。

1
最多可以有多少种不同类型的操作? - artahian
实际上不会超过12个。 - jorto_plorto
3个回答

3

编辑:这里有一个使用高度修改的Dijkstra算法的正确解决方案:

Dijkstra算法用于在给定地图(Graph Abstract)中查找最短路径,该地图是一系列节点(通常是位置,但对于此示例,让我们假设它们是操作),这些节点由弧线连接(在这种情况下,每个弧线将具有一个'值'而不是距离)

这是本质上的结构。

Graph{//in most implementations these are not Arrays, but Maps. Honestly, for your needs you don't a graph, just nodes and arcs... this is just used to keep track of them.
node[] nodes;
arc[] arcs;
}
Node{//this represents an action
arc[] options;//for this implementation, this will always be a list of all possible Actions to use.
float value;//Action value
}
Arc{
node start;//the last action used
node end;//the action after that
dist=1;//1 second
}

我们可以使用这种数据类型来制作一张地图,显示所有可行的选择方案,以得出基于每条路径的总和而得到的最优解。因此,你在寻找模式时越往前看的秒数越多,就越有可能找到非常优秀的路径。
地图上的每一段路都有一个距离,表示它的价值,每个路段的停留点是一个一秒的标记,因为这是决定下一步去哪里(执行什么操作)的时间。 为了简单起见,假设A和B是唯一的可行选项。na表示无动作,因为没有可用的行动。 如果你旅行4秒(数量越高,结果越好),你的选择是...
A->na->A->na->A
B->na->na->B->na
A->B->A->na->B
B->A->na->B->A
...

还有更多的内容,但我已经知道最优路径是B->A->na->B->A,因为它的价值最高。因此,处理这种行动组合的最佳模式(在分析了4秒后)是B->A->na->B->A。这实际上将是一个相当简单的递归算法。

    /*
     cur is the current action that you are at, it is a Node. In this example, every other action is seen as a viable option, so it's as if every 'place' on the map has a path going to every other path.
     numLeft is the amount of seconds left to run the simulation. The higher the initial value, the more desirable the results.

This won't work as written, but will give you a good idea of how the algorithm works.
*/
function getOptimal(cur,numLeft,path){
  if(numLeft==0){
    var emptyNode;//let's say, an empty node wiht a value of 0.
    return emptyNode;
  }
  var best=path;
  path.add(cur);
  for(var i=0;i<cur.options.length;i++){
    var opt=cur.options[i];//this is a COPY
    if(opt.timeCooled<opt.cooldown){
      continue;
    }
    for(var i2=0;i2<opt.length;i2++){
      opt[i2].timeCooled+=1;//everything below this in the loop is as if it is one second ahead
    }
    var potential=getOptimal(opt[i],numLeft-1,best);
    if(getTotal(potential)>getTotal(cur)){best.add(potential);}//if it makes it better, use it! getTotal will sum up the values of an array of nodes(actions)
  }
  return best;
}
function getOptimalExample(){
  log(getOptimal(someNode,4,someEmptyArrayOfNodes));//someNode will be A or B
}

编辑结束。

我有点困惑这个问题,但是......

如果你只有有限的动作,那么总是选择价值最大的动作,除非冷却时间还没有达到。

听起来你想要类似于以下伪代码:

function getOptimal(){
var a=[A,B,C,D];//A,B,C, and D are actions
a.sort()//(just pseudocode. Sort the array items by how much value they have.)
var theBest=null;
for(var i=0;i<a.length;++i){//find which action is the most valuable
     if(a[i].timeSinceLastUsed<a[i].cooldown){
        theBest=a[i];
        for(...){//now just loop through, and add time to each OTHER Action for their timeSinceLastUsed...
             //...
         }//That way, some previously used, but more valuable actions will be freed up again.
        break;
    }//because a is worth the most, and you can use it now, so why not?
}
}

这原本是我尝试过的,但似乎最优解涉及在较慢的操作之间使用许多快速冷却的动作,这不能仅通过从高到低来捕捉。 - jorto_plorto
我认为实际上被捕获; 任何时候都没有慢速操作可用,您将转而使用更快的操作。如果高价值操作足够缓慢,则会看到许多这些快速操作; 否则,您将不会看到。 - Kyle Strand
当你只有有限数量的不同操作可用时,仅仅通过最好和最差的方式去做,特别是当最好的操作具有更长的冷却时间时,最终会导致你没有剩余的操作。以我在主贴中的例子为例,如果你在前四秒钟内执行了DCBA,那么在第5秒钟你将没有任何操作可做。目标是能够每秒执行一次操作,因此像ABACABADABACB这样符合冷却要求的操作序列会更好。如果我解释得不好,请见谅。 - jorto_plorto
1
哦,我终于明白了。我将使用Dijkstra算法来解释一个解决方案,就好像这个问题是在地图上寻找最长可能的路线一样。看看我的修改...(一旦我完成) - Ben Yep
我并不完全理解为什么它会返回最优解,但它似乎是有效的。太棒了。 - jorto_plorto
显示剩余2条评论

1
EDIT: 经过再次阅读您的问题,我发现加权调度算法需要进行修改才能适应您的问题陈述; 在我们的情况下,我们只想从集合中取出与我们选择的动作类别匹配且在同一时间开始的那些重叠动作。例如,如果我们选择 a1,我们希望从集合中删除 a2b1,但不删除 b2
这看起来非常类似于加权调度问题,该问题在此pdf中详细讨论。实质上,权重是您的操作值,而间隔是(starttime,starttime + cooldown)。动态规划解决方案可以被记忆化,使其在O(nlogn)时间内运行。唯一困难的部分将是修改您的问题,使其看起来像加权间隔问题,从而使我们能够利用预定的解决方案。
因为您的间隔没有固定的开始和结束时间(即您可以选择何时开始某个动作),我建议假设一些设置时间范围,枚举所有给定动作的所有可能开始时间,然后使用这些静态的开始/结束时间与动态规划解决方案。假设您只能在整秒钟开始执行操作A的间隔(0-2、1-3、2-4等),操作B的间隔为(0-3、1-4、2-5等),操作C的间隔为(0-5、1-6、2-7等)等。然后,您可以将操作的集合并起来,以获得类似于原加权间隔问题的问题空间。
|---1---2---3---4---5---6---7---| time
|{--a1--}-----------------------| v=1
|---{--a2---}-------------------| v=1
|-------{--a3---}---------------| v=1
|{----b1----}-------------------| v=1.5
|---{----b2-----}---------------| v=1.5
|-------{----b3-----}-----------| v=1.5
|{--------c1--------}-----------| v=2
|---{--------c2---------}-------| v=2
|-------{-------c3----------}---| v=2
etc... 

不幸的是,通过强制枚举所有可能的动作开始时间和结束时间,解决方案不再是“O(n lg n)”。 - Gio Borje
假设最大爆发时间为m,则每个动作的时间应该是m/action.cooldown。假设所有动作的冷却时间相等为n,则总时间应该是n * m/action.cooldown - Gio Borje
是的,我认为我们的想法是一致的,因为爆发时间maction.cooldown不必为集合中的每个动作重新计算,因此c = m/action.cooldown - ryanbwork
如果我们将其直接插入当前运行时间中,我们就可以实现 O((n * m/action.cooldown) log (n * m/action.cooldown)),这似乎没有任何明显的简化形式。 - Gio Borje
后者是有效的,因为加权区间调度算法是在枚举产生的新元素集上操作的。 - Gio Borje
显示剩余6条评论

0

始终选择价值最高的可用操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接