Algorithme de Dijkstra. Min tas comme un min-file d'attente de priorité
je suis en train de lire sur L'algorithme de Dijkstra dans CLRS, Third Edition (p. 662). Voici une partie du livre je ne comprends pas:
si le graphe est suffisamment clairsemé-en particulier, <!-On peut améliorer l'algorithme en implémentant la file min-priority avec un min-heap binaire.
Pourquoi le graphe éparses?
Voici une autre partie:
chaque clé de diminution l'opération prend du temps
O(log V)
, et il y a encore à la plupart des E de telles opérations.
supposons que mon graphique ressemble à ceci:
j'aimerais calculer le chemin le plus court de 1 à 6 et utiliser l'approche min-heap. Tout d'abord, j'ajoute tous mes noeuds à une file d'attente de priorité minimale. Après avoir construit un tas min, le noeud min est le noeud source (puisque sa distance à lui-même est 0). Je l'extrait et met à jour les distances de tous ses voisin.
Alors j'ai besoin d'appeler decreaseKey
sur le noeud avec la distance la plus basse pour faire un nouveau minimum du tas. Mais comment connaître son indice en temps constant?
Noeud
private static class Node implements Comparable<Node> {
final int key;
int distance = Integer.MAX_VALUE;
Node prev = null;
public Node(int key) {
this.key = key;
}
@Override
public int compareTo(Node o) {
if (distance < o.distance) {
return -1;
} else if (distance > o.distance) {
return 1;
} else {
return 0;
}
}
@Override
public String toString() {
return "key=" + key + " distance=" + distance;
}
@Override
public int hashCode() {
return key;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Node)) {
return false;
}
Node other = (Node) obj;
return key == other.key;
}
}
MinPriorityQueue
public static class MinPriorityQueue {
private Node[] array;
private int heapSize;
public MinPriorityQueue(Node[] array) {
this.array = array;
this.heapSize = this.array.length;
}
public Node extractMin() {
Node temp = array[0];
swap(0, heapSize - 1, array);
heapSize--;
sink(0);
return temp;
}
public boolean isEmpty() {
return heapSize == 0;
}
public void buildMinHeap() {
for (int i = heapSize / 2 - 1; i >= 0; i--) {
sink(i);
}
}
public void decreaseKey(int index, Node key) {
if (key.compareTo(array[index]) >= 0) {
throw new IllegalArgumentException("the new key must be greater than the current key");
}
array[index] = key;
while (index > 0 && array[index].compareTo(array[parentIndex(index)]) < 0) {
swap(index, parentIndex(index), array);
index = parentIndex(index);
}
}
private int parentIndex(int index) {
return (index - 1) / 2;
}
private int left(int index) {
return 2 * index + 1;
}
private int right(int index) {
return 2 * index + 2;
}
private void sink(int index) {
int smallestIndex = index;
int left = left(index);
int right = right(index);
if (left < heapSize && array[left].compareTo(array[smallestIndex]) < 0) {
smallestIndex = left;
}
if (right < heapSize && array[right].compareTo(array[smallestIndex]) < 0) {
smallestIndex = right;
}
if (index != smallestIndex) {
swap(smallestIndex, index, array);
sink(smallestIndex);
}
}
public Node min() {
return array[0];
}
private void swap(int i, int j, Node[] array) {
Node temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
2 réponses
Pourquoi le graphe éparses?
la durée d'exécution de L'algorithme de Dijkstra dépend de la combinaison de la structure de données sous-jacente et de la forme du graphe (arêtes et sommets).
par exemple, l'utilisation d'une liste liée nécessiterait O(V²)
temps, c'est à dire qu'il ne dépend que du nombre de sommets.
L'utilisation d'un tas exigerait O((V + E) log V)
, c'est à dire qu'il dépend à la fois du nombre de sommets et le nombre d'arêtes.
si votre E est suffisamment plus petite que V (comme dans E << V² / logV
), puis l'utilisation de tas devient plus efficace.
Alors j'ai besoin d'appeler
decreaseKey
sur le noeud avec la distance la plus basse pour faire un nouveau minimum du tas. Mais comment connaître son indice en temps constant?
Si vous utilisez un tas binaire, puis extractMin
s'exécute toujours dans O(log V)
temps et vous donne le noeud avec la distance la plus basse (A. K. A. clé).
par exemple, si vous mettez en œuvre binaire min-tas comme un tableau H
, puis le premier élément du tableau H[1]
(par convention, nous comptons à partir de 1
) sera toujours l'élément avec la plus faible distance, donc trouver ne prend que O(1)
.
Cependant, après chaque extractMin
,insert
ou decreaseKey
vous devez exécuter swim
ou sink
pour restaurer la condition de tas, déplaçant par conséquent le noeud le plus bas de la distance vers le haut. Cela prend O(log V)
.
Ce que vous voulez faire est de maintenir une cartographie entre les clés dans le tas et les sommets, comme mentionné dans le livre: "assurez-vous que les sommets et les éléments de tas correspondants maintiennent les poignées les uns par rapport aux autres" (brièvement discuté dans la section 6.5).
supposons que votre graphe se compose de Sommets (noeuds) dans votre cas vous avez 7 (0 ->6 ) et des bords. Ils sont représentés par le modèle suivant :
Noeud modèle :
public class Vertex{
final private String id;
final private String name;
public Vertex(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Vertex other = (Vertex) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public String toString() {
return name;
}
}
Et les bords seront présents par ce modèle : Bord
public class Edge {
private final String id;
private final Vertex source;
private final Vertex destination;
private final int weight;
public Edge(String id, Vertex source, Vertex destination, int weight) {
this.id = id;
this.source = source;
this.destination = destination;
this.weight = weight;
}
public String getId() {
return id;
}
public Vertex getDestination() {
return destination;
}
public Vertex getSource() {
return source;
}
public int getWeight() {
return weight;
}
@Override
public String toString() {
return source + " " + destination;
}
}
le graphe (noeuds + arêtes) sera présent par cette classe:Graphique
public class Graph {
private final List<Vertex> vertexes;
private final List<Edge> edges;
public Graph(List<Vertex> vertexes, List<Edge> edges) {
this.vertexes = vertexes;
this.edges = edges;
}
public List<Vertex> getVertexes() {
return vertexes;
}
public List<Edge> getEdges() {
return edges;
}
}
il s'agit d'une simple implémentation de L'algorithme de Dijkstra. Il n'utilise aucune optimisation des performances:
public class DijkstraAlgorithm {
private final List<Vertex> nodes;
private final List<Edge> edges;
private Set<Vertex> settledNodes;
private Set<Vertex> unSettledNodes;
private Map<Vertex, Vertex> predecessors;
private Map<Vertex, Integer> distance;
public DijkstraAlgorithm(Graph graph) {
// create a copy of the array so that we can operate on this array
this.nodes = new ArrayList<Vertex>(graph.getVertexes());
this.edges = new ArrayList<Edge>(graph.getEdges());
}
public void execute(Vertex source) {
settledNodes = new HashSet<Vertex>();
unSettledNodes = new HashSet<Vertex>();
distance = new HashMap<Vertex, Integer>();
predecessors = new HashMap<Vertex, Vertex>();
distance.put(source, 0);
unSettledNodes.add(source);
while (unSettledNodes.size() > 0) {
Vertex node = getMinimum(unSettledNodes);
settledNodes.add(node);
unSettledNodes.remove(node);
findMinimalDistances(node);
}
}
private void findMinimalDistances(Vertex node) {
List<Vertex> adjacentNodes = getNeighbors(node);
for (Vertex target : adjacentNodes) {
if (getShortestDistance(target) > getShortestDistance(node)
+ getDistance(node, target)) {
distance.put(target, getShortestDistance(node)
+ getDistance(node, target));
predecessors.put(target, node);
unSettledNodes.add(target);
}
}
}
private int getDistance(Vertex node, Vertex target) {
for (Edge edge : edges) {
if (edge.getSource().equals(node)
&& edge.getDestination().equals(target)) {
return edge.getWeight();
}
}
throw new RuntimeException("Should not happen");
}
private List<Vertex> getNeighbors(Vertex node) {
List<Vertex> neighbors = new ArrayList<Vertex>();
for (Edge edge : edges) {
if (edge.getSource().equals(node)
&& !isSettled(edge.getDestination())) {
neighbors.add(edge.getDestination());
}
}
return neighbors;
}
private Vertex getMinimum(Set<Vertex> vertexes) {
Vertex minimum = null;
for (Vertex vertex : vertexes) {
if (minimum == null) {
minimum = vertex;
} else {
if (getShortestDistance(vertex) < getShortestDistance(minimum)) {
minimum = vertex;
}
}
}
return minimum;
}
private boolean isSettled(Vertex vertex) {
return settledNodes.contains(vertex);
}
private int getShortestDistance(Vertex destination) {
Integer d = distance.get(destination);
if (d == null) {
return Integer.MAX_VALUE;
} else {
return d;
}
}
/*
* This method returns the path from the source to the selected target and
* NULL if no path exists
*/
public LinkedList<Vertex> getPath(Vertex target) {
LinkedList<Vertex> path = new LinkedList<Vertex>();
Vertex step = target;
// check if a path exists
if (predecessors.get(step) == null) {
return null;
}
path.add(step);
while (predecessors.get(step) != null) {
step = predecessors.get(step);
path.add(step);
}
// Put it into the correct order
Collections.reverse(path);
return path;
}
}
alors créez un classe de test et ajouter votre graphique des valeurs :
public class TestDijkstraAlgorithm {
private List<Vertex> nodes;
private List<Edge> edges;
@Test
public void testExcute() {
nodes = new ArrayList<Vertex>();
edges = new ArrayList<Edge>();
for (int i = 0; i < 11; i++) {
Vertex location = new Vertex("Node_" + i, "Node_" + i);
nodes.add(location);
}
addLane("Edge_0", 0, 1, 5);
addLane("Edge_1", 0, 2, 40);
addLane("Edge_2", 0, 3, 21);
addLane("Edge_3", 2, 3, 13);
addLane("Edge_4", 2, 4, 19);
addLane("Edge_5", 4, 5, 32);
addLane("Edge_6", 3, 5, 41);
addLane("Edge_7", 4, 6, 14);
addLane("Edge_8", 5, 6, 8);
// Lets check from location Loc_1 to Loc_10
Graph graph = new Graph(nodes, edges);
DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(graph);
dijkstra.execute(nodes.get(0));
LinkedList<Vertex> path = dijkstra.getPath(nodes.get(10));
assertNotNull(path);
assertTrue(path.size() > 0);
for (Vertex vertex : path) {
System.out.println(vertex);
}
}
private void addLane(String laneId, int sourceLocNo, int destLocNo,
int duration) {
Edge lane = new Edge(laneId,nodes.get(sourceLocNo), nodes.get(destLocNo), duration );
edges.add(lane);
}
}