单源最短路:
所有边都是正权:
1.朴素Dijkstra,使用邻接矩阵,适用稠密图
2.堆优化Dijkstra,使用邻接表,适用稀疏图
存在负权边:
1.bellman-ford (解决带有边数限制的题目:例如求图中从起点到终点的最短路径,且经过的中转点最多为k个【即经过的边最多为k+1条】)
2.SPFA
1.邻接矩阵Dijkstra模板
class Solution {
/*
Dijkstra模板
*/
//N为图中的节点数
int[][] map = new int[N][N];//邻接矩阵
int[] dis = new int[N];//源点到其他点的距离数组
boolean[] vis = new boolean[N];//点访问标记数组
int INF = 0x3f3f3f3f;
public int networkDelayTime() {
//初始化:
//1.map置为INF/0
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
map[i][j] = (i == j) ? 0 : INF;
}
}
//2.建图
for(){
map[u][v] = weight;
}
//执行Dijkstra:
Dijkstra(k,n);
//TODO
}
public void Dijkstra(int k,int n){
//初始化:
Arrays.fill(vis,false);
Arrays.fill(dis,INF);
dis[k] = 0;
for(int i = 1;i<=n;i++){
int w = 0;//w为当前找到的最优节点
for(int j = 1;j<=n;j++){
if(!vis[j] && (w == 0 || dis[j] < dis[w])){
w = j;
}
}
//标记访问
vis[w] = true;
//以w为中转节点接着寻找最短路
for(int j = 1;j<=n;j++){
dis[j] = Math.min(dis[j],dis[w]+map[w][j]);
}
}
}
}
2.邻接表(链式前向星)Dijkstra模板
class Solution {
/*
堆优化Dijkstra
*/
//N为图中的节点数、M为图中的边数
int[] dis = new int[N];//源点到其他点的距离数组
boolean[] vis = new boolean[N];//点访问标记数组
int[] he = new int[N];//每个节点所对应的边的集合(链表)的头结点
int[] e = new int[M];//每条边指向的节点
int[] ne = new int[M];//用于找到下一条边
int[] w = new int[M];//每条边的权重
int idx;//每条边的编号
int INF = 0x3f3f3f3f;
//邻接表加边操作
public void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = he[a];
he[a] = idx;
w[idx] = c;
idx ++;
}
public int main() {
//初始化:
Arrays.fill(he, -1);//初始化链表头
//建图:
for(){
add(u,v,w);
}
//执行Dijkstra:
Dijkstra(k,n);
//TODO
}
public void Dijkstra(int k,int n){
//初始化
Arrays.fill(vis,false);
Arrays.fill(dis,INF);
//优先队列{点编号、点到源点的距离};点到源点的距离从小到大排序
PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[1]-b[1]);
pq.add(new int[]{k, 0});
dis[k] = 0;
while(!pq.isEmpty()){
//取出堆头的 点编号p、点到源点的距离dist
int[] cur = pq.poll();
int p = cur[0];
int dist = cur[1];
if(vis[p]) continue;
vis[p] = true;
//for:遍历所有由p点发出的边
for(int i = he[p];i != -1;i = ne[i]){
int j = e[i];
if(dis[p] + w[i] < dis[j]){
dis[j] = dis[p] + w[i];
pq.add(new int[]{j,dis[j]});
}
}
}
}
}
3.SPFA模板
class Solution {
/*
SPFA
*/
//N为图中的节点数、M为图中的边数
int[] dis = new int[N];//源点到其他点的距离数组
boolean[] vis = new boolean[N];//点访问标记数组
int[] he = new int[N];//每个节点所对应的边的集合(链表)的头结点
int[] e = new int[M];//每条边指向的节点
int[] ne = new int[M];//用于找到下一条边
int[] w = new int[M];//每条边的权重
int idx;//每条边的编号
int INF = 0x3f3f3f3f;
//邻接表加边操作
public void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = he[a];
he[a] = idx;
w[idx] = c;
idx ++;
}
public int main() {
//初始化
Arrays.fill(he, -1);//初始化链表头
//建图
for(){
add(u,v,w);
}
//执行SPFA:
SPFA(k,n);
//TODO
}
public void SPFA(int k,int n){
//初始化
Arrays.fill(vis,false);
Arrays.fill(dis,INF);
//队列
Queue<Integer> q = new LinkedList<>();
q.add(k);
dis[k] = 0;
vis[k] = true;//源点标记以访问!
while(!q.isEmpty()){
int p = q.poll();
vis[p] = false;//队头标记未访问!
for(int i = he[p];i != -1;i = ne[i]){
int j = e[i];
if(dis[p]+w[i] < dis[j]){
dis[j] = dis[p]+w[i];
//在这里与堆优化djikstra区别,增加了vis判断和标记
if(vis[j]) continue;
vis[j] = true;
q.add(j);
}
}
}
}
}
4.bellman-ford模板 787. K 站中转内最便宜的航班
class Solution {
int[][] map = new int[N][N];
int[] dis = new int[N];
int INF = 0x3f3f3f3f;
public int main() {
//初始化
for(int i = 0;i<n;i++){
for(int j = 0;j<n;j++){
map[i][j] = (i == j) ? 0 : INF;
}
}
//建图
for(){
map[u][v] = w;
}
//bellman-ford k:经过的中转点最多为k个
bf(start,end,k,n);
}
public void bf(int start,int end,int k,int n){
//初始化
Arrays.fill(dis,INF);
dis[start] = 0;
//三层for
//最外层for为边数限制条件 经过的中转点最多为k个 转换为 经过的边数最多为k+1条
for(int limit = 0;limit < k+1;limit++){
int[] clone = dis.clone();//克隆一份!!!
for(int i = 0;i<n;i++){
for(int j = 0;j<n;j++){
dis[j] = Math.min(dis[j],clone[i]+map[i][j]);
}
}
}
}
}
多源最短路:
1.Folyd
Folyd模板 1334. 阈值距离内邻居最少的城市
class Solution {
/*
Floyd
*/
//N为图中的节点数
int[][] map = new int[N][N];
int INF = 0x3f3f3f3f;
public int main() {
//初始化:
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
map[i][j] = (i == j) ? 0 : INF;
}
}
//建图:
for(){
map[u][v] = w
}
//执行Floyd:
Floyd(n);
//TODO:
}
public void Floyd(int n){
//枚举中转点-枚举起点-枚举终点
for(int k = 1;k<=n;k++){
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
map[i][j] = Math.min(map[i][j],map[i][k]+map[k][j]);
}
}
}
}
}
拓扑排序
对一个有向无环图(DAG)进行拓扑排序,是将图中所有节点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。
算法步骤:
1.建图、记录节点的入度数
2.将入度为0的节点存入队列中
3.while循环直到队列为空,依次弹出入度为0的节点,加入答案中,并依次将入度为0节点的所有出边删除(即在a->b中,【删除->】等价于【将节点b的入度减1】),再寻找并入队入度为0的节点。
1.邻接矩阵拓扑排序模板
class Solution {
int[][] map = new int[N][N];//邻接矩阵
int[] in = new int[N];//记录节点入度
public int[] main() {
//建图及记录节点的入度数
for(int i = 0;i<n;i++){
map[a][b] = 1;
in[b]++;
}
//Deque
Deque<Integer> q = new ArrayDeque<>();
//答案数组
int[] ans = new int[n];
//存所有入度为0的节点
for(int i = 0;i<n;i++){
if(in[i] == 0) q.addLast(i);
}
int c = 0;
while(!q.isEmpty()){
int u = q.pollFirst();//取出节点
ans[c] = u;//记录答案序列
c++;
for(int i = 0;i<n;i++){
if(map[u][i] == 1){//找出u的所有出边
in[i]--;//删除出边
if(in[i] == 0) q.addLast(i);//若删除后出边指向的节点的入度为0则加入队列
}
}
}
return ans;
}
}
2.邻接表拓扑排序模板(推荐) 207. 课程表 210. 课程表 II 851. 喧闹和富有
class Solution {
public int[] main() {
int[] in = new int[N];//记录节点入度
List<List<Integer>> map = new ArrayList<>();//链表存图
//初始化
for(int i = 0; i < n; i++)
map.add(new ArrayList<>());
//存图和存节点的入度
for() {
map.get(u).add(v);
in[v]++;
}
//将入度为0的节点放入队列中
Deque<Integer> q = new ArrayDeque<>();
for(int i = 0; i < n; i++)
if(in[i] == 0) q.addLast(i);
//答案数组
int[] ans = new int[n];
int c = 0;
while(!q.isEmpty()) {
int v = q.pollFirst();
ans[c] = v;//记录答案序列
c++;
for(int u : map.get(v))//get出来的就是v直接连接的节点
if(--in[u] == 0) q.add(u);
}
}
}
未完待续…………