图算法的总结

难阿,菜阿。。本文章持续更新。。。。。。。

1. 图的表示

(u,v) 表示一条边,u、v 都表示图的节点

  • 邻接矩阵

    二维数组存储即可:int grab[][]

      无向图:grab[i][j] == grab[i][j]
      需要注意的是,在初始化无向图的权值时,一定要初始化grab[i][j],grab[i][j]
    
      有向图:grab[i][j] != grab[i][j]
      权值:grab[i][j]
    

    优点:访问快,算法简单,修改也非常简单。

    缺点:只适合密集类型的图,疏松的就很浪费空间。

  • 邻接链表

    表示方法非常多,链表,集合都可以。
    例如:Map<List<int[]>>, Map<List<Node>>, Node[]
    
    优点也很明显,对于稀疏的图来说,相对于邻接矩阵,是非常节省空间的。
    <br>链表的缺点就是这个存储的缺点
    
  • 链式前向星

2. 拓扑排序

一个图能进行拓扑排序的充要条件是 它是一个有向无环(DAG)。
所以不是每张图都可以使用拓扑排序的。拓扑排序有两种 bfs/dfs。
bfs适合处理入度一样的同级关系的问题,而dfs适合处理上下级关系的问题。

2.1 bfs

需要清楚入度/出度的概念,首先要知道边的表示(u, v),u、v都是节点。
    入度:以u为起点的边的数量。
    出度:以v为终点的边的数量。

入度为0的先进入队列,然后一直维护入度。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Main {
    private void bfs(int[][] grid, int n) {
        // 初始化
        int[] inDegrees = new int[n];
        List<List<Integer>> edges = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            edges.add(new ArrayList<>());
        }

        for (int[] edge : grid) {
            int u = edge[0], v = edge[1];
            inDegrees[v]++;
            edges.get(u).add(v);
        }

        // 进队列
        LinkedList<Integer> query = new LinkedList<>();
        for (int i = 0; i < inDegrees.length; i++) {
            if (inDegrees[i] == 0) {
                query.offer(inDegrees[i]);
            }
        }
        
        // 拓扑排序核心
        while (!query.isEmpty()) {
            int u = query.poll();
            List<Integer> edge = edges.get(u);
            // 维护每个节点的入度
            for (int v : edge) {
                inDegrees[v]--;
                if (inDegrees[v] == 0) {
                    query.offer(v);
                }
            }
        }
    }
}

2.2 dfs

3. 最短路径路径问题

定义:有向或无向图,n个点、m条边、边有权值。起点是s,终点是t,在所有能连接到s和t的路径中寻找边的权值之和
最小的路径。

3.1 dijkstra

贪心思想:抄近路走,肯定能找到最端路径
思路:每次都选择最短的边进行计算。
问题场景:适合图规模大,且边的权值非负。

3.1.2 for循环写法

下面代码是最小路径和的模板。for循环这种适合邻接矩阵。

public class Main{
    // 假设 {n | 0 <= n <= 2^31 - 1}
    private void dijkstra(int[][] grid, int n) {
        int info = Integer.MAX_VALUE;
        // 注意我假设 n >= 0
        int[] dis = new int[n];
        boolean[] use = new boolean[n];
        for (int i = 0; i < n; i++) {
            int x = -1;
            // 每次获取最小的路径
            for (int y = 0; y < n; y++) {
                if (!use[y] && (x == -1 || dis[y] < dis[x])) {
                    x = y;
                }
            }
            use[x] = true;
            for (int y = 0; y < n; y++) {
                if (dis[y] > grid[x][y] + dis[x]) {
                    dis[y] = grid[x][y] + dis[x];
                }
            }
        }
    }
}

3.2.2 优先队列

下面代码是最小路径和的模板。优先队列这种比较适合邻接链表类型的数据结构。

import java.util.*;

public class Main {

    enum Type {
        undirected, // 无向
        directed  // 有向
    }

    // 注意无向图和有向图的初始化
    private void setVal(Type type, Map<Integer, List<int[]>> map, int[] edge) {
        int u = edge[0], v = edge[1], w = edge[2];
        if (!map.containsKey(u)) map.put(u, new ArrayList<>());
        switch (type) {
            // 无向
            case directed:
                map.get(u).add(new int[]{v, w});
                map.get(v).add(new int[]{u, w});
                break;
            // 有向
            case undirected:
                map.get(u).add(new int[]{v, w});
                break;
        }
    }

    private void dijkstra(int[][] data, int n) {
        // int[]{u, w} u是节点,w是权值
        Map<Integer, List<int[]>> map = new HashMap<>();
        for (int[] edge : data) {
            // 选择无向图
            setVal(Type.undirected, map, edge);
        }
        // 注意我假设 n >= 0
        int[] dis = new int[n];
        boolean[] use = new boolean[n];
        LinkedList<int[]> query = new LinkedList<>();
        while (!query.isEmpty()) {
            int[] u = query.poll();
            if (use[u[0]]) continue;
            use[u[0]] = true;
            List<int[]> edges = map.get(u[0]);
            if (edges != null) {
                for (int[] edge : edges) {
                    int to = edge[0];
                    if (use[to]) {
                        continue;
                    } else if (dis[to] > u[1] + edge[1]) { // 求最小路径和
                        dis[to] = u[1] + edge[1];
                        query.offer(new int[]{to, dis[to]});
                    }
                }
                // 最小路径和,这里按照权值进行升序
                // 因为poll是从出口拿元素,所以我们必须保证出口是一个权值最小的边。
                // 如果觉得自己很牛。。可以优化一下这里。
                query.sort(new Comparator<int[]>() {
                    @Override
                    public int compare(int[] o1, int[] o2) {
                        return o1[1] - o2[1];
                    }
                });
            }
        }
    }
}

LeetCode统计路径数量问题

LeetCode 1976. 到达目的地的方案数

1976: 求最短路径的路径数量。
要做出这道题目,必须了解Dijkstra的过程,同一个节点可能会被多次遍历。比如:(u, v)边,u的权值+v
的权值如果小于v已经存在记录的权值和,那从起点0到v的最小路径就要被更换,此时v点的路径数量应该换成
u的路径数量。如果等于那就加起来。
public class Main{  
    public int countPaths(int n, int[][] roads) {
        int mod = 1000000007, inf = Integer.MAX_VALUE;
        Map<Integer, List<int[]>> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            map.put(i, new ArrayList<>());
        }
        for (int[] edge : roads) {
            int u = edge[0],
                    v = edge[1],
                    w = edge[2];
            map.get(u).add(new int[]{v, w});
            map.get(v).add(new int[]{u, w});
        }
        int[] dis = new int[n];
        boolean[] use = new boolean[n];
        int[] pre = new int[n];
        Arrays.fill(pre, 1);
        LinkedList<int[]> query = new LinkedList<>();
        Arrays.fill(dis, inf);
        dis[0] = 0;
        query.add(new int[]{0, 0});
        while (!query.isEmpty()) {
            int[] u = query.poll();
            if (use[u[0]]) continue;
            use[u[0]] = true;
            List<int[]> edges = map.get(u[0]);
            if (edges != null) {
                for (int[] edge : edges) {
                    int to = edge[0];
                    if (use[to]) {
                        continue;
                    } else if (dis[to] > u[1] + edge[1]) {
                        // u点到v点权值和更小,直接替换路径数量
                        pre[to] = pre[u[0]];
                        dis[to] = u[1] + edge[1];
                        query.offer(new int[]{to, dis[to]});
                    } else if (dis[to] == u[1] + edge[1]) {
                        // 权值和相同,就是多条路径到v点的距离都相同。
                        pre[to] += pre[u[0]];
                        pre[to] %= mod;
                    }
                }
                query.sort(new Comparator<int[]>() {
                    @Override
                    public int compare(int[] o1, int[] o2) { 
                        return o1[1] - o2[1];
                    }
                });
            }
        }
        //System.out.println(Arrays.toString(pre));
        return pre[n - 1];
    }
}

LeetCode计算路径权值

1514. 概率最大的路径

class Solution {
        public double maxProbability(int n, int[][] edges, double[] succProb, int start, int end) {
            Map<Integer ,List<Node>> table = new HashMap<>();
            int row = edges.length;
            for (int i = 0; i < n; i++) {
                table.put(i, new ArrayList<>());
            }
            for (int i = 0; i < row; i++) {
                int u = edges[i][0],
                        v = edges[i][1];
                double w = succProb[i];
                table.get(u).add(new Node(v, w));
                table.get(v).add(new Node(u, w));
            }
            double[] dis = new double[n];
            boolean[] use = new boolean[n];
            PriorityQueue<Node> query = new PriorityQueue<>();
            query.offer(new Node(start, 1));
            dis[start] = 1;
            while (!query.isEmpty()) {
                Node u = query.poll();
                if (use[u.v]) continue;
                use[u.v] = true;
                List<Node> v = table.get(u.v);
                if (v != null) {
                    for (Node node : v) {
                        int to = node.v;
                        if (use[to]) {
                            continue;
                        } else if (dis[to] < u.w * node.w) {
                            dis[to] = u.w * node.w;
                            query.offer(new Node(to, dis[to]));
                        }
                    }
                }
            }
            return dis[end];
        }

        class Node implements Comparable<Node>{
            int v;
            double w;

            Node(int v, double w) {
                this.v = v;
                this.w = w;
            }

            @Override
            public int compareTo(Node o) {
                return this.w - o.w > 0 ? -1 : 1;
            }
        }
}

1334. 阈值距离内邻居最少的城市

class Solution {
    public int findTheCity(int n, int[][] edges, int distanceThreshold) {
        int[][] grid = new int[n][n];
        for (int[] edge : edges) {
            int u = edge[0],
                    v = edge[1],
                    w = edge[2];
            grid[u][v] = w;
            grid[v][u] = w;
        }
//        System.out.println("grid");
//        for (int[] edge : grid) {
//            System.out.println(Arrays.toString(edge));
//        }

        int inf = Integer.MAX_VALUE ;
        // 记录节点的权值
        int[] dis = new int[n];
        boolean[] use = new boolean[n];
        int curr = 0;
        long number = inf;
        for (int i = 0; i < n; i++) {
            Arrays.fill(dis, inf);
            Arrays.fill(use, false);
            dis[i] = 0;
            for (int j = 0; j < n; j++) {
                int x = -1;
                for (int k = 0; k < n; k++) {
                    if (!use[k] && (x == -1 || dis[k] < dis[x])) {
                        x = k;
                    }
                }
                use[x] = true;
                for (int k = 0; k < n; k++) {
                    if (grid[x][k] != 0 && dis[x] != inf) {
                        int w = grid[x][k] + dis[x];
                        dis[k] = Math.min(w, dis[k]);
                    }
                }
            }
//            System.out.println(Arrays.toString(dis));
            long count = Arrays.stream(dis).filter(a -> a <= distanceThreshold).count();
            if (count <= number) {
                curr = i;
                number = count;
            }
        }
        return curr;
    }

}

3.2 floyd-warshall

思想:floyd用到了动态规划的思想:即求两点i,j的最短路径,分两种情况
     第一种是经过图中某个点k的路径和不经过点k的路径,取两者最短。
缺点:这个算法时间复杂度n^3,只能用于小规模的图 < 200。
优点:程序简单,可以判断负圈、可以一次求出所有节点的最短路径。

下面模板是最小路径和。

public class Main{
    // 我这个写法是会改变grid的数据,注意啊!!!!
    private void floyd(int[][] grid, int n) {
        for (int k = 0; k < n; k++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (grid[i][j] > grid[i][k] + grid[k][j]) {
                        grid[i][j] = grid[i][k] + grid[k][j];
                    }
                }
            }
        }
        // gird[x][y] 就是计算好的最小路径和,拿出来就行了。
    }
}

3.3 SPFA

4. 连通性

4.1 有向图连通性

4.2 无向图连通性

5. 欧拉图

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值