冬月小站

第四章:图

Feb 14, 2025
26
0

1. 图的遍历

图的遍历是指访问图中的所有顶点,确保每个顶点被访问且仅被访问一次。常见的遍历方法有深度优先搜索(DFS)和广度优先搜索(BFS)。

深度优先搜索(DFS)

DFS 通过递归或栈实现,沿着一条路径尽可能深入地访问顶点,直到无法继续为止,然后回溯并访问其他路径。

代码实现:

import java.util.*;

public class Graph {
    private int V; // 顶点数
    private LinkedList<Integer> adj[]; // 邻接表

    Graph(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; ++i)
            adj[i] = new LinkedList<>();
    }

    void addEdge(int v, int w) {
        adj[v].add(w); // 添加边
    }

    void DFSUtil(int v, boolean visited[]) {
        visited[v] = true; // 标记当前顶点为已访问
        System.out.print(v + " ");

        // 递归访问所有邻接顶点
        for (Integer n : adj[v]) {
            if (!visited[n])
                DFSUtil(n, visited);
        }
    }

    void DFS(int v) {
        boolean visited[] = new boolean[V]; // 初始化访问标记数组
        DFSUtil(v, visited); // 从顶点 v 开始 DFS
    }

    public static void main(String args[]) {
        Graph g = new Graph(4);
        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 2);
        g.addEdge(2, 0);
        g.addEdge(2, 3);
        g.addEdge(3, 3);

        System.out.println("DFS starting from vertex 2:");
        g.DFS(2);
    }
}

广度优先搜索(BFS)

BFS 使用队列实现,从起始顶点开始,逐层访问其邻接顶点。

代码实现:

void BFS(int v) {
    boolean visited[] = new boolean[V]; // 初始化访问标记数组
    LinkedList<Integer> queue = new LinkedList<>(); // 使用队列实现 BFS

    visited[v] = true;
    queue.add(v);

    while (!queue.isEmpty()) {
        v = queue.poll(); // 取出队首顶点
        System.out.print(v + " ");

        // 将所有未访问的邻接顶点加入队列
        for (Integer n : adj[v]) {
            if (!visited[n]) {
                visited[n] = true;
                queue.add(n);
            }
        }
    }
}

public static void main(String args[]) {
    Graph g = new Graph(4);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 2);
    g.addEdge(2, 0);
    g.addEdge(2, 3);
    g.addEdge(3, 3);

    System.out.println("BFS starting from vertex 2:");
    g.BFS(2);
}

2. 最短路径算法

最短路径算法用于找到图中两个顶点之间的最短路径。常见算法有 Dijkstra 算法和 Floyd-Warshall 算法。

Dijkstra 算法

Dijkstra 算法适用于带权图(权值为非负数),从单源顶点出发,找到到其他所有顶点的最短路径。

代码实现:

import java.util.*;

public class Dijkstra {
    private static final int INF = Integer.MAX_VALUE;

    void dijkstra(int graph[][], int src) {
        int V = graph.length;
        int dist[] = new int[V]; // 存储最短距离
        boolean visited[] = new boolean[V]; // 标记顶点是否已处理

        Arrays.fill(dist, INF);
        dist[src] = 0;

        for (int count = 0; count < V - 1; count++) {
            int u = minDistance(dist, visited); // 选择未处理的最小距离顶点
            visited[u] = true;

            // 更新邻接顶点的距离
            for (int v = 0; v < V; v++) {
                if (!visited[v] && graph[u][v] != 0 && dist[u] != INF && dist[u] + graph[u][v] < dist[v]) {
                    dist[v] = dist[u] + graph[u][v];
                }
            }
        }

        printSolution(dist);
    }

    int minDistance(int dist[], boolean visited[]) {
        int min = INF, minIndex = -1;
        for (int v = 0; v < dist.length; v++) {
            if (!visited[v] && dist[v] <= min) {
                min = dist[v];
                minIndex = v;
            }
        }
        return minIndex;
    }

    void printSolution(int dist[]) {
        System.out.println("Vertex \t Distance from Source");
        for (int i = 0; i < dist.length; i++)
            System.out.println(i + " \t\t " + dist[i]);
    }

    public static void main(String args[]) {
        int graph[][] = new int[][] {
            { 0, 4, 0, 0, 0, 0, 0, 8, 0 },
            { 4, 0, 8, 0, 0, 0, 0, 11, 0 },
            { 0, 8, 0, 7, 0, 4, 0, 0, 2 },
            { 0, 0, 7, 0, 9, 14, 0, 0, 0 },
            { 0, 0, 0, 9, 0, 10, 0, 0, 0 },
            { 0, 0, 4, 14, 10, 0, 2, 0, 0 },
            { 0, 0, 0, 0, 0, 2, 0, 1, 6 },
            { 8, 11, 0, 0, 0, 0, 1, 0, 7 },
            { 0, 0, 2, 0, 0, 0, 6, 7, 0 }
        };
        Dijkstra t = new Dijkstra();
        t.dijkstra(graph, 0);
    }
}

3. 最小生成树

最小生成树(MST)是连接图中所有顶点的边权值最小的树。常用算法有 Prim 算法和 Kruskal 算法。

Prim 算法

Prim 算法从一个顶点开始,逐步扩展生成树,每次选择权值最小的边。

代码实现:

import java.util.*;

public class Prim {
    private static final int INF = Integer.MAX_VALUE;

    void primMST(int graph[][]) {
        int V = graph.length;
        int parent[] = new int[V]; // 存储 MST
        int key[] = new int[V]; // 存储顶点的键值
        boolean mstSet[] = new boolean[V]; // 标记顶点是否在 MST 中

        Arrays.fill(key, INF);
        key[0] = 0; // 从第一个顶点开始
        parent[0] = -1;

        for (int count = 0; count < V - 1; count++) {
            int u = minKey(key, mstSet); // 选择最小键值顶点
            mstSet[u] = true;

            // 更新邻接顶点的键值
            for (int v = 0; v < V; v++) {
                if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {
                    parent[v] = u;
                    key[v] = graph[u][v];
                }
            }
        }

        printMST(parent, graph);
    }

    int minKey(int key[], boolean mstSet[]) {
        int min = INF, minIndex = -1;
        for (int v = 0; v < key.length; v++) {
            if (!mstSet[v] && key[v] < min) {
                min = key[v];
                minIndex = v;
            }
        }
        return minIndex;
    }

    void printMST(int parent[], int graph[][]) {
        System.out.println("Edge \t Weight");
        for (int i = 1; i < graph.length; i++)
            System.out.println(parent[i] + " - " + i + "\t" + graph[i][parent[i]]);
    }

    public static void main(String args[]) {
        int graph[][] = new int[][] {
            { 0, 2, 0, 6, 0 },
            { 2, 0, 3, 8, 5 },
            { 0, 3, 0, 0, 7 },
            { 6, 8, 0, 0, 9 },
            { 0, 5, 7, 9, 0 }
        };
        Prim t = new Prim();
        t.primMST(graph);
    }
}

4. 图匹配

图匹配是指在图中的边集中选择一些边,使得这些边没有共同的顶点。常见算法有匈牙利算法(用于二分图匹配)。


5. 强连通分支算法

强连通分支(SCC)是指有向图中的极大强连通子图。常用算法有 Kosaraju 算法和 Tarjan 算法。

Kosaraju 算法

Kosaraju 算法通过两次 DFS 找到所有强连通分支。

代码实现:

import java.util.*;

public class Kosaraju {
    private int V;
    private LinkedList<Integer> adj[];

    Kosaraju(int v) {
        V = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; ++i)
            adj[i] = new LinkedList<>();
    }

    void addEdge(int v, int w) {
        adj[v].add(w);
    }

    void DFSUtil(int v, boolean visited[], List<Integer> component) {
        visited[v] = true;
        component.add(v);

        for (Integer n : adj[v]) {
            if (!visited[n])
                DFSUtil(n, visited, component);
        }
    }

    Kosaraju getTranspose() {
        Kosaraju g = new Kosaraju(V);
        for (int v = 0; v < V; v++) {
            for (Integer n : adj[v]) {
                g.adj[n].add(v);
            }
        }
        return g;
    }

    void fillOrder(int v, boolean visited[], Stack<Integer> stack) {
        visited[v] = true;

        for (Integer n : adj[v]) {
            if (!visited[n])
                fillOrder(n, visited, stack);
        }

        stack.push(v);
    }

    void printSCCs() {
        Stack<Integer> stack = new Stack<>();
        boolean visited[] = new boolean[V];
        Arrays.fill(visited, false);

        for (int i = 0; i < V; i++) {
            if (!visited[i])
                fillOrder(i, visited, stack);
        }

        Kosaraju gr = getTranspose();
        Arrays.fill(visited, false);

        while (!stack.isEmpty()) {
            int v = stack.pop();
            if (!visited[v]) {
                List<Integer> component = new ArrayList<>();
                gr.DFSUtil(v, visited, component);
                System.out.println(component);
            }
        }
    }

    public static void main(String args[]) {
        Kosaraju g = new Kosaraju(5);
        g.addEdge(1, 0);
        g.addEdge(0, 2);
        g.addEdge(2, 1);
        g.addEdge(0, 3);
        g.addEdge(3, 4);

        System.out.println("Strongly Connected Components:");
        g.printSCCs();
    }
}

总结

以上是Java中图的遍历、最短路径算法、最小生成树、图匹配和强连通分支算法的基本实现。这些算法是图论中的基础,掌握它们对于解决实际问题非常有帮助。