您如何看待为此酒店问题开发算法?

时间:2011-02-09 21:53:02

标签: algorithm

我正在编写一个编程课程的问题,我在开发适合该问题的算法时遇到问题。这是:

  

你要去长途旅行。你从0英里处的道路开始。沿途有n   酒店,在英里的帖子a1< a2< ......< a,其中每个ai从起点开始测量。该   只允许您停在这些酒店的地方,但您可以选择哪家酒店   你停下来。您必须在最终的酒店(距离a)停留,这是您的目的地。   理想情况下,您希望每天行驶200英里,但这可能无法实现(取决于间距   的酒店)。如果您在一天内行驶x英里,则当天的罚款为(200 - x)^ 2。你要   计划你的旅行,以尽量减少总罚款,即所有旅行天数的总和   每日处罚。   提供一种有效的算法,确定要停止的酒店的最佳顺序。

所以,我的直觉告诉我从后面开始,检查惩罚值,然后以某种方式匹配它们返回前进方向(导致O(n ^ 2)运行时,这对于情况来说是最佳的。)< / p>

任何人都认为有任何可能的方法可以解决这个想法,或者对可能存在的意见有任何想法吗?

11 个答案:

答案 0 :(得分:9)

如果x是标记号,ax是该标记的里程数,px是到达该标记的最低罚分,您可以计算pn如果您在n之前了解pm所有标记m,则可以使用标记n

要计算pn,请找出pm + (200 - (an - am))^2m小于{{1}当前最佳标记am < an的所有标记(200 - (an - am))^2的最小值pn (最后一部分是优化)。

对于标记0a0 = 0的起始标记p0 = 01p1 = (200 - a1)^2。根据该起始信息,您可以计算p2,然后p3等,直至pn

编辑:使用OP评论中的示例切换到Java代码。请注意,这没有第二段中描述的优化检查。

public static void printPath(int path[], int i) {
    if (i == 0) return;
    printPath(path, path[i]);
    System.out.print(i + " ");
}

public static void main(String args[]) {
    int hotelList[] = {0, 200, 400, 600, 601};
    int penalties[] = {0, (int)Math.pow(200 - hotelList[1], 2), -1, -1, -1};
    int path[] = {0, 0, -1, -1, -1};
    for (int i = 2; i <= hotelList.length - 1; i++) {
        for(int j = 0; j < i; j++){
            int tempPen = (int)(penalties[j] + Math.pow(200 - (hotelList[i] - hotelList[j]), 2));
            if(penalties[i] == -1 || tempPen < penalties[i]){
                penalties[i] = tempPen;
                path[i] = j;
            }
        }
    }
    for (int i = 1; i < hotelList.length; i++) {
        System.out.print("Hotel: " + hotelList[i] + ", penalty: " + penalties[i] + ", path: ");
        printPath(path, i);
        System.out.println();
    }
}

输出是:

Hotel: 200, penalty: 0, path: 1 
Hotel: 400, penalty: 0, path: 1 2 
Hotel: 600, penalty: 0, path: 1 2 3 
Hotel: 601, penalty: 1, path: 1 2 4 

答案 1 :(得分:6)

您似乎可以使用动态编程解决此问题。子问题如下:

d(i):从一开始到酒店旅行的最低罚款i

递归公式如下:

d(0) = 0其中0是起始位置。

d(i) = min_{j=0, 1, ... , i-1} ( d(j) + (200-(ai-aj))^2)

到达酒店i的最低罚款是通过尝试前一天的所有停靠位置,添加今天的罚款并采取最低罚款。

为了找到路径,我们存储在一个单独的数组(路径[])中,我们必须从哪个酒店出发,以达到该特定酒店的最低罚款。通过向后遍历数组(从路径[n]),我们获得路径。

运行时为O(n ^ 2)。

答案 2 :(得分:3)

这相当于在方向非循环图中找到两个节点之间的最短路径。 Dijkstra的算法将在O(n ^ 2)时间运行。

但是,你的直觉更好。从后面开始,计算在该酒店停车的最低罚款。第一家酒店的罚款只是(200-(200-x)^ 2)^ 2。然后,对于其他每家酒店(按相反顺序),向前扫描以找到最低罚款的酒店。一旦惩罚成本开始增加,就会停止一个简单的优化,因为这意味着你已经超过全局最低值。

答案 3 :(得分:1)

我认为你不能像sysrqb那样容易地做到这一点。

另一方面,从开始或结束开始确实没有区别;我们的目标是找到每条路的最小停靠量,每个站点尽可能接近200米。

如上所述的问题似乎允许每天超过200米,并且惩罚同样适用于超过或低于(因为它是平方的)。由于惩罚相等,但是目标更接近,这更倾向于每天超过英里而不是未成年人。

但是,考虑到这种布局

A ----- B----C-------D------N
0       190  210     390    590

并非总是如此。最好转到B-> D-> N,总罚分仅为(200-190)^2 = 100。通过C-> D-> N进一步给出100+400=500的惩罚。

如果您已经有一个达到P点的最佳解决方案,那么答案看起来就像是一个完全广泛的首次搜索和主动修剪,到目前为止删除了所有解决方案

sum(penalty-x) > sum(penalty-p)  AND  distance-to-x <= distance-to-p - 200

这将是一个O(n ^ 2)算法

<小时/> 类似......

  • 从开始到远离所有酒店(丢弃任何有距离的酒店&gt; hotelN)
  • 创建一个解决方案的数组/列表,每个解决方案包含(ListOfHotels,I,DistanceSoFar,Penalty)
  • 按顺序检查每家酒店,每家酒店_I
    从每个先前的解决方案开始计算对I的惩罚
  • 修剪
    对于超过200距离的每个先前解决方案,距离左 current,and Penalty&gt; current.penalty,将其从列表中删除

答案 4 :(得分:1)

以下是酒店问题的MATLAB代码。

clc
clear all

% Data     
% a = [0;50;180;150;50;40];    
% a = [0, 200, 400, 600, 601];    
  a = [0,10,180,350,450,600];    
% a = [0,1,2,3,201,202,203,403];

n = length(a);    
opt(1) = 0;    
prev(1)= 1;

for i=2:n
    opt(i) =Inf;
    for j = 1:i-1
        if(opt(i)>(opt(j)+ (200-a(i)+a(j))^2))
            opt(i)= opt(j)+ (200-a(i)+a(j))^2;
            prev(i) = j; 
        end
    end
    S(i) = opt(i);
end

k = 1;
i = n;
sol(1) = n;

while(i>1)
   k = k+1;
   sol(k)=prev(i);
   i = prev(i);   
end

for i =k:-1:1
    stops(i) = sol(i);
end
stops

答案 5 :(得分:1)

第1步(共2步)

子问题:

在这种情况下,当“0 <= i <= n”时,“C(j)”被认为是直到酒店“ai”获得的最小罚分的子问题。问题所需的值是“C(n)”。

查找最小总惩罚的算法:

如果行程在“aj”位置停止,那么前一个停止将是“ai”,而i的值应该小于j。然后,“ai”的所有可能性如下:

C(j)min {C(i)+(200-(aj-ai))^ 2},0 <= i <= j。

  • 将“C(0)”的值初始化为“0”,将“a0”初始化为“0”以找到剩余的值。

  • 要找到最佳路线,请为每次迭代增加“j”和“i”的值,并使用此详细信息从“C(n)”回溯。

这里,“C(n)”是指最后一家酒店的罚款(即“i”的值介于“0”和“n”之间。

伪代码:

  

//功能定义

     

程序min_tot()

     

//外部循环表示j = 1到n的值:

     

//计算每个站点的距离C(j)=(200-aj)^ 2

     

//内部循环表示i = 1到j-1的值:

     

//计算总惩罚并指定最小//总惩罚   “C(j)的”

     

C(j)= min(C(i),C(i)+(200 - (aj-ai))^ 2}

     

//返回上一家酒店的总罚款值

     

返回C(n)

第2步(共2步)

说明: 上述算法用于找出从起点到终点的最小总罚分。

  • 它使用函数“min()”来查找行程中每个站点的总惩罚并计算最小值 惩罚价值。

算法的运行时间:

该算法包含“n”个子问题,每个子问题需要“O(n)”次才能解决。

  • 只需要计算“O(n)”的最小值。

  • 回溯过程需要“O(n)”次。

  • 算法的总运行时间为nxn = n ^ 2 = O(n ^ 2)。

因此,该算法完全采用“0(n ^ 2)”次来解决整个问题。

答案 6 :(得分:0)

为了简洁地回答你的问题,对于大多数约束满足问题,PSPACE完整算法通常被认为是“有效的”,所以如果你有一个O(n ^ 2)算法,那就是“有效”。

我认为最简单的方法,即每天N英里和200英里,将N除以200得到X;你旅行的天数。将其舍入到最接近的整数天X',然后将N除以X'得到Y,即一天中行驶的最佳里程数。这实际上是一个恒定时间操作。如果每隔Y英里有一家酒店,那么在这些酒店停车会产生尽可能低的分数,最大限度地减少每天罚款的影响。例如,如果总行程为605英里,则每天行驶201英里(最后一次为202)的罚款为1 + 1 + 4 = 6,远低于0 + 0 + 25 = 25(200 + 200 + 205)你去的时候最大限度地减少每一天的旅行罚款。

现在,您可以遍历酒店列表。最快的方法是简单地选择最接近Y英里每个倍数的酒店。这是线性时间,会产生“好”的结果。但是,我不认为这会在所有情况下产生“最佳”结果。

更复杂但更简单的方法是将两个最接近的酒店分配到Y的每个倍数;紧接着之前的那个和紧接着之后的那个。这产生了一对X'对,可以在2 ^ X'时间内以所有可能的排列遍历。您可以通过将Dijkstra应用于这些对的地图来缩短这一点,这将确定每天旅行中成本最低的路径,并将在大约(2X')^ 2时间内执行。这可能是保证产生最佳结果的最有效算法。

答案 7 :(得分:0)

正如@rmmh所说,你找到了最小距离路径。这里距离是罚分(200-x)^ 2

因此,您将尝试通过查找最低罚款来找到停止计划。

让我们说D(ai)给出起点的距离

P(i)= min {P(j)+(200 - (D(ai) - D(dj))^ 2}其中j为:0 <= j

从偶然的分析来看,它似乎是

O(n ^ 2)算法(= 1 + 2 + 3 + 4 + .... + n)= O(n ^ 2)

答案 8 :(得分:0)

我最近遇到了这个问题,想分享用Java语言编写的解决方案。

与以上大多数解决方案没有什么不同,我使用了动态编程方法。为了计算penalties[i],我们需要搜索前一天的停靠地点,以使惩罚最小。 penalties(i) = min_{j=0, 1, ... , i-1} ( penalties(j) + (200-(hotelList[i]-hotelList[j]))^2)解决方案假定第一个惩罚是Math.pow(200 - hotelList[1], 2)。我们不知道停止在第一高位是否是最佳选择,因此不应进行此假设。 为了找到最佳路径并存储沿途的所有停靠点,正在使用辅助数组path。最后,将数组向后遍历以计算finalPath

function calculateOptimalRoute(hotelList) {
const path = [];
const penalties = [];

for (i = 0; i < hotelList.length; i++) {
    penalties[i] = Math.pow(200 - hotelList[i], 2)
    path[i] = 0
    for (j = 0; j < i; j++) {
        const temp = penalties[j] + Math.pow((200 - (hotelList[i] - hotelList[j])), 2)
        if (temp < penalties[i]) {
            penalties[i] = temp;
            path[i] = (j + 1);
        }

    }
}

const finalPath = [];
let index = path.length - 1

while (index >= 0) {
    finalPath.unshift(index + 1);
    index = path[index] - 1;
}
console.log('min penalty is ', penalties[hotelList.length - 1])
console.log('final path is ', finalPath)

return finalPath;

}

// calculateOptimalRoute([20, 40, 60, 940, 1500])
// Outputs [3, 4, 5]

// calculateOptimalRoute([190, 420, 550, 660, 670])
// Outputs [1, 2, 5]

// calculateOptimalRoute([200, 400, 600, 601])
// Outputs [1, 2, 4]

// calculateOptimalRoute([])
// Outputs []

答案 9 :(得分:0)

作为概念证明,这是我在动态编程中没有嵌套循环的JavaScript解决方案。

  1. 我们从零英里处开始。

  2. 通过将循环中当前酒店的罚款与先前酒店的罚款进行比较,将罚款保持在最低水平,我们找到了下一站。

  3. 一旦我们有了当前的最低要求,我们就会找到当天的止损点。我们将此点指定为下一个起点。

  4. (可选),我们可以保留罚款总数:

public class ComedServelt extends HttpServlet {
...

public void init(ServletConfig config) throws ServletException {
...
}

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
...
  doPost(request, response);
...
}

public void doPost(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws ServletException,IOException {

...
}

public void destroy() {
LogManager.shutdown();
}
}

答案 10 :(得分:0)

这是我使用动态编程的Python解决方案:

distance = [150,180,250,340]

def hotelStop(distance):
    n = len(distance)
    DP = [0 for _ in distance]
    for i in range(n-2,-1,-1):
        min_penalty = float("inf")
        for j in range(i+1,n):
            # going from hotel i to j in first day
            x = distance[j]-distance[i]
            penalty = (200-x)**2
            total_pentalty = penalty+ DP[j]
            min_penalty = min(min_penalty,total_pentalty)
        DP[i] = min_penalty
    return DP[0]

hotelStop(distance)