【用JAVA实现图论算法】1.最短路径算法(Dijkstra、Floyd-Warshall) 2.最小生成树算法(Prim、Kruskal法) 3.拓扑排序(Topological Sort)

1.最短路径算法(如Dijkstra算法、Floyd-Warshall算法)

Dijkstra算法

import java.util.PriorityQueue;

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

    public static void dijkstra(int[][] graph, int start) {
        int V = graph.length;
        int[] dist = new int[V];
        boolean[] visited = new boolean[V];

        // 初始化距离和访问状态
        for (int i = 0; i < V; i++) {
            dist[i] = (i == start) ? 0 : INF;
            visited[i] = false;
        }

        // 使用优先队列来保存待处理的节点
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[1], b[1]));
        pq.offer(new int[]{start, 0});

        while (!pq.isEmpty()) {
            int[] current = pq.poll();
            int u = current[0];
            int weight = current[1];

            if (visited[u]) {
                continue;
            }

            visited[u] = true;

            for (int v = 0; v < V; v++) {
                if (!visited[v] && graph[u][v] != INF && weight + graph[u][v] < dist[v]) {
                    dist[v] = weight + graph[u][v];
                    pq.offer(new int[]{v, dist[v]});
                }
            }
        }

        // 打印结果
        for (int i = 0; i < V; i++) {
            System.out.println("Distance from " + start + " to " + i + ": " + (dist[i] == INF ? "INF" : dist[i]));
        }
    }

    public static void main(String[] args) {
        int[][] graph = {
                {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(graph, 0);
    }
}

Bellman-Ford算法

要检查图中是否存在负权重的环,我们需要在执行完Bellman-Ford算法的主要部分(即V-1次松弛操作)之后,再进行一次额外的松弛操作。如果在这次额外的操作中,还有边的距离被更新,那么说明图中存在负权重的环。

以下是完整的Bellman-Ford算法实现,包括负权重环的检查:

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

    public static boolean bellmanFord(int[][] graph, int start) {
        int V = graph.length;
        int[] dist = new int[V];

        // 初始化距离
        for (int i = 0; i < V; i++) {
            dist[i] = INF;
        }

        dist[start] = 0;

        // 对图中的每一条边进行V-1次松弛操作
        for (int i = 0; i < V - 1; i++) {
            for (int u = 0; u < V; u++) {
                for (int v = 0; v < V; v++) {
                    if (graph[u][v] != INF && dist[u] != INF && dist[u] + graph[u][v] < dist[v]) {
                        dist[v] = dist[u] + graph[u][v];
                    }
                }
            }
        }

        // 检查负权重环
        for (int u = 0; u < V; u++) {
            for (int v = 0; v < V; v++) {
                if (graph[u][v] != INF && dist[u] != INF && dist[u] + graph[u][v] < dist[v]) {
                    // 如果在V-1次松弛操作后还有距离被更新,说明存在负权重环
                    return false; // 返回false表示存在负权重环
                }
            }
        }

        // 如果没有发现负权重环,返回true
        return true;
    }

    public static void main(String[] args) {
        int[][] graph = {
                // ... 图的邻接矩阵表示
        };

        boolean hasNegativeCycle = bellmanFord(graph, 0);
        if (hasNegativeCycle) {
            System.out.println("图中不存在负权重的环");
        } else {
            System.out.println("图中存在负权重的环");
        }

        // 如果需要,还可以打印出每个节点到起点的最短距离
        // ...
    }
}

在这个实现中,如果bellmanFord方法返回false,则表示图中存在负权重的环;如果返回true,则表示图中不存在负权重的环。此外,在检查负权重环的循环中,我们并没有更新dist数组,只是检查是否还有距离可以被更新。如果有,则说明存在负权重的环。

2.最小生成树算法(如Prim算法、Kruskal算法)

Prim算法

Prim算法是一种贪心算法,用于求解最小生成树。它从任意一个顶点开始,每次选择权值最小的边,并且这条边连接的另一个顶点必须不在已选择的顶点集合中。

import java.util.*;

class Graph {
    int V; // 顶点数量
    int[][] adjMatrix; // 邻接矩阵
    boolean[] inMST; // 标记顶点是否已在最小生成树中

    Graph(int v) {
        V = v;
        adjMatrix = new int[v][v];
        inMST = new boolean[v];
    }

    void addEdge(int v, int w, int weight) {
        adjMatrix[v][w] = weight;
        adjMatrix[w][v] = weight;
    }

    void primMST() {
        // 初始化,选择第一个顶点作为起始点
        int startVertex = 0;
        inMST[startVertex] = true;
        
        // 用于存储已选择的边的集合
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[2] - b[2]);
        
        // 将起始点相邻的边加入优先队列
        for (int i = 0; i < V; i++) {
            if (adjMatrix[startVertex][i] != 0 && !inMST[i]) {
                pq.offer(new int[]{startVertex, i, adjMatrix[startVertex][i]});
            }
        }

        while (!pq.isEmpty()) {
            int[] edge = pq.poll();
            int u = edge[0];
            int v = edge[1];
            int weight = edge[2];

            // 如果顶点v已经在MST中,则跳过这条边
            if (inMST[v]) continue;

            // 将顶点v添加到MST中
            inMST[v] = true;

            // 打印选择的边
            System.out.println("Edge " + u + " -- " + v + " == " + weight);

            // 将与顶点v相邻且不在MST中的边加入优先队列
            for (int i = 0; i < V; i++) {
                if (adjMatrix[v][i] != 0 && !inMST[i]) {
                    pq.offer(new int[]{v, i, adjMatrix[v][i]});
                }
            }
        }
    }

    // 主方法,用于测试Prim算法
    public static void main(String[] args) {
        Graph g = new Graph(4);
        g.addEdge(0, 1, 10);
        g.addEdge(0, 2, 6);
        g.addEdge(0, 3, 5);
        g.addEdge(1, 3, 15);
        g.addEdge(2, 3, 4);
        
        g.primMST();
    }
}

Kruskal算法

Kruskal算法是一种基于并查集的算法,用于求解最小生成树。它首先将所有边按权重从小到大排序,然后依次选择边,如果选择的边不构成环,则将其加入最小生成树中。

import java.util.*;

class UnionFind {
    int[] parent;

    UnionFind(int n) {
        parent = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

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

    void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            parent[rootX] = rootY;
        }
    }

    boolean connected(int x, int y) {
        return find(x) == find(y);
    }
}

class Edge implements Comparable<Edge> {
    int src, dest, weight;

    Edge(int src, int dest, int weight) {
        this.src = src
                this.dest = dest;  
        this.weight = weight;  
    }  
  
    @Override  
    public int compareTo(Edge other) {  
        return Integer.compare(this.weight, other.weight);  
    }  
}

class KruskalMST {
    List<Edge> edges;
    int V; // 顶点数量

    KruskalMST(int v) {
        V = v;
        edges = new ArrayList<>();
    }

    void addEdge(int src, int dest, int weight) {
        edges.add(new Edge(src, dest, weight));
    }

    void kruskalMST() {
        Collections.sort(edges); // 对边按权重排序

        UnionFind uf = new UnionFind(V);

        int e = 0; // 已选择的边数
        for (Edge edge : edges) {
            int src = edge.src;
            int dest = edge.dest;

            // 如果src和dest不在同一个集合中,则将它们合并,并加入最小生成树
            if (!uf.connected(src, dest)) {
                uf.union(src, dest);
                e++;

                // 打印选择的边
                System.out.println("Edge " + src + " -- " + dest + " == " + edge.weight);

                // 如果选择的边数等于顶点数减一,则最小生成树已完成
                if (e == V - 1) {
                    break;
                }
            }
        }
    }

    // 主方法,用于测试Kruskal算法
    public static void main(String[] args) {
        KruskalMST g = new KruskalMST(4);
        g.addEdge(0, 1, 10);
        g.addEdge(0, 2, 6);
        g.addEdge(0, 3, 5);
        g.addEdge(1, 3, 15);
        g.addEdge(2, 3, 4);

        g.kruskalMST();
    }
}

// 将UnionFind和Edge类放在这里,以便它们可以互相访问

// UnionFind类保持不变

// Edge类也已给出,现在它包含了完整的实现

// 当你运行KruskalMST的main方法时,它将打印出使用Kruskal算法找到的最小生成树的边

在这个示例中,KruskalMST类表示一个使用Kruskal算法来找到最小生成树的图。Edge类表示图中的边,并实现了Comparable接口以便可以对边进行排序。UnionFind类用于实现并查集数据结构,以便在Kruskal算法中检查两个顶点是否属于同一个集合(即是否在同一棵树中)。

3.拓扑排序(Topological Sort)

拓扑排序是对有向无环图(DAG, Directed Acyclic Graph)的顶点进行排序,使得对每一条有向边 (u, v),均有 u(在排序记录中)比 v 先出现。拓扑排序通常用于有向无环图中安排任务顺序的场景。

以下是使用Java实现拓扑排序的一个例子:

import java.util.*;

public class TopologicalSort {
    private List<List<Integer>> adjList; // 邻接表表示图
    private boolean[] visited; // 标记节点是否已访问
    private Stack<Integer> stack; // 存储拓扑排序结果的栈

    public TopologicalSort(int V) {
        adjList = new ArrayList<>(V);
        for (int i = 0; i < V; i++) {
            adjList.add(new ArrayList<>());
        }
        visited = new boolean[V];
        stack = new Stack<>();
    }

    // 添加边
    public void addEdge(int v, int w) {
        adjList.get(v).add(w);
    }

    // 拓扑排序的递归实现
    public void topologicalSortUtil(int v) {
        // 标记当前节点为已访问
        visited[v] = true;

        // 遍历所有相邻节点
        for (int i : adjList.get(v)) {
            if (!visited[i]) {
                topologicalSortUtil(i);
            }
        }

        // 将当前节点压入栈中,注意这里使用栈是为了逆序输出,如果要正序输出则可以用队列
        stack.push(v);
    }

    // 拓扑排序的主方法
    public List<Integer> topologicalSort() {
        // 遍历所有节点
        for (int i = 0; i < adjList.size(); i++) {
            if (!visited[i]) {
                topologicalSortUtil(i);
            }
        }

        // 检查图中是否有环,如果有环则无法进行拓扑排序
        for (boolean isVisited : visited) {
            if (!isVisited) {
                return null; // 或者抛出异常,表示图中存在环
            }
        }

        // 将栈中的元素转为列表并返回
        List<Integer> result = new ArrayList<>();
        while (!stack.isEmpty()) {
            result.add(stack.pop());
        }

        // 注意:由于我们使用栈来存储结果,这里返回的是拓扑排序的逆序结果
        // 如果需要正序结果,则可以在添加元素到栈时改为添加到列表的头部

        return result;
    }

    // 主方法,用于测试
    public static void main(String[] args) {
        TopologicalSort ts = new TopologicalSort(6);

        // 添加边
        ts.addEdge(5, 2);
        ts.addEdge(5, 0);
        ts.addEdge(4, 0);
        ts.addEdge(4, 1);
        ts.addEdge(2, 3);
        ts.addEdge(3, 1);

        // 执行拓扑排序
        List<Integer> result = ts.topologicalSort();

        // 输出结果
        if (result != null) {
            for (int vertex : result) {
                System.out.print(vertex + " ");
            }
        } else {
            System.out.println("图中存在环,无法进行拓扑排序。");
        }
    }
}

在这个实现中,我们首先创建了一个TopologicalSort类,它包含了一个邻接表adjList来存储图,一个布尔数组visited来标记节点是否已访问,以及一个栈stack来存储拓扑排序的结果。我们使用了递归的topologicalSortUtil方法来遍历图的节点,并在遍历完成后将节点压入栈中。最后,在topologicalSort方法中,我们遍历所有节点并调用递归方法。如果图中存在未访问的节点,则图中存在环,无法进行拓扑排序。否则,我们返回栈中的元素作为拓扑排序的结果。

main方法中,我们创建了一个TopologicalSort对象,并向图中添加了一些边。然后,我们调用topologicalSort方法来获取拓扑排序的结果,并输出结果。如果图中存在环,则输出相应的提示信息。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值