普里姆算法介绍:
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点。
图解过程:
算法思想:
- 一个加权连通图,其中顶点集合为V,边集合为E;
- 初始化:Vnew = {x},其中x为集合V中的任一节点(起始点),Enew = {},为空;
- 重复下列操作,直到Vnew = V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
算法实现思路:
4. 首先,构造一个图,此处我用矩阵表示,矩阵里的节点值表示权值。(无边时权值为无穷大)
5. 选择接收的第一个点为起点,开始进行搜索。
6. 然后,我们要有一个数组(prims),保存每次所得到顶点,还要有一个数组(weights),存放起始点到其他每个顶点的权值,每加一个点都要改变一下,都要改变一下数组权值。
7. 遍历所有顶点,在未被加入的点中,找出权值最小的,放入prims数组,将此点的权值改为0,(表示已经加入了最小树结果中,下次不再找这个点)。
8. 计算总的权值,遍历prims顶点数组,从数组中找它前面的顶点到此顶点的权值的最小值,进行累加。
前期准备代码(输入图的一些信息):
import java.io.IOException;
import java.util.Scanner;
public class Graph {
private int matrixNum;
private char[] vertex; //顶点集合
private int[][] matrix; //邻接矩阵
private static final int INF = Integer.MAX_VALUE; //最大值
/**
* 创建图
*/
public Graph() {
Scanner sca = new Scanner(System.in);
int vertexNum = sca.nextInt(); //顶点数
matrixNum = sca.nextInt(); //边数
vertex = new char[vertexNum];
//初始化顶点
for(int i = 0; i < vertexNum; i++) {
vertex[i] = readChar();
}
//初始化 边 的权值
matrix = new int [vertexNum][vertexNum];
for(int i = 0; i < vertexNum; i++)
for(int j = 0; j < vertexNum; j++)
matrix[i][j] = (i == j) ? 0 : INF;
//根据用户输入的边的信息:初始化边
for(int i = 0; i < matrixNum; i++) {
char start = readChar();
char end = readChar();
int weight = sca.nextInt();
int startInedx = getLocation(start);
int endIndex = getLocation(end);
if(startInedx == -1 || endIndex == -1) return;
matrix[startInedx][endIndex] = weight;
matrix[endIndex][startInedx] = weight;
}
sca.close();
}
/**
* 返回字符的位置
*/
private int getLocation(char c) {
for(int i = 0; i < vertex.length; i++)
if(vertex[i] == c) return i;
return -1;
}
/**
* 读取一个输入字符
* @return
*/
private char readChar() {
char ch = '0';
do {
try {
ch = (char)System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}while(!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')));
return ch;
}
普里姆核心算法代码:
/**
* prim算法
* @param start
*/
public void prim(int start) {
int num = vertex.length; //顶点个数
int index = 0; //prim最小的索引,即prims数组的索引
char[] prims = new char[num];//prim最小树的结果
int[] weights = new int[num];//顶点间边的权值
//prim最小生成树中的第一个数是 "图中第start个顶点",因为是从strat开始的
prims[index++] = vertex[start];
//初始化"顶点的权值数组"
//将每个顶点的权值初始化为第start个顶点 到"该顶点"的权值
for(int i = 0; i < num; i++)
weights[i] = matrix[start][i];
//自己到自己的路径为0
weights[start] = 0;
for(int i = 0; i < num; i++) {
//由于从start开始的,因此不再需要再第start个顶点进行处理
if(start == i) continue;
int j = 0;
int k = 0;
int min = INF;
//在未被加入到最小生成树的顶点中,找出权值最小的顶点
while(j < num) {
//若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)
if(weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}
// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = vertex[k];
//将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for(j = 0; j < num; j++) {
//当第j个节点没有被处理,并且需要更新的时才更新
if(weights[j] != 0 && matrix[k][j] < weights[j])
weights[j] = matrix[k][j];
}
}
//计算最小生成树的权值
int sum = 0;
for(int i = 1; i < index; i++) {
int min = INF;
//获取prims[i]在matrix中的位置
int n = getLocation(prims[i]);
// 在vertex[0...i]中,找出到j的权值最小的顶点
for(int j = 0; j < i; j++) {
int m = getLocation(prims[j]);
if(matrix[m][n] < min) {
min = matrix[m][n];
}
}
sum +=min;
}
//打印最小生成树
System.out.println("PRIM算法从" + vertex[start] + "点开始:");
System.out.println("路径总权值为:" + sum);
for(int i = 0; i < index; i++)
System.out.println("第" + (i + 1) + "次选择:" + prims[i] + "点");
System.out.println();
}
}
克鲁斯卡尔算法介绍:
Kruskal算法是一种用来查找最小生成树的算法,由Joseph Kruskal在1956年发表。用来解决同样问题的还有Prim算法和Boruvka算法等。三种算法都是贪心算法的应用。和Boruvka算法不同的地方是,Kruskal算法在图中存在相同权值的边时也有效。
算法思想:
- 先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,
- 之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。
- 依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。
算法实现思路:
- 前面的算法思路和普里姆算法一样。
- 区别在于:我们需要俩个结构体数组(EData:result/edges),保存Kruskal最小生成树的边,还有图对应的边。
- 我们还需要一个数组(endData),保存"已有最小生成树"中每个顶点在该最小树中的终点。
- 获取图的所有边信息,赋给结构体数组。
- 按边的权值进行排序。
- 开始循环的选取刚刚排好序列的边结构体数组,如果添加此边,没有构成环,那么把此边加入到结果集,更改endData在"已有的最小生成树"中的终点。
边的结构体信息(单独写一个结构体类java文件):
public class EData {
char start; //边的起点
char end; //边的终点
int weight; //边的权重
public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
}
Kruskal算法代码:
/**
* 克鲁斯卡尔(Kruskal)最小生成树
* @param args
*/
public void Kruskal() {
int index = 0; //result数组的索引
int[] endData = new int[matrixNum]; //用于保存"已有最小生成树"中每个顶点在该最小树中的终点
EData[] result = new EData[matrixNum];//结果数组,保存kruskal最小生成树的边
EData[] edges; //图对应的所有边
//获取"图中所有的边"
edges = getEdges();
//将边按"权"的大小进行排序(从小到大)
sortEdges(edges, matrixNum);
for(int i = 0; i < matrixNum; i++) {
int start = getLocation(edges[i].start);//获取第i条边的"起点"的序号
int end = getLocation(edges[i].end); //获取第i条边的"终点"的序号
int m = getEnd(endData, start); //获取start在"已有的最小生成树"中的终点
int n = getEnd(endData, end); //获取end在"已有的最小生成树"中的终点
//如果m != n,意味着"边i"与"已经添加到最小生成树中的顶点"没有形成环路
if(m != n) {
endData[m] = n; //设置m在"已有的最小生成树"中的终点为n
result[index++] = edges[i]; //保存结果
}
}
//统计并打印"kruskal最小生成树"的信息
int length = 0;
for(int i = 0; i < index; i++)
length += result[i].weight;
System.out.println("Kruskal算法:");
System.out.println("路径总权值为:" + length);
for(int i = 0; i < index; i++)
System.out.println("第"+ (i + 1) + "次选:" + result[i].start + result[i].end + "边");
System.out.println();
}
/**
* 对边按照权值大小进行排序(从小到大)
*/
private void sortEdges(EData[] edges, int len) {
for(int i = 0; i < len; i++) {
for(int j = i + 1; j < len; j++) {
if(edges[i].weight > edges[j].weight) {
//交换 边 i 和 边 j
EData temp = edges[i];
edges[i] = edges[j];
edges[j] = temp;
}
}
}
}
/**
* 获取i的终点
*/
private int getEnd(int[] endData, int i) {
while(endData[i] != 0)
i = endData[i];
return i;
}
/**
* 获取图中的边
* @param args
*/
private EData[] getEdges() {
int index = 0;
EData[] edges;
edges = new EData[matrixNum];
for(int i = 0; i < vertex.length; i++) {
for(int j = i + 1; j < vertex.length; j++) {
if(matrix[i][j] != INF) {
edges[index++] = new EData(vertex[i], vertex[j], matrix[i][j]);
}
}
}
return edges;
}
}
main方法:
public static void main(String[] args) {
Graph ga = new Graph();
ga.prim(0); //普里姆算法 (从接收的第一个顶点开始)
ga.Kruskal(); //Krustal算法
}
测试数据:
7 11
A B C D E F G
A B 7
A D 5
D B 9
B C 8
B E 7
D E 15
D F 6
F E 8
F G 11
E G 9
C E 5
运行结果:
提示:
普里姆算法和Kruskal算法的各自代码写到一个类里就可以直接运行。(结构体,单独一个文件)