图论相关算法实现,Java

在这里插入图片描述

package _09_图;

import java.util.*;

/**
 * 输入格式:
 * V, E : V个顶点, 1-V, E条边
 * from
 * to
 * weight
 */
public class MyGraphy {


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //构造图
        int v = sc.nextInt();
        int e = sc.nextInt();
        Graphy g = new Graphy(v, e);
        int start, end, weight;
        //输入按[1,V]输入, 保存时-1按[0,V-1]保存
        for (int i = 0; i < e; i++) {
            g.start[i] = sc.nextInt() - 1;
        }
        for (int i = 0; i < e; i++) {
            g.end[i] = sc.nextInt() - 1;
        }
        for (int i = 0; i < e; i++) {
            g.weight[i] = sc.nextInt();
        }
        g.init();

//        System.out.println(g.prim());
//        System.out.println(g.Kruskal());
//        g.Dijkstra(1 - 1);
//        g.BellmanFord(1 - 1);
//        System.out.printf("A->C的最短路径为%d\n", g.Floyd(1 - 1, 3 - 1));
//        System.out.println(g.topologicalSort().toString());
        g.dfs(1 - 1);
        System.out.println();
        g.bfs(1 - 1);
    }


    static class Graphy {
        int vertexSize;     //顶点个数
        int edgeSize;       //边的条数
        //存放接收数据
        int[] start;
        int[] end;
        int[] weight;
        Set<Edge>[] from;   //存放顶点的入边
        Set<Edge>[] to;     //存放顶点的出边
        int[] path;         //存放最短路径的路径信息
        int[] dist;         //存放最短路径的路径长度
        boolean[] used;     //标记顶点是否访问过

        public Graphy(int vertexSize, int edgeSize) {
            this.vertexSize = vertexSize;
            this.edgeSize = edgeSize;
            //初始化每个顶点对应的出入边
            from = new HashSet[vertexSize];
            to = new HashSet[vertexSize];
            //初始化最短路径的路径信息和路径长度
            dist = new int[vertexSize];
            path = new int[vertexSize];
            //按从0~V-1存放
            for (int i = 0; i < vertexSize; i++) {
                from[i] = new HashSet<>();
                to[i] = new HashSet<>();
                dist[i] = Integer.MAX_VALUE;
                path[i] = -1;
            }
            //初始化标记数组
            used = new boolean[vertexSize];
            //初始化边的信息
            start = new int[edgeSize];
            end = new int[edgeSize];
            weight = new int[edgeSize];
        }

        public void bfs(int begin) {
            clear();
            Set<Edge> edges;
            Queue<Set<Edge>> queue = new LinkedList<>();
            queue.offer(to[begin]);
            used[begin] = true;
            System.out.println(begin + 1);

            while (!queue.isEmpty()) {
                edges = queue.poll();
                for (Edge edge : edges) {
                    if (used[edge.to])
                        continue;
                    queue.offer(to[edge.to]);
                    used[edge.to] = true;
                    System.out.println(edge.to + 1);
                }
            }
        }

        public void dfs(int begin) {
            System.out.println(begin + 1);
            used[begin] = true;
            for (Edge edge : to[begin]) {
                if (used[edge.to])
                    continue;
                dfs(edge.to);
            }
        }

        /**
         * 拓扑排序
         */
        public List<Integer> topologicalSort() {
            //保存返回结果
            List<Integer> list = new ArrayList<>();
            //存放当前入度为0的顶点, 会动态更新
            Queue<Set<Edge>> queue = new LinkedList<>();
            //存放各个顶点对应的入读, 会动态更新
            int[] fromSize = new int[vertexSize];
            //顶点的出边集合, 存放在队列中
            Set<Edge> outEdges;
            //初始化为-1
            Arrays.fill(fromSize, -1);
            //遍历当前顶点的入度信息
            for (int i = 0; i < vertexSize; i++) {
                //将入度为0的压入队列, 并更新结果
                if (from[i].size() == 0) {
                    queue.offer(to[i]);
                    list.add(i + 1);
                } else {
                    //否则记录其入度
                    fromSize[i] = from[i].size();
                }
            }

            while (!queue.isEmpty()) {
                //抛出队头顶点的出边
                outEdges = queue.poll();
                //判断其出边to的入度
                for (Edge edge : outEdges) {
                    //如果为1, 则删除当前顶点后其入度为0
                    if (fromSize[edge.to] == 1) {
                        queue.offer(to[edge.to]);
                        list.add(edge.to + 1);
                    } else {
                        //否则更新to的入度信息
                        fromSize[edge.to]--;
                    }
                }
            }
            return list;
        }

        /**
         * 城市总距离最小值, 成本最小值, 最小生成树
         * Kruskal简单
         */

        /**
         * 将当前顶点的所有边加入最小堆, 每次取出一个最小权值边
         * 当前边没有访问过, 则标记from, to
         * 否则舍弃
         */
        public int prim() {
            clear();
            //要返回的最小值
            int min = 0;
            //默认以0位起始顶点
            int usedSize = 1;     //判断是否所有顶点都被标记过, 结束搜索
            //利用最小堆来获取权值最小的边
            PriorityQueue<Edge> minHeap = new PriorityQueue<>(((o1, o2) -> o1.weight - o2.weight));
            //将顶点1的边入堆
            minHeap.addAll(to[0]);
            while (!minHeap.isEmpty() && usedSize < vertexSize) {
                //取出堆中当前最小权值边
                Edge edge = minHeap.remove();
                //判断出边邻接顶点是否标记过
                if (used[edge.from] && used[edge.to])
                    continue;
                //更新最小值
                min += edge.weight;
                System.out.println(edge);
                //更新标记过的顶点数
                usedSize++;
                //更新出边邻接顶点标记
                used[edge.from] = true;
                used[edge.to] = true;
                //将出边邻接顶点的出边入堆
                minHeap.addAll(from[edge.to]);
                minHeap.addAll(to[edge.to]);
            }
            return min;
        }

        /**
         * 将所有的边加入最小堆, 每次取出最小权值边
         * 利用并查集判断是否有环路, 有则放弃当前边
         */
        public int Kruskal() {
            int min = 0;        //记录返回结果
            int edgeSize = 0;   //保存标记的边的条数, ==V-1时结束搜索
            //利用最小堆选取最小权值边
            PriorityQueue<Edge> minHeap = new PriorityQueue<>(((o1, o2) -> o1.weight - o2.weight));
            //将所有边入堆
            for (Set<Edge> edges : to) {
                minHeap.addAll(edges);
            }
            //利用并查集判断当前是否存在环
            UF uf = new UF(vertexSize);
            while (!minHeap.isEmpty() && edgeSize < vertexSize - 1) {
                //拿到最小权值边
                Edge edge = minHeap.remove();
                //如果存在环则舍弃
                if (uf.isSame(edge.from, edge.to))
                    continue;
                //否则合并from,to
                uf.union(edge.from, edge.to);
                //更新最小结果
                min += edge.weight;
            }
            return min;
        }

        /**
         * 最短路径问题
         * */

        /**
         * 一个顶点到其他所有顶点的最短路径和
         * 不能用负权边
         */
        public void Dijkstra(int vertex) {
            clear();  //清空标记数组
            int v;        //记录当前节点
            int size;     //记录已标记的顶点数, ==V时停止
            int min;      //记录搜索的最小顶点索引
            int minDist;  //记录搜索到的最小顶点路径值


            //标记起始顶点
            used[vertex] = true;
            //更新标记数
            size = 1;
            //将起始节点的出边更新到dist和path中去, 默认起始节点from为空
            for (Edge edge : to[vertex]) {
                v = edge.to;
                path[v] = vertex;
                dist[v] = edge.weight;
            }

            //标记数!=V, 循环继续
            while (size < vertexSize) {
                minDist = Integer.MAX_VALUE;
                min = -1;
                //遍历dist, 找到当前到下一个未标记顶点路径最小的顶点
                for (int i = 0; i < vertexSize; i++) {
                    if (!used[i] && dist[i] < minDist) {
                        minDist = dist[i];
                        min = i;
                    }
                }

                //标记当前顶点
                used[min] = true;
                size++;
                //利用最小顶点更新出边信息
                for (Edge edge : to[min]) {
                    v = edge.to;
                    if (!used[v] && minDist + edge.weight < dist[v]) {
                        dist[v] = minDist + edge.weight;
                        path[v] = min;
                    }
                }
                //更新入边信息
                for (Edge edge : from[min]) {
                    v = edge.from;
                    if (!used[v] && minDist + edge.weight < dist[edge.from]) {
                        dist[v] = minDist + edge.weight;
                        path[v] = min;
                    }
                }

            }
            System.out.println("------------Dijkstra-----------");
            prt(dist, path, vertex);
        }

        /**
         * 支持负权边, 还能检测出是否有负权环
         * 对所有边最多进行V-1次松弛操作即可
         * 针对有向图
         */
        public void BellmanFord(int vertex) {
            clear();  //清空标记数组
            //最大循环次数
            int count = vertexSize - 1;
            //标记起始顶点
            used[vertex] = true;
            dist[vertex] = 0;

            int from;
            int to;
            int weight;


            //最多循环V-1次
            while (count-- > 0) {
                //遍历所有边
                for (int i = 0; i < edgeSize; i++) {
                    from = start[i];
                    to = end[i];
                    weight = this.weight[i];
                    //如果当前边的from没有标记过, 则无法更新to
                    if (!used[from])
                        continue;
                    relax(from, to, weight);
                }
            }

            //遍历所有边
            for (int i = 0; i < edgeSize; i++) {
                from = start[i];
                to = end[i];
                weight = this.weight[i];
                //如果当前边的from没有标记过, 则无法更新to
                if (!used[from])
                    continue;
                //如果再次进行了松弛操作, 则有负权环
                if (relax(from, to, weight)) {
                    System.out.println("有负权环");
                    return;
                }
            }
            System.out.println("------------BallmanFord-----------");
            prt(dist, path, vertex);
        }

        //松弛操作
        private boolean relax(int from, int to, int weight) {
            //如果to已经标记过, 且当前新的长度>=原有路径长度, 则退出
            if (used[to] && dist[from] + weight >= dist[to])
                return false;

            //此时有两种情况
            //1. to没有被标记过
            //2. to被标记过, 且路径值需要更新
            used[to] = true;
            dist[to] = dist[from] + weight;
            path[to] = from;
            return true;
        }

        /**
         * 可以求出任意两个顶点的最短路径, 支持负权边
         */
        public int Floyd(int a, int b) {
            //V*V大小数组, 类似DP
            int[][] dists = new int[vertexSize][vertexSize];
            //初始化为-1
            for (int i = 0; i < vertexSize; i++) {
                Arrays.fill(dists[i], -1);
            }
            //将当前的边导入dists
            for (int i = 0; i < edgeSize; i++) {
                dists[start[i]][end[i]] = weight[i];
            }

            //判断dists[i][k]+dists[k][j]<dists[i][j]
            for (int k = 0; k < vertexSize; k++) {
                for (int i = 0; i < vertexSize; i++) {
                    for (int j = 0; j < vertexSize; j++) {
                        int p1 = dists[i][k];
                        int p2 = dists[k][j];
                        int p3 = dists[i][j];
                        //如果路径信息不存在, 则无法更新
                        if (p1 == -1 || p2 == -1)
                            continue;
                        //如果原来有路径信息且小于新的路径信息, 则不更新
                        if (p3 != -1 && p1 + p2 >= p3)
                            continue;
                        //更新路径信息
                        dists[i][j] = p1 + p2;
                    }
                }
            }

            //输出所有两个顶点间的最短路径
//            for (int i = 0; i < vertexSize; i++) {
//                for (int j = i + 1; j < vertexSize; j++) {
//                    System.out.printf("%c->%c的最短路径为%d\n", 'A' + i, 'A' + j, dists[i][j]);
//                }
//            }

            return dists[a][b];
        }

        /**
         * 输出最小路径信息
         */
        public void prt(int[] dist, int[] path, int vertex) {
            //倒序输出vertex到其余所有顶点的路径信息
            for (int i = 0; i < vertexSize; i++) {
                if (i == vertex)
                    continue;
                int tmp = i;
                while (path[tmp] != -1) {
                    System.out.printf("v%d->", tmp + 1);
                    tmp = path[tmp];
                }
                System.out.printf("v1, %d\n", dist[i]);
            }
        }

        /**
         * 更新每个顶点的出入边信息
         */
        public void init() {
//            //有向图
            for (int i = 0; i < edgeSize; i++) {
                Edge edge = new Edge(start[i], end[i], weight[i]);
                to[start[i]].add(edge); //出边放入该顶点的to
                from[end[i]].add(edge); //入边放入该顶点的from
            }

            //无向图
//            for (int i = 0; i < edgeSize; i++) {
//                Edge edge = new Edge(start[i], end[i], weight[i]);
//                from[start[i]].add(edge);
//                from[end[i]].add(edge);
//            }
        }

        /**
         * 清空标记数组
         */
        private void clear() {
            Arrays.fill(used, false);
            Arrays.fill(dist, Integer.MAX_VALUE);
            Arrays.fill(path, -1);
        }
    }

    static class Edge {
        int from;
        int to;
        int weight;

        public Edge() {
        }

        public Edge(int from, int to, int weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }

        @Override
        public String toString() {
            return "Edge{" +
                    "from=" + from +
                    ", to=" + to +
                    ", weight=" + weight +
                    '}';
        }
    }

    static class UF {
        int size;
        int[] parents;
        int[] ranks;

        public UF(int size) {
            this.size = size;
            parents = new int[size];
            for (int i = 0; i < size; i++)
                parents[i] = i;
            ranks = new int[size];
        }

        public void union(int x, int y) {
            int a = find(x);
            int b = find(y);
            if (a == b)
                return;

            if (ranks[a] > ranks[b]) {
                parents[b] = a;
            } else if (ranks[a] < ranks[b]) {
                parents[a] = b;
            } else {
                parents[b] = a;
                ranks[a]++;
            }
        }

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

        public int find(int x) {
            while (x != parents[x]) {
                parents[x] = parents[parents[x]];
                x = parents[x];
            }
            return x;
        }
    }
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值