朴素dijkstra算法
适用情况:稠密图(边的数量远远多余点的数量),不能适用有负权边的图
存储方式:邻接矩阵
算法思路:进行 n 次迭代 , 每次使用未确定的最短距离的点去更新其他点。
1)每次迭代过程中都要先找到当前未确定的最短距离的点中的距离最短的点
int t = -1;
for(int j = 1 ; j <= n ; j ++){
if(!st[j] &&(t == -1 || dist[t] > dist[j] )
t = j ;
}
2)用当前这个距离最短的点去去更新其他的未确定最短距离的点
for(int j = 1 ; j <= n ; j ++ ){
if( !st[j] && g[t][k] != inf ){
dist[j] = min(dist[j] , dist[t] + g[t][j])
}
}
3)每次迭代完把该点标记为已确定
st[t] = true;
完整的代码
int dijsktra(){
fill(dist ,inf); dist[1] = 0;
for(int i = 1; i <= n ;i ++){
int t = -1;
for(int j = 1 ; j <= n ;j ++)
if( !st[j] && (t == -1 || dist[j] < dist[t] ) t = j ;
for(int j = 1 ; j <= n ;j ++ ){
if( !st[j] && g[t][j] != inf)
d[j] = min( d[j] , d[t] + g[t][j]);
}
st[t] = true;
}
if(dist[n] == inf ) return -1;
return dist[n];
}
堆优化版 dijkstra
适用情况:稀疏图,不能适用有负权边的图
存储结构:邻接表
算法思路:朴素版的dijkstra每次找当前未确定的最近的结点都要循环一遍,可以试用堆进行优化
1)初始化 dist[1] = 0 , dist[i] = inf
2)初始把1号点放入堆中。
3)当堆不为空时 :
弹出堆顶
用该点更新邻近的距离,如果邻近点的距离变小的,则把更新过的邻近点也加入堆中
实现代码
int heap_dijkstra(){
boolean[] st = new boolean[n+1];
int[] d = new int[n+1];
fill(dist , inf); d[1] = 0;
PriorityQueue heap = new PriorityQueue<>( (x ,y) -> x.dist - y.dist )
heap.add( new Pair(1 , 0) ); //节点编号 ,距离
while( !heap.isEmpty() ){
Pair p = heap.poll();
int u = p.u , dist = p.dist;
if( st[u] ) continue;
st[u] = true;
for(int i = h[u] ; i != -1 ; i = ne[i] ){
int j = e[i] ;
if( !st[j] && d[j] > d[t] + w[i] ){
d[j] = d[t] + w[i];
heap.add(new Pair(u ,d[j]));
}
}
}
return d[n] == inf ? -1 : d[u];
}
bellman-ford算法
适用情况:有边数限制的最短路,含有负权边,判断是否有负环
存储结构:Edge(a , b, w)的数组
算法思路:其原理为连续进行松弛,在每次松弛的时候把每条边都更新一下,若找n-1此松弛后还能更新,则说明图中有负环。
假设1号点到n号点事可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环n-1次操作,若途中不存在负环,则1号点一定会达到n号点,若途中存在负环,则在n-1次松弛后一定还会更新。
具体步骤:
for (int i = 1 ; i <= n ;i ++){
for (Edge :e : edges){
//Edge:a , b , w
dist[b] = min( dist[b] , backup[a] + w);
//bakcup 是上一轮迭代的备份,因为在本次迭代中 a的距离可能改变,如果使用改变了的 d[a],后续的点会受到影响。
}
}
在最后判断是否可达时 , 不能直接 d[n] == inf ,因为虽然1 到 n是不可达的,但是 和 n号邻近的点可能更新 d[n],所以 d[n] > inf / 2 判断 d[n]的量级
完整代码
int ballman_ford(){
int[] dist = new int[N];
int[] backup = new int[N];
Arrays.fill(dist , inf);
dist[1] = 0;
for(int i = 0 ; i < k ; i ++){ // 限制为最多 k 条边
backup = Arrays.copyOf(dist , N);
for(int j = 0 ; j < m ; j++ ){
Edge e = edges[j];
dist[e.b] = Math.min(dist[e.b] , backup[e.a] + e.w );
}
}
return dist[n] > inf/2 ? -1 :dist[n];
}
spfa算法
适用情况:含负权边单源最短路,判断负权环
存储结构:邻接表
算法思路:是Bellman-Ford算法的队列优化
Bellman-ford思路:
for n 次
for 所有边 a,b,w(松弛操作)
dist[b] = min(dist[b] , backup[a] + w )
spfa算法对第二hang所有边进行松弛操作进行了优化,原因是在bellman_ford算法中,即时该点的最短距离没有被更新过,但还是需要用尚未更新过的值去更新其他点,但这是没有必要的,因此只需要找到更新过的值去更新其他点即可。
spfa思路:
queue <- 1
while queue 不为空
(1) t = queue.pop()
(2) 用 t 更新所有 t的邻接点 b
queue <- b(如果该点被更新过且此时不再队列中,则加入队列)
完整代码:
int spfa(){
int inf = 0x3f3f3f3f;
int[] dist = new int[N];
boolean[] st = new boolean[N];
int[] queue = new int[N];
int hh = 0 , tt = -1;
Arrays.fill(dist , inf); dist[1] = 0;
queue[++tt] = 1;
while(hh <= tt){
int t = queue[hh++];
st[t] = false;
for(int i = h[t] ; i != -1 ; i = ne[i] ){
int j = e[i];
if( dist[j] > dist[t] + w[i] ){
dist[j] = dist[t] + w[i];
if(!st[j]){
queue[++tt] = j;
st[j] = true;
}
}
}
}
return dist[n] == inf ? -1 : dist[n];
}
spfa判负环思路:
(1)统计每个带你入队次数,如果某个点入队n次,则说明存在负环
(2)统计当前每个点的最短路中包含的边数,如果某点的最短路包含的边数大于等于n,则也说明存在环
实现代码
boolean spfa(){
//int inf = 0x3f3f3f3f;
int[] dist = new int[N];
// Arrays.fill(dist , inf); 这里不用是因为如果有负环 0 > 0 + 负权边
boolean[] st = new boolean[N];
Queue queue = new LinkedList<>();
for(int i = 1 ; i <= n ;i ++){
queue.add(i);
st[i] = true;
}
int[] cnt = new int[N];
while(!queue.isEmpty()){
int t = queue.poll();
st[t] = false;
for(int i = h[t] ; i != -1 ; i = ne[i]){
int j = e[i];
if( dist[j] > dist[t] + w[i] ){
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if(cnt[j] >= n) return true;
if( !st[j] ){
queue.add(j);
st[j] = true;
}
}
}
}
return false;
}
floyd算法
适用场景:多源最短路
存储结构:邻接矩阵
算法思路:枚举 每一个结点 , 把该结点作为中间点 ,看其他经过该点的路径长度是否能够变短
实现代码
void floyd(){
for(int k = 1 ; k <= n ;k ++)
for(int i = 1 ;i <= n ;i ++)
for(int j = 1 ; j <= n ; j++)
g[i][j] = Math.min( g[i][j] , g[i][k] + g[k][j] );