迪杰斯特拉算法。最小堆作为最小优先级队列
Dijkstra algorithm. Min heap as a min-priority queue
我正在阅读 CLRS 第三版(第 662 页)中有关 Dijkstra 算法的内容。这是书中我不明白的部分:
If the graph is sufficiently sparse — in particular, E = o(V^2/lg V)
— we can improve the algorithm by implementing the min-priority queue with a binary min-heap.
为什么图形应该是稀疏的?
这是另一部分:
Each DECREASE-KEY operation takes time O(log V)
, and there are still
at most E such operations.
假设我的图表如下所示:
我想计算从 1 到 6 的最短路径并使用最小堆方法。所以首先,我将所有节点添加到一个最小优先级队列中。构建最小堆后,最小节点就是源节点(因为它到自身的距离为 0)。我提取它并更新它所有邻居的距离。
然后我需要在距离最短的节点上调用 decreaseKey
来创建堆的新最小值。但是我怎么知道它在常数时间内的索引呢?
节点
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;
}
}
最小优先级队列
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;
}
}
假设您的图由顶点(节点)组成,在您的情况下您有 7 (0 ->6) 和边。这些由以下模型表示:
节点模型:
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;
}
}
并且边缘将由该模型呈现:边缘
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;
}
}
图(节点 + 边)将由此 class 呈现:图
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;
}
}
这是 Dijkstra 算法的简单实现。它不使用任何性能优化:
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;
}
}
然后创建一个 测试 class 并添加您的图形值:
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);
}
}
Why should the graph be sparse?
Dijkstra 算法的 运行ning 时间取决于底层数据结构和图形形状(边和顶点)的组合。
例如,使用链表需要 O(V²)
时间,即它只取决于顶点的数量。
使用堆需要 O((V + E) log V)
,即它取决于顶点数和边数。
如果您的 E 与 V 相比足够小(如 E << V² / logV
),那么使用堆会变得更有效率。
Then I need to call decreaseKey
on the node with the lowest distance to make a new minimum of the heap. But how do I know its index in constant time?
如果您使用的是二叉堆,那么 extractMin
总是在 O(log V)
时间内 运行s 并为您提供距离最短的节点 (a.k.a。键)。
例如,如果您将二进制 min-heap 实现为数组 H
,则数组的第一个元素 H[1]
(按照惯例,我们从 1
) 将永远是距离最短的元素,所以找到它只需要 O(1)
.
但是,在每个 extractMin
、insert
或 decreaseKey
之后,您必须 运行 swim
或 sink
来恢复堆条件,因此将 lowest-distance 节点移动到顶部。这需要 O(log V)
.
你还想做的是维护堆中的键和顶点之间的映射,如书中所述:“确保
顶点和相应的堆元素维护彼此的句柄”(在第 6.5 节中简要讨论)。
我正在阅读 CLRS 第三版(第 662 页)中有关 Dijkstra 算法的内容。这是书中我不明白的部分:
If the graph is sufficiently sparse — in particular,
E = o(V^2/lg V)
— we can improve the algorithm by implementing the min-priority queue with a binary min-heap.
为什么图形应该是稀疏的?
这是另一部分:
Each DECREASE-KEY operation takes time
O(log V)
, and there are still at most E such operations.
假设我的图表如下所示:
我想计算从 1 到 6 的最短路径并使用最小堆方法。所以首先,我将所有节点添加到一个最小优先级队列中。构建最小堆后,最小节点就是源节点(因为它到自身的距离为 0)。我提取它并更新它所有邻居的距离。
然后我需要在距离最短的节点上调用 decreaseKey
来创建堆的新最小值。但是我怎么知道它在常数时间内的索引呢?
节点
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;
}
}
最小优先级队列
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;
}
}
假设您的图由顶点(节点)组成,在您的情况下您有 7 (0 ->6) 和边。这些由以下模型表示:
节点模型:
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;
}
}
并且边缘将由该模型呈现:边缘
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;
}
}
图(节点 + 边)将由此 class 呈现:图
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;
}
}
这是 Dijkstra 算法的简单实现。它不使用任何性能优化:
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;
}
}
然后创建一个 测试 class 并添加您的图形值:
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);
}
}
Why should the graph be sparse?
Dijkstra 算法的 运行ning 时间取决于底层数据结构和图形形状(边和顶点)的组合。
例如,使用链表需要 O(V²)
时间,即它只取决于顶点的数量。
使用堆需要 O((V + E) log V)
,即它取决于顶点数和边数。
如果您的 E 与 V 相比足够小(如 E << V² / logV
),那么使用堆会变得更有效率。
Then I need to call
decreaseKey
on the node with the lowest distance to make a new minimum of the heap. But how do I know its index in constant time?
如果您使用的是二叉堆,那么 extractMin
总是在 O(log V)
时间内 运行s 并为您提供距离最短的节点 (a.k.a。键)。
例如,如果您将二进制 min-heap 实现为数组 H
,则数组的第一个元素 H[1]
(按照惯例,我们从 1
) 将永远是距离最短的元素,所以找到它只需要 O(1)
.
但是,在每个 extractMin
、insert
或 decreaseKey
之后,您必须 运行 swim
或 sink
来恢复堆条件,因此将 lowest-distance 节点移动到顶部。这需要 O(log V)
.
你还想做的是维护堆中的键和顶点之间的映射,如书中所述:“确保 顶点和相应的堆元素维护彼此的句柄”(在第 6.5 节中简要讨论)。