有向边

  1. public class Edge<E extends Number & Comparable> implements Comparable<Edge>{
  2. //定义改变的起始节点和终止节点
  3. private int from;
  4. private int to;
  5. //该边的权值
  6. private E weight;
  7. public Edge(int v, int w, E weight)
  8. {
  9. this.from = v;
  10. this.to = w;
  11. this.weight = weight;
  12. }
  13. public Edge(Edge<E> e)
  14. {
  15. this.from = e.from;
  16. this.to = e.to;
  17. this.weight = e.weight;
  18. }
  19. //获取起点
  20. public int v(){
  21. return from;
  22. }
  23. //获取终点
  24. public int w(){
  25. return to;
  26. }
  27. //获取权值
  28. public E wt(){
  29. return weight;
  30. }
  31. // 给定一个顶点, 返回另一个顶点
  32. public int other(int x){
  33. assert x == from || x == to;
  34. return x == from ? to : from;
  35. }
  36. //输出边的具体信息
  37. @Override
  38. public String toString() {
  39. return "" + from + "-" + to + ": " + weight;
  40. }
  41. @Override
  42. public int compareTo(Edge that) {
  43. int num=weight.compareTo(that.wt());
  44. return num;
  45. }
  46. }

带权图

  1. // 带权图的接口
  2. public interface WeightedGraph<E extends Number & Comparable> {
  3. public int V();
  4. public int E();
  5. public void addEdge(Edge<E> edge);
  6. boolean hasEdge(int v, int w);
  7. void show();
  8. public Iterable<Edge<E>> adj(int v);
  9. }

稠密图(带权图)

  1. public class DenseWeightedGraph<E extends Number & Comparable> implements WeightedGraph<E>{
  2. private int n; // 节点数
  3. private int m; // 边数
  4. private boolean directed; // 是否为有向图
  5. private Edge[][] g; // 图的具体数据
  6. // 构造函数
  7. public DenseWeightedGraph(int n , boolean directed ){
  8. assert n >= 0;
  9. this.n = n;
  10. this.m = 0; // 初始化没有任何边
  11. //false表示无向图
  12. this.directed = directed;
  13. // g初始化为n*n的有向边矩阵
  14. g = new Edge[n][n];
  15. }
  16. @Override
  17. public int V(){ return n;} // 返回节点个数
  18. @Override
  19. public int E(){ return m;} // 返回边的个数
  20. // 向图中添加一个边
  21. @Override
  22. public void addEdge(Edge edge){
  23. assert edge.v() >= 0 && edge.v() < n ;
  24. assert edge.w() >= 0 && edge.w() < n ;
  25. if( hasEdge( edge.v() , edge.w() ) ){
  26. return;
  27. }
  28. g[edge.v()][edge.w()] = new Edge(edge);
  29. if( edge.v() != edge.w() && !directed ){
  30. g[edge.w()][edge.v()] = new Edge(edge.w(), edge.v(), edge.wt());
  31. }
  32. m ++;
  33. }
  34. // 验证图中是否有从v到w的边
  35. @Override
  36. public boolean hasEdge( int v , int w ){
  37. assert v >= 0 && v < n ;
  38. assert w >= 0 && w < n ;
  39. return g[v][w]!=null;
  40. }
  41. @Override
  42. public void show() {
  43. for(int i=0;i<n;i++){
  44. for(int j=0;j<n;j++){
  45. if(g[i][j]!=null){
  46. System.out.print(g[i][j].wt()+"\t");
  47. }else{
  48. System.out.print("NULL\t");
  49. }
  50. }
  51. System.out.println();
  52. }
  53. }
  54. // 返回图中一个顶点的所有邻边
  55. // 由于java使用引用机制,返回一个Vector不会带来额外开销
  56. @Override
  57. public Iterable<Edge<E>> adj(int v) {
  58. assert v >= 0 && v < n;
  59. Vector<Edge<E>> adjV = new Vector<>();
  60. for (int i = 0; i < n; i++) {
  61. if (g[v][i]!=null) {
  62. adjV.add(g[v][i]);
  63. }
  64. }
  65. return adjV;
  66. }
  67. }

稀疏图(带权图)

  1. public class SparseWeightedGraph<E extends Number & Comparable> implements WeightedGraph<E>{
  2. private int n; // 节点数
  3. private int m; // 边数
  4. private boolean directed; // 是否为有向图
  5. private Vector<Edge<E>>[] g; // 图的具体数据
  6. // 构造函数
  7. public SparseWeightedGraph(int n , boolean directed ){
  8. assert n >= 0;
  9. this.n = n;
  10. this.m = 0; // 初始化没有任何边
  11. this.directed = directed;
  12. // g初始化为n个空的vector, 表示每一个g[i]都为空, 即没有任和边
  13. g = (Vector<Edge<E>>[])new Vector[n];
  14. for(int i = 0 ; i < n ; i ++){
  15. g[i] = new Vector<Edge<E>>();
  16. }
  17. }
  18. @Override
  19. public int V(){ return n;} // 返回节点个数
  20. @Override
  21. public int E(){ return m;} // 返回边的个数
  22. // 向图中添加一个边
  23. @Override
  24. public void addEdge(Edge edge){
  25. assert edge.v() >= 0 && edge.v() < n ;
  26. assert edge.w() >= 0 && edge.w() < n ;
  27. g[edge.v()].add(edge);
  28. if( edge.v() != edge.w() && !directed ){
  29. g[edge.w()].add(new Edge(edge.w(),edge.v(),edge.wt()));
  30. }
  31. m ++;
  32. }
  33. // 验证图中是否有从v到w的边 -->时间复杂度:O(n)
  34. @Override
  35. public boolean hasEdge(int v, int w){
  36. assert v >= 0 && v < n ;
  37. assert w >= 0 && w < n ;
  38. for( int i = 0 ; i < g[v].size() ; i ++ ){
  39. if( g[v].elementAt(i).other(v) == w ){
  40. return true;
  41. }
  42. }
  43. return false;
  44. }
  45. @Override
  46. public void show() {
  47. for( int i = 0 ; i < n ; i ++ ){
  48. System.out.print("vertex " + i + ":\t");
  49. for( int j = 0 ; j < g[i].size() ; j ++ ){
  50. Edge e = g[i].elementAt(j);
  51. System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
  52. }
  53. System.out.println();
  54. }
  55. }
  56. // 返回图中一个顶点的所有邻边
  57. // 由于java使用引用机制,返回一个Vector不会带来额外开销
  58. @Override
  59. public Iterable<Edge<E>> adj(int v) {
  60. assert v >= 0 && v < n;
  61. return g[v];
  62. }
  63. }

最小生成树

最小生成树问题针对的是带权无向图,并且该图是一个连通图

切分定理

1.切分

把图中的的顶点分成两部分,成为一个切分

image.png

其中图中顶点被分为蓝色部分和红色部分,这就是一个切分。

2.横切边

如果一个边的两个端点,分别属于不同的切分,则这个边就被成为横切边

image.png

其中使用绿色标出了横切边。

3.切分定理

给定任意切分,横切边中权值最小边必然属于最小生成树

image.png

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

image.png

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

Lazy Prim

  1. public class LazyPrimMST<E extends Number & Comparable> {
  2. private WeightedGraph<E> G; // 图的引用
  3. private PriorityQueue<Edge<E>> pq; // 最小堆, -->Java默认使用小根堆
  4. private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问
  5. private Vector<Edge<E>> mst; // 最小生成树所包含的所有边
  6. private Number mstWeight; // 最小生成树的权值
  7. public LazyPrimMST(WeightedGraph graph){
  8. this.G=graph;
  9. pq=new PriorityQueue<>();
  10. marked=new boolean[graph.V()];
  11. mst=new Vector<>();
  12. //从0节点开始访问
  13. visit(0);
  14. while(!pq.isEmpty()){
  15. //获取最小边
  16. Edge<E> e=pq.poll();
  17. //看看这条最小边是否是横切边
  18. if(marked[e.v()]==marked[e.w()]){
  19. continue;
  20. }
  21. //是横切边,说明就是MST的边
  22. mst.add(e);
  23. // 访问和这条边连接的还没有被访问过的节点
  24. if(! marked[e.v()]){
  25. visit(e.v());
  26. }else{
  27. visit(e.w());
  28. }
  29. //计算最小生成树的权值
  30. mstWeight=mst.elementAt(0).wt();
  31. for(int i=1;i<mst.size();i++){
  32. mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
  33. }
  34. }
  35. }
  36. //v顶点是未访问过的
  37. private void visit(int v){
  38. assert !marked[v];
  39. marked[v]=true;
  40. //将与v节点相连的顶点边加入到堆中
  41. for(Edge e:G.adj(v)){
  42. if(!marked[e.other(v)]){
  43. pq.add(e);
  44. }
  45. }
  46. }
  47. // 返回最小生成树的所有边
  48. public Vector<Edge<E>> mstEdges(){
  49. return mst;
  50. }
  51. // 返回最小生成树的权值
  52. public Number result(){
  53. return mstWeight;
  54. }
  55. }

Prim 算法的优化

  1. /**
  2. * 优化的Prim算法求图的最小生成树
  3. */
  4. public class PrimMST<E extends Number & Comparable> {
  5. private WeightedGraph G; // 图的引用
  6. private IndexMinHeap<E> ipq; // 最小索引堆, 算法辅助数据结构
  7. private Edge<E>[] edgeTo; // 访问的点所对应的边, 算法辅助数据结构
  8. private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问
  9. private Vector<Edge<E>> mst; // 最小生成树所包含的所有边
  10. private Number mstWeight; // 最小生成树的权值
  11. public PrimMST(WeightedGraph graph){
  12. G = graph;
  13. assert( graph.E() >= 1 );
  14. ipq = new IndexMinHeap<E>(graph.V());
  15. // 算法初始化
  16. marked = new boolean[G.V()];
  17. edgeTo = new Edge[G.V()];
  18. mst=new Vector<>();
  19. //Lazy Prim算法的改进
  20. visit(0);
  21. while (!ipq.isEmpty()){
  22. // 使用最小索引堆找出已经访问的边中权值最小的边
  23. // 最小索引堆中存储的是点的索引, 通过点的索引找到相对应的边
  24. int v=ipq.extractMinIndex();
  25. assert( edgeTo[v] != null );
  26. mst.add(edgeTo[v]);
  27. visit(v);
  28. }
  29. //计算最小生成树的权值
  30. mstWeight=mst.elementAt(0).wt();
  31. for(int i=1;i<mst.size();i++){
  32. mstWeight=mstWeight.doubleValue()+mst.elementAt(i).wt().doubleValue();
  33. }
  34. }
  35. private void visit(int v){
  36. assert !marked[v];
  37. marked[v] = true;
  38. // 将和节点v相连接的未访问的另一端点, 和与之相连接的边, 放入最小堆中
  39. for(Object edge:G.adj(v)){
  40. Edge<E> e=(Edge<E>)edge;
  41. int w=e.other(v);
  42. // 如果边的另一端点未被访问
  43. if(!marked[w]){
  44. if(edgeTo[w]==null){
  45. //如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆
  46. //edgeTo[w] 表示访问w定点所对应的边<v,w>
  47. edgeTo[w]=e;
  48. ipq.insert(w,e.wt());
  49. }else if(e.wt().compareTo(edgeTo[w].wt())<0){
  50. //如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换
  51. edgeTo[w]=e;
  52. ipq.change(w,e.wt());
  53. }
  54. }
  55. }
  56. }
  57. // 返回最小生成树的所有边
  58. Vector<Edge<E>> mstEdges(){
  59. return mst;
  60. }
  61. // 返回最小生成树的权值
  62. Number result(){
  63. return mstWeight;
  64. }
  65. }

Kruskal

  1. public class KruskalMST<E extends Number & Comparable> {
  2. private Vector<Edge<E>> mst; // 最小生成树所包含的所有边
  3. private Number mstWeight; // 最小生成树的权值
  4. public KruskalMST(WeightedGraph graph){
  5. mst=new Vector<>();
  6. // 将图中的所有边存放到一个最小堆中
  7. PriorityQueue<Edge<E>> pq=new PriorityQueue<>(graph.E());
  8. for(int i=0;i<graph.V();i++){
  9. for(Object item:graph.adj(i)){
  10. Edge<E> e=(Edge<E>)item;
  11. if(e.w() <e.v()){ //由于是无向图,只要加入一条边就好了
  12. pq.add(e);
  13. }
  14. }
  15. }
  16. //创建一个并查集, 来查看已经访问的节点的联通情况
  17. UnionFind uf = new UnionFind(graph.V());
  18. while (!pq.isEmpty() && mst.size()<graph.V()-1){
  19. // 从最小堆中依次从小到大取出所有的边
  20. Edge<E> e = pq.poll();
  21. //如果该边的两个端点是联通的, 说明加入这条边将产生环, 扔掉这条边
  22. if(uf.isConnected(e.w(),e.v())){
  23. continue;
  24. }
  25. // 否则, 将这条边添加进最小生成树, 同时标记边的两个端点联通
  26. mst.add(e);
  27. uf.unionElements(e.w(),e.v());
  28. }
  29. // 计算最小生成树的权值
  30. mstWeight = mst.elementAt(0).wt();
  31. for( int i = 1 ; i < mst.size() ; i ++ ){
  32. mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
  33. }
  34. }
  35. // 返回最小生成树的所有边
  36. Vector<Edge<E>> mstEdges(){
  37. return mst;
  38. }
  39. // 返回最小生成树的权值
  40. Number result(){
  41. return mstWeight;
  42. }
  43. }

时间复杂度

算法 时间复杂度
Lazy Prim O(ElogE)
Prim O(ElogV)
Kruskal O(ElogE)