本次要解决的问题是:你们村里那些坑坑洼洼的路,到底哪些路才是主干道?
小明:肯定是哪里都能到得了,并且去哪里都相对比较近,并且被大家共用程度高的路是啊!
具体是哪几条路呢?今天就可以给出准确答案
最小生成树的特点
1。可以到达图中任何一个顶点
2. 是一颗树(无环)
3. 最小生成树的边的权重之和是可以链接图中所有顶点的边的集合中,权值之和最小的(运用了贪婪算法思想)
4. 边数 = 图的顶点数量-1
先看主要代码,再看库代码
//Prim算法的 最小生成树 //时间 ElogE E为遍历原图中每条边,logE为优先队列(二叉堆)找到最小权重边的平均成本 //空间 V-1条Edge + V个顶点 public class LazyPrimMST implements MST { EdgeWeightedGraph orgEwg; //原始加权图 EdgeWeightedGraph singleEwg; //只有最小生成树的加权图 Listedges; //最小生成树的边 boolean[] marked; //顶点的访问 float weightSum; public LazyPrimMST(EdgeWeightedGraph ewg) { this.orgEwg = ewg; edges = new LinkedList<>(); marked = new boolean[ewg.v]; weightSum = 0; //只考虑一个连通图的情况 //改进:排除有多个子图的情况 //假设 ewg 是连通的 BinHeap2 pqedges = new BinHeap2 (); //最小生成树的性质:边数 = 图的顶点数量-1 visit2(pqedges, 0); while (!pqedges.isEmpty()) { Edge e = pqedges.pop(); int v = e.either(); int w = e.other(v); if (marked[v] && marked[w]) //已失效的横切边不处理 continue; edges.add(e); //将权重最小的加入到MST中 if (!marked[v]) visit2(pqedges, v); if (!marked[w]) visit2(pqedges, w); } //将找到的最小生成树转换为一个 EWG对象 singleEwg = new EdgeWeightedGraph(orgEwg.v()); for (Edge e : edges) { singleEwg.addEdge(e); } this.orgEwg = null; } private void visit2(BinHeap2 pqedges, int v) { marked[v] = true; //将访问顶点加入MST中 for (Edge e : orgEwg.adj(v)) if (!marked[e.other(v)]) pqedges.add(e); } //1.将关注顶点 周围的边加入 横切边集合 //2.找到横切边集合中权重最小的边 //3.将改边的对面顶点作为下一个关注顶点返回 private int visit(BinHeap2 pqedges, int v) { marked[v] = true; //将当前关注点加入最小生成树 for (Edge e : orgEwg.adj(v)) { //加入关注顶点的边到优先队列, 横切边集合 if (!marked[e.other(v)]) //只加入未失效的横切边 pqedges.add(e); } Edge tmpe = null; while (tmpe == null && !pqedges.isEmpty()) { tmpe = pqedges.pop(); if (marked[tmpe.either()] && marked[tmpe.other(tmpe.either())]) tmpe = null; //失效的横切边 } if (tmpe == null) //没有足够的边 return -1; edges.add(tmpe); //将最小权重的边加入到最小生成树 if (!marked[tmpe.either()]) //从最小权重的边里,找到未探索的对面顶点作为新的关注点 v = tmpe.either(); else v = tmpe.other(tmpe.either()); return v; } @Override public Iterable edges() { return edges; } @Override public float weight() { if (weightSum == 0) { for (Edge e : edges) { weightSum += e.weight(); } } return weightSum; } public EdgeWeightedGraph getSingleEWGraph() { //只保留最小生成树的加权有向图 return singleEwg; } public static void main(String[] args) { // 村口 二狗子家 // 0--------------1 // |\ /| // | \ 你家 / | // | -----2---- | // | | // +---------3----+ // 希望小学 EdgeWeightedGraph ewg = new EdgeWeightedGraph(4); ewg.addEdge(0, 1, 2,"二麻二麻路"); ewg.addEdge(0, 2, 3,"挨打巷西段"); ewg.addEdge(0, 3, 4,"挨打巷东段"); ewg.addEdge(1, 2, 3.5f,"恶犬巷"); ewg.addEdge(1, 3, 2.5f,"希望之路"); System.out.println(ewg); System.out.println("======="); LazyPrimMST lp = new LazyPrimMST(ewg); System.out.println("最小生成树权重总和(村里主干道总长度): " + lp.weight()); for (Edge e : lp.edges()) { System.out.println(e.either() + "和" + e.other(e.either()) + "之间的路["+e.name+"], 路长:" + e.weight()); } } }
输出
最小生成树权重总和(村里主干道总长度): 7.5 0和1之间的路[二麻二麻路], 路长:2.0 1和3之间的路[希望之路], 路长:2.5 0和2之间的路[挨打巷西段], 路长:3.0
即时版
主要使用了带索引的优先队列IndexMinPQ,支持替换操作(change)
并且对已失效的边用不加入优先队列,以及替换操作减少对失效横切边在优先队列中的排序操作(将时间复杂度从 ElogE 降为 ElogV,因为优先队列里同时只存有最多V-1个边)
代码:
//即时prim算法 可对加权无向图生成最小生成树 //时间 ElogV //空间 3V+(V-1) //允许平行边,自环(不起作用?),负权重 public class PrimMst implements Mst { EdgeWeightedGraph ewg; boolean[] marked; //记录已经被访问的,加入到最小生成树中的顶点;标记为true说明其edges 和 weights 被纳入最小生成树 float[] weights; //从最小生成树到顶点v的最小权重边的权重值 IndexMinPQimpq; //小根索引二叉堆,记录最多V-1个,从最小生成树到目标顶点的边的权重;index:目标顶点索引,val:最小权重边的权重 Edge[] edges; //属于最小生成树中的边的集合,有V-1条 float weightSum = Float.POSITIVE_INFINITY;//最小生成树的边的权重总和 public PrimMst(EdgeWeightedGraph ewg) { this.ewg = ewg; marked = new boolean[ewg.v()]; weights = new float[ewg.v()]; edges = new Edge[ewg.v()]; //最小生成树中最多有 v-1 条边,为了方便直接用顶点号做下标索引号直接申请v个元素(0号下标的边不会被更新) impq = new IndexMinPQ<>(ewg.v()); //每个顶点对应一条最小生成树到它的边 Arrays.fill(weights, Float.POSITIVE_INFINITY); visit(0, 0); //从0开始访问 while (!impq.isEmpty()) { //从优先队列中取出权重最小的边,以及从最小生成树通过该边所到达的顶点号 visit(impq.topIndex() - 1, impq.delTop()); } this.ewg = null; } //对顶点v进行访问,并给出从最小生成树到达该顶点的边的最小权重 private void visit(int v, float weight) { marked[v] = true; weights[v] = weight; for (Edge e : ewg.adj(v)) { int w = e.other(v); if (marked[w] || weights[w] < e.weight()) //已经在mst中,则跳过 或 未在mst中,但存在另一条到达该点的权重更小的边 也跳过(contain) continue; if (impq.contain(w + 1)) //优先队列中存在到达该点的边,将权重更新为更小的 impq.change(w + 1, e.weight()); else impq.insert(w + 1, e.weight()); //首次遇到该顶点,将到达该顶点的边的权重和顶点号加入优先队列 edges[w] = e; weights[w] = e.weight(); } } @Override public Iterable edges() { List list = new LinkedList<>(); for (int v = 1; v < edges.length; v++) { list.add(edges[v]); } return list; } @Override public float weight() { if (weightSum == Float.POSITIVE_INFINITY && edges.length > 0) { weightSum = 0; for (float f : weights) { weightSum += f; } } return weightSum; } public static void main(String[] args) { demo2(); } private static void demo2() { // 村口 二狗子家 // 0--------------1 // |\ /| // | \ 你家 / | // | -----2---- | // | | // +---------3----+ // 希望小学 EdgeWeightedGraph ewg = new EdgeWeightedGraph(4); ewg.addEdge(0, 1, 2, "二麻二麻路"); ewg.addEdge(0, 2, 3, "挨打巷西段"); ewg.addEdge(0, 3, 4, "挨打巷东段"); ewg.addEdge(1, 2, 3.5f, "恶犬巷"); ewg.addEdge(1, 3, 2.5f, "希望之路"); System.out.println(ewg); System.out.println("======="); PrimMst lp = new PrimMst(ewg); System.out.println("最小生成树权重总和(村里主干道总长度): " + lp.weight()); for (Edge e : lp.edges()) { System.out.println(e.either() + "和" + e.other(e.either()) + "之间的路[" + e.name + "], 路长:" + e.weight()); } System.out.println("\n======="); } }
输出:
======= 最小生成树权重总和(村里主干道总长度): 7.5 0和1之间的路[二麻二麻路], 路长:2.0 0和2之间的路[挨打巷西段], 路长:3.0 1和3之间的路[希望之路], 路长:2.5 =======
库代码:
//加权无向图 public class EdgeWeightedGraph { LinkedList[] edges; //边的集合 final int v; //顶点数量 int e; //边的数量 public EdgeWeightedGraph(int v) { this.v = v; this.e = 0; edges = new LinkedList[v]; for (int i = 0; i < v; i++) edges[i] = new LinkedList<>(); } public void addEdge(int v, int w, float weight) { addEdge(new Edge(v, w, weight)); } public void addEdge(Edge e) { //添加一条边,在无向图中等于向2边顶点添加边(互相连通) edges[e.v].add(e); edges[e.w].add(e); this.e++; } public Iterable adj(int v) { return edges[v]; } //返回所有边的集合 public Iterable edges() { List es = new LinkedList<>(); for (int v = 0; v < edges.length; v++) { for (Edge e : adj(v)) { if (e.other(v) > v) //加入顺序: 顶点序号逆序 es.add(e); } } return es; } public int v() {//顶点数 return v; } public int e() {//边数 return e; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (int v = 0; v < v(); v++) { sb.append(v); sb.append(": "); for (Edge e : adj(v)) { sb.append(e.toString()); sb.append(", "); } sb.append('\n'); } return sb.toString(); } public static void main(String[] args) { EdgeWeightedGraph ewg = new EdgeWeightedGraph(4); ewg.addEdge(0, 1, 1); ewg.addEdge(0, 2, 2); ewg.addEdge(0, 3, 3); ewg.addEdge(1, 2, 3); System.out.println(ewg); } }
边对象
public class Edge extends Vertex implements Comparable{ public int w; public float weight; public String name; //from - to public Edge(int v, int w, float weight) { super(v); this.w = w; this.weight = weight; } public Edge(int v, int w, float weight, String name) { super(v); this.w = w; this.weight = weight; this.name = name; } public float weight() { return weight; } public int either() { return v; } public int other(int vertex) { if (vertex == this.v) return w; else if (vertex == this.w) return v; throw new RuntimeException("no such vertex " + vertex + " ,[v:" + v + " w:" + w + "]"); } @Override public int compareTo(@NonNull Edge another) { if (weight > another.weight) return 1; else if (weight < another.weight) return -1; return 0; } @Override public String toString() { return String.format("%d-%d %.2f", v, w, weight); } }
链接: 索引优先队列
IndexMinPQ