性质:
给定两点间的最短路径:
并行任务调度:
1:图是有向的。2:并不是所有顶点都可达。3:负权会使问题边复杂。 4: 最短路径没有环。最短路径不唯一。
表示加权有向图的基本类:
package WeiDigraph;
//加权由向图的边
public class DirectedEdge {
private final int v;
private final int w;
private final double weight;
public DirectedEdge(int v,int w,double weight){
this.v = v; this.w = w; this.weight = weight;
}
public String toString(){
return String.format("%d->%d %.2f",v,w,weight);
}
public int form() {
return v;
}
public int to() {
return w;
}
public double getWeight() {
return weight;
}
}
import java.util.ArrayList;
//加权有向图
public class EdgeWeightedDigraph {
private final int v;
private int E;
private ArrayList<ArrayList<DirectedEdge>> adj;
public EdgeWeightedDigraph(int V){
this.v = V;
this.E = 0;
adj = new ArrayList<>();
for(int i=0;i<V;i++){
adj.add(new ArrayList<DirectedEdge>());
}
}
public void addEdge(DirectedEdge e){
adj.get(e.form()).add(e);
E++;
}
public ArrayList<DirectedEdge> adj(int v){
return adj.get(v);
}
//返回所有边
public ArrayList<DirectedEdge> edges(){
ArrayList<DirectedEdge> bag = new ArrayList<>();
for(int i=0;i<v;i++){
for(DirectedEdge e : adj.get(i))
bag.add(e);
}
return bag;
}
}
单源最短路径:
松弛技术
边的松弛:放松边v-->w意味着:检查从s到w的最短路径是否是先从s到v,然后再由v到w。如果是,则根据情况更新数据结构的内容。
顶点的松弛 :实现会放松从一个给定顶点指出的所有边。
Dijkstra算法: (可以有环,不饿处理负权)类似与Prim算法,将图分为两部分,T:已经找到最短路径的定点 W: 还未找到最短路径的顶点.
每次选择W中离起点最近的顶点加入T, 直到所有起点可达的顶点都被加入到T中.
import java.util.PriorityQueue;
import NoWeight.*;
//迪杰特斯拉单源最短路径算法算法,权值不能为负
public class DijkstraSP {
private DirectedEdge[] edgeTo;
private double[] disTo;
private PriorityQueue<ToPoint> pq;
public DijkstraSP(EdgeWeightedDigraph G,int s){
edgeTo = new DirectedEdge[G.getV()];
disTo = new double[G.getV()];
pq = new PriorityQueue<>(G.getV());
for(int i=0;i<G.getV();i++)
disTo[i] = Double.POSITIVE_INFINITY;
disTo[s] = 0.0;
pq.add(new ToPoint(s,0.0));
while(!pq.isEmpty())
relax(G,pq.poll().v);
}
private void relax(EdgeWeightedDigraph G, int v) {//每次对新加入的节点进行松弛,保证树到非树顶点为当前情况下的最短
for(DirectedEdge e : G.adj(v)){ //路径
int w = e.to();
if(disTo[w] > disTo[v] +e.getWeight()){
disTo[w] = disTo[v] + e.getWeight();
edgeTo[w] = e;
pqContains(w);//如果横切边中已经有到顶点w的横切边,删除,以便插入新的到w权值更小的横切边
pq.add(new ToPoint(w,e.getWeight()));
}
}
}
//判断横切边是否已经有到顶点w的横切边,有则删除
private void pqContains(int w) {
for(ToPoint ele : pq){
if (ele.v == w) {
pq.remove(ele);
return;
}
}
}
}
给定两点间的最短路径:
无环加权有向图的最短路径算法: 只要将顶点的放松和拓扑排序结合起来,就可以解决无环加权有向图中的最短路径问题. 按照拓扑顺序放松顶点即可.
求无环加权有向图中的最长路径 : 将原始的图中的所有的边的权值取反即可. 这样求出的最短路径就是原图的最长路.
import Digraph.Digraph;
import Digraph.Topological;
// 无环加权有向图的最短路径算法: 只要将顶点的放松和拓扑排序结合起来,就可以解决无环加权有向图中的最短路径问题.
public class AcyclicSP {
private DirectedEdge[] edgeTo;
private double[] distTo;
public AcyclicSP(EdgeWeightedDigraph G,int s){
edgeTo = new DirectedEdge[G.getV()];
distTo = new double[G.getV()];
for(int i=0;i<G.getV();i++){
distTo[i] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0;
Topological top = new Topological(G);
for(int v : top.order())
relax(G,v);
}
//顶点放松
private void relax(EdgeWeightedDigraph G, int v) {
for(DirectedEdge edge : G.adj(v)){
int w = edge.to();
if(distTo[w] > distTo[v] + edge.getWeight()){
distTo[w] = distTo[v] + edge.getWeight();
edgeTo[w] = edge;
}
}
}
}
并行任务调度:
优先级线之下的并行任务调度: 建立一个加权有向无环图, 然后用求最长路径算法解决.
相对于最后期限下的并行任务调度是一个加权有向图中的最短路径问题.
含有负权重的图中的最短路径:
加权有向图中的负权重环是一个总权重为负的环. 在负权重环存在的情况下,最短路径问题没有意义,因为可以构造任意短的路径.
所以定义的算法应该:
(1)对于起点不可达的顶点,最短路径为正无穷。
(2)对于起点不可达但路径上某顶点属于一个负权环的顶点,最短路径为负无穷;
(3)对于其他顶点,计算最短路径树。
Bellman-Ford算法:以任意顺序放松有向图的所有边,重复V轮。
基于队列的Bellman-Ford算法:
只有上一轮中的distTo[]发生变化的顶点指出的边才能够改变其他distTo[]元素的值。 在将所有边放松V轮之后当且仅当队列非空时有向图中才存在从起点可达的负权重环。
import java.util.LinkedList;
// Bellman-Ford算法:以任意顺序放松有向图的所有边,重复V轮。
//基于队列的Bellman-Ford算法;
public class BellmanFordSP {
private double[] distTo; //从起点到某个顶点的路径长度
private DirectedEdge[] edgeTo; //从起点到某个顶点的最后一条边
private boolean[] onQ; //该顶点是否在队列中
private LinkedList<Integer> queue; //正在被放松的顶点
private int cost; //relax()的调用次数
private Iterable<DirectedEdge> cycle; //edgeTo[]中是否有负环权重
public BellmanFordSP(EdgeWeightedDigraph G,int s){
distTo = new double[G.getV()];
edgeTo = new DirectedEdge[G.getV()];
onQ = new boolean[G.getV()];
queue = new LinkedList<>();
for(int i=0;i<G.getV();i++){
distTo[i] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
queue.add(s);
onQ[s] = true;
while(!queue.isEmpty() && !hasNegativeCycle()){
int v = queue.pollFirst();
onQ[v] = false;
relax(G,v);
}
}
//放松边
private void relax(EdgeWeightedDigraph G, int v) {
for(DirectedEdge e : G.adj(v)){
int w = e.to();
if(distTo[w] > distTo[v] + e.getWeight()){//只有上一轮中的distTo[]发生变化的顶点指出的边才能够改变其他distTo[]元素的值
distTo[w] = distTo[v] + e.getWeight();
edgeTo[w] = e;
if(!onQ[w]){
queue.add(w);
onQ[w] = true;
}
}
if(cost++ % G.getV() == 0)//存在负权环在将所有边放松V轮之后当且仅当
//队列非空时有向图中才存在从起点可达的负权重环。
findNegativeCycle();
}
}
//找出负权环,用distTo[]构造一个加权有向图,然后找出其中的环
private void findNegativeCycle() {
int V = edgeTo.length;
EdgeWeightedDigraph spt = new EdgeWeightedDigraph(V);
for(int v=0;v<V;v++){
if(edgeTo[v] != null)
spt.addEdge(edgeTo[v]);
}
EdgeWeightedCycleFinder cf = new EdgeWeightedCycleFinder(spt);
cycle = cf.cycle();
}
//是否存在负权环
private boolean hasNegativeCycle() {
return cycle != null;
}
}
import java.util.LinkedList;
// Bellman-Ford算法:以任意顺序放松有向图的所有边,重复V轮。
//基于队列的Bellman-Ford算法;
public class BellmanFordSP {
private double[] distTo; //从起点到某个顶点的路径长度
private DirectedEdge[] edgeTo; //从起点到某个顶点的最后一条边
private boolean[] onQ; //该顶点是否在队列中
private LinkedList<Integer> queue; //正在被放松的顶点
private int cost; //relax()的调用次数
private Iterable<DirectedEdge> cycle; //edgeTo[]中是否有负环权重
public BellmanFordSP(EdgeWeightedDigraph G,int s){
distTo = new double[G.getV()];
edgeTo = new DirectedEdge[G.getV()];
onQ = new boolean[G.getV()];
queue = new LinkedList<>();
for(int i=0;i<G.getV();i++){
distTo[i] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
queue.add(s);
onQ[s] = true;
while(!queue.isEmpty() && !hasNegativeCycle()){
int v = queue.pollFirst();
onQ[v] = false;
relax(G,v);
}
}
//放松边
private void relax(EdgeWeightedDigraph G, int v) {
for(DirectedEdge e : G.adj(v)){
int w = e.to();
if(distTo[w] > distTo[v] + e.getWeight()){//只有上一轮中的distTo[]发生变化的顶点指出的边才能够改变其他distTo[]元素的值
distTo[w] = distTo[v] + e.getWeight();
edgeTo[w] = e;
if(!onQ[w]){
queue.add(w);
onQ[w] = true;
}
}
if(cost++ % G.getV() == 0)//存在负权环在将所有边放松V轮之后当且仅当
//队列非空时有向图中才存在从起点可达的负权重环。
findNegativeCycle();
}
}
//找出负权环,用distTo[]构造一个加权有向图,然后找出其中的环
private void findNegativeCycle() {
int V = edgeTo.length;
EdgeWeightedDigraph spt = new EdgeWeightedDigraph(V);
for(int v=0;v<V;v++){
if(edgeTo[v] != null)
spt.addEdge(edgeTo[v]);
}
EdgeWeightedCycleFinder cf = new EdgeWeightedCycleFinder(spt);
cycle = cf.cycle();
}
//是否存在负权环
private boolean hasNegativeCycle() {
return cycle != null;
}
}