增長一個邊的類,鄰接表和鄰接矩陣中保存的都是類的對象java
public class Edge<Weight extends Number & Comparable> implements Comparable<Edge>{ private int a, b;//邊的兩個頂點 private Weight weight; public Edge(int a, int b, Weight weight) { this.a = a; this.b = b; this.weight = weight; } public Edge(Edge<Weight> e) { this.a = e.a; this.b = e.b; this.weight = e.weight; } public int v() { return a; } public int w() { return b; } public Weight wt() { return weight; } public int other(int x) { if (x != a && x != b) { return -1; } return x == a ? b : a; } @Override public int compareTo(Edge that) { return 0; } @Override public String toString() { return "" + a + "-" + ": " + weight; } }
加權圖的接口算法
public interface WeightedGraph<Weight extends Number & Comparable> { public int getNodesCount(); public int getEdgesCount(); public void addEdge(Edge<Weight> e); public boolean hasEdge(int v, int w); public void show(); public Iterable<Edge<Weight>> adj(int v); }
import java.util.List; import java.util.ArrayList; public class SparseWeightedGraph<Weight extends Number & Comparable> implements WeightedGraph { private int n; private int m; private boolean directed; private List<Edge<Weight>>[] g; public SparseWeightedGraph(int n, boolean directed) { if (n < 0) { return; } this.n = n; this.directed = directed; this.g = (ArrayList<Edge<Weight>>)new ArrayList[n]; for (int i = 0; i < n; i++) { g[i] = new ArrayList<Edge<Weight>>(); } } @Override public int getNodesCount() { return n; } @Override public int getEdgesCount() { return m; } @Override public boolean hasEdge(int v, int w) { if (!isLegal(v) || !isLegal(w)) { return false; } for (Edge e : g.adj(v)) { if (e.other(v) == w) { return true; } } return false; } @Override public void addEdge(Edge e) { if (e == null) { return; } int v = e.v(); int w = e.w(); if (!isLegal(v) || !isLegal(w)) { return; } g[v].add(new Edge(e)); if (v != w && !directed) { g[w].add(new Edge(w, v, e.wt())); } m++; } @Override public Iterable<Edge<Weight>> adj(int v) { List<Edge<Weight>> res = new ArrayList<>(); if (!isLegal(v)) { return res; } return g[v]; } @Override public void show() { for( int i = 0 ; i < n ; i ++ ){ System.out.print("vertex " + i + ":\t"); for( int j = 0 ; j < g[i].size() ; j ++ ){ Edge e = g[i].get(j); System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t"); } System.out.println(); } } private boolean isLegal(int i) { return i >= 0 && i < n; } }
在此處任性地增長一段知識點數組
| Summary of Queue methods |ide
Method | Throws exception | Returns special value |
---|---|---|
Insert | add(e) | offer(e) |
Remove | remove() | poll() |
Examine | element() | peek() |
針對帶權無向圖 針對連通圖優化
找v-1條邊,鏈接v個節點,使總權值最小ui
把圖中的節點分爲兩部分,成爲一個切分(Cut).
若是一個邊的兩個端點,屬於切分(Cut)不一樣的兩邊,這個邊稱爲橫切邊。
this
切分定理:
給定任意切分,橫切邊中權值最小的邊必然屬於最小生成樹。
spa
Lazy prim的時間複雜度O(ElogE),E爲邊數
須要維護一個最小堆設計
import java.lang.reflect.Array; public class MinHeap<T extends Comparable> { private T[] data; private int count; private int capacity; private Class<T> type; public MinHeap(int capacity, Class<T> type) { this.capacity = capacity; this.count = 0; this.type = type; data = (T[]) Array.newInstance(type, capacity + 1); } public int size() { return count; } public boolean isEmpty() { return count == 0; } public void insert(T item) { if (count + 1 >= capacity && capacity * 2 + 1 < Integer.MAX_VALUE) { this.capapcity *= 2; T[] newData = (T[]) Array.newInstance(this.type, this.capacity + 1); System.arraycopy(data, 0, newData, 0, count + 1); data = newData; } data[++count] = item; shiftUp(count); } private void shiftUp(int v) { while (v > 1 && data[v].compareTo(data[v / 2]) < 0) { swap(data, v, v / 2); v /= 2; } } public T extractMin() { if (count <= 0) { return null; } T res = data[1]; swap(data, 1, count--); shiftDown(1); return res; } private void shiftDown(int k) { while (k * 2 <= count) { int j = k * 2; if (j + 1 < count && data[j + 1].compareTo(data[j]) < 0) { j++; } if (data[k].compareTo(data[j]) <= 0) { break; } swap(data, k, j); k = j; } } private void swap(T[] data, int i, int j) { T t = data[i]; data[i] = data[j]; data[j] = t; } }
lazy prim實現code
public class LazyPrimMST<Weight extends Number & Comparable> { private MinHeap<Edge<Weight>> minHeap; private List<Edge<Weight>> mst; private Number mstWeight; private Graph g; private boolean[] marked; public LazyPrimMST(Graph g) { this.g = g; int n = g.getNodesCount(); int m = g.getEdgesCount(); minHeap = new MinHeap<>(Edge.class, m); marked = new boolean[n]; visit(0); while (!minHeap.isEmpty()) { Edge<Weight> e = minHeap.extractMin(); //這條邊已經不是橫切邊了 if (marked[e.v()] == markded[e.w()]) { continue; } //否則的話,這條橫切邊應該在最小生成樹中,將其加入list mst.add(e); if (marked[e.v()]) { visit(e.w()); } else { visit(e.v()); } } mstWeight = mst.get(0).wt(); for (int i = 1; i < mst.size(); i++) { mstWeight += mst.get(i).wt(); } } //處於還未處於的結點,發現橫切邊,將橫切邊存入最小堆中 private void visit(int k) { if (marked[k]) { return; } marked[k] = true; for (Edge<Weight> e : g.adj(v)) { //若是這是一條橫切邊 if (!marked[e.other(v)]) { minHeap.insert(e); } } } }
須要採用並查集的算法,用來判斷是否存在環。
並查集的歸併過程,是將元素放入一個集合中,集合用一個數組id表示
先回顧quickUnion版本
public class QuickUnion { private int[] id;//元素分組 private int count;//有count個元素 public QuickUnion(int count) { this.count = count; this.id = new int[count]; //初始化時,每一個元素一個分組 for (int i = 0; i < count; i++) { id[i] = i; } } public void union(int p, int q) { int pRoot = find(p); int qRoot = find(q); if (qRoot == pRoot) { return; } //任意使一個結點指向另外一個的父結點便可 id[p] = qRoot; } //每一個元素的分組是按根節點分組 public int find(int p) { if (p < 0 || p >= count) { return -1; } //根結點本身指向本身 while (p != id[p]) { p = id[p]; } return p; } public boolean isConnected(int p, int q) { return find(p) == find(q); } }
優化的方向是使樹的高度儘量合理地低,每一步可使結點數量少的移到數量多的根上,下降樹的高度。可是可能出現的問題是,結點數多不見得樹就高。所以,有基於rank的優化,其中rank[i]表示以i爲根的集合所表示的樹的層數
public class RankUF { private int[] parent;//用這樣一個數組來表示元素所屬的集合,表示元素指向的根結點 private int count; private int[] rank;//rank[i]表示以i爲根的集合所表示的樹的層數 public RankUF(int count) { this.count = count; this.parent = new int[count]; this.rank = new int[count]; for (int i = 0; i< count; i++) { parent[i] = i; rank[i] = i; } } public int find(int p) { if (p < 0 || p >= count) { return -1; } // 不斷去查詢本身的父親節點, 直到到達根節點 // 根節點的特色: parent[p] == p while (p != parent[p]) { p = parent[p]; } return p; } public boolean isConnected(int p, int q) { return find(p) == find(q); } /** * 關鍵在union的過程 * 根據兩個元素所在樹的元素個數不一樣判斷合併方向 * 將元素個數少的集合合併到元素個數多的集合上 */ public void union(int p, int q) { int pRoot = find(p); int qRoot = find(q); //已是一家子了,什麼都不用作 if (pRoot == qRoot) { return; } //若是其中一方小於另外一方,將小的移到大的一方便可,其他什麼都不用作,由於不會所以增長大的樹的高度! if (rank[pRoot] < rank[qRoot]) { parent[pRoot] = qRoot; } else if (rank[pRoot] > rank[qRoot]) { parent[qRoot] = pRoot; } else { //此時,選擇一個做爲根結點 parent[pRoot] = qRoot; //此時,須要維護rank的值 rank[qRoot] += 1; } } }
最後的優化爲路徑壓縮
此時有兩種方法,第一種是每隔一層,壓縮一層,修改find方法便可,以下
public int find(int p) { if (p < 0 || p >= count) { return -1; } while (p != parent[p]) { parent[p] = parent[parent[p]];//增長這一行便可 p = parent[p]; } return p; }
第二種方法是壓縮到底,這裏效率不見得最高,由於用到了遞歸,會有必定的損失,因此上述方法已經很好了。正則壓縮到底的優化
public int find(int p) { if (p < 0 || p >= count) { return -1; } if (p != parent[p]) { parent[p] = find(parent[p]); } return parent[p]; }
每次找最短邊,只要不構成環,那麼這條邊就是最小生成樹中的邊
import java.util.List; import java.util.ArrayList; public class KruskalMST<Weight extends Number & Comparable> { private List<Edge<Weight>> mst; private Number mstWeight; public KruskalMST(WeightedGraph g) { int n = g.getNodesCount(); int m = g.getEdgesCount(); MinHeap<Edge> minHeap = new MinHeap<>(m, Edge.class); for (int i = 0; i < n; i++) { for (Object o : g.adj(i)) { Edge<Weight> e = (Edge<Weight>) o; //防止重複放入邊 if (e.v() <= e.w()) { minHeap.insert(e); } } } RankUF uf = new RankUF(n); while (!minHeap.isEmpty() && mst.size() < n - 1) { Edge<Weight> e = minHeap.extractMin(); if (uf.isConnected(e.v(), e.w())) { continue; } mst.add(e); uf.union(e.v(), e.w()); } mstWeight = mst.get(0).wt(); for (int i = 1; i < mst.size(); i++) { mstWeight = mstWeight.doubleValue() + mst.get(i).wt().doubleValue(); } } public List<Edge<Weight>> getMSTEdges() { return mst; } public Number getWeight() { return mstWeight; } }
總結:此時有泛型擦除問題
public interface Interface1<Weight extends Number & Comparable> { public Iterable<Edge<Weight>> adj(); }
public class Test { public Test(Interface1 t) { for (Edge<Weight> e : t.adj()) { //此時編譯會報錯,由於Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。 //此時在Test眼中,是看不到Interface1的返回值中的泛型的,只能看到Iterable,在編寫java代碼時,要時刻提醒本身,「這不是個泛型,這只是個Object」 } } }
有兩種解決方法
第一種,即代碼中,用Object接收,而後強制類型轉換
第二種,泛型在當前類中,所以編譯時能夠識別到
public class Test { public Test(Interface2 t) { for (Edge<Weight> e : t.adj()) { //此時不會報錯 } } } interface Interface2 { public Iterable<Edge<Weight>> adj(); }