一、 加权有向图概述
加权有向图是在加权无向图的基础上,给边添加了方向,并且一条加权有向边只会在一个顶点的邻接表中出现。
二、 加权有向图实现
为了体现边的有向性,我们需要知道边的起点和终点,参照如下来构建有向边,而有向图的构建只需在前面无向图的基础上,将无向边对象更换为有向边对象即可
/**
* 有向边对象
* @author jiyukai
*/
public class DirectedEdge {
//有向边起点
private int v;
//有向边终点
private int w;
//有向边权重
private double weight;
public DirectedEdge(int v, int w, double weight) {
super();
this.v = v;
this.w = w;
this.weight = weight;
}
/**
* 起点
* @return
*/
public int from() {
return v;
}
/**
* 终点
* @return
*/
public int to() {
return w;
}
/**
* 有向边权重
* @return
*/
public Double weight() {
return weight;
}
}
|
import com.data.struct.common.list.queue.Queue;
/**
* 有向图实现
* @author jiyukai
*/
public class Digraph {
//定点个数
public int V;
//边的数量
public int E;
//图的邻接表
public Queue<DirectedEdge>[] qTable;
public Digraph(int v) {
this.V = v;
this.E = 0;
//初始化邻接表,数组中的索引为顶点,值为已队列,存放相邻的顶点
qTable = new Queue[v];
for(int i=0;i<v;i++) {
qTable[i] = new Queue<DirectedEdge>();
}
}
/**
* 向图中添加一条有向边
* @param v
* @param w
*/
public void addEdge(DirectedEdge e) {
//获取边的起点
int v = e.from();
//起点到终点的指向
qTable[v].enqueue(e);
//边加1
E++;
}
/**
* 返回当前顶点的数量
* @return
*/
public int V() {
return V;
}
/**
* 返回当前边的数量
* @return
*/
public int E() {
return E;
}
/**
* 获取与顶点V相邻的顶点
* @param V
* @return
*/
public Queue<DirectedEdge> adjoin(int V) {
return qTable[V];
}
/**
* 获取加权有向图的所有边
* @return
*/
public Queue<DirectedEdge> directEdges(){
Queue<DirectedEdge> diedges = new Queue<DirectedEdge>();
//由于有向图中,有向边只存在一个顶点的邻接表中,不存在相同边出现在不同顶点的邻接表中的情况,所以遍历添加即可
for(int v=0;v<V;v++) {
for(DirectedEdge e : qTable[v]) {
diedges.enqueue(e);
}
}
return diedges;
}
}
|
三、 最短路径
最短路径用来在加权有向图中,寻找顶点v到顶点w经过的有向边的最短路径,如下为一幅加权的有向图,各边的指向和权限已在图和表格中标出,红色边即为顶点0到顶点4经过的最短路径。
四、 松弛思想和Disjstra实现思路
那么如何在一副有向图中求得最短路径呢,这里我们用到了一种思想叫松弛思想,如下图
同样是顶点0到顶点2的路径,图一的蓝色边框看做皮筋,那么皮筋圈住的距离是0.35+0.15=0.5,图二的蓝色边框看做皮筋,那么皮筋圈住的距离是0.04,同样是起点为0,终点为2
我们把图一的皮筋改成图二后,皮筋很明显的松弛了,即在松弛状态下达到了和图一一样的效果。
这种松弛的原理和方法可以用来解决最短路径的问题,我们在算最短路径时,需要有个数组edges[]存放当前顶点和上一个顶点的最短边,索引为顶点,值为边
edgesWeight[]存放起点s到其他顶点的权重之和,还有一个索引优先队列minQueue存放最短路径到各个顶点的有效最短边。
松弛:
原路径顶点0到顶点3放松到边4-3,意味着需要判断顶点0到顶点3的最短路径是否从0-4,再从4-3,从如下图来看若放松到4-3,则最小路径变成了7.2+1.1=8.3>4.5,所以这时忽略放到4-3的请求。
从如下图来看若放松到4-3,则最小路径变成了0.6+1.1=1.7<4.5,所以这时需要将4-3添加到edges[3]的最小路径边中,同时更改edgesWeight[3]=1.7
如下通过一个简单的过程来演示找到一副图中的最短路径,初始化状态如下
步骤一:遍历起点0的邻接边,找到最短的边0-1添加到最短路径中,同时加入到edges数组中,并将0到1的权重之和添加到edgesWeight[]数组中,修改顶点0到各个顶点的有效横切边minQueue。
步骤二:遍历0和1组成的最短路径的邻接边,通过比较0-2的距离0.35>0-1,1-2的距离0.19,找到最短路径1-2加入到最短路径中,并同时修改edges[]数组和edgesWeight数组
修改顶点0和1组成的最短路径到各个顶点的有效横切边minQueue。
步骤三:找起点v到w的最短路径时,只需从edges数组逆向查找即可,比如查找0-2的最短路径,首先获取edges[2],然后通过边对象取到2的起点1,找到edge[1],依次找到起点0
则可以发现最短路径为0-1,1-2
五、 Disjstra代码实现
import com.data.struct.common.list.queue.Queue;
import com.data.struct.common.tree.priority.queue.IndexMinPriorityQueue;
/**
* Dijkstra算法实现
* @author jiyukai
*/
public class DijkstraSP {
// 存放当前顶点和上一个顶点的最短边,索引为顶点,值为边
private DirectedEdge[] edges;
// 存放起点s到其他顶点的权重之和
private double[] edgesWeight;
// 存放最小生成树顶点与非最小生成树顶点的目标横切边,索引为顶点
private IndexMinPriorityQueue<Double> minQueue;
public DijkstraSP(Digraph G, int s) throws Exception {
edges = new DirectedEdge[G.V()];
edgesWeight = new double[G.V()];
// 初始化时将edgesWeight的值暂时设置为无穷大,起点处设置为0
for (int i = 0; i < edgesWeight.length; i++) {
edgesWeight[i] = Double.POSITIVE_INFINITY;
}
edgesWeight[0] = 0.0;
minQueue = new IndexMinPriorityQueue<>(G.V());
minQueue.insert(0, 0.0);
while (!minQueue.isEmpty()) {
// 松弛图G中的顶点
relax(G, minQueue.delMin());
}
}
/**
* 进行松弛操作
* @param g
* @param delMin
* @throws Exception
*/
private void relax(Digraph G, int v) throws Exception {
// 首先遍历顶点的邻接边
for (DirectedEdge e : G.qTable[v]) {
// 获取边的终点
int w = e.to();
// 起点到顶点w的权重是否大于起点到顶点v的权重+边e的权重
// 如果大于,则修改s->w的路径,edges[w]=e,并修改edgesWeight[v] =
// edgesWeight[v]+e.weight(),如果不大于,则忽略
if (edgesWeight[v] + e.weight() < edgesWeight[w]) {
edges[w] = e;
edgesWeight[w] = edgesWeight[w] + e.weight();
// 如果顶点w已经存在于优先队列minQueue中,则重置顶点w的权重
if (minQueue.contains(w)) {
minQueue.changeItem(w, edgesWeight[w]);
} else {
// 如果顶点w没有出现在优先队列pq中,则把顶点w及其权重加入到pq中
minQueue.insert(w, edgesWeight[w]);
}
}
}
}
/**
* 判断从顶点s到顶点v是否可达
* @param v
* @return
*/
public boolean hasPathTo(int v) {
return edgesWeight[v] < Double.POSITIVE_INFINITY;
}
/**
* 逆向查询从起点s到顶点v的最短路径中所有的边
* @param v
* @return
*/
public Queue<DirectedEdge> pathTo(int v){
Queue<DirectedEdge> minPaths = new Queue<>();
//不存在连通的路径,则返回null
if(!hasPathTo(v)) {
return null;
}
//逆向查找过程
DirectedEdge e = null;
while(true){
e = edges[v];
if(e==null) {
break;
}
minPaths.enqueue(e);
v = e.from();
}
return minPaths;
}
}
|