【问题标题】:Minimizing Cost using Dijkstra's?使用 Dijkstra 最小化成本?
【发布时间】:2020-08-15 16:43:30
【问题描述】:

我又花了几个小时在一个所谓的“简单”练习题上,并认为我可以提出一个更有力的问题。希望得到反馈。

问题依旧:

您需要安排一张办公桌,以便一个人始终在那里,连续 24 小时。你可以雇佣 6 个人来做这件事,但你想把成本降到最低。任何工作 8 小时的人每小时赚 20 美元。可以使用什么贪心算法来最小化成本? (即并确定是否雇用 2 人每人 12 小时、1 人 24 小时、3 人每人 8 小时等)?

所以我知道 Dijkstra 是一种“贪婪”算法,允许我们使用加权边缘。

我了解问题的症结所在 - 我可以使用 Dijkstra 找到能够最大限度降低 24 小时轮班成本的设备。 (例如从顶层节点,将子节点拆分为“1人班”、“2人班”、“3人班”、“4人班”、“5人班”、“6人班” “ ...然后在进一步的子节点中,列出每个组合。边缘的“权重”是成本。我想找到“最便宜”的路径但显然这很笨拙,因为我必须列出所有可能的分组(即 5!和 6!保安)及其成本。

In [4]:
shift = {}
shift["1_guard"] = {}
shift["2_guards"] = {}
shift["3_guards"] = {}
shift["4_guards"] = {}
shift["5_guards"] = {}
shift["6_guards"] = {}
#at each step we're doing a brute force attack to figure out ok ... if we have x guards, whats the breakdown of the total costs possible?
shift["1_guard"]["Shifts_Filled"] = (24 * 20)

shift["2_guards"]["1_hour_23_hours"] = ((15 * 1)+ (20 * 23))
shift["2_guards"]["2_hour_22_hours"] = ((15 * 2)+ (20 * 22))
shift["2_guards"]["3_hour_21_hours"] = ((15 * 3)+ (20 * 21))
shift["2_guards"]["4_hour_20_hours"] = ((15 * 4)+ (20 * 20))
shift["2_guards"]["5_hour_19_hours"] = ((15 * 5)+ (20 * 19))
shift["2_guards"]["6_hour_18_hours"] = ((15 * 6)+ (20 * 18))
shift["2_guards"]["7_hour_17_hours"] = ((15 * 7)+ (20 * 17))
shift["2_guards"]["8_hour_16_hours"] = ((15 * 8)+ (20 * 16))
shift["2_guards"]["9_hour_15_hours"] = ((15 * 9)+ (20 * 15))
shift["2_guards"]["10_hour_14_hours"] = ((15 * 10)+ (20 * 14))
shift["2_guards"]["11_hour_13_hours"] = ((15 * 11)+ (20 * 13))
shift["2_guards"]["12_hour_12_hours"] = ((15 * 12)+ (20 * 12))
#you dont need to do more than this because it will be one of these combos (it doesnt matter which guard works which shift)

shift["3_guards"]["1_hour_1_hour_22_hours"] = ((15 * 1) + (15 * 1) + (20*22)
#fill in for every combination of hours 3 guards could have
                                               
shift("4_guards")["1_hour_1_hour_1_hour_20_hours"] = ((15 * 1) + (15 * 1) (15 * 1) + (20*22)
#fill in for every combination of hours 3 guards could have

def find_lowest_cost_node(costs):
    lowest_cost = float("inf")
    lowest_cost_node = None
    # Go through each node.
    for node in costs:
        cost = costs[node]
        # If it's the lowest cost so far and hasn't been processed yet...
        if cost < lowest_cost and node not in processed:
            # ... set it as the new lowest-cost node.
            lowest_cost = cost
            lowest_cost_node = node
    return lowest_cost_node
start_time = time.perf_counter()

# Find the lowest-cost node that you haven't processed yet.
node = find_lowest_cost_node(costs)
# If you've processed all the nodes, this while loop is done.
while node is not None:
    cost = costs[node]
    # Go through all the neighbors of this node.
    neighbors = shift[node]
    for n in neighbors.keys():
        new_cost = cost + neighbors[n]
        # If it's cheaper to get to this neighbor by going through this node...
        if costs[n] > new_cost:
            # ... update the cost for this node.
            costs[n] = new_cost
            # This node becomes the new parent for this neighbor.
            parents[n] = node
    # Mark the node as processed.
    processed.append(node)
    # Find the next node to process, and loop.
    node = find_lowest_cost_node(costs)

print("Cost from the start to each node:")
print(costs)

print(time.perf_counter() - start_time, "seconds")
dijkstras_time = time.perf_counter() - start_time
                                                      
# the costs table
infinity = float("inf")
costs = {}
costs["1_guard"] = 
costs["2_guards"] = 
costs["3_guards"] = 
costs["4_guards"] = infinity


# the parents table
#need help here
parents = {}
parents[""] = "NYC"
parents[""] = "NYC"
parents[""] = "NYC"
#all the ones below a certain threshold should have None*
parents[""] = None


processed = []

我这样做的方式是否正确?对我的代码的反馈会非常有帮助。

【问题讨论】:

  • 这似乎没什么意义。您需要 3 个人每人 8 小时(或更多人用更少时间,没关系)。您需要搜索什么样的图表才能找到它?
  • 记得对过去回答的问题提供反馈。

标签: python algorithm optimization dijkstra


【解决方案1】:

我认为 Dijkstra 在这里不一定是最有用的,因为它产生从 A 到 B 的最低成本路径。再说一次,如果你真的想使用它,你可以让中间节点表示状态,即节点G 表示状态 (08:00, 00:00),在计划 08:00 到上午 8 点之后,当前警卫在午夜开始。这样,最佳路径将是

(00:00, 00:00) -&gt; (01:00, 00:00) -&gt; ... -&gt; (08:00, 00:00) -&gt; (09:00, 08:00) -&gt; ...

那么你正在做的实际上是动态规划,你将问题分解成更小的子问题,然后以最优的方式解决这些问题。

现在,我在上面提到的新状态下使用了您的代码。这将搜索空间减少到只有约 325 个节点。实际上,您在路上做得很好,只是关于我没有得到(也不需要)的父母的部分。相反,我使用带有元组键的字典,并让值表示成本。

infinity = float("inf")

nodes = {
    (current_hour, last_guard_started_hour): infinity
    for current_hour in range(25)
    for last_guard_started_hour in range(25)
    if last_guard_started_hour <= current_hour
}
start = current = (0, 0)
nodes[start] = 0
processed = []

然后,如果您将其视为正方形 (x, y) 网格,则邻居是 (x-1, y), (x+1, y), (x, y-1), (x, y + 1) 处的节点,除非您在边界处。

def find_neighbours(current_node):
    current_hour, last_guard_started_hour = current_node
    
    # Neighbours are when either the guard started earlier/later or the current
    # hour progresses/regresses
    neighbours = []
    if current_hour > 0 and last_guard_started_hour != current_hour:
        neighbours.append((current_hour - 1, last_guard_started_hour))
    if current_hour < 24:
        neighbours.append((current_hour + 1, last_guard_started_hour))

    if last_guard_started_hour > 0:
        neighbours.append((current_hour, last_guard_started_hour - 1))
    if last_guard_started_hour < current_hour:
        neighbours.append((current_hour, last_guard_started_hour + 1))

    return neighbours

然后您可以在知道解决方案的情况下逐步更新节点直到当前保护开始是最佳的,因此不需要重新计算。

def get_cheapest_for_hour(hour):
    candidates = { n: nodes[n] for n in nodes
                   if n[0] == hour }

    # Get the entry with the lowest cost, but also prefer longer shifts
    node = min(candidates, key=lambda k: (nodes[k], k[1]))
    return node

def set_cost(node):
    hour, last_guard_started_hour = node
    if hour == 0 and last_guard_started_hour == 0:
        return

    cost = 0
    current_guard_hours_worked = hour - last_guard_started_hour

    # If the current guard starts just now, we get the cheapest solution for the
    # previous hour and add wages to that
    if current_guard_hours_worked == 0:
        nodes[node] = nodes[get_cheapest_for_hour(hour - 1)] + 15
        return
        
    if current_guard_hours_worked > 8:
        cost += current_guard_hours_worked * 20
    else:
        cost += current_guard_hours_worked * 15

    cost += nodes[(hour - current_guard_hours_worked, hour - current_guard_hours_worked)]
    nodes[node] = cost

现在这个功能我留下来了,我觉得很聪明!

def find_lowest_cost_node():
    lowest_cost = infinity
    lowest_cost_node = None
    # Go through each node.
    for node in nodes:
        cost = nodes[node]
        # If it's the lowest cost so far and hasn't been processed yet...
        if cost < lowest_cost and node not in processed:
            # ... set it as the new lowest-cost node.
            lowest_cost = cost
            lowest_cost_node = node
    return lowest_cost_node

在这里我简化了代码,不用父母做事

while current is not None:
    cost = nodes[current]
    # Go through all the neighbours of the current node
    for n in find_neighbours(current):
        # Update their costs
        set_cost(n)
    
    processed.append(current)
    current = find_lowest_cost_node()

print("Cost from the start to each node:")
print(nodes)

print(f"Optimal schedule cost: {nodes[(24, 24)]}")
print("The optimal schedule is:")

node = (24, 24)
while True:
    hour, last_guard_started_hour = node
    current_guard_hours_worked = hour - last_guard_started_hour

    node = get_cheapest_for_hour(hour - current_guard_hours_worked)
    if node[0] == 0:
        break

    print(f"From {node[1]}:00 to {node[0]}:00")

现在,我没有包括最多 6 个守卫,但显然您可以通过将元组扩展为 (current_hour, last_guard_started_hour, number_of_guards_used) 来做到这一点。祝你好运!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-01
    • 1970-01-01
    相关资源
    最近更新 更多