Кратчайший путь (наименьшее число узлов) для невзвешенного графа
Я пытаюсь создать метод, который возвращает кратчайший путь от одного узла к другому в невзвешенном графе. Я рассматривал использование Dijkstra, но это кажется немного излишним, так как я хочу только одну пару. Вместо этого я реализовал поиск по ширине, но проблема в том, что мой возвращаемый список содержит некоторые узлы, которые я не хочу - как я могу изменить свой код для достижения своей цели?
public List<Node> getDirections(Node start, Node finish){
List<Node> directions = new LinkedList<Node>();
Queue<Node> q = new LinkedList<Node>();
Node current = start;
q.add(current);
while(!q.isEmpty()){
current = q.remove();
directions.add(current);
if (current.equals(finish)){
break;
}else{
for(Node node : current.getOutNodes()){
if(!q.contains(node)){
q.add(node);
}
}
}
}
if (!current.equals(finish)){
System.out.println("can't reach destination");
}
return directions;
}
5 ответов
на самом деле ваш код не будет готово в циклических графах, рассмотрим график 1 -> 2 -> 1. У вас должен быть некоторый массив, где вы можете пометить, какой узел вы уже посетили. А также для каждого узла можно сохранить предыдущие узлы, из которых вы пришли. Так вот правильный код:
private Map<Node, Boolean>> vis = new HashMap<Node, Boolean>(); private Map<Node, Node> prev = new HashMap<Node, Node>(); public List getDirections(Node start, Node finish){ List directions = new LinkedList(); Queue q = new LinkedList(); Node current = start; q.add(current); vis.put(current, true); while(!q.isEmpty()){ current = q.remove(); if (current.equals(finish)){ break; }else{ for(Node node : current.getOutNodes()){ if(!vis.contains(node)){ q.add(node); vis.put(node, true); prev.put(node, current); } } } } if (!current.equals(finish)){ System.out.println("can't reach destination"); } for(Node node = finish; node != null; node = prev.get(node)) { directions.add(node); } directions.reverse(); return directions; }
спасибо Giolekva!
я переписал его, переделав некоторые:
- коллекция посещенных узлов не обязательно должна быть картой.
- для реконструкции пути можно было бы искать следующий узел вместо предыдущего узла, устраняя необходимость в обратном направлении.
public List<Node> getDirections(Node sourceNode, Node destinationNode) {
//Initialization.
Map<Node, Node> nextNodeMap = new HashMap<Node, Node>();
Node currentNode = sourceNode;
//Queue
Queue<Node> queue = new LinkedList<Node>();
queue.add(currentNode);
/*
* The set of visited nodes doesn't have to be a Map, and, since order
* is not important, an ordered collection is not needed. HashSet is
* fast for add and lookup, if configured properly.
*/
Set<Node> visitedNodes = new HashSet<Node>();
visitedNodes.add(currentNode);
//Search.
while (!queue.isEmpty()) {
currentNode = queue.remove();
if (currentNode.equals(destinationNode)) {
break;
} else {
for (Node nextNode : getChildNodes(currentNode)) {
if (!visitedNodes.contains(nextNode)) {
queue.add(nextNode);
visitedNodes.add(nextNode);
//Look up of next node instead of previous.
nextNodeMap.put(currentNode, nextNode);
}
}
}
}
//If all nodes are explored and the destination node hasn't been found.
if (!currentNode.equals(destinationNode)) {
throw new RuntimeException("No feasible path.");
}
//Reconstruct path. No need to reverse.
List<Node> directions = new LinkedList<Node>();
for (Node node = sourceNode; node != null; node = nextNodeMap.get(node)) {
directions.add(node);
}
return directions;
}
на самом деле не проще получить ответ только для одной пары, чем для всех пар. Обычный способ вычислить кратчайший путь-начать, как вы это делаете, но делать заметку всякий раз, когда вы сталкиваетесь с новым узлом и записываете предыдущий узел на пути. Затем, когда вы достигнете целевого узла, вы можете следовать обратным ссылкам к источнику и получить путь. Итак, удалите directions.add(current)
из цикла и добавьте код примерно следующим образом
Map<Node,Node> backlinks = new HashMap<Node,Node>();
в начале, а затем в петля
if (!backlinks.containsKey(node)) {
backlinks.add(node, current);
q.add(node);
}
а затем, в конце концов, просто построить directions
список в обратном порядке, используя backlinks
карта.
каждый раз, когда через ваш цикл, вы вызываете
directions.Add(current);
вместо этого вы должны переместить это в место, где вы действительно знаете, что хотите эту запись.
вы должны включить родительский узел в каждый узел, когда вы помещаете их в свою очередь. Тогда вы можете просто рекурсивно прочитать путь из этого списка.
скажем, вы хотите найти кратчайший путь от A до D На этом графике:
/B------C------D
/ |
A /
\ /
\E---------
каждый раз, когда вы запрашиваете узел, следите за тем, как вы попали сюда. Таким образом, на шаге 1 B(A) E(A) помещается в очередь. На шаге два B получает dequeued и C (B) помещается в очередь и т. д. Затем легко найти свой путь обратно, просто рекурсивно "назад."
лучший способ, вероятно, сделать массив, пока есть узлы и сохранить ссылки там (что обычно делается в ie. Дийкстры).