1 加权有向图中边的数据结构
/**
* 该类用于表示有向图中的一条有向边
* @author lhever 2017年3月2日 下午11:25:30
* @version v1.0
*/
public class DirectedEdge
{
private final int v;
private final int w;
private final double weight;
/**
* 有向边对象的构造函数
*
* @param v
* 起点
* @param w
* 终点
* @param weight
* 边的权重
* @author lhever 2017年3月2日 下午11:26:05
* @since v1.0
*/
public DirectedEdge(int v, int w, double weight)
{
if (v < 0)
{
throw new IllegalArgumentException("起始顶点名必须是非负整数");
}
if (w < 0)
{
throw new IllegalArgumentException("终点名必须是非负整数");
}
if (Double.isNaN(weight))
{
throw new IllegalArgumentException("权重不合法");
}
this.v = v;
this.w = w;
this.weight = weight;
}
/**
* 获取起始顶点
* @return
* @author lhever 2017年3月2日 下午11:29:32
* @since v1.0
*/
public int from()
{
return v;
}
/**
* 获取结束顶点
*
* @return
* @author lhever 2017年3月2日 下午11:29:55
* @since v1.0
*/
public int to()
{
return w;
}
/**
* 获取权重
* @return
* @author lhever 2017年3月2日 下午11:30:15
* @since v1.0
*/
public double weight()
{
return weight;
}
public String toString()
{
return v + "->" + w + " " + String.format("%5.2f", weight);
}
/**
* 测试
* @param args
* @author lhever 2017年3月2日 下午11:30:37
* @since v1.0
*/
public static void main(String[] args)
{
DirectedEdge e = new DirectedEdge(12, 34, 5.67);
System.out.println(e);
}
}
2 加权有向图的数据结构
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 该类抽象了加权有向图这种数据结构
* @author xxx 2017年3月2日 下午11:34:33
* @version v1.0
*/
public class EdgeWeightedDigraph {
private static final String NEWLINE = System.getProperty("line.separator");
private final int V;
private int E;
private List<DirectedEdge>[] adj;
private int[] indegree;
private static long seed;
private static Random random;
static
{
seed = System.currentTimeMillis();
random = new Random(seed);
}
/**
* 初始化加权有向图对象,构造函数需要指定图中的顶点总数
* @param V
* @author xxx 2017年3月2日 下午11:37:03
* @since v1.0
*/
@SuppressWarnings("unchecked")
public EdgeWeightedDigraph(int V)
{
if (V < 0)
{
throw new IllegalArgumentException("加权有向图中的顶点数必须是非负数");
}
this.V = V;
this.E = 0;
this.indegree = new int[V];
adj = (List<DirectedEdge>[]) new ArrayList[V];
for (int v = 0; v < V; v++)
{
adj[v] = new ArrayList<DirectedEdge>();
}
}
/**
* 初始化一个有向图,初始化的有向图有V个顶点,E条边
* @param V
* @param E
* @author xxx 2017年3月2日 下午11:40:05
* @since v1.0
*/
public EdgeWeightedDigraph(int V, int E)
{
this(V);
if (E < 0)
{
throw new IllegalArgumentException("加权有向图中的顶点数必须是非负数");
}
for (int i = 0; i < E; i++)
{
int v = random.nextInt(V);
int w = random.nextInt(V);
double weight = 0.01 * random.nextInt(100);
DirectedEdge e = new DirectedEdge(v, w, weight);
addEdge(e);
}
}
/**
* 使用一个已知的加权有向图实例化另外一个加权优先图,深拷贝?
* @param G
* @author xxx 2017年3月2日 下午11:45:11
* @since v1.0
*/
public EdgeWeightedDigraph(EdgeWeightedDigraph G)
{
this(G.V());
this.E = G.E();
for (int v = 0; v < G.V(); v++)
{
this.indegree[v] = G.indegree(v);
}
for (int v = 0; v < G.V(); v++)
{
List<DirectedEdge> edgeList = new ArrayList<DirectedEdge>();
for (DirectedEdge e : G.adj[v])
{
edgeList.add(e);
}
for (DirectedEdge e : edgeList)
{
adj[v].add(e);
}
}
}
/**
* 返回顶点总数
* @return
* @author xxx 2017年3月2日 下午11:48:06
* @since v1.0
*/
public int V()
{
return V;
}
/**
* 返回边的总数
* @return
* @author xxx 2017年3月2日 下午11:48:24
* @since v1.0
*/
public int E()
{
return E;
}
private void validateVertex(int v)
{
if (v < 0 || v >= V)
{
throw new IllegalArgumentException("顶点 " + v + " 必须介于 0 和 " + (V - 1) + " 之间");
}
}
/**
* 添加一条边
* @param e
* @author xxx 2017年3月2日 下午11:49:51
* @since v1.0
*/
public void addEdge(DirectedEdge e)
{
int v = e.from();
int w = e.to();
validateVertex(v);
validateVertex(w);
adj[v].add(e);
indegree[w]++;
E++;
}
/**
* 获取起点是v的所有有向边
* @param v
* @return
* @author xxx 2017年3月2日 下午11:50:36
* @since v1.0
*/
public Iterable<DirectedEdge> adj(int v)
{
validateVertex(v);
return adj[v];
}
/**
* 获取起点是v的所有有向边的总数,也即获取顶点v的出度
* @return
* @author xxx 2017年3月2日 下午11:50:36
* @since v1.0
*/
public int outdegree(int v)
{
validateVertex(v);
return adj[v].size();
}
/**
* 获取终点是v的所有有向边的总数,也即获取顶点v的入度
* @return
* @author xxx 2017年3月2日 下午11:50:36
* @since v1.0
*/
public int indegree(int v)
{
validateVertex(v);
return indegree[v];
}
/**
* 获取图中的所有有向边
* @return
* @author xxx 2017年3月2日 下午11:52:46
* @since v1.0
*/
public Iterable<DirectedEdge> edges()
{
List<DirectedEdge> list = new ArrayList<DirectedEdge>();
for (int v = 0; v < V; v++)
{
for (DirectedEdge e : adj(v))
{
list.add(e);
}
}
return list;
}
@Override
public String toString()
{
StringBuilder s = new StringBuilder();
s.append(V + " " + E + NEWLINE);
for (int v = 0; v < V; v++)
{
s.append(v + ": ");
for (DirectedEdge e : adj[v])
{
s.append(e + " ");
}
s.append(NEWLINE);
}
return s.toString();
}
/**
* 测试
* @param args
* @author xxx 2017年3月2日 下午11:53:37
* @since v1.0
*/
public static void main(String[] args)
{
EdgeWeightedDigraph g = new EdgeWeightedDigraph(5, 8);
System.out.println(g);
}
}
3 解决边的权重全部非负的最短路径问题的Dijikstra算法
import java.util.Stack;
/**
* 解决加权有向图中单点最短路径的Dijkstra算法,该算法要求图中边的权重是非负
* @author xxx 2017年3月3日 上午12:02:22
* @version v1.0
*/
public class DijkstraSP
{
private double[] distTo;
private DirectedEdge[] edgeTo;
private IndexMinPQ<Double> pq;
/**
* 计算加权有向图G中从起点s到图中其他所有顶点的最短路径
* @param G
* @param s
* @author xxx 2017年3月3日 上午12:07:11
* @since v1.0
*/
public DijkstraSP(EdgeWeightedDigraph G, int s)
{
for (DirectedEdge e : G.edges())
{
if (e.weight() < 0)
{
throw new IllegalArgumentException("边 " + e + " 的权重为非负数,该算法不适用");
}
}
distTo = new double[G.V()];
edgeTo = new DirectedEdge[G.V()];
for (int v = 0; v < G.V(); v++)
{
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
pq = new IndexMinPQ<Double>(G.V());
pq.insert(s, distTo[s]);
while (!pq.isEmpty())
{
int v = pq.delMin();
for (DirectedEdge e : G.adj(v))
{
relax(e);
}
}
assert check(G, s);
}
/**
* 采用边的松弛算法,修正到各个终点的最短距离,同时不断修正到各个终点的最短路径上的最后一条边。
* @param e
* @author xxx 2017年3月3日 上午12:14:34
* @since v1.0
*/
private void relax(DirectedEdge e)
{
int v = e.from(), w = e.to();
if (distTo[w] > distTo[v] + e.weight())
{
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
if (pq.contains(w))
{
pq.decreaseKey(w, distTo[w]);
} else
{
pq.insert(w, distTo[w]);
}
}
}
/**
* 返回起点s到终点v的最短距离(权重)
* @param v
* @return
* @author xxx 2017年3月3日 上午12:14:53
* @since v1.0
*/
public double distTo(int v)
{
return distTo[v];
}
/**
* 判断是否一条从起点s到终点v的路径
* @param v
* @return
* @author xxx 2017年3月3日 上午12:15:51
* @since v1.0
*/
public boolean hasPathTo(int v)
{
return distTo[v] < Double.POSITIVE_INFINITY;
}
/**
* 返回从起点s到终点v的一条最短路径,如果存在的话
* @param v
* @return
* @author xxx 2017年3月3日 上午12:17:33
* @since v1.0
*/
public Iterable<DirectedEdge> pathTo(int v)
{
if (!hasPathTo(v))
{
return null;
}
Stack<DirectedEdge> path = new Stack<DirectedEdge>();
for (DirectedEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()])
{
path.push(e);
}
return path;
}
/**
* 验证算法求得的最短路径是不是确实最短
* @param G
* @param s
* @return
* @author xxx 2017年3月3日 上午12:19:34
* @since v1.0
*/
private boolean check(EdgeWeightedDigraph G, int s)
{
for (DirectedEdge e : G.edges())
{
if (e.weight() < 0)
{
System.err.println("检测到负权重的边存在");
return false;
}
}
if (distTo[s] != 0.0 || edgeTo[s] != null)
{
System.err.println("distTo[s] 的值与 edgeTo[s] 隐含的信息不一致,s 居然不是起点,算法逻辑矛盾");
return false;
}
for (int v = 0; v < G.V(); v++)
{
if (v == s)
{
continue;
}
if (edgeTo[v] == null && distTo[v] != Double.POSITIVE_INFINITY)
{
System.err.println("distTo[v] 的值与 edgeTo[v] 隐含的信息不一致,算法逻辑矛盾");
return false;
}
}
for (int v = 0; v < G.V(); v++)
{
for (DirectedEdge e : G.adj(v))
{
int w = e.to();
if (distTo[v] + e.weight() < distTo[w])
{
System.err.println("边 " + e + " 不是最短边");
return false;
}
}
}
for (int w = 0; w < G.V(); w++)
{
if (edgeTo[w] == null)
{
continue;
}
DirectedEdge e = edgeTo[w];
int v = e.from();
if (w != e.to())
{
return false;
}
if (distTo[v] + e.weight() != distTo[w])
{
System.err.println("最短路径上的边 " + e + "居然不是最短边 ");
return false;
}
}
return true;
}
/**
* 测试
* @param args
* @author xxx 2017年3月3日 上午12:28:19
* @since v1.0
*/
public static void main(String[] args)
{
EdgeWeightedDigraph g = new EdgeWeightedDigraph(5, 9);
System.out.println(g);
DijkstraSP sp = new DijkstraSP(g, 0);
for (int t = 0; t < g.V(); t++)
{
if (sp.hasPathTo(t))
{
System.out.printf("%d -> %d (%.2f) ", 0, t, sp.distTo(t));
for (DirectedEdge e : sp.pathTo(t))
{
System.out.print(e + " ");
}
System.out.println();
} else
{
System.out.printf("顶点 %d 到顶点 %d 的路径不存在\n", 0, t);
}
}
}
}