有向边
public class Edge<E extends Number & Comparable> implements Comparable<Edge>{//定义改变的起始节点和终止节点private int from;private int to;//该边的权值private E weight;public Edge(int v, int w, E weight){this.from = v;this.to = w;this.weight = weight;}public Edge(Edge<E> e){this.from = e.from;this.to = e.to;this.weight = e.weight;}//获取起点public int v(){return from;}//获取终点public int w(){return to;}//获取权值public E wt(){return weight;}// 给定一个顶点, 返回另一个顶点public int other(int x){assert x == from || x == to;return x == from ? to : from;}//输出边的具体信息@Overridepublic String toString() {return "" + from + "-" + to + ": " + weight;}@Overridepublic int compareTo(Edge that) {int num=weight.compareTo(that.wt());return num;}}
带权图
// 带权图的接口public interface WeightedGraph<E extends Number & Comparable> {public int V();public int E();public void addEdge(Edge<E> edge);boolean hasEdge(int v, int w);void show();public Iterable<Edge<E>> adj(int v);}
稠密图(带权图)
public class DenseWeightedGraph<E extends Number & Comparable> implements WeightedGraph<E>{private int n; // 节点数private int m; // 边数private boolean directed; // 是否为有向图private Edge[][] g; // 图的具体数据// 构造函数public DenseWeightedGraph(int n , boolean directed ){assert n >= 0;this.n = n;this.m = 0; // 初始化没有任何边//false表示无向图this.directed = directed;// g初始化为n*n的有向边矩阵g = new Edge[n][n];}@Overridepublic int V(){ return n;} // 返回节点个数@Overridepublic int E(){ return m;} // 返回边的个数// 向图中添加一个边@Overridepublic void addEdge(Edge edge){assert edge.v() >= 0 && edge.v() < n ;assert edge.w() >= 0 && edge.w() < n ;if( hasEdge( edge.v() , edge.w() ) ){return;}g[edge.v()][edge.w()] = new Edge(edge);if( edge.v() != edge.w() && !directed ){g[edge.w()][edge.v()] = new Edge(edge.w(), edge.v(), edge.wt());}m ++;}// 验证图中是否有从v到w的边@Overridepublic boolean hasEdge( int v , int w ){assert v >= 0 && v < n ;assert w >= 0 && w < n ;return g[v][w]!=null;}@Overridepublic void show() {for(int i=0;i<n;i++){for(int j=0;j<n;j++){if(g[i][j]!=null){System.out.print(g[i][j].wt()+"\t");}else{System.out.print("NULL\t");}}System.out.println();}}// 返回图中一个顶点的所有邻边// 由于java使用引用机制,返回一个Vector不会带来额外开销@Overridepublic Iterable<Edge<E>> adj(int v) {assert v >= 0 && v < n;Vector<Edge<E>> adjV = new Vector<>();for (int i = 0; i < n; i++) {if (g[v][i]!=null) {adjV.add(g[v][i]);}}return adjV;}}
稀疏图(带权图)
public class SparseWeightedGraph<E extends Number & Comparable> implements WeightedGraph<E>{private int n; // 节点数private int m; // 边数private boolean directed; // 是否为有向图private Vector<Edge<E>>[] g; // 图的具体数据// 构造函数public SparseWeightedGraph(int n , boolean directed ){assert n >= 0;this.n = n;this.m = 0; // 初始化没有任何边this.directed = directed;// g初始化为n个空的vector, 表示每一个g[i]都为空, 即没有任和边g = (Vector<Edge<E>>[])new Vector[n];for(int i = 0 ; i < n ; i ++){g[i] = new Vector<Edge<E>>();}}@Overridepublic int V(){ return n;} // 返回节点个数@Overridepublic int E(){ return m;} // 返回边的个数// 向图中添加一个边@Overridepublic void addEdge(Edge edge){assert edge.v() >= 0 && edge.v() < n ;assert edge.w() >= 0 && edge.w() < n ;g[edge.v()].add(edge);if( edge.v() != edge.w() && !directed ){g[edge.w()].add(new Edge(edge.w(),edge.v(),edge.wt()));}m ++;}// 验证图中是否有从v到w的边 -->时间复杂度:O(n)@Overridepublic boolean hasEdge(int v, int w){assert v >= 0 && v < n ;assert w >= 0 && w < n ;for( int i = 0 ; i < g[v].size() ; i ++ ){if( g[v].elementAt(i).other(v) == w ){return true;}}return false;}@Overridepublic 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].elementAt(j);System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");}System.out.println();}}// 返回图中一个顶点的所有邻边// 由于java使用引用机制,返回一个Vector不会带来额外开销@Overridepublic Iterable<Edge<E>> adj(int v) {assert v >= 0 && v < n;return g[v];}}
最小生成树
最小生成树问题针对的是带权无向图,并且该图是一个连通图。
切分定理
1.切分
把图中的的顶点分成两部分,成为一个切分。

其中图中顶点被分为蓝色部分和红色部分,这就是一个切分。
2.横切边
如果一个边的两个端点,分别属于不同的切分,则这个边就被成为横切边

其中使用绿色标出了横切边。
3.切分定理
给定任意切分,横切边中权值最小边必然属于最小生成树

在这个切分中横切边中权值最小的是0.16,已使用红色标出。

在这个切分中横切边中权值最小的是0.4,已使用红色标出。
Lazy Prim
public class LazyPrimMST<E extends Number & Comparable> {private WeightedGraph<E> G; // 图的引用private PriorityQueue<Edge<E>> pq; // 最小堆, -->Java默认使用小根堆private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问private Vector<Edge<E>> mst; // 最小生成树所包含的所有边private Number mstWeight; // 最小生成树的权值public LazyPrimMST(WeightedGraph graph){this.G=graph;pq=new PriorityQueue<>();marked=new boolean[graph.V()];mst=new Vector<>();//从0节点开始访问visit(0);while(!pq.isEmpty()){//获取最小边Edge<E> e=pq.poll();//看看这条最小边是否是横切边if(marked[e.v()]==marked[e.w()]){continue;}//是横切边,说明就是MST的边mst.add(e);// 访问和这条边连接的还没有被访问过的节点if(! marked[e.v()]){visit(e.v());}else{visit(e.w());}//计算最小生成树的权值mstWeight=mst.elementAt(0).wt();for(int i=1;i<mst.size();i++){mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();}}}//v顶点是未访问过的private void visit(int v){assert !marked[v];marked[v]=true;//将与v节点相连的顶点边加入到堆中for(Edge e:G.adj(v)){if(!marked[e.other(v)]){pq.add(e);}}}// 返回最小生成树的所有边public Vector<Edge<E>> mstEdges(){return mst;}// 返回最小生成树的权值public Number result(){return mstWeight;}}
Prim 算法的优化
/*** 优化的Prim算法求图的最小生成树*/public class PrimMST<E extends Number & Comparable> {private WeightedGraph G; // 图的引用private IndexMinHeap<E> ipq; // 最小索引堆, 算法辅助数据结构private Edge<E>[] edgeTo; // 访问的点所对应的边, 算法辅助数据结构private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问private Vector<Edge<E>> mst; // 最小生成树所包含的所有边private Number mstWeight; // 最小生成树的权值public PrimMST(WeightedGraph graph){G = graph;assert( graph.E() >= 1 );ipq = new IndexMinHeap<E>(graph.V());// 算法初始化marked = new boolean[G.V()];edgeTo = new Edge[G.V()];mst=new Vector<>();//Lazy Prim算法的改进visit(0);while (!ipq.isEmpty()){// 使用最小索引堆找出已经访问的边中权值最小的边// 最小索引堆中存储的是点的索引, 通过点的索引找到相对应的边int v=ipq.extractMinIndex();assert( edgeTo[v] != null );mst.add(edgeTo[v]);visit(v);}//计算最小生成树的权值mstWeight=mst.elementAt(0).wt();for(int i=1;i<mst.size();i++){mstWeight=mstWeight.doubleValue()+mst.elementAt(i).wt().doubleValue();}}private void visit(int v){assert !marked[v];marked[v] = true;// 将和节点v相连接的未访问的另一端点, 和与之相连接的边, 放入最小堆中for(Object edge:G.adj(v)){Edge<E> e=(Edge<E>)edge;int w=e.other(v);// 如果边的另一端点未被访问if(!marked[w]){if(edgeTo[w]==null){//如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆//edgeTo[w] 表示访问w定点所对应的边<v,w>edgeTo[w]=e;ipq.insert(w,e.wt());}else if(e.wt().compareTo(edgeTo[w].wt())<0){//如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换edgeTo[w]=e;ipq.change(w,e.wt());}}}}// 返回最小生成树的所有边Vector<Edge<E>> mstEdges(){return mst;}// 返回最小生成树的权值Number result(){return mstWeight;}}
Kruskal
public class KruskalMST<E extends Number & Comparable> {private Vector<Edge<E>> mst; // 最小生成树所包含的所有边private Number mstWeight; // 最小生成树的权值public KruskalMST(WeightedGraph graph){mst=new Vector<>();// 将图中的所有边存放到一个最小堆中PriorityQueue<Edge<E>> pq=new PriorityQueue<>(graph.E());for(int i=0;i<graph.V();i++){for(Object item:graph.adj(i)){Edge<E> e=(Edge<E>)item;if(e.w() <e.v()){ //由于是无向图,只要加入一条边就好了pq.add(e);}}}//创建一个并查集, 来查看已经访问的节点的联通情况UnionFind uf = new UnionFind(graph.V());while (!pq.isEmpty() && mst.size()<graph.V()-1){// 从最小堆中依次从小到大取出所有的边Edge<E> e = pq.poll();//如果该边的两个端点是联通的, 说明加入这条边将产生环, 扔掉这条边if(uf.isConnected(e.w(),e.v())){continue;}// 否则, 将这条边添加进最小生成树, 同时标记边的两个端点联通mst.add(e);uf.unionElements(e.w(),e.v());}// 计算最小生成树的权值mstWeight = mst.elementAt(0).wt();for( int i = 1 ; i < mst.size() ; i ++ ){mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();}}// 返回最小生成树的所有边Vector<Edge<E>> mstEdges(){return mst;}// 返回最小生成树的权值Number result(){return mstWeight;}}
时间复杂度
| 算法 | 时间复杂度 |
|---|---|
| Lazy Prim | O(ElogE) |
| Prim | O(ElogV) |
| Kruskal | O(ElogE) |
