本博客并无技术参考价值,若想学习SPFA算法,请找寻其他博客
Dijkstra算法,较之spfa,对于我来说,是相对容易接受的,因为前者使用二维数组进行图的存储,但凡题目中给出边的信息也好,点的信息也好,都可以将图的信息保存在一个map数组中。 之后在进行 Djk算法实现的时候,基本流程也很简洁。
1 .初始化dis 数组
2. for n 次 ,每次找出 没处理过的那些点之中 最短的那个
3. 再在其中 for n次 ,进行没处理过的那些点的距离的更新
我刚入门这玩意,就觉的写起来很顺手,思路也很通畅。
但是如果点很多很多,请问你该怎么办,你的map数组将很大,可能超内存。
而SPFA算法,则是注重于边的存储,他按顺序存储一个一个边,一般把边弄成个结构体,而我们使用一个结构体数组按顺序来存储所有的边。 但是如何让让这些数组里的边产生关系?
我们就可以使用 链式前向星来实现。
何谓链式前向星? 简而言之就是:
边数组 存储 边
每个边都有个起点和终点 ,按上到下按顺序来看,如果我这个边,是这个起点的第一个边,那么这个边的next是-1
如果我这个边,不是起点上的第一个边,那就很简单了,我的next指向上一个边。
看图:
对于这个图,我们这样描述:
1 2 0号边
2 3 1号边
3 4 2号边
1 3 3号边
4 1 4号边
1 5 5号边
4 5 6号边
如果使用链式前向星处理,有以下几个东西。
- 存储边的数组: edges[] , 之中的元素一般是个边,也就是结构体
- edges[ x ].next : 也就是某个边的next是谁? 可能是-1,因为起点上就这一个,也可能是 4, 意思是,x边的的起点上 还连了一个4号边。
- head[x] : x 边的起点 上 ,不是有很多边嘛, 这个head[x]就是,这么多边中,最后面的那一个
举例子:
上面的0号边, 3号边, 5号边 都是起点1
因此 : edges[0].next = -1
edges[3].next=0
edges[5].next=3
还有:head[1] = 5
拿hdu2066为例子:
hdu2066
就是个基本的前向星+spfa, 因为最大节点是1000,实际上也可以用djk来做,因为1000*1000 也不是不能接受的数据规模。
import java.io.BufferedInputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class Main{
static int n;
static int m;
static private final int inf = 0x3f3f3f;
static edge[] edges;
static int[] head;
static int cont;
static int[] near;
static int[] want;
static int[] dis;
static int[] in;
static int[] nums; //记录一个点入队次数
static class edge{ //一个边的结构体
int begin,end,value,next;
}
static void add(int begin,int end,int value) {
edges[cont] = new edge();
edges[cont].end = end;
edges[cont].value = value;
edges[cont].next = head[begin];
head[begin]= cont++;
}
static int spfa(int begin ) {
Queue<Integer> q= new LinkedList<Integer>();
in = new int [10005];
nums = new int [10005];
//初始化dis数组为inf
dis = new int[10006]; for(int i=0;i<10006;i++)dis[i]=inf;
dis[begin]=0;
in[begin]=1;
nums[begin]++;
q.add(begin);
while(!q.isEmpty()) {
int top = q.poll();
in[top]=0;
for(int i=head[top];i>=0;i=edges[i].next) { //链式前向星遍历
//System.out.println(i);
int end = edges[i].end; //这个边的终点
if(dis[end] > dis[top]+edges[i].value) {
dis[end] = dis[top]+edges[i].value;
if(in[end]==0) {
q.add(end);
in[end]=1;
nums[end]++;
//如果这个点加入的次数超过边数,证明存在负圈
if(nums[end]>m)
return -1;
}
}
}
}
return 1;
}
public static void main(String[] args) {
Scanner sc = new Scanner (new BufferedInputStream(System.in));
while(sc.hasNext()) {
m=sc.nextInt();
n=sc.nextInt();
int e = sc.nextInt();
edges = new edge[10005];
near = new int[n+2];
want =new int[e+2];
cont =0;
head = new int[10006]; for(int i=0;i<10006;i++)head[i]=-1;
for(int i=0;i<m;i++) {
int a= sc.nextInt();int b=sc.nextInt();int c = sc.nextInt();
add(a,b,c);
add(b,a,c);
}
for(int i=0;i<n;i++)
near[i]=sc.nextInt();
for(int i=0;i<e;i++)
want[i] =sc.nextInt();
int res = inf;
for(int i=0;i<n;i++) {
spfa(near[i]);
for(int j=0;j<e;j++) {
res = Math.min(res, dis[want[j]]);
}
}
System.out.println(res);
}
}
}
poj1511
大意:求1号点到其他点的最短路 + 其他点到1号点的最短
方法:
先正常建图(因为这题点太多了,所以用链式前向星存储边),使用spfa,求一次dis的和
再反向建图,求dis的和,加在一起就好了。
烦人的地方就在于: 第一dis[]数组用long ,结果也用long ,第二 inf一定切记要足够大