spfa java_SPFA算法学习笔记

本文详细介绍了SPFA(Shortest Path Faster Algorithm)算法,这是一种用于解决有向图中最短路径问题的算法,由段凡丁于1994年提出。SPFA在处理存在负权边的情况下比Dijkstra算法更具优势,但不适用于存在负权回路的图。文章解释了SPFA算法的基本思想,包括队列为空的原因和判断负权回路的方法。同时,提到了SLF和LLL两种优化策略,以提高算法效率。此外,还给出了使用C++实现SPFA算法的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.理论准备

为了学习网络流,先水一道spfa。

SPFA算法是1994年西南交通大学段凡丁提出,只要最短路径存在,SPFA算法必定能求出最小值,SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。为什么队列为空就不改变了呢?就是因为要到下一点必须经过它的前一个邻接点。。SPFA可以处理负权边。很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路。

初始化: dis数组全部赋值为Inf(无穷大,不能是map[s][i]),path数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱,然后dis[s]=0;  表示源点不用求最短路径,或者说最短路就是0。将源点入队;另外记住在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记(可能多次入队)。

核心:读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队以此循环,直到队空为止就完成了单源最短路的求解。

判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图),假设这个节点的入度是k(无向权则就是这个节点的连接的边)如果进入这个队列超过k,说明必然有某个边重复了,即成环;换一种思路:用DFS,假设存在负环a1->a2->…->an->a1。那么当从a1深搜下去时又遇到了a1,那么直接可以判断负环了所有用。当某个节点n次进入队列,则存在负环,此时时间复杂度为O(n*m),n为节点,m为边。

SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。个人觉得LLL优化每次要求平均值,不太好,为了简单,我们可以之间用c++STL里面的优先队列来进行SLF优化。

二.算法实现

直接去把HDU1874AC了吧。

1: import java.util.Comparator;

2: import java.util.PriorityQueue;

3: import java.util.Queue;

4: import java.util.Scanner;

5: /*

6: * 原来一直wa,重写了一遍,AC了

7: * SLF优化

8: */

9: public class HD1874 {

10:

11: static int n,m;

12: static int[][] map = new int[205][205];

13: static int[] dis = new int[205];

14: static boolean[] vis = new boolean[205];

15: /*

16: * 路径最大值是10000,不能设置成10005就行,还要考虑和

17: * 也不能是整形最大值,否则一加就溢出了

18: */

19: static final int Inf = 0x3f3f3f3f;

20:

21: public static void main(String[] args) {

22: Scanner sc = new Scanner(System.in);

23: // 记录前驱点 。若path[i]=j,表示从s到i的最短路径中i的前一个点是j

24: //int[] path;

25: int u,v,w;

26: while(sc.hasNext()) {

27: n = sc.nextInt();

28: m = sc.nextInt();

29: for(int i=0; i<205; i++) {

30: for(int j=i; j<205; j++) {

31: map[i][j] = Inf;

32: map[j][i] = Inf;

33: }

34: }

35: for(int i=0; i

36: u = sc.nextInt();

37: v = sc.nextInt();

38: w = sc.nextInt();

39: //多重边

40: if(map[u][v]>w) {

41: map[v][u] = w;

42: map[u][v] = w;

43: }

44: }

45: int s = sc.nextInt();

46: int t = sc.nextInt();

47: spfa(s);

48: //题目上有st

49: //起点终点相同的话答案是0

50: if(Inf==dis[t]) {

51: System.out.println(-1);

52: }else {

53: System.out.println(dis[t]);

54: }

55: }

56: }

57:

58: private static void spfa(int s) {

59:

60: for(int i=0; i<205; i++) {

61: vis[i] = false;

62: //初始化为map[s][i]第一组数据就错了

63: dis[i] = Inf;

64: }

65: dis[s] = 0;

66: vis[s] = true;

67: Comparator cmp = new Comparator() {

68:

69: public int compare(Integer o1, Integer o2) {

70: int i = (int)o1;

71: int j = (int)02;

72: if(dis[i]>dis[j]) {

73: return 1;

74: }else if(dis[i]==dis[j]){

75: return 0;

76: }else {

77: return -1;

78: }

79: }

80: };

81: //面向接口编程;205代表优先队列(是类)的容量

82: Queue q = new PriorityQueue(205, cmp);

83: q.clear();

84: q.offer(s);

85: while(!q.isEmpty()) {

86: int head = q.poll();

87: //该注意的是有些点可能重复入队,所以出队的点也要重新置未标记

88: vis[head] = false;

89: for(int i=0; i

90: //dis[head]不可能是INF,map[head][i]可能是INF

91: int temp = dis[head] + map[head][i];

92: if(temp

93: //path[i] = head

94: dis[i] = temp;

95: if(!vis[i]) {

96: //用一个数组在此记录入队次数,大于n就存在负环;如何事先判断

97: q.offer(i);

98: vis[i] = true;

99: }

100: }

101: }

102: }

103: }

104:

105: }

106:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值