1. 最小生成树
1.1 Prim算法
算法思路:
- 随机选取起点加入最小生成树集
- 遍历所有顶点,选择出未在最小生成树集中,且离最小生成树最近的顶点
- 将选择的顶点加入最小生成树集,并计算cost
- 更新剩下的顶点到最小生成树集的最近距离 ```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 {
/**** @param graph 图对象* @param verxs 图对应的顶点个数* @param data 图的各个顶点的值* @param weight 图的邻接矩阵*/public void createGraph(MGraph graph,int verxs,char[] data,int[][] weight) {for(int i = 0;i < verxs;i++) {graph.data[i] = data[i];for(int j = 0;j < verxs;j++) {graph.weight[i][j] = weight[i][j];}}}//显示图的邻接矩阵public void showGraph(MGraph graph) {for (int[] link : graph.weight) {System.out.println(Arrays.toString(link));}}/**** @param graph 图对象* @param v 从哪个节点开始*/public void prim(MGraph graph,int v) {boolean[] visited = new boolean[graph.verxs];visited[v] = true;//记录两个顶点下标int h1 = -1;int h2 = -1;int minWeight = Integer.MAX_VALUE;//每次比较的最小权重for(int k = 1;k < graph.verxs;k++) {//因为有 graph.verxs 顶点,普利姆算法结束后,有 graph.verxs-1 边for(int i = 0;i < graph.verxs;i++) {if(!visited[i]) continue;for(int j = 0;j < graph.verxs;j++) {if(!visited[j] && graph.weight[i][j] < minWeight) {minWeight = graph.weight[i][j];h1 = i;h2 = j;}}}//找到一条边是最小System.out.println("边<" + graph.data[h1] + "," + graph.data[h2] + "> 权值:" + minWeight); //将当前这个结点标记为已经访问visited[h2] = true;minWeight = Integer.MAX_VALUE;}}public void prim1(MGraph graph,int v) {boolean[] visited = new boolean[graph.verxs];int[] dist = new int[graph.verxs];Arrays.fill(dist,Integer.MAX_VALUE);int cost = 0;List<Character> list = new ArrayList<>();for(int i = 0;i < graph.verxs;i++) {int t = -1;//找到离最小生成树最近的点for(int j = 0;j < graph.verxs;j++) {if(!visited[j] && (t == -1 || dist[j] < dist[t])) t = j;}if(i != 0) cost += dist[t];visited[t] = true;list.add(graph.data[t]);System.out.println("最小生成树集合:" + list);//更新各个点到最小生成树的最小距离for(int j = 0;j < graph.verxs;j++) {if(dist[j] > graph.weight[t][j]) dist[j] = graph.weight[t][j];}}System.out.println("代价:" + cost);}
} class MGraph { int verxs;//节点个数 char[] data;//存放节点的数据 int[][] weight;//存放边,邻接矩阵
public MGraph(int verxs) {this.verxs = verxs;data = new char[verxs];weight = new int[verxs][verxs];}
}
时间复杂度:O(n*n)<br />空间复杂度:O(n*n)<a name="dD5Yv"></a>## 1.2 Kruskal算法算法思路:该算法的基本思想是从小到大加入边,是一个贪心算法。1. 将所有的边从小到大排序1. 从小到大选择边,判断边对应的两个顶点是否会构成环,如果构成环,则放弃,继续选择下一条边,否则将这两个顶点加入最小生成树集。是否成环的判断:可以利用并查集,如果两个顶点在同一集合里面,则会构成环。代码例子是LC 1584```javaclass UnionSet {int[] fa;int[] size;UnionSet(int n) {fa = new int[n];size = new int[n];for(int i = 0;i < n;i++) {fa[i] = i;size[i] = 1;}}int find(int x) {if(fa[x] == x) return x;int root = find(fa[x]);fa[x] = root;return root;}void merge(int x,int y) {int ra = find(x);int rb = find(y);if(ra == rb) return;if(size[ra] < size[rb]) {fa[ra] = rb;size[rb] += size[ra];} else {fa[rb] = ra;size[ra] += size[rb];}}boolean isConnect(int x,int y) {return find(x) == find(y);}}public int minCostConnectPoints(int[][] points) {int n = points.length;List<int[]> edge = new ArrayList();for(int i = 0;i < n;i++) {for(int j = i + 1;j < n;j++) {int w = Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]);edge.add(new int[]{i,j,w});}}Collections.sort(edge,(o1,o2) -> {return o1[2] - o2[2];});int cost = 0;UnionSet u = new UnionSet(n);for(int i = 0;i < edge.size();i++) {int[] g = edge.get(i);int t = g[0];int v = g[1];int w = g[2];if(u.isConnect(t,v)) continue;cost += w;u.merge(t,v);}return cost;}
时间复杂度:O(m log(m) + m α(m) ), 排序带来了m log(m)的时间复杂度,并查集合并带来m α(m)的时间复杂度,m为索引对的数量,近似于n n。
空间复杂度:O(nn)
1.3 小结
Prim算法,该算法以顶点为单元,与图中边数无关,比较适合于稠密图
Kruskal算法,该算法以边为单元,时间主要取决于边数,比较适合于稀疏图
2. 最短路径
2.1 Dijkstra算法
public static int N = 100000;public static void main(String[] args) {int[][] matrix=new int[7][7];matrix[0]=new int[]{N, 5, 7, N, N, N, 2};matrix[1]=new int[]{5, N, N, 9, N, N, 3};matrix[2]=new int[]{7, N, N, N, 8, N, N};matrix[3]=new int[]{N, 9, N, N, N, 4, N};matrix[4]=new int[]{N, N, 8, N, N, 5, 4};matrix[5]=new int[]{N, N, N, 4, 5, N, 6};matrix[6]=new int[]{2, 3, N, N, 4, 6, N};int source = 0;dijstra(matrix, source);}public static void dijstra(int[][] matrix, int source) {//最短路径长度int[] shortest = new int[matrix.length];//判断该点的最短路径是否求出int[] visited = new int[matrix.length];//存储输出路径String[] path = new String[matrix.length];//初始化输出路径for (int i = 0; i < matrix.length; i++) {path[i] = new String(source + "->" + i);}Arrays.fill(shortest,N);//初始化源节点shortest[source] = 0;visited[source] = 1;for(int i = 0;i < matrix.length;i++) {if(i != source) {if(matrix[source][i] != N) {shortest[i] = matrix[source][i];}}}for (int i = 1; i < matrix.length; i++) {int min = N;int index = -1;for (int j = 0; j < matrix.length; j++) {//已经求出最短路径的节点不需要再加入计算并判断加入节点后是否存在更短路径if (visited[j] == 0 && shortest[j] < min) {min = shortest[j];index = j;}}//更新最短路径shortest[index] = min;visited[index] = 1;//更新从index跳到其它节点的较短路径for (int m = 0; m < matrix.length; m++) {if (visited[m] == 0 && shortest[index] + matrix[index][m] < shortest[m]) {shortest[m] = shortest[index] + matrix[index][m];path[m] = path[index] + "->" + m;}}}//打印最短路径for (int i = 0; i < matrix.length; i++) {if (i != source) {if (shortest[i] == N) {System.out.println(source + "到" + i + "不可达");} else {System.out.println(source + "到" + i + "的最短路径为:" + path[i] + ",最短距离是:" + shortest[i]);}}}}
