最小生成树


最小生成树

加权无向图的最小生成树

1.加权边API

//加权边API
public class Edge implements Comparable<Edge>{
    private final int v; //边的一个顶点
    private final int w; //边的另一个顶点
    private final double weight;    //边的权重
    public Edge(int v,int w,double weight) {
        this.v = v;
        this.w = w;
        this.weight = weight;
    }
    public double weight(){ return weight; }
    public int either(){ return v; }    //获取其中一个顶点
    public int other(int vertex){       //获取另一个顶点
        if(vertex == v) return w;
        else if(vertex == w) return v;
        else throw new RuntimeException("不包含顶点"+v);
    } //方便遍历所有边
    public int compareTo(Edge that) {   //可比较
        if(weight-that.weight < 0) return -1;
        else if(weight-that.weight > 0) return 1;
        else return 0;
    }
    public String toString() {  //字符串表示
        return v + "-" + w +" " + weight;
    }
}

2.加权无向图API

//加权无向图API
public class EdgeWeightedGraph {
    private int v;    //顶点数
    private int e;    //边数
    private LinkedList<Edge>[] adj;   //邻接表
    public EdgeWeightedGraph(int v) {   //创建一副含有v个顶点的空图
        this.v = v;
        e = 0;
        adj = new LinkedList[v];
        for(int i=0;i<v;++i)
            adj[i] = new LinkedList<>();
    }
    public EdgeWeightedGraph(InputStream input) {   //从输入流中读取一幅图
        BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        String value = null;
        try {
            int v = Integer.parseInt(reader.readLine());
            this.v = v;
            adj = (LinkedList<Edge>[]) new LinkedList[v];
            for(int i = 0 ; i < v;i++)
                adj[i] = new LinkedList<>();
            int e = Integer.parseInt(reader.readLine());
            for(int i=0; i<e; i++){
                String[] str = reader.readLine().split(" +");
                addEdge(new Edge(Integer.parseInt(str[0]),Integer.parseInt(str[1]),Double.parseDouble(str[2])));
            }

        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
    //顶点数
    public int V(){ return v; }
    //边数
    public int E(){ return e; }
    //向图中添加一条边e
    public void addEdge(Edge e){
        adj[e.either()].add(e);
        adj[e.other(e.either())].add(e);
        this.e++;
    }
    //和v相连的所有边
    public Iterable<Edge> adj(int v){ return adj[v]; }
    //图的所有边
    public Iterable<Edge> edges(){
        ArrayDeque<Edge> queue = new ArrayDeque<>();
        for(int i=0;i<v;++i)    //按照顶点从小到大顺序遍历
            for(Edge e : adj[i])
                if(e.other(i) > i) queue.offer(e);  //大顶点-小顶点的边肯定在之前被遍历过
        return queue;
    }
    //图的字符串表示
    public String toString() {
        String s = V() + "vertices, " + E() + " edges\n";
        for(int v = 0;v < V();v++){
            s += v + ": ";
            for(Edge e : adj(v))
                s += e.toString() + " ";
            s += "\n";
        }
        return s;
    }
}

3.Prim算法延时版本(最小生成树)

/**
 * 最小生成树的Prim延时算法
 *    (假设为连通图,非连通时取每个连通分量的最小生成树合并为森林即可)
 * 概述: 将图所有顶点分为两个非空且不重复的两个集合,横切边是连接两个属于不同集合的边,
 *       而连接这两个集合的所有横切边中最小边必然属于最小生成树
 * 实现: 以任意一个顶点开始,作为最小生成树的初始阶段,将图分为2部分(一个顶点为一部分(最小生成树起始阶段),剩余顶点为一部分)
 *       获取连接这两个集合的所有横切边的最小者,加入最小生成树中,此时最小生成树的2个顶点和其余顶点作为图的两部分,
 *       重复上面的步骤继续取横切边中最小的边加入最小生成树,直到得到v-1条边为止
 */
public class LazyPrimMST {
    private boolean[] marked;   //标记是否已加入到最小生成树中
    private Queue<Edge> mst;    //维护最小生成树的边
    private PriorityQueue<Edge> pq; //使用优先队列保存横切边(包含失效的),每次可取出最小的横切边
    private double weight;

    public LazyPrimMST(EdgeWeightedGraph g) {
        marked = new boolean[g.V()];
        mst = new ArrayDeque<>();
        pq = new PriorityQueue<>();
        visit(g,0);     //假设g连通
        while(!pq.isEmpty()){
            Edge e = pq.poll(); //取出最小的横切边
            int v = e.either(), w = e.other(v); //横切边的两个顶点v-w
            if(marked[v] && marked[w]) continue; //顶点都在树中,失效的横切边跳过
            mst.offer(e);   //有效的最小横切边加入最小生成树中
            weight += e.weight();   //更新总权重
            if(marked[v]) visit(g,w); //将刚加入的顶点加入树中,并遍历相连的边
            if(marked[w]) visit(g,v);
        }
    }
    private void visit(EdgeWeightedGraph g,int v){
        marked[v] = true;   //标记顶点V已经加入树中
        for(Edge e : g.adj(v))  //遍历新加入顶点相连的所有边
            if(!marked[e.other(v)]) pq.offer(e); //将加入顶点后新增加的横切边加入优先队列
        /**
        * 可在此进行优化,只加入有效的横切边,变成及时Prim算法
        */
    }
    //最小生成树所有边
    public Iterable<Edge> edges(){ return mst; }
    //最小生成树的权重
    public double weight(){ return weight; }
}

4.Prim算法及时版本(最小生成树)

/**
 *  Prim算法及时版本(最小生成树)
 *  概述: 只需对延时版本的Prim算法稍作更改即可,只将有效的横切边加入优先队列
 *        此处使用索引优先队列,索引为顶点,按权重(到达顶点的边的权重)构造优先队列
 *        生成的索引优先队列可获取最小权重的顶点,还可以取出指定顶点的权重
 *        索引优先队列api在以前博客记录
 */
public class PrimMST {
    private boolean[] marked;   //标记是否已加入最小生成树
    private Edge[] edgeTo;      //索引为顶点,值为到达该顶点的边,最后保存了最小生成树的边
    private IndexMaxPQ<Double> pq; //索引优先队列(见堆排序)维护有效的横切边

    public PrimMST(EdgeWeightedGraph g) {
        marked = new boolean[g.V()];
        edgeTo = new Edge[g.V()];
        pq = new IndexMaxPQ<>(g.V(),(x,y) -> -x.compareTo(y));
        pq.insert(0,0.0);   //起始顶点
        while(!pq.isEmpty())
            visit(g,pq.delMax());
    }
    private void visit(EdgeWeightedGraph g,int v){
        marked[v] = true;
        for(Edge e : g.adj(v)){
            int w = e.other(v);
            if(marked[w]) continue;     //如果另一个顶点已经加入最小生成树,则跳过
            if(!pq.contains(w)) pq.insert(w,e.weight()); //与新顶点构成的横切边,直接加入
            else if(pq.peek(w) < e.weight()) continue;   //无效横切边跳过:以前记录的到达w顶点的横切边权重更小
            else pq.change(w,e.weight());   //新的横切边权重更小,更新权重
            edgeTo[w] = e;  //执行到此为有效横切边,加入或者替换
        }
    }
    //最小生成树的边
    public Iterable<Edge> edges(){ return Arrays.stream(edgeTo).skip(1).collect(Collectors.toList()); }
    //最小生成树的权重
    public double weight(){ return Arrays.stream(edgeTo).skip(1).mapToDouble(x -> x.weight()).sum(); }
}

5.Kruskal算法(最小生成树)

/**
 * Kruskal算法
 * 概述: 将所有边按照边的权重加入优先队列,每次取出一个最小的边,该边不会与已经加入的边构成环,
 *       则加入最小生成树,直到含有v-1条边为止
 * 实现: 使用union-find数据结构维护顶点的连通性,可以判断两个顶点是否连通,
 *       如果在加入一条边时,此边的两个顶点已经连通, 则会构成环,此时为无效边
 */
public class KruskalMST {
    private Queue<Edge> mst;    //维护最小生成树的边
    private double weight;      //维护权重

    public KruskalMST(EdgeWeightedGraph g) {
        mst = new ArrayDeque<>();
        PriorityQueue<Edge> minPQ = new PriorityQueue<>();
        g.edges().forEach(minPQ::offer);    //将所有边按照边的权重加入优先队列
        WeightedQuickUnion un = new WeightedQuickUnion(g.V());//union-find(见union-find算法)
        while(!minPQ.isEmpty() && mst.size() < g.V()-1){    //v-1条边为止
            Edge e = minPQ.poll();  //取出最小边
            int v = e.either(),w = e.other(v);
            if(un.connected(v,w)) continue; //跳过无效边: v与w已经连通,此时再加入该边会构成环
            un.union(v,w);  //加入新的边,更新连通性
            mst.offer(e);   //加入新边
            weight += e.weight(); //更新总权重
        }
    }

    //最小生成树的所有边
    public Iterable<Edge> edges(){ return mst; }
    //最小生成树的权重
    public double weight(){ return weight; }
}

文章作者: Bryson
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Bryson !
评论
 上一篇
下一篇 
  目录