图(五):最短路径

定义:在一幅加权有向图中,从顶点s到顶点t的最短路径是所有从s到t的路径中权重最小者。
(单源最短路径、单终点最短路径、单对顶点最短路径、每对顶点最短路径)
引理:最短路径的子路径是最短路径。
当且仅当加权有向图中至少存在一条从s到v的有向图路径且所有从s到v的有向路径上的任意顶点都不存在于任何负权重环中时,s到v的最短路径才是存在的。

松弛(relaxation)操作
图(五):最短路径_第1张图片

Bellman-Ford算法
利用松弛技术,对每个属于V的顶点v,逐步减小从源s到v的最短路径的权的估计值d[v],直到其到达实际最短路径的权w(s, v)。当图中不包含从源点可达的负权回路时,算法返回TRUE;否则,返回FALSE。

图(五):最短路径_第2张图片

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算法每次添加的都是离起点最近的非树顶点。

图(五):最短路径_第3张图片

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

图(五):最短路径_第4张图片

//=============================================================
// 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;
}

有向无回路最短路径

使用拓扑排序

图(五):最短路径_第5张图片

总结:Bellman-Ford算法,用来解决一般(权值无负环)的单源最短路径问题;拓扑排序可以处理有向无回路单元最短路径问题;Dijkstra算法,要求所有的边的权值为非负。

图(五):最短路径_第6张图片

你可能感兴趣的:(Java,C语言基础,算法)