搜索与图论


image-20230330113532200

DFS

回溯,剪枝

题目

import java.util.*;

public class Main{
    static int N = 10, n;
    static int[] path = new int[N];
    static boolean[] st = new boolean[N];
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        dfs(0);
    }
    
    public static void dfs(int u){
        if(u == n){
            for(int i = 0; i < n; i++){
                System.out.print(path[i] + " ");
            }
            System.out.println();
            return;
        }
        
        for(int i = 1; i <= n; i++){
            if(!st[i]){
                path[u] = i;
                st[i] = true;
                dfs(u + 1);
                st[i] = false;
            }
        }
    }
}

题目

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

代码:

import java.util.*;

public class Main{
    static int N = 10, n;
    static char[][] chessboard = new char[N][N];
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        for(char[] ch : chessboard){
            Arrays.fill(ch, '.');
        }
        n = in.nextInt();
        dfs(0);
    }
    
    public static void dfs(int row){
         if(row == n){
            for(int i = 0; i < n; i++){
               for(int j = 0; j < n; j++){
                   System.out.print(chessboard[i][j]);
               }
               System.out.println();
            }
            System.out.println();
            return;
        }

        for(int col = 0; col < n; col++){
            if(isValid(row, col)){
                chessboard[row][col] = 'Q';
                dfs(row + 1);
                chessboard[row][col]  = '.';
            }
        }
    }
    
    
    public static boolean isValid(int row, int col){
        for(int i = 0; i < row; i++){
            if(chessboard[i][col] == 'Q'){
                return false;
            }
        }

        for(int i = row, j = col; i >= 0 && j >= 0; i--, j--){
            if(chessboard[i][j] == 'Q'){
                return false;
            }
        }

        for(int i = row, j = col; i  >= 0 && j < n; i--, j++){
            if(chessboard[i][j] == 'Q'){
                return false;
            }
        }

        return true;
    } 
}

BFS

求解最短路问题(边权为1)

DP是特殊的最短路问题(无环)

image-20230329223210536

输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0

输出样例
8
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] matrix = new int[n][m];
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                matrix[i][j] = scanner.nextInt();
            }
        }
        
        int res = bfs(matrix, n, m);
        System.out.print(res);
    }
    
    public static int bfs(int[][] matrix, int n, int m){
        Queue<Node> queue = new LinkedList<>();
        //将起点添加进队列
        queue.offer(new Node(0, 0));
        //表示从左上角开始到达当前位置走了多远距离
        int[][] dis = new int[n][m]; 
        //利用向量的方法进行上下左右前进
        int[] dx = {-1, 0, 1, 0};
        int[] dy = {0, 1, 0, -1};
        while(!queue.isEmpty()) {
             //每次都处理队列的头结点
            Node node = queue.poll();
             //当扫描到了右下角的位置时说明搜索完毕,退出循环
            if(node.x == n - 1 && node.y == m - 1) {
                break;
            }
            
            for(int i = 0; i < 4; i++) {
                int newx = node.x + dx[i];
                int newy = node.y + dy[i];
                
                 //当新位置在合法范围内并且新位置的元素为0且没有被访问过的请情况下才可以继续查找
                if(newx >= 0 && newy >= 0 && newx < n && newy < m && dis[newx][newy] == 0 && matrix[newx][newy] == 0) {
                    //将新节点添加进队列
                    queue.offer(new Node(newx, newy));
                    //记录距离
                    dis[newx][newy] = dis[node.x][node.y] + 1;
                }
            }
        }
        
        return dis[n - 1][m - 1];
    }
    
}

class Node{
    int x;
    int y;
    
    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

image-20230330011542016

输入样例
2 3 4 1 5 x 7 6 8
输出样例
19

image-20230329232513218

import java.io.*;
import java.util.*;

public class Main{
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        String start = "";
        for(int i = 0; i < 9; i++){
            start = start + scan.next();
        }
        String end = "12345678x";
        int res = bfs(start, end); 
        System.out.print(res);
    }
    
    public static int bfs(String start, String end){
        //队列,用来存储状态
        Queue<String> que = new LinkedList<>();
        //从start走到当前状态的步数(距离)
        Map<String,Integer> map = new HashMap<>();
        //将起点添加进队列
        que.offer(start);
        map.put(start,0);
        
        int[] dx = {-1, 0, 1, 0};
        int[] dy = {0, -1, 0, 1};
        
        
        while(!que.isEmpty()){
            String head = que.poll();
            //找到k在String中的下标
            int k  = head.indexOf('x');
            //然后进行一维数组转化成二维的下标操作
            int x = k / 3 ; int y = k % 3;
            
            int distance = map.get(head);
            
            if(head.equals(end)){
                return distance;
            }
            
            //状态转移
            for(int i = 0; i < 4; i++){
                int a = x + dx[i];
                int b = y + dy[i];
                
                if(a >=0 && a < 3 && b >= 0 && b < 3){
                    char[] arr = head.toCharArray(); 
                    swap(arr, k, a * 3 + b);
                    String s = new String(arr);
                    if(!map.containsKey(s)){
                        map.put(s, distance + 1);
                        que.offer(s);
                    }
                }
            }
        }
        return -1;
    }
    
    public static void swap(char[] arr,int x,int y){
        char temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }   
}

树与图的存储

树是无环连通图

无向图可以转化为有向图

image-20230330143858427

邻接矩阵

image-20230330144054802

邻接表: 拉链法

树与图的深度优先遍历

数组建立邻接表

//邻接表
//idx 存储当前要分配的下标
static int idx;
//head数组,存储head指向的节点下标
static int[] h = new int[N];
//e[i] 存储节点i的值,即编号
static int[] e = new int[M];
//ne[i] 表示节点i的next指针,存储下标
static int[] ne= new int[M];

//头插法
static void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

树与图的ddfs模板

// 需要标记数组st[N],  遍历节点的每个相邻的边
void dfs(int u) {
    st[u] = true; // 标记一下,记录为已经被搜索过了,下面进行搜索过程
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]) {
            dfs(j);
        }
    }
}

题目

image-20230401161959204

输入样例

9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6

输出样例:

4

本题的本质是树的dfs, 每次dfs可以确定以u为重心的最大连通块的节点数,并且更新一下ans。

也就是说,dfs并不直接返回答案,而是在每次更新中迭代一次答案。

这样的套路会经常用到,在 树的dfs 题目中

import java.io.*;
import java.util.*;

public class Main{
    //邻接表存储方式
    static int N = 100010, M = N * 2, n;
    //idx 存储当前要分配的下标
    static int idx;
    //头节点数组,存储head指向的节点下标
    static int[] h = new int[N];
    //e[i] 表示节点i的值
    static int[] e = new int[M];
    //ne[i] 表示节点i的next指针,存储下标
    static int[] ne= new int[M];
    //标记数组st
    static boolean[] st = new boolean[N];
    static int ans = N;
    
    public static void add(int a, int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    //返回以u为根的子树中节点的个数,包括u节点
    public static int dfs(int u){
        st[u] = true;
        int res = 0;
        int sum = 1;
        
        for(int i = h[u]; i != -1 ; i = ne[i]){
            int j = e[i];
            if(st[j] == false){
                int s = dfs(j);
                res = Math.max(res, s);
                sum += s;
            }
        }
        
        res = Math.max(res, n - sum);
        ans = Math.min(res, ans);
        
        return sum;
    }
    
    public static void main(String[] args){
        Scanner  scan = new Scanner(System.in);
        n = scan.nextInt();
        for(int i = 1 ; i < N ; i++ ){
            h[i] = -1;
        }
        for(int i = 0; i < n - 1; i++){
            int a = scan.nextInt();
            int b = scan.nextInt();
            add(a, b);
            add(b, a);
        }
        
        dfs(1);
        System.out.println(ans);
        
    }
    
}

树与图的宽度优先遍历

层次遍历:每次pop出队时,将与它距离为1的节点全部加到队列中

树与图的bfs模板

queue <- 起点
while queue 不空
{
    t <- 队头
    拓展t的所有邻点s{
         if(s未被遍历过){
             queue <- s;
            d[s] = d[t] + 1;
         }
    }
}

题目

image-20230401161624207

输入样例:

4 5
1 2
2 3
3 4
1 3
1 4

输出样例:

1
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;


public class Main{
    static int N = 100010, n, m, idx;
    static int[] e = new int[N];
    static int[] ne = new int[N];
    static int[] h = new int[N];
    //结点1到结点i的最短距离
    static int[] d = new int[N];
    
    
    public static void add(int a, int b){
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    public static int bfs(){
        Queue<Integer> q = new LinkedList<>();
        q.offer(1);
        d[1] = 0;
        while(!q.isEmpty()) {
            int t = q.poll();
            for(int i = h[t]; i != - 1; i = ne[i]) {
                int s = e[i];
                if(d[s] == -1) {
                    q.offer(s);
                    d[s] = d[t] + 1; 
                }
            }
        }
        return d[n];
    }
    
    
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        m = scanner.nextInt();
        for(int i = 0; i < N; i++) {
            h[i] = -1;
            d[i] = -1;
        }
        for(int i = 0; i < m; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            add(a,b);
        }
        
        int res = bfs();
        System.out.print(res);
    }

}

拓扑排序

只适用于 AOV网 (有向无环图)

算法流程:

用队列来执行 ,初始化讲所有入度为0的顶点入队。

主要由以下两步循环执行,直到不存在入度为 0 的顶点为止

  1. 选择一个入度为 0 的顶点,并将它输出;

  2. 删除图中从顶点连出的所有边。

循环结束,

若输出的顶点数小于图中的顶点数,则表示该图存在回路,即无法拓扑排序,

否则,输出的就是拓扑序列 (不唯一)

queue <- 所有入度为0的点
while queue 不空
{
    t <- 队头
    枚举t的所有出边 t -> j{
        删除t -> j, d[j]--;
         if(d[j] == 0){
             queue <- j;
         }
    }
}

题目

image-20230401160439120

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3
import java.util.Scanner;


public class Main {
    static int N = 100010, n, m, idx;
    static int[] e = new int[N];
    static int[] ne = new int[N];
    static int[] h = new int[N];
    //数组模拟栈
    static int[] q = new int[N];
    //结点i的入度
    static int[] d = new int[N];
    
    public static void add(int a, int b) {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    
    public static boolean topSort() {
        int hh = 0, tt = -1;
        for(int i = 1; i <= n; i++) {
            if(d[i] == 0) {
                q[++tt] = i;
            }
        }
        while(hh <= tt) {
            int t = q[hh++];
            for(int i = h[t]; i != -1; i = ne[i]) {
                int s = e[i];
                d[s]--;
                if(d[s] == 0) {
                    q[++tt] = s;
                }
            }
        }
        return tt == n -1; 
    }
    
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        for(int i = 0 ; i < N ; i ++ ){
            h[i] = -1; 
         }
        for(int i = 0; i< m; i++) {
            int a = in.nextInt();
            int b = in.nextInt();
            add(a, b);
            d[b]++;
        }
        
        if(topSort() == true){
          for(int i = 0 ; i < n; i ++ ){ 
                System.out.print(q[i] + " ");
            }
        }else {
            System.out.print(-1);
        }
    }

}

最短路

最短路.png

稠密图用邻接矩阵存储

稀疏图用邻接表存储

image-20230402223622882

Dijkstra

Dijkstra 的整体思路:即进行n(n为结点的个数)次迭代去确定每个点到起点的最小值 最后输出的终点的即为我们要找的最短路的距离

朴素版本

适用于稠密图

dijkstra.PNG

集合S为已经确定最短路径的点集。

  1. 初始化距离
    一号结点的距离为零,其他结点的距离设为无穷大(看具体的题)。
  2. 循环n次,每一次将集合S之外距离最短的点 t 加入到S中去(这里的距离最短指的是距离1号点最近。
    点 t 的路径一定最短,基于贪心,严格证明待看)。然后用点t更新 t 邻接点的距离

image-20230402005633332

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3
import java.util.*;
import java.io.*;

public class Main {
    /**
     * Java快速读写模板
     */
    public static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    public static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    
    public static int nextInt() throws IOException {
        in.nextToken();
        return (int) in.nval;
    }
    
    public static double nextDouble() throws IOException {
        in.nextToken();
        return in.nval;
    }
    
    public static String nextString() throws IOException {
        in.nextToken();
        return in.sval;
    }

    
    static int N = 510, n, m;
    //邻接矩阵存储图
    static int[][] g = new int[N][N];
    //记录每一个点距离第一个点的距离
    static int [] dist = new int[N];
    //用于记录该点的最短距离是否已经确定
    static boolean[] st = new boolean[N];
    
    public static int dijkstra() {
        Arrays.fill(dist, 0x3f3f3f3f);
        dist[1] = 0;
        
        ////迭代n次,每次可以确定一个点到起点的最短路
        for(int i = 0; i < n; i++) {
            int t = -1;
            //t就是未确定最短路径的点的集合中距离起点最短的点
            for(int j = 1; j <= n ; j++) {
                if(st[j] == false && (t== -1 || dist[j] < dist[t])) {
                    t = j;
                }
            }
            
            //加入到s集合中
            st[t] = true;
            
             //找到了距离最小的点t,并用最小的点t去更新其他的点到起点的距离
            for(int j = 1 ; j <= n ; j ++ ){
                dist[j] = Math.min(dist[j], dist[t] + g[t][j]);
            }
        }
   
        if(dist[n] == 0x3f3f3f3f) return -1;
        else return dist[n];
    }
    
    public static void main(String[] args) throws IOException {
        n = nextInt();
        m = nextInt();
        
        for(int i = 1 ; i <= n ; i ++){
            Arrays.fill(g[i], 0x3f3f3f3f);
        }
        for(int i = 0; i < m; i++) {
            int a = nextInt();
            int b = nextInt();
            int z = nextInt();
             //如果有重边,保留权值最小的一条边
            g[a][b] = Math.min(g[a][b], z);
        }
        
        int res = dijkstra();
        out.write(res + "");
        out.flush();
    }
}

堆优化版本

适用于稀疏图

稀疏图

堆优化版的dijkstra是对朴素版dijkstra进行了优化,在朴素版dijkstra中时间复杂度最高的寻找距离最短的点O(n^2)可以使用最小堆优化。

  1. 一号点的距离初始化为零,其他点初始化成无穷大。
  2. 将一号点放入堆中。
  3. 不断循环,直到堆空。每一次循环中执行的操作为:
    弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
    用该点更新临界点的距离,若更新成功就加入到堆中。

image-20230402005611973

import java.io.*;
import java.util.*;

public class Main{
    static int N = 100010;
    static int n, m, idx;

    static int[] h = new int[N];
    static int[] e = new int[N];
    static int[] ne = new int[N];
    static int[] w = new int[N];
    static int[] dist = new int[N];// 存储1号点到每个点的最短距离
    static boolean[] st = new boolean[N];
    static int INF = 0x3f3f3f3f;//设置无穷大
    public static void add(int a,int b,int c)
    {
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }

    // 求1号点到n号点的最短路,如果不存在则返回-1
    public static int dijkstra()
    {
        //维护当前未在st中标记过且离源点最近的点
        Queue<int[]> pq = new PriorityQueue<>((a, b) -> {return a[1] - b[1];});
        Arrays.fill(dist, INF);
        dist[1] = 0;
        queue.add(new int(1,0));
        while(!queue.isEmpty())
        {
            //1、找到当前未在s中出现过且离源点最近的点
            int[] p = queue.poll();
            int t = p[0];
            int distance = p[1];
            if(st[t]) continue;
            //2、将该点进行标记
            st[t] = true;
            //3、用t更新其他点的距离
            for(int i = h[t]; i != -1; i = ne[i])
            {
                int j = e[i];
                if(dist[j] > distance + w[i])
                {
                    dist[j] = distance + w[i];
                    queue.add(new int(dist[j], j));
                }
            }

        }
        if(dist[n] == INF) return -1;
        return dist[n];
    }

    public static void main(String[] args) throws IOException{
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        Arrays.fill(h,-1);
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            add(a,b,c);
        }
        int res = dijkstra();
        System.out.println(res);
    }
}

Bellman - ford

Bellman - ford 算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在 n-1 次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

通俗的来讲就是:假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,若图中存在负环,则在 n-1 次松弛后一定还会更新

for n次
    for 所有边 a,b,w (松弛操作)
        dist[b] = min(dist[b],back[a] + w)

back[] 数组是上一次迭代后 dist[] 数组的备份,由于是每个点同时向外出发,因此需要对 dist[] 数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点

image-20230402192109836

输入样例:

3 3 1
1 2 1
2 3 1
1 3 3

输出样例:

3
import java.util.Arrays;
import java.util.Scanner;


class Edge{
    int x,y,z;
    public Edge(int x,int y,int z){
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

public class Main {
    static int N = 510, M = 10010, n, m, k;
    static int[] dist = new int[N];
    static int[] back = new int[N];
    static Edge[] edgs = new Edge[M];
    
    static void bellman_ford() {
        Arrays.fill(dist, (int)1e9);
        dist[1] = 0;
        for(int i = 0; i < k; i++) {
            back = Arrays.copyOf(dist, n + 1);
            for(int j = 0; j < m; j++){
               Edge edg = edgs[j]; //每一条边的结构体 
                int a = edg.x;     
                int b = edg.y;
                int c = edg.z;
                dist[b] = Math.min(dist[b], back[a] + c); //用上面的点来更新后面的点
            }
        }
        
        if(dist[n] > (int)1e9 / 2) System.out.println("impossible");
        else System.out.println(dist[n]);        
    }
    
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        k = in.nextInt();
        for (int i = 0; i < m; i++) {
            int x = in.nextInt();
            int y = in.nextInt();
            int z = in.nextInt();
            edgs[i] = new Edge(x, y, z);
        }
        
        bellman_ford();
    }

}

SPFA

spa求最短路

Bellman_ford算法会遍历所有的边,但是有很多的边遍历了其实没有什么意义,我们只用遍历那些到源点距离变小的点所连接的边即可,只有当一个点的前驱结点更新了,该节点才会得到更新;因此考虑到这一点,我们将创建一个队列每一次加入距离被更新的结点。

SPFA算法和Dijstra算法

SPFA可以处理负权边,但是不能处理有负权回路的图;而Dijkstra不能处理带有负权边和负权回路的图,因为Dijkstra算法在计算最短路径时,不会因为负边的出现而更新已经计算过(收录过)的顶点的路径长度;

st数组

  • Dijkstra算法中的st数组保存的是当前确定了到源点距离最小的点,且一旦确定了最小那么就不可逆了(不可标记为true后改变为false);
  • SPFA算法中的st数组仅仅只是表示的当前发生过更新的点,且spfa中的st数组可逆(可以在标记为true之后又标记为false)。
  • 顺带一提的是BFS中的st数组记录的是当前已经被遍历过的点。

队列

  • Dijkstra算法里使用的是优先队列保存的是当前未确定最小距离的点,目的是快速的取出当前到源点距离最小的点;
  • SPFA算法中使用的是队列(也可以使用别的数据结构),目的只是记录一下当前发生过更新的点。

image-20230402201809661

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2
import java.util.*;
import java.io.*;


public class Main {
    static int N = 100010;
    static int n, m, idx;

    static int[] h = new int[N];
    static int[] e = new int[N];
    static int[] ne = new int[N];
    static int[] w = new int[N];
    static int[] dist = new int[N];// 存储1号点到每个点的最短距离
    static boolean[] st = new boolean[N];//判断当前的点是否已经加入到队列当中了;
    static int INF = 0x3f3f3f3f;//设置无穷大
    public static void add(int a,int b,int c)
    {
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }

    // 求1号点到n号点的最短路,如果不存在则返回-1
    public static void spfa()
    {
       
        Arrays.fill(dist, INF);
        dist[1] = 0;
        
        //声明一个队列保存更新过的节点
        Queue<Integer> q = new LinkedList<>();
        q.offer(1);
        st[1] = true;
        
        while(!q.isEmpty()) {
            int t = q.poll();
            st[t] = false;
            for(int i = h[t]; i != -1; i = ne[i]) {
                int j = e[i];
                 //如果当前节点可以被更新,就做更新操作,并将该节点加入到队列中
                if(dist[j] > dist[t] + w[i]) {
                    dist[j] = dist[t] + w[i];
                    if(st[j] == false) {
                        q.offer(j);
                        st[j] = true;
                    }
                }
            }
        }
        
        if(dist[n] == INF) System.out.println("impossible");
        else System.out.println(dist[n]);
    }

    public static void main(String[] args) throws IOException{
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        Arrays.fill(h,-1);
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            add(a,b,c);
        }
        spfa();
    }
}

spa求负环

统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于n,则也说明存在环

image-20230402210745373

输入样例:

3 3
1 2 -1
2 3 4
3 1 -4

输出样例:

Yes

image-20230402211233928

import java.util.*;
import java.io.*;


public class Main {
    static int N = 100010;
    static int n, m, idx;

    static int[] h = new int[N];
    static int[] e = new int[N];
    static int[] ne = new int[N];
    static int[] w = new int[N];
    static int[] dist = new int[N];// 存储1号点到每个点的最短距离
    static boolean[] st = new boolean[N];//判断当前的点是否已经加入到队列当中了
    static int[] cnt = new int[N];//1到i的最短路径边数
    static int INF = 0x3f3f3f3f;//设置无穷大
    public static void add(int a,int b,int c)
    {
        e[idx] = b;
        w[idx] = c;
        ne[idx] = h[a];
        h[a] = idx ++;
    }

    // 求1号点到n号点的最短路,如果不存在则返回-1
    public static boolean spfa()
    {
       
        //Arrays.fill(dist, INF);
        dist[1] = 0;
        
        //声明一个队列保存更新过的节点
        Queue<Integer> q = new LinkedList<>();
        //可能起点到不了负环,所以将所有的点都加入到队列中去
        for(int i = 1; i <= n; i++){
            q.offer(i);
            st[i] = true;
        }
        
        while(!q.isEmpty()) {
            int t = q.poll();
            st[t] = false;
            for(int i = h[t]; i != -1; i = ne[i]) {
                int j = e[i];
                 //如果当前节点可以被更新,就做更新操作,并将该节点加入到队列中
                if(dist[j] > dist[t] + w[i]) {
                    dist[j] = dist[t] + w[i];
                    cnt[j] = cnt[t] + 1;
                    
                    if(cnt[j] >= n) return true;
                    if(st[j] == false) {
                        q.offer(j);
                        st[j] = true;
                    }
                }
            }
        }
        
        return false;
    }

    public static void main(String[] args) throws IOException{
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        Arrays.fill(h,-1);
        while(m -- > 0){
            int a = scan.nextInt();
            int b = scan.nextInt();
            int c = scan.nextInt();
            add(a,b,c);
        }
        if(spfa() == true) System.out.println("Yes");
        else  System.out.println("No");
    }
}

Floyd

多源汇最短路问题-具有多个源点

image-20230402211444068

输入样例:

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例:

impossible
1
import java.util.Scanner;

public class Main {
    static int N = 210, INF = (int)1e9, n, m, k;
    static int[][] d = new int[N][N];
    /**
    f[k][i][j]代表(k的取值范围是从1到n),在考虑了从1到k的节点作为中间经过的节点时,从i到j的最短路径的长度。
    f[k][i][j]可以从两种情况转移而来:
    【1】从f[k−1][i][j]转移而来,表示i到j的最短路径不经过k这个节点
    【2】从f[k−1][i][k]+f[k−1][k][j]转移而来,表示i到j的最短路径经过k这个节点
    */
    static void floyd() {
        for(int k = 1; k <= n; k++) {
            for(int i = 1; i <= n; i++) {
                for(int j = 1; j <= n; j++) {
                    d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]);
                }
            }
        }
    }
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        k = in.nextInt();
        
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if(i == j) d[i][j] = 0;
                else d[i][j] = INF;
            }
            
        }
        
        while(m-- > 0) {
            int a = in.nextInt();
            int b = in.nextInt();
            int c = in.nextInt();
            d[a][b] = Math.min(d[a][b], c);
        }
        
        floyd();
        
        while(k-- > 0) {
            int x = in.nextInt();
            int y = in.nextInt();
            if (d[x][y] > INF / 2) System.out.println("impossible");
            else System.out.println(d[x][y]);
        }
    }
}

最小生成树

image-20230403140551679

最小生成树:给定一个无向图,在图中选择若干条边把图的所有节点连起来,要求边长之和最小

应用场景:地图上,有n个城市,知道坐标,要在城市之间铺设公路,让城市相互连通,铺设最小距离是多少

Prim

prim 算法采用的是一种贪心的策略:

每次将离连通部分的最近的点和点对应的边加入的连通部分,连通部分逐渐扩大,最后将整个图连通起来,并且边长之和最小。

image-20230403140752763

Prim算法与Dijkstra算法的区别:

Dijkstra算法是更新不在集合中的点 离起点的距离

dist[j]=min(dist[j], dist[t]+g[t][j])

Prim是更新不在集合中的点 离集合S的距离

dist[j] = min(dist[j], g[t][j])

image-20230403135226395

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6
import java.io.*;
import java.util.*;

public class Main{
    static int N = 510, INF = 0X3F3F3F3F, n, m;
    static int[][] g = new int[N][N];
    static int[] dist = new int[N];//点到连通部分的距离
    static boolean[] st = new boolean[N];//表示节点是否已经连通
    
    static int prim(){
        Arrays.fill(dist, INF);
        int res = 0;
        //迭代n次找出n个点
        for(int i = 0; i < n; i++){
            int t = -1;//t表示还没有找到数
            //找到离连通部分最近的点
            for(int j = 1; j <= n; j++){
                if(!st[j] && (t == -1 || dist[j] < dist[t])){
                    t = j;
                }
            }
            
            //第一次任选一个点为起点,不更新res
            if(i > 0){
                //如果这个点的距离是正无穷,说明没有连通,结束返回INF
                if(dist[t] == INF) return INF;
                res += dist[t];   
            }
            st[t] = true;
            
             //用t去更新其他集合外面的点
            for (int j = 1; j <= n; j ++ ) dist[j] = Math.min(dist[j], g[t][j]);
        }
        return res;
    }
    
    
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        for(int i = 1 ; i <= n ; i ++ ){
            Arrays.fill(g[i],INF);
        }
        while(m-- > 0){
            int u = in.nextInt();
            int v = in.nextInt();
            int w = in.nextInt();
            g[u][v] = g[v][u] = Math.min(g[u][v], w);
        }
        
        int res = prim();
        if(res == INF) System.out.println("impossible");
        else System.out.println(res);  
    }
}

Kruskal

image-20230403142418136

并查集判断点a,b是否连通(属于同一集合)

image-20230403151815022

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6
import java.io.*;
import java.util.*;

public class Main{
    static int N = 100010, n, m;
    static int[] p = new int[N];
    
    //并查集模板,所有的集合在过程中全部指向根节点
    public static int find(int x){ 
        if(p[x] != x) p[x] = find(p[x]);
        return  p[x];
    }
    
    static void  kruskal(Edge[] g){
        Arrays.sort(g, new Comparator<Edge>(){

           @Override
           public int compare(Edge e1, Edge e2){
               return e1.c - e2.c;
           }
        });
        
        int res = 0;
        int cnt = 0;
        //枚举所有的边
        for(int i = 0; i < m; i++){
            int a = g[i].a;
            int b = g[i].b;
            int c = g[i].c;
            //判断一下a和b是不是在同一个集合中,不在集合中执行以下操作
            a = find(a); b = find(b);
            if(a != b){
                p[a] = b; //将两个集合合并
                res += c;//res增加权重
                cnt ++;//边数加1
            }
        }
        if(cnt < n - 1) System.out.println("impossible");
        else System.out.println(res);
    }
    
    
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        Edge[] g = new Edge[m];//存储边
        for(int i = 1 ; i<= n ; i ++ ) p[i] = i;
        for(int i = 0 ; i < m ; i ++ ){
            int u = in.nextInt();
            int v = in.nextInt();
            int w = in.nextInt();
            g[i] = new Edge(u, v, w);
        }
        kruskal(g);
    }
}

class Edge{
    int a,b,c;
    public Edge(int a, int b, int c){
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

二分图

image-20230403140507322


文章作者: liuzhangjie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 liuzhangjie !
  目录