最小生成树之prim算法概念与实现

基本概念

prim算法是以顶点为视角的,每一步都会为生长中的树添加一条边。从初始顶点出发,然后找到顶点周围最小的边,然后移动到最小边的另一个端点,继续找到顶点周围最小的边,直到得到最小生成树。所以prim算法的轨迹是一颗从初始顶点不断蔓延的树。

prim算法懒惰实现

prim算法的懒惰实现很简单,将需要考察的顶点的边放进一个优先队列中,并考察边的顶点是否存在于最小生成树中或者是否形成一个环。如果都没有,就将边放进MST。

/**
 * 懒惰实现普里姆斯算法
 * @author yuli
 *
 */
public class LazyPrimMst {
    private boolean[] marked;//标记最小生成树的顶点已经被访问过了
    private Queue<Edge> mst;//最小生成树容器
    private PriorityQueue<Edge> pq;//横切边优先队列
    public LazyPrimMst(EdgeWeightedGraph graph){
        pq = new PriorityQueue<>();
        mst = new LinkedList<Edge>();
        marked = new boolean[graph.getV()];
        //将最开始的节点放进mst,并把最开始的节点的邻接点放进优先队列
        visit(graph,0);
        while(!pq.isEmpty()){
            //从优先队列中得到权值最小的边
            Edge e = pq.poll();
            //得到两个顶点
            int v = e.getV();int w = e.getW();
            //跳过已经加入mst的边
            if(marked[v] && marked[w])
                continue;
            //将权重最小的边加入mst中
            mst.add(e);
            //标记已经被加入到mst中的顶点,并把他们的邻接点加入到优先队列中
            if(!marked[v])
                visit(graph, v);
            if(!marked[w])
                visit(graph, w);

        }
    }
    /**
     * 将顶点放进mst中
     * @param graph
     * @param v
     */
    private void visit(EdgeWeightedGraph graph,int v){
        //标记顶点已经在mst中了
        marked[v] = true;
        Iterable<Edge> adj = graph.adj(v);
        //将顶点没被访问过(加入mst)的邻接点放进优先队列中
        for (Edge edge : adj) {
            if(!marked[edge.other(v)]){
                pq.add(edge);
            }
        }
    }
    /**
     * 获取最小生成树
     * @return
     */
    public Iterable<Edge> edges(){
        return mst;
    }
    /**
     * 获取最小生成树的权重
     * @return
     */
    public double weight(){
        double weight = 0;
        Iterable<Edge> edges = edges();
        for (Edge edge : edges) {
            weight += edge.getWeight();
        }
        return weight;
    }
}

prim算法的即时实现

prim的懒惰算法简单方便,然是在把所有边都放进优先队列的做法不够优雅,会消耗很多的内存和性能。所以出现了prim算法的即时实现。
即时的prim算法会逐个考察通向顶点的最优边(权重最小)。因为贪心算法的缘故,通向顶点最小的权值边一定会组成一个最小生成树。
用一个索引优先队列维护通向最小生成树的最近的顶点
将需要考察的顶点放进索引优先队列中,考察通向这个顶点的最近的边,如果有更新权值。考察完后将队列中离树最近的顶点出队并加入到树中。

/**
 * 普里姆算法即时实现
 * @author yuli
 *
 */

public class PrimMst {
    private Edge[] edgeTo;//距离树最近的边
    private double[] distTo;//最近边的权重
    private boolean[] marked;//是否在树中
    private IndexMinPQ<Double> pq;//最小索引优先队列,用来维护到树的最短距离,最小权重
    public PrimMst(EdgeWeightedGraph graph) {
        edgeTo = new Edge[graph.getV()];
        distTo = new double[graph.getV()];
        marked = new boolean[graph.getV()];
        pq = new IndexMinPQ<>(graph.getV());//初始化索引优先队列
        for(int i = 0;i<graph.getV();i++){
            distTo[i] = Double.POSITIVE_INFINITY;//初始化权重,最大化
        }
        //把顶点0设置为最小,并考察
        distTo[0] = 0.0d;
        pq.insert(0, 0.0d);
        while(!pq.isEmpty()){
            //出队优先队列中最小的顶点,也就是离树最近的那个顶点。
            visit(graph,pq.delMin());
        }
    }
    private void visit(EdgeWeightedGraph graph, int v) {
        //将顶点设置为已经考察过了,加入树中
        marked[v] = true;
        //获取已经加入树中的顶点的邻接点
        for(Edge e:graph.adj(v)){
            int w = e.other(v);//获取通往顶点(同往树)的另一个端点
            //如果该端点已经被考察过了,就说明该端点已经存在于树中了,所以跳过
            if(marked[w]){
                continue;
            }
            //如果当前的边权重比离端点最近的一条边的权重轻,就替换掉
            if(e.getWeight() < distTo[w]){
                //记录到端点的最短边为多少
                distTo[w] = e.getWeight();
                //记录到端点最短路径
                edgeTo[w] = e;
                //如果队列中中包含该索引(顶点),就添加顶点到队列中,这个队列是记录离树最近的端点
                if(pq.contains(w)){
                    //修改该端点离树的距离
                    pq.change(w, distTo[w]);
                }else{
                    //添加队列中到树的最短距离
                    pq.insert(w, distTo[w]);
                }
            }
        }
    }
    /**
     * 获取最小生成树
     * @return
     */
    public Iterable<Edge> edges(){
        List<Edge> edges = new ArrayList<>();
        for (Edge edge : edgeTo) {
            if(edge != null){
                edges.add(edge);
            }
        }
        return edges;
    }
    /**
     * 获取最小生成树的权重
     * @return
     */
    public double weight(){
        double weight = 0;
        Iterable<Edge> edges = edges();
        for (Edge edge : edges) {
            weight += edge.getWeight();
        }
        return weight;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值