定义:在一幅加权有向图中,从顶点s到顶点t的最短路径是所有从s到t的路径中权重最小者。
(单源最短路径、单终点最短路径、单对顶点最短路径、每对顶点最短路径)
引理:最短路径的子路径是最短路径。
当且仅当加权有向图中至少存在一条从s到v的有向图路径且所有从s到v的有向路径上的任意顶点都不存在于任何负权重环中时,s到v的最短路径才是存在的。
Bellman-Ford算法
利用松弛技术,对每个属于V的顶点v,逐步减小从源s到v的最短路径的权的估计值d[v],直到其到达实际最短路径的权w(s, v)。当图中不包含从源点可达的负权回路时,算法返回TRUE;否则,返回FALSE。
package mysp;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;
import myutil.*;
public class MyBellmanFordSP {
private double[] distTo;
private MyDiEdge[] edgeTo;
private boolean[] onQueue;
private MyQueue queue;
private int cost;
private Iterable cycle;
public MyBellmanFordSP(MyEWDigraph G, int s) {
distTo = new double[G.V()];
edgeTo = new MyDiEdge[G.V()];
onQueue = new boolean[G.V()];
queue = new MyQueue();
for (int v = 0; v < G.V(); v++) {
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
queue.enqueue(s);
onQueue[s] = true;
while (!queue.isEmpty() && !this.hasNegativeCycle()) {
int v = queue.dequeue();
onQueue[v] = false;
relax(G, v);
}
}
private void relax(MyEWDigraph G, int v) {
for (MyDiEdge e : G.adj(v)) {
int w = e.to();
if (distTo[w] > distTo[v] + e.weight()) {
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
if (!onQueue[w]) {
queue.enqueue(w);
onQueue[w] = true;
}
}
if (cost++ % G.V() == 0) {
findNegativeCycle();
}
}
}
public double distTo(int v) {
return distTo[v];
}
public boolean hasPathTo(int v) {
return distTo[v] < Double.POSITIVE_INFINITY;
}
public Iterable pathTo(int v) {
if (!hasPathTo(v)) {
return null;
}
MyStack path = new MyStack();
for (MyDiEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
path.push(e);
}
return path;
}
private void findNegativeCycle() {
int V = edgeTo.length;
MyEWDigraph spt = new MyEWDigraph(V);
for (int v = 0; v < V; v++) {
if (edgeTo[v] != null) {
spt.addEdge(edgeTo[v]);
}
}
MyEWDiCycle cf = new MyEWDiCycle(spt);
cycle = cf.cycle();
}
public boolean hasNegativeCycle() {
return cycle != null;
}
public Iterable negativeCycle() {
return cycle;
}
public static void main(String[] args) {
MyEWDigraph G = new MyEWDigraph(new In("tinyEWDGn.txt"), true);
int s = 0;
MyBellmanFordSP sp = new MyBellmanFordSP(G, s);
for (int t = 0; t < G.V(); t++) {
StdOut.print(s + " to " + t);
StdOut.printf(" (%4.2f): ", sp.distTo(t));
if (sp.hasPathTo(t)) {
for (MyDiEdge e : sp.pathTo(t)) {
StdOut.print(e + " ");
}
}
StdOut.println();
}
}
}
//=============================================================
// BellmanFord算法实现
//=============================================================
#include
#include
#define INFINITY 0xff
#define VERTEXS 5
#define EDGES 10
// 0 1 2 3 4
// s t x y z
int G[VERTEXS][VERTEXS] = {
INFINITY, 6, INFINITY, 7, INFINITY,
INFINITY, INFINITY, 5, 8, -4,
INFINITY, -2, INFINITY, INFINITY, INFINITY,
INFINITY, INFINITY, -3, INFINITY, 9,
2, INFINITY, 7, INFINITY, INFINITY
};
struct edge {
int u;
int v;
int weight;
};
// edge set
struct edge ES[EDGES] = { {0, 1, 6}, {0, 3, 7}, {1, 3, 8},
{1, 2, 5}, {2, 1, -2}, {1, 4, -4}, {3, 2, -3},
{3, 4, 9}, {4, 2, 7}, {4, 0, 2} };
int d[VERTEXS]; // distance
int parent[VERTEXS];
bool BellmanFord(int s)
{
for (int i = 0; i < VERTEXS; i++) {
d[i] = INFINITY;
parent[i] = 0;
}
d[s] = 0;
for (int v = 0; v < VERTEXS - 1; v++) {
for (int i = 0; i < EDGES; i++) { // 对边进行放松
if (d[ES[i].v] > d[ES[i].u] + ES[i].weight) {
d[ES[i].v] = d[ES[i].u] + ES[i].weight;
parent[ES[i].v] = ES[i].u;
}
}
}
for (int i = 0; i < EDGES; i++) {
if (d[ES[i].v] > d[ES[i].u] + ES[i].weight) {
return false;
}
}
return true;
}
void PrintPath(int s, int t)
{
if (s != t) {
PrintPath(s, parent[t]);
}
printf("%d-->", t);
}
int main()
{
if (BellmanFord(0)) {
for (int i = 0; i < VERTEXS; i++) {
printf("%d->%d: %d\n", i, parent[i], d[i]);
}
}
PrintPath(0, 4);
system("pause");
return 0;
}
Dijkstra算法
在任意含有V个顶点的正加权有向图中给顶起点为s ,将distTo[s]初始化为0,distTo[]中其他元素初始化为正无穷。然后将distTo[]最小的非树顶点放松并加入树中,如此这般,直到所有的顶点都在树中或者所有的非树顶点的distTo[]值均为无穷大。
Prim算法和Dijkstra算法都会用添加边的方式构造一棵树:Prim算法每次添加的都是离树最近的非树顶点,Dijkstra算法每次添加的都是离起点最近的非树顶点。
package mysp;
import edu.princeton.cs.algs4.In;
import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.IndexMinPQ;
import myutil.*;
public class MyDijkstraSP {
private MyDiEdge[] edgeTo;
private double[] distTo;
private IndexMinPQ pq;
public MyDijkstraSP(MyEWDigraph G, int s) {
edgeTo = new MyDiEdge[G.V()];
distTo = new double[G.V()];
pq = new IndexMinPQ(G.V());
for (int v = 0; v < G.V(); v++) {
distTo[v] = Double.POSITIVE_INFINITY;
}
dijkstra(G, s);
}
private void dijkstra(MyEWDigraph G, int s) {
distTo[s] = 0.0;
pq.insert(s, 0.0);
while (!pq.isEmpty()) {
relax(G, pq.delMin());
}
}
private void relax(MyEWDigraph G, int v) {
for (MyDiEdge e : G.adj(v)) {
int 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]);
}
}
}
}
public double distTo(int v) {
return distTo[v];
}
public boolean hasPathTo(int v) {
return distTo[v] < Double.POSITIVE_INFINITY;
}
public Iterable pathTo(int v) {
if (!hasPathTo(v)) {
return null;
}
MyStack path = new MyStack();
for (MyDiEdge e = edgeTo[v]; e != null; e = edgeTo[e.from()]) {
path.push(e);
}
return path;
}
/**********************************************/
/* 绘制接口 */
/**********************************************/
public MyDijkstraSP(MyEWDigraph G, int s, boolean draw) {
edgeTo = new MyDiEdge[G.V()];
distTo = new double[G.V()];
pq = new IndexMinPQ(G.V());
for (int v = 0; v < G.V(); v++) {
distTo[v] = Double.POSITIVE_INFINITY;
}
dijkstra(G, s, draw);
}
private void dijkstra(MyEWDigraph G, int s, boolean draw) {
distTo[s] = 0.0;
pq.insert(s, 0.0);
StdDraw.pause(1000);
MyDraw.drawCircle(G.getPoint(s), 6, 0.003, StdDraw.RED);
while (!pq.isEmpty()) {
relax(G, pq.delMin(), draw);
}
}
private void relax(MyEWDigraph G, int v, boolean draw) {
for (MyDiEdge e : G.adj(v)) {
int 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]);
StdDraw.pause(1000);
MyDraw.drawCircle(G.getPoint(w), 6, 0.003, StdDraw.ORANGE);
MyDraw.drawArrow(G.getPoint(v), G.getPoint(w), 8, 0.003, StdDraw.ORANGE);
} else {
pq.insert(w, distTo[w]);
StdDraw.pause(1000);
MyDraw.drawCircle(G.getPoint(w), 6, 0.003, StdDraw.RED);
StdDraw.pause(1000);
MyDraw.drawArrow(G.getPoint(v), G.getPoint(w), 8, 0.003, StdDraw.RED);
}
}
}
}
public static void main(String[] args) {
MyEWDigraph G = new MyEWDigraph(new In("tinyEWDG.txt"), true);
int s = 0;
MyDijkstraSP sp = new MyDijkstraSP(G, s, true);
for (int t = 0; t < G.V(); t++) {
StdOut.print(s + " to " + t);
StdOut.printf(" (%4.2f): ", sp.distTo(t));
if (sp.hasPathTo(t)) {
for (MyDiEdge e : sp.pathTo(t)) {
StdOut.print(e + " ");
}
}
StdOut.println();
}
}
}
//=============================================================
// Dijkstra算法实现
//=============================================================
#include
#include
#define INFINITY 0xff
#define VERTEXS 5
// s, t, x, y, x
// 0, 1, 2, 3, 4
int G[VERTEXS][VERTEXS] = {
INFINITY, 10, INFINITY, 5, INFINITY,
INFINITY, INFINITY, 1, 2, INFINITY,
INFINITY, INFINITY, INFINITY, INFINITY, 4,
INFINITY, 3, 9, INFINITY, 2,
7, INFINITY, 6, INFINITY, INFINITY
};
int d[VERTEXS] = { 0 }; // 记录源点到目的点的距离
bool visited[VERTEXS] = { false };
int parent[VERTEXS] = { 0 };
//=============================================================
// 获取pq中最小元素
//=============================================================
int ExtractMin(int *pq, int n)
{
int min = INFINITY;
int index = -1;
for (int i = 0; i < n; i++) {
if (!visited[i] && pq[i] < min) {
min = pq[i];
index = i; // 获取最小元素的小标
}
}
if (index > -1) {
visited[index] = true;
}
return index;
}
//=============================================================
// 判断Q是否为空
//=============================================================
bool IsEmpty(int *pq, int n)
{
for (int i = 0; i < n; i++) {
if (!visited[i])
return false;
}
return true;
}
//=============================================================
// Dijkstra算法实现
//=============================================================
void Dijkstra(int s)
{
for (int i = 0; i < VERTEXS; i++) {
d[i] = INFINITY;
visited[i] = false;
parent[i] = 0;
}
d[s] = 0;
while (!IsEmpty(d, VERTEXS)) {
int u = ExtractMin(d, VERTEXS);
for (int v = 0; v < VERTEXS; v++) {
if ((G[u][v] < INFINITY)
&& (d[v] > d[u] + G[u][v])) {
d[v] = d[u] + G[u][v];
parent[v] = u;
}
}
}
}
void PrintPath(int s, int t)
{
if (s != t) {
PrintPath(s, parent[t]);
}
printf("%d(%d)-->", t, d[t]);
}
int main()
{
int min = 0;
Dijkstra(0);
PrintPath(0, 1);
printf("\n");
PrintPath(0, 2);
printf("\n");
PrintPath(0, 3);
printf("\n");
PrintPath(0, 4);
printf("\n");
system("pause");
return 0;
}
有向无回路最短路径
使用拓扑排序
总结:Bellman-Ford算法,用来解决一般(权值无负环)的单源最短路径问题;拓扑排序可以处理有向无回路单元最短路径问题;Dijkstra算法,要求所有的边的权值为非负。