简介:
一、单源最短路
dijkstra适用于仅有正权的图
带有负权的图:bellmen-ford、spfa
二、多源最短路
floyd:动态规划
1、dijkstra 【O(n^2)】 【O(mlogn)】
dijkstra处理不了负权图。因为如果源点到终点距离比较短,但是有另外一条距离(一开始距离较长)由于有负权路,导致最后到终点距离反而更短,从而导致错误。
如 A->C【5】 A->B【6】->C【-2】,会先选择A->C的路径,但是实际最短路是A->B->C。
贪心思想:
1.找到已经确定的最短距离的点,去更新相邻点的距离
2.不再更新已经确定的点
枚举:【O(n^2)】
public int networkDelayTime(int[][] times, int n, int k) {
int[][] g=new int[n][n];
int max=Integer.MAX_VALUE/2;
for (int i = 0; i < n; i++) {
Arrays.fill(g[i],max);
}
int[] dist=new int[n];
Arrays.fill(dist,max);
boolean[] vis=new boolean[n];
for (int[] time : times) {
g[time[0]-1][time[1]-1]=time[2];
}
dist[k-1]=0;
for (int i = 0; i < n; i++) {
int t=-1;
for (int j = 0; j < n; j++) {
if(!vis[j]&&(t==-1||dist[t]>dist[j])){
t=j;
}
}
vis[t]=true;
for (int j = 0; j < n; j++) {
dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans==max?-1:ans;
}
堆:【O(mlogn)】
public int networkDelayTime(int[][] times, int n, int k) {
int max=Integer.MAX_VALUE/2;
int[] dist=new int[n];
Arrays.fill(dist,max);
boolean[] vis=new boolean[n];
Map<Integer,List<int[]>> map=new HashMap<>();
for (int[] time : times) {
if(!map.containsKey(time[0]-1)){
map.put(time[0]-1,new ArrayList<>());
}
map.get(time[0]-1).add(new int[]{time[1]-1,time[2]});
}
dist[k-1]=0;
PriorityQueue<Integer> pq=new PriorityQueue<>((o1, o2) -> dist[o1]-dist[o2]);
pq.add(k-1);
while (!pq.isEmpty()){
Integer t = pq.poll();
if(vis[t]) continue;
vis[t]=true;
List<int[]> temp = map.getOrDefault(t, new ArrayList<>());
for (int[] arr : temp) {
if(dist[t]+arr[1]<dist[arr[0]]){
dist[arr[0]]=dist[t]+arr[1];
if(!vis[arr[0]]) pq.add(arr[0]);
}
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans==max?-1:ans;
}
2、floyd 【O(n^3)】
思想:三层循环,枚举中间节点k,去计算i点、j点中间的最短距离
public int networkDelayTime(int[][] times, int n, int k) {
int[][] g=new int[n][n];
int max=Integer.MAX_VALUE/2;
for (int i = 0; i < n; i++) {
Arrays.fill(g[i],max);
}
for (int[] time : times) {
g[time[0]-1][time[1]-1]=time[2];
}
for (int t = 0; t < n; t++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if(g[i][t]+g[t][j]<g[i][j]){
g[i][j]=g[i][t]+g[t][j];
}
}
}
}
g[k-1][k-1]=0; //剔除自己
int ans = Arrays.stream(g[k-1]).max().getAsInt();
return ans==max?-1:ans;
}
3、bellmen-ford 【O(nm)】
n个节点,最差的情况是一条直线,这时最多有n-1次松弛,所以当第n次还能松弛时,说明图里有负环。
主要用于判断负环。
思想:广度搜索,得到一个节点,去松弛相邻的节点,再根据这些节点,去松弛相应相邻的节点。
public int networkDelayTime(int[][] times, int n, int k) {
int max=Integer.MAX_VALUE/2,m= times.length;
int[] dist=new int[n];
Arrays.fill(dist,max);
dist[k-1]=0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int from=times[j][0]-1,to=times[j][1]-1,val=times[j][2];
if(dist[from]+val<dist[to]){
dist[to]=dist[from]+val;
// if(i==n-1){
// return -1; //表示有负环
// }
}
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans==max?-1:ans;
}
4、spfa 最好[O(n)] 最坏 [O(nm)]
最好的情况:所有点与一个点相连
最坏的情况:一条直线
思想:是bellmen-ford算法的优化,通过队列储存节点,因为bellmen每次都要去循环每条边,然后看是否是当前节点可以松弛的,而且spfa通过队列存储节点,直接通过这些节点去松弛相邻的节点。
可以通过cnt[]数组来判断是否有负环,当cnt[v]>=n时,边数>=n,经过的点数>=n+1,有负环。
public int networkDelayTime(int[][] times, int n, int k) {
int max=Integer.MAX_VALUE/2,m= times.length;
int[] dist=new int[n];
boolean[] inq=new boolean[n];
Arrays.fill(dist,max);
dist[k-1]=0;
Map<Integer,List<int[]>> map=new HashMap<>();
for (int[] time : times) {
if(!map.containsKey(time[0]-1)){
map.put(time[0]-1,new ArrayList<>());
}
map.get(time[0]-1).add(new int[]{time[1]-1,time[2]});
}
Queue<Integer> q=new LinkedList<>();
q.add(k-1);
inq[k-1]=true;
while(!q.isEmpty()){
Integer t = q.poll();
inq[t]=false;
List<int[]> temp = map.getOrDefault(t, new ArrayList<>());
for (int[] arr : temp) {
if(dist[t]+arr[1]<dist[arr[0]]){
dist[arr[0]]=dist[t]+arr[1];
if(!inq[arr[0]]) {
inq[arr[0]]=true;
q.add(arr[0]);
}
}
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans==max?-1:ans;
}
判断是否有负环:
public int networkDelayTime(int[][] times, int n, int k) {
int max=Integer.MAX_VALUE/2,m= times.length;
int[] dist=new int[n];
boolean[] inq=new boolean[n];
Arrays.fill(dist,max);
dist[k-1]=0;
Map<Integer,List<int[]>> map=new HashMap<>();
for (int[] time : times) {
if(!map.containsKey(time[0]-1)){
map.put(time[0]-1,new ArrayList<>());
}
map.get(time[0]-1).add(new int[]{time[1]-1,time[2]});
}
// TODO 核心思想
Queue<Integer> q=new LinkedList<>();
//记录更新过的边数
int[] cnt=new int[n];
//所有节点入队列,负环可能存在所有节点出发的最短路上
for (int i = 0; i < n; i++) {
q.add(i);
inq[i]=true;
}
while(!q.isEmpty()){
Integer t = q.poll();
inq[t]=false;
List<int[]> temp = map.getOrDefault(t, new ArrayList<>());
for (int[] arr : temp) {
if(dist[t]+arr[1]<dist[arr[0]]){
dist[arr[0]]=dist[t]+arr[1];
cnt[arr[0]]=cnt[t]+1;
if(cnt[arr[0]]>=n) return -1; //表示有负环
if(!inq[arr[0]]) {
inq[arr[0]]=true;
q.add(arr[0]);
}
}
}
}
int ans = Arrays.stream(dist).max().getAsInt();
return ans==max?-1:ans;
}