Prime
最小生成树算法,该算法解决的问题就是寻找无向连通图中的最短路径,保证每个节点同时能够相连。用到的思想就是贪心算法,每次寻求边的时候,都是在已经访问过的边中寻找与还没访问过的边之间距离最小的那条边。这一块可能有点绕,代码写起来但是比较好理解。我们先来看一个场景题。
场景
有一个乡,分别有7个村庄A,B,C,D,E,F,G现在需要修路把7个村庄都连通起来。各个村庄的距离用边线表示。问如何修路才能保证各个村庄都能够连通,并且总修的公里数最短?
思路
就是选取尽可能少的路线,并且每条路线最小,保证总共修路最短。
上边也介绍了prime算法的思想,下边直接给出Java代码求解上述问题。代码注释也比较好理解,感兴趣可以自己运行。
public class Prime {
final static int MAX = Integer.MAX_VALUE;
public static void main(String[] args) {
int vertex = 7;
char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
// 邻接矩阵必须转置相等的
int[][] weight = new int[][]{
{MAX, 5, 7, MAX, MAX, MAX, 2},
{5, MAX, MAX, 9, MAX, MAX, 3},
{7, MAX, MAX, MAX, 8, MAX, MAX},
{MAX, 9, MAX, MAX, MAX, 4, MAX},
{MAX, MAX, 8, MAX, MAX, 5, 4},
{MAX, MAX, MAX, 4, 5, MAX, 6},
{2, 3, MAX, MAX, 4, 6, MAX}};
Graph graph = new Graph(vertex, data, weight);
prim(graph, 0);
}
/**
* @param graph
* @param start 从几个顶点开始生成
*/
private static void prim(Graph graph, int start) {
int[] visited = new int[graph.vertexs];
visited[start] = 1;
int h1 = -1;// 保存当前边
int h2 = -1; // 保存当前边
int minWeight = MAX;
// 对于n个节点的数 我们最多只需要找到n-1个边就能把所有的节点相连,所以下边的循环从1开始
for (int k = 1; k < graph.vertexs; k++) {
// 第一层表示找访问过的节点
for (int i = 0; i < graph.vertexs; i++) {
// 第二层找没有访问过的节点 同时找最小权值的边
for (int j = 0; j < graph.vertexs; j++) {
if (visited[i] == 1 && visited[j] == 0 && minWeight > graph.weight[i][j]) {
// 不断更新
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
// 重置
minWeight = MAX;
// 这次循环找到的边
System.out.println("边为:<" + graph.data[h1] + "," + graph.data[h2] + "> 权重为:" + graph.weight[h1][h2]);
// 访问过的边记录下来
visited[h2] = 1;
}
}
}
class Graph {
public int vertexs;
public char[] data;
public int[][] weight;
/**
* @param vertexs 定点的个数
* @param data 定点的名称
* @param weight 临街举证
*/
public Graph(int vertexs, char[] data, int[][] weight) {
if (vertexs != data.length || weight.length != vertexs || weight[0].length != vertexs) {
throw new RuntimeException("初始化异常!");
}
this.vertexs = vertexs;
this.data = data;
this.weight = weight;
}
}
程序运行输出如下:
边为:<A,G> 权重为:2
边为:<G,B> 权重为:3
边为:<G,E> 权重为:4
边为:<E,F> 权重为:5
边为:<F,D> 权重为:4
边为:<A,C> 权重为:7
结果集的保存
上述代码中只是对找到的路径(边)进行了输出,没有进行保存,我们可采用索引数组的方式进行存储返回。
/**
* @param graph
* @param start 从几个顶点开始生成
*/
private static int[] prim(Graph graph, int start) {
int[] res = new int[graph.vertexs];
Arrays.fill(res, -1);
int[] visited = new int[graph.vertexs];
visited[start] = 1;
int h1 = -1;
int h2 = -1;
int minWeight = MAX;
// 对于n个节点的数 我们最多只需要找到n-1个边就能把所有的节点相连,所以下边的循环从1开始
for (int k = 1; k < graph.vertexs; k++) {
// 第一层表示找访问过的节点
for (int i = 0; i < graph.vertexs; i++) {
// 第二层找没有访问过的节点 同时找最小权值的边
for (int j = 0; j < graph.vertexs; j++) {
if (visited[i] == 1 && visited[j] == 0 && minWeight > graph.weight[i][j]) {
// 不断更新
minWeight = graph.weight[i][j];
h1 = i;
h2 = j;
}
}
}
// 重置
minWeight = MAX;
// 这次循环找到的边
System.out.println("边为:<" + graph.data[h1] + "," + graph.data[h2] + "> 权重为:" + graph.weight[h1][h2]);
// 访问过的边记录下来
visited[h2] = 1;
if (res[h1] == -1) {
res[h1] = h2;
}
if (res[h2] == -1) {
res[h2] = h1;
}
}
// 返回索引数组
return res;
}
Kruskal
该算法的思想就是对所有的边进行排序,依次将最小的边加入到集合当中,在加入到集合的过程中,不断判断能否构成回路,不能的话就添加集合中,同时维护一个计数器,当添加的边达到节点数-1时就可以提前返回。下边给出代码,有注释,应该比较好理解。输出部分可以选择性注释掉。
/**
* @Description:
* @Create 2020-08-26 13:16
* @Email:1173748742@qq.com
*/
public class Kruskal {
final static int MAX = Integer.MAX_VALUE;
public static void main(String[] args) {
int vertex = 7;
char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};
// 邻接矩阵必须转置相等的
int[][] weight = new int[][]{
{MAX, 5, 7, MAX, MAX, MAX, 2},
{5, MAX, MAX, 9, MAX, MAX, 3},
{7, MAX, MAX, MAX, 8, MAX, MAX},
{MAX, 9, MAX, MAX, MAX, 4, MAX},
{MAX, MAX, 8, MAX, MAX, 5, 4},
{MAX, MAX, MAX, 4, 5, MAX, 6},
{2, 3, MAX, MAX, 4, 6, MAX}};
Graph graph = new Graph(vertex, data, weight);
kruskal(graph);
}
public static int[] kruskal(Graph graph) {
Edg[] edgs = new Edg[graph.edg_num];
int index = 0;
for (int i = 0; i < graph.vertexs; i++) {
for (int j = i + 1; j < graph.vertexs; j++) {
if (graph.weight[i][j] != Integer.MAX_VALUE) {
edgs[index++] = new Edg(graph.data[i], graph.data[j], graph.weight[i][j]);
}
}
}
// 对边进行排序
Arrays.sort(edgs);
// 开始选择边
int[] res = new int[graph.vertexs];
// 为了通过节点的名字获取索引就定义了一个map
HashMap<Character, Integer> getIndex = new HashMap<>();
index = 0;
for (char c : graph.data) {
res[index] = index;
getIndex.put(c, index++);
}
int sum = 0;
// 因为字需要找到节点数减1条边 所以定义一个计数器提前返回
int count = 0;
for (int i = 0; i < edgs.length && count < graph.vertexs; i++) {
Edg edg = edgs[i];
int start = getIndex.get(edg.start);
int end = getIndex.get(edg.end);
int start_root = find(res, start); // 寻找节点的根节点
int end_root = find(res, end); // 寻找节点的根节点
// 如果不相等 那么说明不构成回路,可以添加到候选集合中
if (start_root != end_root) {
res[end_root] = start_root;
sum += edg.dis;
System.out.println(graph.data[start] + ">" + graph.data[end] + " 距离为:" + edg.dis);
count++;
}
}
System.out.println("总路径为:" + sum);
// 返回索引数组
return res;
}
/**
* 寻找根节点 类似并查集的思路
*
* @param res
* @param i
* @return
*/
private static int find(int[] res, int i) {
int i_root = i;
while (res[i_root] != i_root) {
i_root = res[i_root];
}
return i_root;
}
}
/**
* 需要实现lang包下的接口 这样可以直接进行排序
*/
class Edg implements Comparable<Edg> {
public char start;
public char end;
public int dis;
public Edg(char start, char end, int dis) {
this.start = start;
this.end = end;
this.dis = dis;
}
@Override
public String toString() {
return "start=" + start + ">" + "end=" + end + " 路径:" + dis;
}
/**
* 重写方法
*
* @param o
* @return
*/
@Override
public int compareTo(Edg o) {
return dis - o.dis;
}
}
class Graph {
public int vertexs;
public char[] data;
public int[][] weight;
int edg_num; // 边的条数
/**
* @param vertexs 定点的个数
* @param data 定点的名称
* @param weight 临街举证
*/
public Graph(int vertexs, char[] data, int[][] weight) {
if (vertexs != data.length || weight.length != vertexs || weight[0].length != vertexs) {
throw new RuntimeException("初始化异常!");
}
this.vertexs = vertexs;
this.data = data;
this.weight = weight;
//计算边的条数
for (int i = 0; i < vertexs; i++) {
for (int j = i + 1; j < vertexs; j++) {
if (weight[i][j] < Integer.MAX_VALUE) {
edg_num++;
}
}
}
}
}
程序输出结果如下:
A>G 距离为:2
B>G 距离为:3
D>F 距离为:4
E>G 距离为:4
E>F 距离为:5
A>C 距离为:7
总路径为:25
希望对你有所帮助!