定义
图由定点(vertex)的集V和边(edge)的集E组成。
每一条边就是一幅点对(v,w)。
如果点对是有序的,则图是有向的,又称为有向图。
图中的一条路径是一个顶点序列:w1,w2,w3…wN是的(wi,wi+1)属于E。
如果有一个顶点v到它自身的的边(v,v),那么(v,v)也被称为环。
如果一个无向图中从每个顶点到其他每个顶点都存在一条路径,则称无向图是连通的,又称为强连通的。如果一个有向图不是强连通的,但是它的基础图图是联通的,那么有向图称为弱连通的。
完全图是其每一对顶点建都存在一条边的图。
图的表示
图是稀疏的,用邻接表(顶点表)表示;图是稠密的(|E|=O(V^2)),用邻接矩阵(二维数组)表示。
拓扑排序
拓扑排序是对有向无环图顶点的一种排序,使得如果存在一条从vi到vj的路径,那么在排序中vj就出现在vi的后面。拓扑排序不是唯一的。
拓扑排序首先要找到一个入度为0的点,将其加入到队列中,标记为已删除;将该顶点的邻接点入度减1;再继续寻找入度为0的点,直至所有顶点遍历完成。要注意判断是否有环。时间复杂度:O(|E|+|V|)。
public void topSort() throws CycleFoundException {
Queue<Vertex> q = new LinkedList<Vertex>();
int counter = 0;
for (Vertex v : directedGraph.values()) {
if (v.inDegree == 0) {
q.offer(v);
}
}
while (!q.isEmpty()) {
Vertex v = q.poll();
v.topNum = ++counter;
if (v.adjEdges != null) {
for (Edge edge : v.adjEdges) {
edge.endVertex.inDegree--;
if (edge.endVertex.inDegree == 0) {
q.offer(edge.endVertex);
}
}
}
}
if (counter != directedGraph.size()) {
throw new CycleFoundException();
}
}
最短路径
输入一个赋权图:与每条边(vi,vj)相联系的是穿越该路径的代价
ci,j
。
c(i,j)
可能是正,也可能是负。
赋权路径长是指一条路径
v1v2...vN
的值
∑N−1i=1ci,i+1
。
无权路径长是指一条路径上的边数。
无权最短路径
只要经过的路径的边数最少就是无权最短路径。无权最短路径适用于无权无向图或者无权有向图(?)。
使用广度优先搜索:从开始顶点开始,按层次处理定点,先处理距开始点最近的顶点,最后处理最远的顶点。时间复杂度O(
|v|2
)。
改进:使用队列保存
dv=currentDist
的节点。在处理
dv=currentDist
的过程中,将
dv=currentDist+1
的节点加入到队列。时间复杂度:O(|E|+|V|)。与拓扑排序类似。与拓扑排序不同的地方是:1 拓扑排序首先入队的是入度=0的点;最短路径算法首先入队的是源顶点。2 拓扑排序中当顶点入度=0可以继续入队;最短路径算法中是处理节点的邻边顶点且未处理过,就可以入队。
public void unweightedShortestPath(Vertex s) {
Queue<Vertex> q = new LinkedList<Vertex>();
for (Vertex v : directedGraph.values()) {
v.dist = Integer.MAX_VALUE;
}
s.dist = 0;
q.offer(s);
while (!q.isEmpty()) {
Vertex v = q.poll();
for (Edge edge : v.adjEdges) {
if (edge.endVertex.dist == Integer.MAX_VALUE) {
edge.endVertex.dist = v.dist + 1;
edge.endVertex.path = v;
System.out.println(edge.endVertex.vertexLabel+" 最短路径 "+edge.endVertex.dist+" 上一节点"+v.vertexLabel);
q.offer(edge.endVertex);
}
}
}
}
赋权最短路径–Dijkstra算法
Dijkstra(迪克斯特拉,D-ijk-srtr-a)算法是贪心算法的一个例子,用来解决赋权有向图中,从单个顶点到其他顶点的最短路径。也可以用于赋权无向图。
算法描述:每个顶点维护两个属性:最短路径变量dist表源顶点到该顶点最短路径值;是否已知,如果处理过了就是已知。
在起始阶段设置所有顶点的【最短路径】=无穷大。源顶点s设置为
v3
。s.dist=0。
在每个阶段找到所有未处理顶点中dist最小值的顶点
vx
。
vx
的是否已知=已知,这相当于去掉了从上一步顶点到
vx
顶点的边。设置相邻顶点的dist。
证明:反证法证明这样做是有效的。
(这段描述有问题)
v3
顶点是未处理顶点中具有最小dist的顶点。选择处理从v3到其他点权值最小的边处理。图中选择边(v3,v1)是最小权值的边。选择边(v3,v1),之后删除边(v3,v1),也就是v3和v1合为v1点,v1的最短路径=4。这被认为从v3到v1点的最小路径值。这里用反证法证明。假如(v3,v1)不是最短的,v3->vx->v1才是最短的,x!=1,也就是说
c3,x+cx,1<c3,1=4
,那处理v3顶点的时候最小边就不是(v3,v1),而是(v3,vx),这与(v3,v1)是最小权值的边矛盾。这里局部最优就等于全局最优,因为这里的局部决策无后效性,当前决策不影响将来的决策。
Dijkstra算法选择一个顶点v,在所有未处理顶点中找到最小的
dv
。
以上图为具体例子。计算从 v1 到其他各个顶点的最短距离。初始表格如下:
顶点 | 是否已知 | 最短距离 | 上一步顶点 |
v1 | F | 0 | |
v2 | F | ∞ | |
v3 | F | ∞ | |
v4 | F | ∞ | |
v5 | F | ∞ | |
v6 | F | ∞ | |
v7 | F | ∞ |
在[是否已知]=F,查找到【最短距离】最小的顶点 v1。 设置 v1 为已知。 v1 的邻接点 v2 , v4 计算得到最短路径值分别为:2,1。
顶点 | 是否已知 | 最短距离 | 上一步顶点 |
v1 | T | 0 | |
v2 | F | 2 | v1 |
v3 | F | ∞ | |
v4 | F | 1 | v1 |
v5 | F | ∞ | |
v6 | F | ∞ | |
v7 | F | ∞ |
在[是否已知]=F,查找到【最短距离】最小的顶点
v4。
v_4
的邻接点
v_3
,
v_5