从给定的多组集合中找到最佳组合

时间:2008-08-18 16:39:24

标签: php algorithm puzzle combinations np-complete

说你有货。它需要从A点到B点,从B点到C点,最后从C点到D点。你需要它在五天内到达那里,以尽可能少的钱。每条腿有三种可能的托运人,每条腿各有不同的时间和成本:

Array
(
    [leg0] => Array
        (
            [UPS] => Array
                (
                    [days] => 1
                    [cost] => 5000
                )

            [FedEx] => Array
                (
                    [days] => 2
                    [cost] => 3000
                )

            [Conway] => Array
                (
                    [days] => 5
                    [cost] => 1000
                )

        )

    [leg1] => Array
        (
            [UPS] => Array
                (
                    [days] => 1
                    [cost] => 3000
                )

            [FedEx] => Array
                (
                    [days] => 2
                    [cost] => 3000
                )

            [Conway] => Array
                (
                    [days] => 3
                    [cost] => 1000
                )

        )

    [leg2] => Array
        (
            [UPS] => Array
                (
                    [days] => 1
                    [cost] => 4000
                )

            [FedEx] => Array
                (
                    [days] => 1
                    [cost] => 3000
                )

            [Conway] => Array
                (
                    [days] => 2
                    [cost] => 5000
                )

        )

)

您将如何以编程方式找到最佳组合?

到目前为止,我最好的尝试(第三或第四种算法)是:

  1. 找到每条腿最长的托运人
  2. 消除最“昂贵”的
  3. 找到每条腿最便宜的托运人
  4. 计算总费用&天
  5. 如果天数可以接受,请完成,否则,转到1
  6. 在PHP中快速模拟(请注意,下面的测试数组可以在游戏中运行,但如果您使用上面的测试数组进行测试,则无法找到正确的组合):

    $shippers["leg1"] = array(
        "UPS"    => array("days" => 1, "cost" => 4000),
        "Conway" => array("days" => 3, "cost" => 3200),
        "FedEx"  => array("days" => 8, "cost" => 1000)
    );
    
    $shippers["leg2"] = array(
        "UPS"    => array("days" => 1, "cost" => 3500),
        "Conway" => array("days" => 2, "cost" => 2800),
        "FedEx"  => array("days" => 4, "cost" => 900)
    );
    
    $shippers["leg3"] = array(
        "UPS"    => array("days" => 1, "cost" => 3500),
        "Conway" => array("days" => 2, "cost" => 2800),
        "FedEx"  => array("days" => 4, "cost" => 900)
    );    
    
    $times = 0;
    $totalDays = 9999999;
    
    print "<h1>Shippers to Choose From:</h1><pre>";
    print_r($shippers);
    print "</pre><br />";
    
    while($totalDays > $maxDays && $times < 500){
                $totalDays = 0;
                $times++;
                $worstShipper = null;
                $longestShippers = null;
                $cheapestShippers = null;
    
                foreach($shippers as $legName => $leg){
                    //find longest shipment for each leg (in terms of days)
                    unset($longestShippers[$legName]);
                    $longestDays = null;        
    
                    if(count($leg) > 1){
                        foreach($leg as $shipperName => $shipper){
                            if(empty($longestDays) || $shipper["days"] > $longestDays){
                                $longestShippers[$legName]["days"] = $shipper["days"];
                                $longestShippers[$legName]["cost"] = $shipper["cost"];
                                $longestShippers[$legName]["name"] = $shipperName;
                                $longestDays = $shipper["days"];
                            }
                        }           
                    }
                }
    
                foreach($longestShippers as $leg => $shipper){
                    $shipper["totalCost"] = $shipper["days"] * $shipper["cost"];
    
                    //print $shipper["totalCost"] . " &lt;?&gt; " . $worstShipper["totalCost"] . ";";
    
                    if(empty($worstShipper) || $shipper["totalCost"] > $worstShipper["totalCost"]){
                        $worstShipper = $shipper;
                        $worstShipperLeg = $leg;
                    }
                }
    
                //print "worst shipper is: shippers[$worstShipperLeg][{$worstShipper['name']}]" . $shippers[$worstShipperLeg][$worstShipper["name"]]["days"];
                unset($shippers[$worstShipperLeg][$worstShipper["name"]]);
    
                print "<h1>Next:</h1><pre>";
                print_r($shippers);
                print "</pre><br />";
    
                foreach($shippers as $legName => $leg){
                    //find cheapest shipment for each leg (in terms of cost)
                    unset($cheapestShippers[$legName]);
                    $lowestCost = null;
    
                    foreach($leg as $shipperName => $shipper){
                        if(empty($lowestCost) || $shipper["cost"] < $lowestCost){
                            $cheapestShippers[$legName]["days"] = $shipper["days"];
                            $cheapestShippers[$legName]["cost"] = $shipper["cost"];
                            $cheapestShippers[$legName]["name"] = $shipperName;
                            $lowestCost = $shipper["cost"];
                        }
                    }
    
                    //recalculate days and see if we are under max days...
                    $totalDays += $cheapestShippers[$legName]['days'];  
                }
                //print "<h2>totalDays: $totalDays</h2>";
            }
    
            print "<h1>Chosen Shippers:</h1><pre>";
            print_r($cheapestShippers);
            print "</pre>";
    

    我想我可能不得不实际做某种事情,我逐字逐句地制作每个组合(用一系列循环)并将每个组合的总得分加起来,找到最好的一个......

    编辑: 澄清一下,这不是“家庭作业”任务(我不在学校)。这是我目前工作项目的一部分。

    要求(一如既往)不断变化。如果我在开始研究这个问题的时候得到了当前的限制,我会使用A *算法的一些变体(或Dijkstra或最短路径或单纯形或其他东西)。但是一切都在变形和变化,这让我想到了现在的位置。

    所以我想这意味着我需要忘记我在这一点上所做的所有废话,并且按照我所知道的那样去做,这是一种寻路算法。

7 个答案:

答案 0 :(得分:8)

可以改变某些shortest path algorithms,就像Dijkstra一样,按成本对每条路径进行加权,但如果时间超过阈值,还要跟踪时间并停止沿某条路径前进。应该找到最便宜的,以这种方式让你进入门槛

答案 1 :(得分:5)

听起来像你所谓的“线性编程问题”。这听起来像是一个家庭作业问题,没有冒犯。

LP问题的经典解决方案称为“单纯形法”。谷歌吧。

但是,要使用该方法,您必须正确制定问题以描述您的要求。

但是,有可能枚举所有可能的路径,因为你有这么小的一组。但是,这样的事情不会扩展。

答案 2 :(得分:5)

听起来像是Dijkstra's algorithm的工作:

  

Dijkstra的算法,由荷兰计算机科学家Edsger Dijkstra于1959年构思,1是一种图搜索算法,它解决了具有非负边缘路径成本的图的单源最短路径问题,输出最短路径树。该算法通常用于路由。

维基百科文章中也有实施细节。

答案 3 :(得分:3)

如果我知道我只需要按照预定的顺序处理5个城市,而且相邻城市之间只有3条路线,我就会强行说出来。没有优雅的意义。

另一方面,如果这是一个家庭作业,我本来应该制作一个可以实际扩展的算法,我可能采取不同的方法。

答案 4 :(得分:2)

正如Baltimark所说,这基本上是线性编程问题。如果只有托运人的系数(包括1,0不包括)不是每条腿的(二进制)整数,这将更容易解决。现在你需要找到一些(二进制)整数线性规划(ILP)启发式算法,因为问题是NP难的。 有关链接,请参阅Wikipedia on integer linear programming;在我的线性编程课程中,我们至少使用了Branch and bound

实际上现在我想起来了,这个特殊情况可以在没有实际ILP的情况下解决,因为只要它是&lt; = 5就无法计算天数。现在首先选择最便宜的首选载体(Conway 5) :1000)。接下来你又选择了最便宜的8天和4000个货币单位,因此我们放弃了。通过尝试其他人,我们看到他们都结果天&gt; 5所以我们回到第一选择并尝试第二个最便宜的(FedEx 2:3000),然后在第二个和联邦快递在最后。这给了我们总共4天和9000个货币单位。

然后,我们可以使用此成本来修剪树中的其他搜索,这些搜索会使某些子树级结果的成本大于我们已经找到的那些,并且从该点开始未查找该子树。 只有当我们知道在子树中搜索不会产生更好的结果时,这才有效,正如我们在此处所做的那样,当成本不能为负时。

希望这种漫无边际的帮助:)。

答案 5 :(得分:2)

这是knapsack problem。重量是运输中的天数,利润应为5000美元 - 腿的成本。消除所有负面成本并从那里开始!

答案 6 :(得分:-1)

我认为Dijkstra的算法是寻找最短的路径。

cmcculloh 正在寻找受限于他在5天内获得该限制的最低成本。

所以,仅仅找到最快的方式不会让他在那里最便宜,并且以最便宜的方式到达那里,不会在所需的时间内到达那里。