1. 最小生成树

1.1 Prim算法

算法思路:

  1. 随机选取起点加入最小生成树集
  2. 遍历所有顶点,选择出未在最小生成树集中,且离最小生成树最近的顶点
  3. 将选择的顶点加入最小生成树集,并计算cost
  4. 更新剩下的顶点到最小生成树集的最近距离 ```java

public class PrimAlgorithm { public static void main(String[] args) { //测试看看图是否创建 ok char[] data = new char[]{‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’}; int verxs = data.length; //邻接矩阵的关系使用二维数组表示,10000 这个大数,表示两个点不联通 int[][] weight = new int[][]{ {10000, 5, 7, 10000, 10000, 10000, 2}, {5, 10000, 10000, 9, 10000, 10000, 3}, {7, 10000, 10000, 10000, 8, 10000, 10000}, {10000, 9, 10000, 10000, 10000, 4, 10000}, {10000, 10000, 8, 10000, 10000, 5, 4}, {10000, 10000, 10000, 4, 5, 10000, 6}, {2, 3, 10000, 10000, 4, 6, 10000},}; //创建 MGraph 对象 MGraph graph = new MGraph(verxs); //创建一个 MinTree 对象 MinTree minTree = new MinTree(); minTree.createGraph(graph,verxs,data,weight); //minTree.showGraph(graph); minTree.prim(graph,0); } }

//创建最小生成树 class MinTree {

  1. /**
  2. *
  3. * @param graph 图对象
  4. * @param verxs 图对应的顶点个数
  5. * @param data 图的各个顶点的值
  6. * @param weight 图的邻接矩阵
  7. */
  8. public void createGraph(MGraph graph,int verxs,char[] data,int[][] weight) {
  9. for(int i = 0;i < verxs;i++) {
  10. graph.data[i] = data[i];
  11. for(int j = 0;j < verxs;j++) {
  12. graph.weight[i][j] = weight[i][j];
  13. }
  14. }
  15. }
  16. //显示图的邻接矩阵
  17. public void showGraph(MGraph graph) {
  18. for (int[] link : graph.weight) {
  19. System.out.println(Arrays.toString(link));
  20. }
  21. }
  22. /**
  23. *
  24. * @param graph 图对象
  25. * @param v 从哪个节点开始
  26. */
  27. public void prim(MGraph graph,int v) {
  28. boolean[] visited = new boolean[graph.verxs];
  29. visited[v] = true;
  30. //记录两个顶点下标
  31. int h1 = -1;
  32. int h2 = -1;
  33. int minWeight = Integer.MAX_VALUE;//每次比较的最小权重
  34. for(int k = 1;k < graph.verxs;k++) {//因为有 graph.verxs 顶点,普利姆算法结束后,有 graph.verxs-1 边
  35. for(int i = 0;i < graph.verxs;i++) {
  36. if(!visited[i]) continue;
  37. for(int j = 0;j < graph.verxs;j++) {
  38. if(!visited[j] && graph.weight[i][j] < minWeight) {
  39. minWeight = graph.weight[i][j];
  40. h1 = i;
  41. h2 = j;
  42. }
  43. }
  44. }
  45. //找到一条边是最小
  46. System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight); //将当前这个结点标记为已经访问
  47. visited[h2] = true;
  48. minWeight = Integer.MAX_VALUE;
  49. }
  50. }
  51. public void prim1(MGraph graph,int v) {
  52. boolean[] visited = new boolean[graph.verxs];
  53. int[] dist = new int[graph.verxs];
  54. Arrays.fill(dist,Integer.MAX_VALUE);
  55. int cost = 0;
  56. List<Character> list = new ArrayList<>();
  57. for(int i = 0;i < graph.verxs;i++) {
  58. int t = -1;
  59. //找到离最小生成树最近的点
  60. for(int j = 0;j < graph.verxs;j++) {
  61. if(!visited[j] && (t == -1 || dist[j] < dist[t])) t = j;
  62. }
  63. if(i != 0) cost += dist[t];
  64. visited[t] = true;
  65. list.add(graph.data[t]);
  66. System.out.println("最小生成树集合:" + list);
  67. //更新各个点到最小生成树的最小距离
  68. for(int j = 0;j < graph.verxs;j++) {
  69. if(dist[j] > graph.weight[t][j]) dist[j] = graph.weight[t][j];
  70. }
  71. }
  72. System.out.println("代价:" + cost);
  73. }

} class MGraph { int verxs;//节点个数 char[] data;//存放节点的数据 int[][] weight;//存放边,邻接矩阵

  1. public MGraph(int verxs) {
  2. this.verxs = verxs;
  3. data = new char[verxs];
  4. weight = new int[verxs][verxs];
  5. }

}

  1. 时间复杂度:O(n*n)<br />空间复杂度:O(n*n)
  2. <a name="dD5Yv"></a>
  3. ## 1.2 Kruskal算法
  4. 算法思路:该算法的基本思想是从小到大加入边,是一个贪心算法。
  5. 1. 将所有的边从小到大排序
  6. 1. 从小到大选择边,判断边对应的两个顶点是否会构成环,如果构成环,则放弃,继续选择下一条边,否则将这两个顶点加入最小生成树集。
  7. 是否成环的判断:可以利用并查集,如果两个顶点在同一集合里面,则会构成环。
  8. 代码例子是LC 1584
  9. ```java
  10. class UnionSet {
  11. int[] fa;
  12. int[] size;
  13. UnionSet(int n) {
  14. fa = new int[n];
  15. size = new int[n];
  16. for(int i = 0;i < n;i++) {
  17. fa[i] = i;
  18. size[i] = 1;
  19. }
  20. }
  21. int find(int x) {
  22. if(fa[x] == x) return x;
  23. int root = find(fa[x]);
  24. fa[x] = root;
  25. return root;
  26. }
  27. void merge(int x,int y) {
  28. int ra = find(x);
  29. int rb = find(y);
  30. if(ra == rb) return;
  31. if(size[ra] < size[rb]) {
  32. fa[ra] = rb;
  33. size[rb] += size[ra];
  34. } else {
  35. fa[rb] = ra;
  36. size[ra] += size[rb];
  37. }
  38. }
  39. boolean isConnect(int x,int y) {
  40. return find(x) == find(y);
  41. }
  42. }
  43. public int minCostConnectPoints(int[][] points) {
  44. int n = points.length;
  45. List<int[]> edge = new ArrayList();
  46. for(int i = 0;i < n;i++) {
  47. for(int j = i + 1;j < n;j++) {
  48. int w = Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]);
  49. edge.add(new int[]{i,j,w});
  50. }
  51. }
  52. Collections.sort(edge,(o1,o2) -> {
  53. return o1[2] - o2[2];
  54. });
  55. int cost = 0;
  56. UnionSet u = new UnionSet(n);
  57. for(int i = 0;i < edge.size();i++) {
  58. int[] g = edge.get(i);
  59. int t = g[0];
  60. int v = g[1];
  61. int w = g[2];
  62. if(u.isConnect(t,v)) continue;
  63. cost += w;
  64. u.merge(t,v);
  65. }
  66. return cost;
  67. }

时间复杂度:O(m log(m) + m α(m) ), 排序带来了m log(m)的时间复杂度,并查集合并带来m α(m)的时间复杂度,m为索引对的数量,近似于n n。
空间复杂度:O(n
n)

1.3 小结

Prim算法,该算法以顶点为单元,与图中边数无关,比较适合于稠密图
Kruskal算法,该算法以边为单元,时间主要取决于边数,比较适合于稀疏图

2. 最短路径

2.1 Dijkstra算法

  1. public static int N = 100000;
  2. public static void main(String[] args) {
  3. int[][] matrix=new int[7][7];
  4. matrix[0]=new int[]{N, 5, 7, N, N, N, 2};
  5. matrix[1]=new int[]{5, N, N, 9, N, N, 3};
  6. matrix[2]=new int[]{7, N, N, N, 8, N, N};
  7. matrix[3]=new int[]{N, 9, N, N, N, 4, N};
  8. matrix[4]=new int[]{N, N, 8, N, N, 5, 4};
  9. matrix[5]=new int[]{N, N, N, 4, 5, N, 6};
  10. matrix[6]=new int[]{2, 3, N, N, 4, 6, N};
  11. int source = 0;
  12. dijstra(matrix, source);
  13. }
  14. public static void dijstra(int[][] matrix, int source) {
  15. //最短路径长度
  16. int[] shortest = new int[matrix.length];
  17. //判断该点的最短路径是否求出
  18. int[] visited = new int[matrix.length];
  19. //存储输出路径
  20. String[] path = new String[matrix.length];
  21. //初始化输出路径
  22. for (int i = 0; i < matrix.length; i++) {
  23. path[i] = new String(source + "->" + i);
  24. }
  25. Arrays.fill(shortest,N);
  26. //初始化源节点
  27. shortest[source] = 0;
  28. visited[source] = 1;
  29. for(int i = 0;i < matrix.length;i++) {
  30. if(i != source) {
  31. if(matrix[source][i] != N) {
  32. shortest[i] = matrix[source][i];
  33. }
  34. }
  35. }
  36. for (int i = 1; i < matrix.length; i++) {
  37. int min = N;
  38. int index = -1;
  39. for (int j = 0; j < matrix.length; j++) {
  40. //已经求出最短路径的节点不需要再加入计算并判断加入节点后是否存在更短路径
  41. if (visited[j] == 0 && shortest[j] < min) {
  42. min = shortest[j];
  43. index = j;
  44. }
  45. }
  46. //更新最短路径
  47. shortest[index] = min;
  48. visited[index] = 1;
  49. //更新从index跳到其它节点的较短路径
  50. for (int m = 0; m < matrix.length; m++) {
  51. if (visited[m] == 0 && shortest[index] + matrix[index][m] < shortest[m]) {
  52. shortest[m] = shortest[index] + matrix[index][m];
  53. path[m] = path[index] + "->" + m;
  54. }
  55. }
  56. }
  57. //打印最短路径
  58. for (int i = 0; i < matrix.length; i++) {
  59. if (i != source) {
  60. if (shortest[i] == N) {
  61. System.out.println(source + "到" + i + "不可达");
  62. } else {
  63. System.out.println(source + "到" + i + "的最短路径为:" + path[i] + ",最短距离是:" + shortest[i]);
  64. }
  65. }
  66. }
  67. }