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);
}
}
}
}