有障碍的图算法DFS / BFS的实现

问题描述

我想问一下您是否可以给我有关基于图形算法(DFS或BFS)的任务的建议。 任务背后有一个故事。在宇宙中,有很多行星。其中一些行星被危险病毒感染,杀死所有人。 任务是找到从一个行星到一个药物行星的道路,因此有可能治愈这种病毒。 由于病毒,找到路径非常危险。机组人员可能去了病毒所在的星球(他们知道病毒在哪个星球上),但他们去了那里是因为他们别无选择,因为他们必须找到药物。在被感染的星球上,机组人员被感染了,并且有一些生存时间(时间是从一个星球到另一个星球的时间)。该任务的结果应该是从起始行星到存在药物的行星的路径。它应该适用于任何已知宇宙图。

一个已知行星与行星的例子。起始行星是0,终止行星是13。如您所见,行星1、2、4被感染。在此特定示例中,机组感染后有一天可以居住。因此,如果机组人员前往第1行星,则它们会被感染,然后在下一个行星(4或6)上死亡。如果他们去了第2行星,他们就被感染了,他们只有一天的生命,所以下一颗行星就死了……结果就是路径0 3 10 11 12 5 13。

Graph of known planets

行星与已知宇宙的另一个例子。起始行星为0,终止行星为9。如您所见,行星4、6、7被感染。在此特定示例中,机组感染后有两天的时间。因此,如果机组人员前往第4行星,则它们会被感染,然后在两天后(一天=从一个行星到另一个行星的一种方式)死亡。结果是路径0 1 2 3 5 7 6 9或0 1 2 3 5 7 89。如您所见,在某些情况下,可能会有更多路径。任务是只找到一个,而不必是最短的一个。乘员组从头到尾的正确路线。

enter image description here

我曾经使用过命令式DFS算法,但是我不知道如何实现这样的功能:如果机组人员将死在一条路径上,那么该算法应该找出另一条路径。您可以从图表图片获取它。

解决方法

您可以进行修改的BFS来解决此问题。在执行BFS时,应将感染的天数保持为状态的一部分。然后,在遍历时,如果我们要以更好的感染天数(即更少)来遍历,则接受遍历到先前访问的节点。

这也是最短路径,因为BFS将在非加权图中产生最短路径。

这是Python的快速草图,适用于第二个示例:

from collections import deque

def bfs(graph,virus_nodes,start,target,survive_days):
    survive_days = min(survive_days,len(graph)) 
    visited = {start : 0}
    queue = deque([(start,(start,))]) # node,days infected,path

    while queue:
        node,days_infected,path = queue.popleft()
        is_infected = days_infected > 0 or node in virus_nodes

        nxt_days_infected = days_infected + 1 if is_infected else 0
        if nxt_days_infected > survive_days:
            continue

        for nxt in graph[node]:
            if nxt == target:
                return path + (target,)
            if nxt not in visited or visited[nxt] > nxt_days_infected:
                queue.append((nxt,nxt_days_infected,path + (nxt,)))
                visited[nxt] = nxt_days_infected

if __name__ == "__main__":
    graph = {
        0 : [1],1 : [0,2],2 : [1,3],3 : [2,4,5],4 : [3,7],5 : [3,6 : [7,9],7 : [4,5,6,8],8 : [7,9 : [6,}
    virus_nodes = {4,7}
    max_survive_days = 2
    print(bfs(graph,9,max_survive_days))

输出:

(0,1,2,3,7,9)
,

解决此问题的最简单方法是使用BFS,但是您要从结束开始。

让我们说感染后的生存时间为TTL天。

从目标位置运行BFS,但是您只能考虑在小于TTL长的路径上进入被感染的行星。一旦BFS碰到了一条TTL长度的路径,那么您就只能从此一直使用干净的行星,一直到开始。

如果逐级执行BFS,这特别容易。然后,您只需要检查

,

如果您不在意这是不是最短的路径,则可以使用DFS并进行一些调整,以合并消亡时间。 JSFiddle和javascript代码:

function findPath(path,infectedNodes,nodesBeforeDeath,initialNode,targetNode,adjacentTable) {
  const lastNode = path.visitedNodes[path.visitedNodes.length - 1];
  
  if (lastNode === targetNode) {
    return path;
  }

  if (path.nodesLeftBeforeDeath !== null && path.nodesLeftBeforeDeath !== undefined && path.nodesLeftBeforeDeath == 0) {
    return;
  }
 
  const adjacentNodes = adjacentTable[lastNode];
  
  for (const node of adjacentNodes) {
    if (path.visitedNodes.indexOf(node) > -1) {
        continue;
    }
    
    const newPath = {
        visitedNodes: [...path.visitedNodes,node]
    };
    
    if (path.nodesLeftBeforeDeath !== null && path.nodesLeftBeforeDeath !== undefined) {
        newPath.nodesLeftBeforeDeath = path.nodesLeftBeforeDeath - 1;
    }
    else if (infectedNodes.indexOf(node) > - 1) {
        newPath.nodesLeftBeforeDeath = nodesBeforeDeath;
    }
    
    const nodeResult = findPath(newPath,adjacentTable);
    
    if (nodeResult) {
        return nodeResult;
    }
  }
}

const firstExampleResult = findPath({
  visitedNodes: [0]
  },[1,4],13,[[1,[0,6],10],11,12],[6,12,13],[2,14],[3,11],[4,10,[5],[7,8]]
);

console.log(firstExampleResult.visitedNodes);

const secondExampleResult = findPath({
  visitedNodes: [0]
  },[
  [1],8]
  ]
);
console.log(secondExampleResult.visitedNodes);
,

您可以使用Dijkstra's algorithm之类的最短路径算法来查找两个节点之间的最短路径。

边缘权重就是您可能要去的节点的权重。在这种情况下,去往非病毒节点的所有边缘的权重为1。当边缘到达病毒节点时,权重为number of nodes + 1。拜访任何病毒行星都比拜访其他所有行星要容易得多。这样,只有在没有其他选择的情况下才选择病毒行星。

它还处理了必须访问多个病毒星球的情况,方法是选择病毒星球数量最少的路径,而不是在这种情况下确实很重要。虽然如果要列出所有同样有效的解决方案,则可以考虑将任何其他病毒星球的权重设置为1(如果路径中已经包含病毒星球)。如果重量与最佳解决方案相同,则将其显示为另一种有效解决方案。

关于生存时间,我们将其称为T,只需检查当前路径即可。如果搜索中的当前节点不是结束节点,并且自第一个病毒行星越过以来,它就是T个行星。然后拒绝该路径为无效。

生存时间的另一种处理方法是使用数组权重[path weight,virus age]。这意味着,在相同的路径权重下,它将以最低的病毒年龄扩展路径。病毒年龄仅为# of days since exposed to virus