算法学习-搜索与图论

搜索与图论

一.构建图

List<Integer>[] --普通图
List<int[]> --List<int[]>[] --有权图

二.DFS与BFS

1.DFS

// 防止重复遍历同一个节点,若果输入的图是无环的可以不要
boolean[] visited;
// 从节点 s 开始 DFS 遍历,将遍历过的节点标记为 true
void traverse(List<Integer>[] graph, int s) {
    if (visited[s]) {
        return;
    }
    /* 前序遍历代码位置 */
    // 将当前节点标记为已遍历
    visited[s] = true;
    for (int t : graph[s]) {
        traverse(graph, t);
    }
    /* 后序遍历代码位置 */
}

2.BFS

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; // 核心数据结构
    Set<Node> visited; // 避免走回头路
    
    q.offer(start); // 将起点加入队列
    visited.add(start);

    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj()) {
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
            }
        }
    }
    // 如果走到这里,说明在图中没有找到目标节点
}

3.双向BFS

class Solution {
    Queue<String> q1 = new LinkedList<>();
    Queue<String> q2 = new LinkedList<>();
    HashMap<String, Integer> m1 = new HashMap<>();
    HashMap<String, Integer> m2 = new HashMap<>();
    HashSet<String> set;

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        set = new HashSet<>(wordList);
        if (!set.contains(endWord)) {
            return 0;
        }
        int res = bfs(beginWord, endWord);
        return res == -1 ? 0 : res + 1;
    }

    int bfs(String beginWord, String endWord) {
        q1.offer(beginWord);
        m1.put(beginWord, 0);
        q2.offer(endWord);
        m2.put(endWord, 0);

        while (!q1.isEmpty() && !q2.isEmpty()) {
            int t = -1;
            if (q1.size() <= q2.size()) {
                t = update(q1, m1, m2);
            } else {
                t = update(q2, m2, m1);
            }
            if (t != -1) {
                return t;
            }
        }
        return -1;
    }

    int update(Queue<String> q, HashMap<String, Integer> cur, HashMap<String, Integer> other) {
        for (int k = 0; k < q.size(); k++) {
            String poll = q.poll();
            for (int i = 0; i < poll.length(); i++) {
                for (int j = 0; j < 26; j++) {
                    String str = poll.substring(0, i) + String.valueOf((char) ('a' + j)) + poll.substring(i + 1);
                    if (set.contains(str)) {
                        if (cur.containsKey(str)) {
                            continue;
                        }
                        if (other.containsKey(str)) {
                            return cur.get(poll) + 1 + other.get(str);
                        } else {
                            q.offer(str);
                            cur.put(str, cur.get(poll) + 1);
                        }

                    }

                }
            }
        }
        return -1;
    }
}

三.拓扑排序

1.DFS实现拓扑排序

class Solution {
    boolean[] visited ;
    boolean[] onPath ;
    boolean ok = false;
    List<Integer> path = new ArrayList<>();
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<Integer>[] graph = buildGrahp(numCourses,prerequisites);
        visited = new boolean[numCourses];
        onPath = new boolean[numCourses];
        for(int i = 0;i<numCourses;i++){
            traverse(graph,i);
        }

        if(ok){
            return new int[]{};
        }

        Collections.reverse(path);

        int[] res = new int[numCourses];
        for(int i = 0;i<path.size();i++){
            res[i] = path.get(i);
        }

        return res;

    }



    public void traverse(List<Integer>[] graph,int s){
        if(onPath[s]){
            ok=true;
        }

        if(visited[s]||ok){
            return;
        }

        visited[s] = true;
        onPath[s] = true;

        for(int t : graph[s]){
            traverse(graph,t);
        }

        path.add(s);

        onPath[s] = false;
    }



    public List<Integer>[] buildGrahp(int numCourses,int[][] prerequisites){
        List<Integer>[] graph = new ArrayList[numCourses];
        Arrays.setAll(graph,i->new ArrayList<Integer>());

        for(int[] e : prerequisites){
            int from = e[1];
            int to = e[0];
            graph[from].add(to);
        }

        return graph;
    }
}

2.BFS实现拓扑排序

class Solution {
    int[] res ;
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        List<Integer>[] graph = buildGrahp(numCourses,prerequisites);
        Queue<Integer> q = new LinkedList<>();
        int[] indegree = new int[numCourses];
        res = new int[numCourses];
        
        for(int[] e : prerequisites){
            int from = e[1];
            int to = e[0];
            indegree[to]++;
        }

        for(int i = 0;i<numCourses;i++){
            if(indegree[i]==0){
                q.offer(i);
            }
        }

        int count = 0;
        while(!q.isEmpty()){
            int cur = q.poll();
            res[count++] = cur;
            for(int next : graph[cur]){
                indegree[next]--;
                if(indegree[next]==0){
                    q.offer(next);
                }
            }
        }

        if(count!=numCourses){
            return new int[]{};
        }

        return res;


    }






    public List<Integer>[] buildGrahp(int numCourses,int[][] prerequisites){
        List<Integer>[] graph = new ArrayList[numCourses];
        Arrays.setAll(graph,i->new ArrayList<Integer>());

        for(int[] e : prerequisites){
            int from = e[1];
            int to = e[0];
            graph[from].add(to);
        }

        return graph;
    }
}

四.最短路

在这里插入图片描述
LeetCode例题

1.堆优化的Dijkstra

class State {
    // 图节点的 id
    int id;
    // 从 start 节点到当前节点的距离
    int distFromStart;

    State(int id, int distFromStart) {
        this.id = id;
        this.distFromStart = distFromStart;
    }
}

// 输入一个起点 start,计算从 start 到其他节点的最短距离
int[] dijkstra(int start, List<int[]>[] graph) {
    // 定义:distTo[i] 的值就是起点 start 到达节点 i 的最短路径权重
    int[] distTo = new int[graph.length];
    Arrays.fill(distTo, Integer.MAX_VALUE);
    // base case,start 到 start 的最短距离就是 0
    distTo[start] = 0;

    // 优先级队列,distFromStart 较小的排在前面
    Queue<State> pq = new PriorityQueue<>((a, b) -> {
        return a.distFromStart - b.distFromStart;
    });
    // 从起点 start 开始进行 BFS
    pq.offer(new State(start, 0));

    while (!pq.isEmpty()) {
        State curState = pq.poll();
        int curNodeID = curState.id;
        int curDistFromStart = curState.distFromStart;

        // // 在这里加一个判断就行了,其他代码不用改
        // if (curNodeID == end) {
        //     return curDistFromStart;
        // }

        if (curDistFromStart > distTo[curNodeID]) {
            continue;
        }

        // 将 curNode 的相邻节点装入队列
        for (int[] neighbor : graph[curNodeID]) {
            int nextNodeID = neighbor[0];
            int distToNextNode = distTo[curNodeID] + neighbor[1];
            // 更新 dp table
            if (distTo[nextNodeID] > distToNextNode) {
                distTo[nextNodeID] = distToNextNode;
                pq.offer(new State(nextNodeID, distToNextNode));
            }
        }
    }
    return distTo;
}

2.Bellman-Ford

Bellman - ford算法擅长解决有边数限制的最短路问题。

  public static void bellman_ford() {
        Arrays.fill(dist, INF);
        dist[1] = 0;
		
        for (int i = 0; i < k; i ++) {
            //备份dist数组
            backup = Arrays.copyOf(dist, n + 1);
            // m是边数
            for (int j = 0; j < m; j ++) {
            	// node代替点与点的连接关系,用数组也可以
                Node node = list[j];
                int x = node.x;
                int y = node.y;
                int z = node.z;
                dist[y] = Math.min(dist[y], backup[x] + z);
            }
        }

        if (dist[n] > INF / 2) {
            System.out.println("impossible");
        } else {
            System.out.println(dist[n]);
        }
    }

例题:
在这里插入图片描述

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

class Main {
    private static int N = 510;
    private static int M = 10010;
    private static int[] backup = new int[N]; // 用于备份之前迭代的dist数组
    private static int[] dist = new int[N]; // 从1号点到 n号点的距离
    private static Node[] list = new Node[M]; // 结构体
    private static int n; // 总点数
    private static int m; // 总边数
    private static int k; // 最多经过k条边
    private static int INF = 0x3f3f3f3f;

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = bufferedReader.readLine().split(" ");
        n = Integer.parseInt(str1[0]);
        m = Integer.parseInt(str1[1]);
        k = Integer.parseInt(str1[2]);

        for (int i = 0; i < m; i ++) {
            String[] str2 = bufferedReader.readLine().split(" ");
            int x = Integer.parseInt(str2[0]);
            int y = Integer.parseInt(str2[1]);
            int z = Integer.parseInt(str2[2]);
            list[i] = new Node(x, y, z);
        }

        bellman_ford();
    }

    public static void bellman_ford() {
        Arrays.fill(dist, INF);
        dist[1] = 0;

        for (int i = 0; i < k; i ++) {
            //备份dist数组
            backup = Arrays.copyOf(dist, n + 1);
            for (int j = 0; j < m; j ++) {
                Node node = list[j];
                int x = node.x;
                int y = node.y;
                int z = node.z;
                dist[y] = Math.min(dist[y], backup[x] + z);
            }
        }

        if (dist[n] > INF / 2) {
            System.out.println("impossible");
        } else {
            System.out.println(dist[n]);
        }
    }
 }

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


3.SPFA

class Solution {

    private int INF = 0x3f3f3f3f;

    public int networkDelayTime(int[][] times, int n, int k) {
        // spfa
        int[][] g = new int[n + 1][n + 1];
        for (int i = 1; i <= n; i++) {
            Arrays.fill(g[i], INF);
        }

        for (int[] time : times) {
            g[time[0]][time[1]] = time[2];
        }

        int[] dist = new int[n + 1];
        Arrays.fill(dist, INF);
        dist[k] = 0;

        spfa(g, dist, n, k);

        int ans = 0;
        for (int i = 1; i <= n; i++) {
            ans = Math.max(ans, dist[i]);
        }

        return ans >= INF ? -1 : ans;
    }

    private void spfa(int[][] g, int[] dist, int n, int k) {
        boolean[] inque = new boolean[n + 1];
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(k);
        inque[k] = true;

        while (!queue.isEmpty()) {
            int i = queue.poll();
            inque[i] = false;

            for (int j = 1; j <= n; j++) {
                int tmp = dist[i] + g[i][j];
                if (tmp < dist[j]) {
                    dist[j] = tmp;
                    if (!inque[j]) {
                        queue.offer(j);
                        inque[j] = true;
                    }
                }
            }
        }
    }

}

4.Floyd

class Solution {

    private int INF = 0x3f3f3f3f;

    public int networkDelayTime(int[][] times, int n, int k) {
        // floyd
        int[][] g = new int[n + 1][n + 1];
        for (int i = 1; i <= n; i++) {
            Arrays.fill(g[i], INF);
        }

        for (int[] time : times) {
            g[time[0]][time[1]] = time[2];
        }

        int[][] dist = g;
        dist[k][k] = 0;

        floyd(dist, n);

        int ans = 0;
        for (int i = 1; i <= n; i++) {
            ans = Math.max(ans, dist[k][i]);
        }

        return ans >= INF ? -1 : ans;
    }

    private void floyd(int[][] dist, int n) {
        for (int x = 1; x <= n; x++) {
            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= n; j++) {
                    dist[i][j] = Math.min(dist[i][j], dist[i][x] + dist[x][j]);
                }
            }
        }
    }

}

五.最小生成树

在这里插入图片描述

1.Kruskal算法

int minCostConnectPoints(int[][] points) {
    int n = points.length;
    // 生成所有边及权重
    List<int[]> edges = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            int xi = points[i][0], yi = points[i][1];
            int xj = points[j][0], yj = points[j][1];
            // 用坐标点在 points 中的索引表示坐标点
            edges.add(new int[] {
                i, j, Math.abs(xi - xj) + Math.abs(yi - yj)
            });
        }
    }
    // 将边按照权重从小到大排序
    Collections.sort(edges, (a, b) -> {
        return a[2] - b[2];
    });
    // 执行 Kruskal 算法
    int mst = 0;
    UF uf = new UF(n);
    for (int[] edge : edges) {
        int u = edge[0];
        int v = edge[1];
        int weight = edge[2];
        // 若这条边会产生环,则不能加入 mst
        if (uf.connected(u, v)) {
            continue;
        }
        // 若这条边不会产生环,则属于最小生成树
        mst += weight;
        uf.union(u, v);
    }
    return mst;
}


class UF {
    // 连通分量个数
    private int count;
    // 存储每个节点的父节点
    private int[] parent;

    // n 为图中节点的个数
    public UF(int n) {
        this.count = n;
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
    
    // 将节点 p 和节点 q 连通
    public void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        
        if (rootP == rootQ)
            return;

        //可以颠倒
        parent[rootQ] = rootP;
        // 两个连通分量合并成一个连通分量
        count--;
    }

    // 判断节点 p 和节点 q 是否连通
    public boolean connected(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        return rootP == rootQ;
    }

    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    // 返回图中的连通分量个数
    public int count() {
        return count;
    }
}

2.Prim

六.二分图

1.染色法

class Solution {
    // 记录图是否符合二分图性质
    private boolean ok = true;
    // 记录图中节点的颜色,false 和 true 代表两种不同颜色
    private boolean[] color;
    // 记录图中节点是否被访问过
    private boolean[] visited;

    // 主函数,输入邻接表,判断是否是二分图
    public boolean isBipartite(int[][] graph) {
        int n = graph.length;
        color = new boolean[n];
        visited = new boolean[n];
        // 因为图不一定是联通的,可能存在多个子图
        // 所以要把每个节点都作为起点进行一次遍历
        // 如果发现任何一个子图不是二分图,整幅图都不算二分图
        for (int v = 0; v < n; v++) {
            if (!visited[v]) {
                traverse(graph, v);
            }
        }
        return ok;
    }

    // DFS 遍历框架
    private void traverse(int[][] graph, int v) {
        // 如果已经确定不是二分图了,就不用浪费时间再递归遍历了
        if (!ok) return;

        visited[v] = true;
        for (int w : graph[v]) {
            if (!visited[w]) {
                // 相邻节点 w 没有被访问过
                // 那么应该给节点 w 涂上和节点 v 不同的颜色
                color[w] = !color[v];
                // 继续遍历 w
                traverse(graph, w);
            } else {
                // 相邻节点 w 已经被访问过
                // 根据 v 和 w 的颜色判断是否是二分图
                if (color[w] == color[v]) {
                    // 若相同,则此图不是二分图
                    ok = false;
                    return;
                }
            }
        }
    }
}

2.匈牙利算法

匈牙利算法在解决二分图最大匹配问题中的应用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱敲代码的林先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值