这篇讨论加权有向图。
加权有向图是在有向图的基础上,边的赋予权重信息的。
加权有向图有很多应用。最典型的就是最短路径问题。我们日常生活中也经常遇到这种问题,例如从一个点出发,到达另外一个点,怎么过去才是最快的等等。
而由于图的复杂性,最短路径问题并不十分的容易。例如,给定图的边的权重可能是负权重等。
为了解决最短路径问题,我们首先要定义一种加权有向图的数据结构,良好的数据结构是成功的一半。
和之前一样,我们用邻接表矩阵的方式来存放图,链表中存放的是我们定义的数据结构的边。
如下图所示:
那么我们首先要做的就是定义边的数据结构。
如图,我们需要边的起始顶点,边的终止顶点,边的权重信息。
得到如下图的数据结构:
public class DirectedEdge {
private double weight;
private int from;
private int to;
public DirectedEdge(int from, int to, double weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
public int getFrom() {
return from;
}
public int getTo() {
return to;
}
public double weight() {
return weight;
}
public int compareTo(DirectedEdge e) {
if (weight > e.weight()) return 1;
if (weight < e.weight()) return -1;
return 0;
}
public String toString() {
String s = from + " -> " + to + ", weight: " + weight;
return s;
}
}
加权有向图,我们首先需要一个邻接表矩阵,还需要顶点的数量,边的数量。
得到如下数据结构:
public class EdgeWeightDiGraph {
private List<DirectedEdge>[] adj; // 邻接表矩阵
private int V; // 点的数目
private int E; // 边的数目
public EdgeWeightDiGraph(int V) {
this.V = V;
E = 0;
adj = (List<DirectedEdge>[]) new List[V];
for (int i = 0; i < V; i++) {
adj[i] = new ArrayList<>();
}
}
public void addEdge(DirectedEdge e) {
adj[e.getFrom()].add(e);
E++;
}
public int V() {
return V;
}
public int E() {
return E;
}
public Iterable<DirectedEdge> adj(int v) {
return adj[v];
}
public Iterable<DirectedEdge> edges() {
List<DirectedEdge> edges = new ArrayList<>();
for (int i = 0; i < V; i++) {
for (DirectedEdge e : adj[i]) {
edges.add(e);
}
}
return edges;
}
public String toString() {
String s = V + " 个顶点, " + E + " 条边\n";
for (int i = 0; i < V; i++) {
s += i + ": ";
for (DirectedEdge e : adj(i)) {
s += e.getTo() + " [" + e.weight() + "], ";
}
s += "\n";
}
return s;
}
}
首先我们来看最短路径中最出名的算法Dijkstra算法。
思路:
从起点开始,首先初始化到起点的距离为0,再初始化到其他顶点的路径为无穷大。
每次选择到达起点最近的那个顶点未被选择过的顶点,查看这个顶点作为起点的边,如果发现从这个顶点到边的另一个顶点的距离会更短,就更新它。
可以发现,每次选择到起点最近的那个没被选择过的顶点,最终会选择所有的顶点,而这些顶点的边也会被全部遍历一次。因为总是先计算距离最小的那个顶点,所以每个后来的顶点都是在前面最小路径的基础上得到的,无法更短的路径,也就是后来计算的顶点都是最短路径,这个可以用数学归纳法非常简单的理解。
那么我们就来实现Dijkstra算法。
我们发现这个算法有以下关键点:1.记录当前顶点到起点的距离 2.每次获取距离起点最近的顶点3.比较当前边到达的点的距离,如果更短就更新的操作。
1.记录当前顶点到起点的距离:这个记录十分的重要,因为我们的更新距离的操作,每次都要基于当前顶点的距离的比较结果来更新。我们使用一个double[]数组来存放。
2.每次获取距离起点最近的顶点:我们如何获取当前距离起点最近的顶点呢?最容易想到的方法就是遍历一次,这并不难实现,不过