存图方式
邻接矩阵
- 使用二维矩阵进行存图
- 适用于边数较多的稠密图(边数量接近为点的数量的平方)
// 邻接矩阵数组:w[a][b] = c 代表从 a 到 b 有权重为 c 的边
int[][] w = new int[N][N];
// 加边操作
void add(int a, int b, int c) {
w[a][b] = c;
}
邻接表
- 用数组存储单链表(头插法)
- 适用于边数较少的稀疏图(边数量和点数量接近)
Map<Integer,List<int[]>> table;
//初始化
Map<Integer,List<int[]>> table=new HashMap<>();
for(int[] e:edges){
int a=e[0],b=e[1],v=e[2];
table.computeIfAbsent(a,k->new ArrayList<>()).add(new int[]{b,v});
table.computeIfAbsent(b,k->new ArrayList<>()).add(new int[]{a,v});
}
最短路径算法
Floyd(on^3)
- 多源最短路算法
- 跑一遍Floyd可以得到从任意点出发,到达任意起点的最短路径
void floyd() {
// floyd 基本流程为三层循环:
// 枚举中转点 - 枚举起点 - 枚举终点 - 松弛操作
for (int p = 1
for (int i = 1
for (int j = 1
w[i][j] = Math.min(w[i][j], w[i][p] + w[p][j])
}
}
}
}
dijkstra(on^2)(邻接矩阵)
- 单源最短路径算法
- 跑一遍dijkstra可以得到从源点k到其他点x的最短距离
void dijkstra() {
//vis数组用来记录哪些点被更新过
// 起始先将所有的点标记为「未更新」和「距离为正无穷」
Arrays.fill(vis, false)
//dist数组记录的是源点到任意点的最短距离
Arrays.fill(dist, INF)
// 只有起点最短距离为 0
dist[k] = 0
// 迭代 n 次
for (int p = 1
// 每次找到「最短距离最小」且「未被更新」的点 t
int t = -1
for (int i = 1
if (!vis[i] && (t == -1 || dist[i] < dist[t])) t = i
}
// 标记点 t 为已更新
vis[t] = true
// 用点 t 的「最小距离」更新其他点
for (int i = 1
dist[i] = Math.min(dist[i], dist[t] + w[t][i])
}
}
}
堆优化dijkstra(onlogn)(邻接表)
pprivate int[] dijkstra(Map<Integer,List<int[]>> table,int start,int len){
int[] dis=new int[len+1]
Arrays.fill(dis,Integer.MAX_VALUE)
dis[start]=0
dis[0]=0
boolean[] vis=new boolean[len+1]
PriorityQueue<int[]> pq=new PriorityQueue<>((a,b)->a[1]-b[1])
pq.add(new int[]{start,0})
while(!pq.isEmpty()){
// 当队列不空,拿出一个源出来
int temp=pq.poll()[0]
if(vis[temp]) continue
// 标记访问
vis[temp]=true
List<int[]> nodes=table.get(temp)
for(int[] node:nodes){
int next=node[0]
if(vis[next])continue
// 更新到这个相邻节点的最短距离,与 poll出来的节点增加的距离 比较
dis[next]=Math.max(dis[next],dis[temp]+node[1])
/堆中新增节点,这里需要手动传入 next节点堆距离值。否则如果next在队列中,将永远无法上浮。
pq.add(new int[]{next,dis[next]})
}
}
return dis
}
Bellman Ford
- 单源最短路径算法
- 负权图中求最短路
- 解决有边数 解决有边数限制的最短路 问题
void bf() {
// 起始先将所有的点标记为「距离为正无穷」
Arrays.fill(dist, INF)
// 只有起点最短距离为 0
dist[k] = 0
// 迭代 n 次
for (int p = 1
int[] prev = dist.clone()
// 每次都使用上一次迭代的结果,执行松弛操作
for (int a = 1
for (int i = he[a]
int b = e[i]
dist[b] = Math.min(dist[b], prev[a] + w[i])
}
}
}
}
SPFA
- 单源最短路径算法
- 使用队列或者栈进行优化Bellman Ford
void spfa() {
// 起始先将所有的点标记为「未入队」和「距离为正无穷」
Arrays.fill(vis, false)
Arrays.fill(dist, INF)
// 只有起点最短距离为 0
dist[k] = 0
// 使用「双端队列」存储,存储的是点编号
Deque<Integer> d = new ArrayDeque<>()
// 将「源点/起点」进行入队,并标记「已入队」
d.addLast(k)
vis[k] = true
while (!d.isEmpty()) {
// 每次从「双端队列」中取出,并标记「未入队」
int poll = d.pollFirst()
vis[poll] = false
// 尝试使用该点,更新其他点的最短距离
// 如果更新的点,本身「未入队」则加入队列中,并标记「已入队」
for (int i = he[poll]
int j = e[i]
if (dist[j] > dist[poll] + w[i]) {
dist[j] = dist[poll] + w[i]
if (vis[j]) continue
d.addLast(j)
vis[j] = true
}
}
}
}
kruskal
- 求最优路径中的最大权重值lc1631、778
- 算法主要思路
- 我们可以先遍历所有的点,将所有的边加入集合,存储的格式为数组 [a,b,w] ,代表编号为 a的点和编号为 b 的点之间的权重为 w (按照题意, w 为两者的最大高度)。
- 对集合进行排序,按照 w 进行从小到达排序。
- 当我们有了所有排好序的候选边集合之后,我们可以对边从前往后处理,每次加入一条边之后,
- 使用并查集来查询左上角的点和右下角的点是否连通。
- 当我们的合并了某条边之后,判定左上角和右下角的点联通,那么该边的权重即是答案。
class Solution {
int[] p
int m
int n
boolean isConnected(int node1,int node2){
return find(node1)==find(node2)
}
void union(int node1,int node2){
p[find(node1)]=find(node2)
}
int find(int node){
while(node!=p[node]) node=p[node]
return node
}
public int kruskal(int[][] grid) {
m=grid.length
n=grid[0].length
p=new int[m*n]
for(int i=0
List<int[]> list=new ArrayList<>()
for(int i=0
for(int j=0
int idx=getIdx(i,j)
if(i+1<m){
list.add(new int[]{idx,getIdx(i+1,j),Math.max(grid[i][j],grid[i+1][j])})
}
if(j+1<n){
list.add(new int[]{idx,getIdx(i,j+1),Math.max(grid[i][j],grid[i][j+1])})
}
}
}
Collections.sort(list,(a,b)->a[2]-b[2])
int start=0,end=getIdx(m-1,n-1)
for(int i=0
int a=list.get(i)[0],b=list.get(i)[1],v=list.get(i)[2]
union(a,b)
if(isConnected(start,end)) return v
}
return 0
}
private int getIdx(int x,int y){
return x*m+y
}
}