算法_最小生成树
一.概述
? ? ? 加權(quán)無向圖是一種在無向圖的基礎(chǔ)上,為每條邊關(guān)聯(lián)一個(gè)權(quán)值或是成本的圖模型.應(yīng)用可以有很多:例如在一幅航空?qǐng)D中,邊表示導(dǎo)線,權(quán)值則表示導(dǎo)線的長(zhǎng)度或是成本等.
圖的生成樹是它的一顆含有其所有頂點(diǎn)的無環(huán)連通子圖,一幅加權(quán)圖的最小生成樹(MST)是它的一顆權(quán)值(樹中的所有邊的權(quán)值之和)最小的生成樹.下圖為一幅加權(quán)無向圖和它的最小生成樹.(箭頭不指示方向,標(biāo)紅的為最小生成樹).
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
二.原理
1.圖的一種切分是將圖的所有頂點(diǎn)分為兩個(gè)非空且不重疊的兩個(gè)集合.橫切邊是連接兩個(gè)屬于不同集合的頂點(diǎn)的邊.
2.切分定理:在一幅加權(quán)圖中,給定任意的切分,它的橫切邊中的權(quán)重最小者必然屬于最小生成樹.(假設(shè)命題不成立,假設(shè)權(quán)重最小的邊為e,將它加入最小生成樹必然就會(huì)成環(huán),那么這個(gè)環(huán)應(yīng)該還有另一條橫切邊,而該橫切邊f(xié)必然權(quán)重>e,那么就可以刪除f,得到e,來獲取權(quán)重更小的生成樹.就和最小生成樹矛盾,因此命題成立).
三.貪心算法
? ?初始狀態(tài)下,一個(gè)含有V個(gè)頂點(diǎn)的無向圖所有的邊都不為黑色,找到一種切分,它所產(chǎn)生的橫切邊均不為黑色,橫切邊中權(quán)值最小的邊標(biāo)記為黑色(放入最小生成樹),如此往復(fù),直到標(biāo)記了V-1條邊.(根據(jù)切分定理很容易證明),圖的貪心算法的執(zhí)行流程如下,箭頭不指示方向,黑色加粗的邊為最小生成樹的邊.
四.加權(quán)無向圖的數(shù)據(jù)類型表示.
?1.帶權(quán)重的邊的數(shù)據(jù)類型:
邊的數(shù)據(jù)類型記錄了邊的兩點(diǎn),并且給出了獲取邊其中的某一點(diǎn),以及根據(jù)某點(diǎn)獲取另外一個(gè)點(diǎn)的方法.同時(shí)也用了一個(gè)weight的變量來記錄邊的權(quán)重.邊的數(shù)據(jù)結(jié)構(gòu)如下:
//帶權(quán)重的邊的數(shù)據(jù)類型 public class Edge implements Comparable<Edge>{private final int v; //頂點(diǎn)之一private final int w; //另一個(gè)頂點(diǎn)private final double weight; //權(quán)重public Edge(int v, int w, double weight) {super();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("Inconsisitent edge");}@Overridepublic 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);}}2.加權(quán)無向圖的數(shù)據(jù)類型:
這個(gè)數(shù)據(jù)類型和Graph的API基本相似,不同的是,這個(gè)API的基礎(chǔ)是Edge且添加了一個(gè)edges方法.加權(quán)無向圖的代碼如下:
public class EdgeWeightedGraph {private final int V; //頂點(diǎn)總數(shù)private int E; //邊的總數(shù)private Bag<Edge> [] adj; //鄰接表public EdgeWeightedGraph(int V) {this.V=V;this.E=0;adj=(Bag<Edge> [])new Bag[V];//和Queue不同,Bag保證無序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> list = new Bag<Edge>();for (int v = 0; v < V; v++) {int selfLoops = 0;for (Edge e : adj(v)) {if (e.other(v) > v) {list.add(e);}else if (e.other(v) == v) {if (selfLoops % 2 == 0) list.add(e);selfLoops++;}}}return list;} }五.Prim算法
? Prim算法為計(jì)算最小生成樹提供了實(shí)現(xiàn).它的每一步都為樹添加一條邊.每一次都將下一條連接樹中的頂點(diǎn)和不在樹中的頂點(diǎn)且權(quán)重最小的邊加入樹中.代碼如下所示:
//最小生成樹的延遲Prim算法 /*** 先對(duì)樹上的第0個(gè)點(diǎn)進(jìn)行標(biāo)記.* 然后遍歷第0個(gè)點(diǎn)的所有相鄰頂點(diǎn).* 如果沒有被標(biāo)記的話.那么就把對(duì)應(yīng)的邊添加進(jìn)去MinPQ* 在MinPQ中移除權(quán)重最小的(如果兩個(gè)頂點(diǎn)都被標(biāo)記的話,那么直接繼續(xù))* 沒有被標(biāo)記的話,直接visit,標(biāo)記,然后添加到MinPQ中* @author Administrator**/ public class LazyPrimMST {private boolean[] marked; //最小生成樹的頂點(diǎn)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); //假設(shè)G是連通的while(!pq.isEmpty()) {Edge e=pq.delMin();int v=e.either();int w=e.other(v);if(marked[v]&&marked[w]) continue; //忽略無效邊mst.enqueue(e); //將權(quán)重最小的邊加入最小生成樹if(!marked[v]) visit(G,v); //標(biāo)記將點(diǎn)加入到集合if(!marked[w]) visit(G,w);}}private void visit(EdgeWeightedGraph G, int v) {//標(biāo)記頂點(diǎn)v并將所有連接v和未被標(biāo)記的頂點(diǎn)的邊加入pqmarked[v]=true;for(Edge e:G.adj(v)) {if(!marked[e.other(v)]) pq.insert(e);}}public Iterable<Edge> edges() {return mst;} }之所以稱為延遲算法,這是因?yàn)镸inPQ中保存了大量的無效邊.
六.Prim算法的即時(shí)實(shí)現(xiàn)
? Prim算法的即時(shí)實(shí)現(xiàn)遵循下面的思路:只會(huì)在優(yōu)先隊(duì)列中保存每個(gè)非樹頂點(diǎn)w的一條邊.采用索引優(yōu)先隊(duì)列,將頂點(diǎn)和邊關(guān)聯(lián)起來.該邊為將它與樹中的頂點(diǎn)連接起來的權(quán)重最小的邊.
PrimMST類有兩個(gè)數(shù)組edgeTo,distTo,他們具有以下性質(zhì):
1.如果頂點(diǎn)v不在樹中但至少含有一條邊與樹相連接,那么edgeTo[v]是與樹相連接的權(quán)重最小的邊,distTo[v]是該邊的權(quán)重.
2.這類點(diǎn)都存放在索引優(yōu)先隊(duì)列中.
該算法的基本步驟是PrimMST會(huì)從優(yōu)先隊(duì)列中取出一個(gè)頂點(diǎn)v,并且檢查與它相連的點(diǎn)v-w,如果w被標(biāo)記過,則失效,如果w不在優(yōu)先隊(duì)列中或者v-w權(quán)重小于目前已知的最小值edgeTo[w],代碼會(huì)更新數(shù)組,將v-w作為樹的最佳選擇.代碼如下:
?
/*** 先將0加入最小索引隊(duì)列中.然后通過對(duì)于G.adj(v)進(jìn)行遍歷.* 獲得不同的邊,觀察頂點(diǎn)連接到樹的邊是否小于distTo保存的數(shù)值.* 如果小于的話,那么添加進(jìn)去.或者修改.* 不小于不變.一個(gè)個(gè)點(diǎn)進(jìn)行遍歷.不斷獲得各個(gè)點(diǎn)到樹的最小的距離.* 然后將距離最小的點(diǎn)加進(jìn)去* * @author Administrator**/ public class PrimMST {private Edge[] edgeTo; //距離樹最近的邊private double[] distTo; //distTo[w]=edgeTo[w].weight()private boolean[] marked; //如果v在樹中則為trueprivate 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);while(!pq.isEmpty()) {visit(G,pq.delMin());//每次都取出權(quán)重最小的邊 }}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; //v-w失效if(e.weight()<distTo[w]) {edgeTo[w]=e;distTo[w]=e.weight();if(pq.contains(w)) pq.changeKey(w, distTo[w]);else pq.insert(w, distTo[w]);}}}public Iterable<Edge> edges() {Queue<Edge> mst = new Queue<Edge>();for (int v = 0; v < edgeTo.length; v++) {Edge e = edgeTo[v];if (e != null) {mst.enqueue(e);}}return mst;}}七.Kruskal算法
? Kruskal算法的思想按照邊的權(quán)重順序從小到大處理它們,然后將邊按照從小到大的次序加入到最小生成樹中,加入的邊不會(huì)和已經(jīng)加入的邊構(gòu)成環(huán),直到樹中含有v-1條邊為止.原理在于如果加入的邊不會(huì)和已有的邊構(gòu)成環(huán),那么它就是一個(gè)跨越了所有和樹頂點(diǎn)相鄰的頂點(diǎn)組成的集合以及他們的補(bǔ)集所構(gòu)成的切分.而這個(gè)切分的權(quán)重又是最小的,根據(jù)切分定理,它一定是最小生成樹的一條邊.
代碼的實(shí)現(xiàn)如下所示,代碼中用了UF判斷是否構(gòu)成環(huán),并且將一個(gè)兩個(gè)點(diǎn)相連,使之共同構(gòu)成在同一個(gè)連通分量.
/*** 首先將所有的邊都加入到MinPQ中.* 然后根據(jù)權(quán)值從小到大依次加入.* 如果該邊已經(jīng)在最小生成樹里,就忽略.* 如果不在,那么就加入進(jìn)去(union方法).直到mst的size達(dá)到v-1* 原則:如果下一條被加入的邊不會(huì)和已有的形成環(huán).那么它* 就相當(dāng)于是一個(gè)切分,而它的權(quán)重又是最小的.因此可以加入進(jìn)去* @author Administrator**/ public class KruskalMST {private Queue<Edge> mst;public KruskalMST(EdgeWeightedGraph G) {mst=new Queue<Edge>();MinPQ<Edge> pq=new MinPQ<>();for(Edge e:G.edges()) {pq.insert(e);}UF uf=new UF(G.V());while(!pq.isEmpty()&&mst.size()<G.V()-1) {Edge e=pq.delMin();int v=e.either();int w=e.other(v);if(uf.connected(v, w)) continue; //處于同一個(gè)連通分量中. uf.union(v, w);mst.enqueue(e);}}public Iterable<Edge> edges() {return mst;}} public class UF {private int[] id; //分量idprivate int count; //分量數(shù)量public UF(int N) {count=N;id=new int[N];for(int i=0;i<N;i++) {id[i]=i;}}//連通分量的數(shù)量public int count() {return count;}//如果pq存在于同一個(gè)連通分量中,則返回truepublic boolean connected(int p,int q) {return find(p)==find(q);}//p所在的連通分量標(biāo)識(shí)符public int find(int p) {// TODO Auto-generated method stubreturn id[p];}//在p,q之間建立連接(QuickFind算法)public void union(int p,int q) {//將p和q歸并到相同的分量中int pID=find(p);int qID=find(q);//如果已經(jīng)在相同的分量中則不采取行動(dòng)if(pID==qID) return ;for(int i=0;i<id.length;i++) {if(id[i]==pID) id[i]=qID;}count--;} }?
轉(zhuǎn)載于:https://www.cnblogs.com/hlhdidi/p/5959597.html
總結(jié)
- 上一篇: 【leetcode❤python】Mov
- 下一篇: java 中 针对数组进行的工具类