《算法4》加权无向图最小生成树算法(Prime算法(延时实现) | Prime算法(即时实现) | Kruskal)

《算法4》最小生成树3种算法原理:
Prime算法(延时实现):将mst里面顶点的邻接边集合的最小边加入最小生成树mst,并延迟删除2个顶点都已经标记的边;
Prime算法(即时实现):距离最近的顶点加到最小生成树mst里面,并修改距离w顶点到最小生成树mst的的距离;
Kruskal:将邻接边集合的最小边加入最小生成树mst,加入的边不形成环(union-find并查集算法检测);

各种最小生成树算法的性能特点

demo中使用的测试数据,来自《算法4》,配图这里有个小的错误,看的时候注意一下。

用到的数据结构:
邻接边类 Edge.java

/**
 * 边的对象
 *
 * @author ltx
 */
public class Edge implements Comparable<Edge> {
    public int v;   //顶点v
    public int w;   //顶点w
    public double weight;  //权重

    public Edge(int v, int w, double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }

    /**
     * 边的另外一个顶点
     *
     * @param v 顶点v
     * @return
     */
    public int other(int v) {
        if (this.v == v) {
            return this.w;
        } else if (this.w == v) {
            return this.v;
        }
        throw new RuntimeException("边不对!");
    }

    /**
     * 实现compareTo方法,集合排序用
     *
     * @param that
     * @return
     */
    @Override
    public int compareTo(Edge that) {
        if (this.weight > that.weight) {
            return 1;
        } else if (this.weight < that.weight) {
            return -1;
        }
        return 0;
    }

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

加权无向图类 EdgeWeightedGraph.java

/**
 * 加权无向图
 *
 * @author ltx
 */
public class EdgeWeightedGraph {
    public int V;  //顶点数量
    public int E;  //边数量
    public List<Edge>[] adj;  //邻接边

    /**
     * 初始化加权无向图
     *
     * @param V 顶点数
     */
    public EdgeWeightedGraph(int V) {
        //初始化顶点数
        this.V = V;
        //初始化边数
        this.E = 0;
        //初始化邻接表
        this.adj = new List[V];
        for (int i = 0; i < V; i++) {
            adj[i] = new ArrayList<>();
        }
    }

    /**
     * 添加边
     *
     * @param e 边
     */
    public void addEdge(Edge e) {
        adj[e.v].add(e);
        adj[e.w].add(e);
        E++;
    }

    /**
     * 打印邻接表结构
     */
    public void show() {
        System.out.printf("%d 个顶点, %d 条边\n", V, E);
        for (int i = 0; i < V; i++) {
            System.out.println(i + ": " + adj[i]);
        }
    }

    /**
     * 初始化加权无向图
     *
     * @return
     */
    public static EdgeWeightedGraph init() {
        /**
         * 算法4里面的加权无向图
         */
        EdgeWeightedGraph edgeWeightedGraph = new EdgeWeightedGraph(8);
        edgeWeightedGraph.addEdge(new Edge(4, 5, 0.35));
        edgeWeightedGraph.addEdge(new Edge(4, 7, 0.37));
        edgeWeightedGraph.addEdge(new Edge(5, 7, 0.28));
        edgeWeightedGraph.addEdge(new Edge(0, 7, 0.16));
        edgeWeightedGraph.addEdge(new Edge(1, 5, 0.32));
        edgeWeightedGraph.addEdge(new Edge(0, 4, 0.38));
        edgeWeightedGraph.addEdge(new Edge(2, 3, 0.17));
        edgeWeightedGraph.addEdge(new Edge(1, 7, 0.19));
        edgeWeightedGraph.addEdge(new Edge(0, 2, 0.26));
        edgeWeightedGraph.addEdge(new Edge(1, 2, 0.36));
        edgeWeightedGraph.addEdge(new Edge(1, 3, 0.29));
        edgeWeightedGraph.addEdge(new Edge(2, 7, 0.34));
        edgeWeightedGraph.addEdge(new Edge(6, 2, 0.40));
        edgeWeightedGraph.addEdge(new Edge(3, 6, 0.52));
        edgeWeightedGraph.addEdge(new Edge(6, 0, 0.58));
        edgeWeightedGraph.addEdge(new Edge(6, 4, 0.93));
        return edgeWeightedGraph;
    }
}

一、Prime算法(延时实现)

最小生成树的Prime算的延时实现类 LazyPrimeMST.java

/**
 * 最小生成树的Prime算法(延时实现)
 * 原理-将mst里面顶点的邻接边集合的最小边加入最小生成树mst,并延迟删除2个顶点都已经标记的边
 *
 * @author ltx
 */
public class LazyPrimeMST {
    private EdgeWeightedGraph graph;
    private boolean marked[];//标记已经在最小生成树mst里面的顶点
    private List<Edge> pq;    //顺序排列的所有边
    public List<Edge> mst;   //最小生成树mst所有的边
    public double weights;    //最小生成树mst的权重合计

    /**
     * 初始化最小生成树mst
     *
     * @param graph 加权无向图
     */
    public LazyPrimeMST(EdgeWeightedGraph graph) {
        this.graph = graph;
        marked = new boolean[graph.V];
        mst = new ArrayList<>();
        pq = new ArrayList<>();
        //从顶点0开始
        visit(0);
        while (!pq.isEmpty()) {
            //按权重排个序
            Collections.sort(pq);
            //权重最小边
            Edge minE = pq.remove(0);
            //如果边的两个顶点都被标记了,就跳过,继续拿下一条最小边
            if (marked[minE.v] && marked[minE.w]) {
                continue;
            }
            //边加到最小生成树mst里面
            mst.add(minE);
            //权重合计
            weights += minE.weight;
            //未标记的顶点
            if (!marked[minE.v]) {
                visit(minE.v);
            }
            if (!marked[minE.w]) {
                visit(minE.w);
            }
        }
    }

    /**
     * 标记顶点v,并将顶点v的边加入队列中
     *
     * @param v 顶点
     */
    private void visit(int v) {
        //标记
        marked[v] = true;
        //将含有顶点v,且另一个顶点未被标记的边全部加到队列里面
        for (Edge e : graph.adj[v]) {
            if (!marked[e.other(v)]) {
                pq.add(e);
            }
        }
    }
}

二、Prime算法(即时实现)

顶点和权重类 Vertex.java

/**
 * 顶点和权重
 *
 * @author ltx
 */
public class Vertex implements Comparable<Vertex> {
    public int v;   //顶点v
    public double weight;  //顶点w距离最小生成树mst的权重

    public Vertex(int v, double weight) {
        this.v = v;
        this.weight = weight;
    }

    /**
     * 实现compareTo方法,集合排序用
     *
     * @param that
     * @return
     */
    @Override
    public int compareTo(Vertex that) {
        if (this.weight > that.weight) {
            return 1;
        } else if (this.weight < that.weight) {
            return -1;
        }
        return 0;
    }

    /**
     * 重写equals
     *
     * @param o
     * @return
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Vertex vertex = (Vertex) o;
        return v == vertex.v;
    }

    @Override
    public int hashCode() {
        return Objects.hash(v);
    }

    @Override
    public String toString() {
        return "Vertex{" +
                "v=" + v +
                ", weight=" + weight +
                '}';
    }
}

最小生成树的Prime算的即时实现类 PrimeMST.java

/**
 * 最小生成树的Prime算法(即时实现)
 * 原理-距离最近的顶点加到最小生成树mst里面,并修改距离w顶点到最小生成树mst的的距离
 *
 * @author ltx
 */
public class PrimeMST {
    private EdgeWeightedGraph graph;
    private Edge[] edgeTo;  //实时动态存-顶点w离最小生成树mst最小的边
    private double[] distTo;    //实时动态存-顶点w离最小生成树mst最小的边的权重
    private boolean marked[];//标记已经在最小生成树mst里面的顶点
    private List<Vertex> pq;    //顶点队列
    public List<Edge> mst;   //最小生成树mst所有的边
    public double weights;    //最小生成树mst的权重合计

    /**
     * 初始化最小生成树mst
     *
     * @param graph 加权无向图
     */
    public PrimeMST(EdgeWeightedGraph graph) {
        this.graph = graph;
        edgeTo = new Edge[graph.V];
        distTo = new double[graph.V];
        //将顶点v到mst的权重初始化为最大
        for (int i = 0; i < graph.V; i++) {
            //正无穷的常数
            distTo[i] = Double.POSITIVE_INFINITY;
        }
        marked = new boolean[graph.V];
        pq = new LinkedList<>();
        mst = new ArrayList<>();
        //从顶点0开始
        pq.add(new Vertex(0, 0));
        while (!pq.isEmpty()) {
            //按权重排个序
            Collections.sort(pq);
            Vertex minV = pq.remove(0);
            //离mst最近的顶点
            visit(minV.v);
        }
        //权重合计-从1开始,排除0起点
        for (int i = 1; i < graph.V; i++) {
            weights += distTo[i];
        }
        //mst-从1开始,排除0起点初始值
        for (int i = 1; i < graph.V; i++) {
            mst.add(edgeTo[i]);
        }
    }

    /**
     * @param v 顶点v
     */
    private void visit(int v) {
        //标记
        marked[v] = true;
        //将含有顶点v,且另一个顶点未被标记的边全部加到队列里面
        for (Edge e : graph.adj[v]) {
            int w = e.other(v);
            //另一个顶点在mst中则跳过
            if (marked[w]) {
                continue;
            }
            if (e.weight < distTo[w]) {
                //顶点w到mst的最小边-因为是实时修改的
                edgeTo[w] = e;
                //修改顶点w到mst的距离-修改为最小
                distTo[w] = e.weight;
                //如果队列里面有顶点w,则修改它到mst的距离权重
                //Vertex重写了equals,可直接用w对比
                Vertex node = new Vertex(w, e.weight);
                if (pq.contains(node)) {
                    Vertex temp = pq.get(pq.indexOf(node));
                    temp.weight = e.weight;
                } else {
                    //没有就新增
                    pq.add(node);
                }
            }
        }
    }
}

三、Kruskal算法

这里使用到union-find并查集算法,检测顶点是否在连通分量里面,具体算法可以查看之前的文章《《算法4》union-find并查集算法 (quick-find | quick-union | 加权quick-union | 路径压缩的加权quick-union)》

最小生成树的Kruskal算法实现类 KruskalMST.java

/**
 * 最小生成树的Kruskal算法
 * 原理-将邻接边集合的最小边加入最小生成树mst,加入的边不形成环(union-find并查集算法检测)
 *
 * @author ltx
 */
public class KruskalMST {
    private EdgeWeightedGraph graph;
    public List<Edge> mst;   //最小生成树mst所有的边
    public double weights;    //最小生成树mst的权重合计

    /**
     * 初始化最小生成树mst
     *
     * @param graph 加权无向图
     */
    public KruskalMST(EdgeWeightedGraph graph) {
        this.graph = graph;
        mst = new ArrayList<>();
        //所有的边放pq中
        List<Edge> pq = new ArrayList<>();
        for (int i = 0; i < graph.V; i++) {
            for (Edge e : graph.adj[i]) {
                pq.add(e);
            }
        }
        //初始化并查集 union-find并查集算法(加权 quick-union)
        UnionFind un = new WeightedQuickUnion(graph.V);
        //边数==顶点数-1时候就结束,已经找到mst了
        while (!pq.isEmpty() && mst.size() < graph.V - 1) {
            //按权重排个序
            Collections.sort(pq);
            //权重最小边
            Edge minE = pq.remove(0);
            //如果已经在一个连通分量里面(union-find并查集算法),就跳过,继续拿下一条最小边
            if (un.connected(minE.v, minE.w)) {
                continue;
            }
            //边加到最小生成树mst里面
            mst.add(minE);
            //权重合计
            weights += minE.weight;
            //将顶点v和w放一个连通分量里面 union-find并查集算法(加权 quick-union)
            un.union(minE.v, minE.w);
        }
    }
}

测试方法:

@Test
    void mst() {
        System.out.println("################加权无向图################");
        EdgeWeightedGraph graph = EdgeWeightedGraph.init();
        graph.show();
        System.out.println("################最小生成树mst################");
        System.out.println("-------Prime算法(延时实现)-------");
        LazyPrimeMST lazyPrimeMST = new LazyPrimeMST(graph);
        System.out.println(lazyPrimeMST.mst);
        System.out.printf("最小生成树mst权重合计: %.2f\n", lazyPrimeMST.weights);
        System.out.println("-------Prime算法(即时实现)-------");
        PrimeMST primeMST = new PrimeMST(graph);
        System.out.println(primeMST.mst);
        System.out.printf("最小生成树mst权重合计: %.2f\n", primeMST.weights);
        System.out.println("-------Kruskal算法-------");
        KruskalMST kruskalMST = new KruskalMST(graph);
        System.out.println(kruskalMST.mst);
        System.out.printf("最小生成树mst权重合计: %.2f\n", kruskalMST.weights);
    }

测试结果:

################加权无向图################
8 个顶点, 16 条边
0: [Edge{v=0, w=7, weight=0.16}, Edge{v=0, w=4, weight=0.38}, Edge{v=0, w=2, weight=0.26}, Edge{v=6, w=0, weight=0.58}]
1: [Edge{v=1, w=5, weight=0.32}, Edge{v=1, w=7, weight=0.19}, Edge{v=1, w=2, weight=0.36}, Edge{v=1, w=3, weight=0.29}]
2: [Edge{v=2, w=3, weight=0.17}, Edge{v=0, w=2, weight=0.26}, Edge{v=1, w=2, weight=0.36}, Edge{v=2, w=7, weight=0.34}, Edge{v=6, w=2, weight=0.4}]
3: [Edge{v=2, w=3, weight=0.17}, Edge{v=1, w=3, weight=0.29}, Edge{v=3, w=6, weight=0.52}]
4: [Edge{v=4, w=5, weight=0.35}, Edge{v=4, w=7, weight=0.37}, Edge{v=0, w=4, weight=0.38}, Edge{v=6, w=4, weight=0.93}]
5: [Edge{v=4, w=5, weight=0.35}, Edge{v=5, w=7, weight=0.28}, Edge{v=1, w=5, weight=0.32}]
6: [Edge{v=6, w=2, weight=0.4}, Edge{v=3, w=6, weight=0.52}, Edge{v=6, w=0, weight=0.58}, Edge{v=6, w=4, weight=0.93}]
7: [Edge{v=4, w=7, weight=0.37}, Edge{v=5, w=7, weight=0.28}, Edge{v=0, w=7, weight=0.16}, Edge{v=1, w=7, weight=0.19}, Edge{v=2, w=7, weight=0.34}]
################最小生成树mst################
-------Prime算法(延时实现)-------
[Edge{v=0, w=7, weight=0.16}, Edge{v=1, w=7, weight=0.19}, Edge{v=0, w=2, weight=0.26}, Edge{v=2, w=3, weight=0.17}, Edge{v=5, w=7, weight=0.28}, Edge{v=4, w=5, weight=0.35}, Edge{v=6, w=2, weight=0.4}]
最小生成树mst权重合计: 1.81
-------Prime算法(即时实现)-------
[Edge{v=1, w=7, weight=0.19}, Edge{v=0, w=2, weight=0.26}, Edge{v=2, w=3, weight=0.17}, Edge{v=4, w=5, weight=0.35}, Edge{v=5, w=7, weight=0.28}, Edge{v=6, w=2, weight=0.4}, Edge{v=0, w=7, weight=0.16}]
最小生成树mst权重合计: 1.81
-------Kruskal算法-------
[Edge{v=0, w=7, weight=0.16}, Edge{v=2, w=3, weight=0.17}, Edge{v=1, w=7, weight=0.19}, Edge{v=0, w=2, weight=0.26}, Edge{v=5, w=7, weight=0.28}, Edge{v=4, w=5, weight=0.35}, Edge{v=6, w=2, weight=0.4}]
最小生成树mst权重合计: 1.81

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小小绿豆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值