zoukankan      html  css  js  c++  java
  • 加权无向图

    1. 最小生成树的定义

    生成树指的是含有所有顶点的无环连通子图。注意这其中的三个限定条件:

    1)包含了所有的顶点

    2)不存在环

    3)连通图

    如上图所示。就是一个生成树。

    而最小生成树指的是所有的边的权值加起来最小的生成树。最小生成树的重要应用领域太多,包括各种网络问题,例如电力分配网络,航空、铁路规划等问题。

    2. 加权无向图的数据类型

    //带权重的边的数据类型
    public class Edge implements Comparable<Edge>
    {
        int v;
        int w;
        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 == this.v) return this.w;
            else if(vertex == this.w) return this.v;
            else throw new RuntimeException("Inconsistent edge");
    
        }
    
        public int compareTo(Edge that)
        {
            if(this.weight < that.weight) return -1;
            else if(this.weight > that.weight) return 1;
            else return 0;
        }
    
        public String toString()
        {
            return String.format("%d - %d %.2f", v, w, weight);
        }
    
    }

    以上为带权重的边的构造函数。

    其中两个函数,either() 和 other() 目前比较难以理解,但之后会有比较大的用处。

    //加权无向图
    public class EdgeWeightedGraph
    {
        private int V;
        private int E;
        private Bag<Edge>[] adj; //注意这里的邻接矩阵不再是邻接的点了,而是邻接的边的集合
    
        public EdgeWeightedGraph(int V)
        {
            this.V = V;
            E = 0;
            adj = (Bag<Edge>[]) new Bag[V];
            for(int v=0;v<V; v++)
            { adj[v] = new Bag<Edge>();}
        }
    
        public int V()
        { return V;}
    
        public int E()
        { return E; }
    
        public void addEdge(Edge e)
        {
            int v = e.either();
            int w = e.other(v);
            adj[v].add(e);
            adj[w].add(e);
            E++;
        }
    
        public Iterable<Edge> adj(int v)
        { return adj[v]; }
    
        public Iterable<Edge> edges() //返回所有的边的集合
        {
            Bag<Edge> b = new Bag<Edge>();
            for(int v = 0;v < V;v++)
            {
                for(Edge e: adj[v])
                    if(e.other(v) > v) b.add(e);
            }
            return b;
        }
    }

    3. 寻找最小生成树

    首先要了解,假设所有的边的权重不相同的情况下,每幅连通图都只有唯一的一颗最小生成树。而且最小生成树有一个很重要的切分定理。

    切分定理:在一副加权图中,给定任意的切分,它的横切边中权重最小者必然属于最小生成树。也就是说假如把所有顶点任意的分成两堆,那么能够把两堆顶点中连起来的权重最小的边肯定属于最小生成树。

    从上图中可以看出,在两堆顶点中,能够把7和0连起来的边的权重(在这里可以看成是距离)是最小的,所以这条边肯定属于最小生成树。

    要把整颗生成树找出来,可以用贪心算法,一条边一条边找出来加到生成树中。

    4. prim算法

    首先需要厘清prim算法中用到的数据结构。

    1)树中顶点的集合marked[],假如marked[v] = true,则说明,v此时已经在树中。

    2)树中的边。一条队列mst来存储当前树中的所有边。

    3)横切边。一条优先队列MinPQ<Edge>来存储当前考虑的可能在最小生成树中的候选边。

    prim也是贪心算法的一种。具体过程如下:

    1)先从任意一个顶点开始,把该顶点的所有邻接边加入优先队列MinPQ中,并把该顶点加入最小生成树中的marked数组。

    2)在优先队列MinPQ中权重最小的那条边,把这条边加入最小生成树mst中,并把边的另一个顶点加入marked数组,并把新加入的顶点的邻接边也加入到优先队列MinPQ中。

    3)继续在优先队列MinPQ中找到权重最小的边,并把该条边加入mst中,并继续把边的另一个顶点计入marker数组,并把新加入的顶点的邻接边也加入到优先队列MinPQ中。

    4)...不断重复当前过程。

    所以prim算法的效果就相当于从某一点出发,不断在当前的树的基础上延伸新的边,最后形成一颗完整的树。

    public class LazyPrimMST
    {
        private boolean[] marked;
        private Queue<Edge> mst; //队列
        private MinPQ<Edge> pq; //优先队列
    
        public LazyPrimMST(EdgeWeightedGraph G)
        {
            marked = new boolean[G.V()];
            mst = new Queue<Edge>();
            pq = new MinPQ<Edge>();
    
            visit(G, 0);
            while(!pq.isEmpty()) //注意循环条件,等到最小生成树完全生成后,最后MinPQ中会一条一条被删掉,直到为空
            {
                Edge e = pq.delMin();
    
                int v = e.either(), w = e.other(v);
                if(marked[v] && marked[w]) continue; //跳过失效的边
                mst.enqueue(e);                      //将边添加到树中
                if(!marked[v]) visit(G, v);
                if(!marked[w]) visit(G, w);
            }
        }
    
        private void visit(EdgeWeightedGraph G, int v)
        {
            marked[v] = true;
            for(Edge e:G.adj[v])
                if(!marked[e.other(v)]) pq.insert(e);
        }
     }

    但是需要注意的是,以上的代码实现过程中,优先队列会先保留失效的边,到最后再一条一条删掉,这种方法又被称为延时实现。

    还有另一种即时实现的方法。这种方法的核心在于,我们只在优先队列中保存每个非树顶点的一条边,也就是离树顶点最近的那条边。其余的边都会失效,没有必要保留它们。

    public class PrimMST
    {
        private boolean[] marked;
        private Edge[] edgeTo;
        private double[] distTo;
        private IndexMinPQ<Double> pq;
    
        public PrimMST(EdgeWeightedGraph G)
        {
            marked = new boolean[G.V()];
            edgeTo = new Edge[G.V()];
            distTo = new Double[G.V()];
            pq = new IndexMinPQ<>(G.V()); //新建一个容量大小为索引优先队列
    
            for(int v= 0;v<G.V(); v++)
                distTo[v] = Double.POSITIVE_INFINITY;
    
            pq.insert(0, 0.0);
            distTo[0] = 0.0;
            while(!pq.isEmpty())
                visit(G, pq.delMin());
        }
    
        public 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(e.weight() < distTo[w])
                {
                    distTo[w] = e.weight();
                    edgeTo[w] = e;
                    if(pq.contains(w)) pq.change(w, distTo[w]);
                    else pq.insert(w, distTo[w]);
                }
                
            }
        }
    }

    5. kruskal算法

    于prim算法是在一颗小树的基础上,一条边一条边延展从而生成一颗大树不同,kruskal算法则是全面开花,最后连成一棵树。

    kruskal的算法思想也很简单:按照权重将边排序依次添加进树中,如果边会形成环,则弃之,否则,加入树中。

    public class kruskalMST
    {
        private Queue<Edge> mst;
    
        public kruskalMST(EdgeWeightedGraph G)
        {
            
            mst = new Queue<Edge>();
            MinPQ<Edge> pq = new MinPQ<Edge>();
            UF uf = new UF();
            
            for(Edge e:G.edges()) pq.insert(e);
            while(!pq.isEmpty() && mst.size() < G.V() - 1)
            {
                Edge e = pq.delMin();
                int v = e.either(), w = e.other(v);
                if(uf.connected(v, w)) continue; //如果形成了环,就跳过
                uf.union(v, w);                  //合并分量
                mst.enqueue(e);                  //添加到树中
            }
        }
        
        public Iterable<Edge> edges()
        { return mst;}
    }

    参考资料:《算法》第四版

  • 相关阅读:
    jinja模板语法
    flask处理cookie
    django模板语法
    django模型操作
    世间有一种坏
    单纯指望运动减肥的是几乎不可能的?
    主题
    slax自启动程序
    搞定TADA68机械键盘的固件修改
    ubuntu中编译安装gcc 9.2.0
  • 原文地址:https://www.cnblogs.com/corineru/p/10777772.html
Copyright © 2011-2022 走看看