加权无向图的实现最简单的方法是扩展无向图的表示方法:在邻接表的表示中,能够在链表的结点中增长一个权重域。但这里用另外一个方法来实现:咱们实现两个类,权重边类和无向图类。无向图类中组合权重边类来实现加权无向图。java
public class Edge implements Comparable<Edge> { //实现Comparable接口 private int v; private int w; private double weight; public Edge(int v,int w,double weight) { this.v = v; this.w = w; this.weight = weight;} //获取边的一个结点和另外一个节点 public int either() {return v;} public int other(int vertex) { if(vertex == v) return w; else if(vertex == w) return v; } //实现接口中的compareTo()方法 public int compareTo(Edge that) { if(this.weight() < that.weight()) return -1; else if(this.weight() > that.weight()) return 1; else return 0; } public double weight() {return weight;} public String toString() { return String.format("%d-%d %.2f", v,w,weight); } }
public class EdgeWeightedGraph { private int V;//顶点数 private int E;//边数 private Bag<Edge>[] adj;//一个边类型的背包 public EdgeWeightedGraph(int V){ this.V = V; this.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); //由于是无向图,互相添加边,调用的是背包的add()方法 adj[v].add(e); adj[w].add(e); E++; } //和v相关联的全部边 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; } }
图的生成树是它的一棵含有其全部顶点的无环连通子图,加权图的最小生成树(MST)是它的一棵权值最小的生成树。 Prim算法可以获得任意加权连通无向图的最小生成树。算法
图的一种切分是将图的全部顶点分为两个非空且不重合的两个集合。横切边是一条链接两个属于不一样集合的顶点的边。数组
切分定理:在一幅加权图中,给定任意的切分,它横切边中权重最小者必然属于图的最小生成树。数据结构
切分定理是解决最小生成树问题的全部算法的基础。this
使用一个最小优先权队列保存横切边集合,每次新加进来一个结点,就将和该结点关联的全部边添加进最小优先权队列;生成最小树时,从横切边集合中取出最小边,判断是否和目前的树产生环,若是产生环,则舍弃该边;不然将该边加入最小生成树队列。spa
延时实现比较简单,它会在优先权队列中保存已经失效的边。.net
public class LazyPrimMST { private boolean[] marked;//最小生成树的顶点 private Queue<Edge> mst;//最小生成树的边 private MinPQ<Edge> pq;//横切边(包括已经失效的边) public LazyPrimMST(EdgeWeightedGraph G) { pq = new MinPQ<Edge>(); marked = new boolean[G.V()]; mst = new Queue<Edge>(); visit(G,0);//从顶点0 开始 while(!pq.isEmpty()) {//构造最小生成树 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 Iterable<Edge> edges(){//返回最小生成树 return mst; } }
Prim算法的延时实现计算一个含V个顶点和E条边的连通加权无向图的最小生成树所需空间与E成正比,所需时间与ElogE成正比(最坏状况)。code
要改进LazyPrimMST,能够尝试从优先队列中删除用不到的边。关键在于,咱们关注的只是链接树顶点和非树顶点中权重最小的边。当咱们将顶点v加入树中,只可能使非树顶点w到最小生成树更近了。简而言之,咱们没必要保存全部从w到树顶点的边, 只需保存最小的那条便可。在v添加进树中时遍历v的邻接表检查是否须要更新权重最小的边。orm
引进两个顶点索引数组edgeTo[]和distTo[],它们有以下性质:blog
public class PrimMST { private Edge[] edgeTo;//距离树最近的边 private double[] distTo;//distTo[w] = edgeTo[w].weight() private boolean[] marked;//若是v在树中则为true private IndexMinPQ<Double> pq;//有效横切边 public PrimMST(EdgeWeightedGraph G) { edgeTo = new Edge[G.V()]; distTo = new double[G.V()]; marked = new boolean[G.V()]; for(int v = 0;v<G.V();v++) distTo[v] = Double.POSITIVE_INFINITY; pq = new IndexMinPQ<Double>(G.V()); distTo[0] = 0.0; pq.insert(0,0.0);//顶点0初始化pq 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;//v-w失效 if(e.weight()<distTo[w]) { edgeTo[w] = e;//最佳边Edge变为e distTo[w] = e.weight();//更新distTo[] if(pq.contains(w)) pq.change(w, distTo[w]); else pq.insert(w, distTo[w]); } } } }
Prim算法的即时实现计算一个含有V个顶点和E条边的连通加权无向图的最小生成树所需空间和V成正比,所需时间和ElogV成正比(最坏状况)。
Kruskal算法的计算一个含V个顶点和E条边的连通加权无向图的最小生成树所需空间与E成正比,所需时间与ElogE成正比(最坏状况)。
将边都添加进最小优先权队列中,每次从中取出最小的边,检查会不会与已经选出的边构成环(使用union-find算法),若是构成环,则弃掉这条边,不然将这条边加入最小生成树队列。循环执行直到最小优先权队列为空。
public class KruskalMST { private Queue<Edge> mst; //用来保存最小代价生成树的队列 public KruskalMST(EdgeWeightedGraph G) { mst = new Queue<Edge>(); MinPQ<Edge> pq = new MinPQ<Edge>(); //最小优先权队列 for(Edge e: G.edges()) pq.insert(e);//将全部边添加进优先队列 UF uf = new UF(G.V()); //union-find算法 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.qu_union(v,w);//合并份量 mst.enqueue(e);//将边添加进树中 } } public Iterable<Edge> edges(){ return mst; } }