前言
要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,我们可以选择将n个城市当成n个节点,然后将所有的节点相连接形成一个连通图,边上的数字代表与边相邻的两座城市之间铺设光纤的费用(权),但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。
下面我们就将 A B C D E F G五座城市形成的带权连通图通过prim算法转换成最小生成树。
图1 五个城市形成的带权连通图,两个节点边上的数字代表铺设光纤的费用(权),没有表面数字的边表示与这条边关联的两个城市之间无法直接相通。
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是最小生成树?
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得
** w(T) 最小,则此 T 为 G 的最小生成树。**
例如图1 (A,B)代表连接顶点A与顶点B的边 w(A,B) = 5
规定T为无循环图是因为书的
最小生成树其实是最小权重生成树的简称。
二、Prime算法简述
普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。
1.文字描述
1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
图1中
顶点集合V = {A.B,C,D,E,F,G}
边集合E = {(A,B),(B,D),(D,F),(F,E),(E,C),(C,A),(A,G),(B,G),(F,G),(E,G)}
2).初始化:Vnew= {x},其中x为集合V中的任一节点(起始点),Enew= {},为空;
这里以A节点为起始点
Vnew = {A}
3).重复下列操作,直到Vnew= V:
相等的意思是 原顶点V集合的全部节点经过下面你的操作全部加入到了新的节点集合Vnew中,这样每个节点找到的与之相连的节点形成的边满足权值最小。
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且
v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
第一步:刚开始集合Vnew中只有一个节点A,现在我们要做的是,在V集合中寻找除了A节点以外的其他与A直接连接的节点(除A以外是因为节点A已经存在与Vnew集合之中),满足条件的节点有节点B,C,G。现在我们要对节点A与这三个节点形成的边的权值进行判断,选出权值最小的。w(A,C) = 7,w(A,G) = 2, w(A,B) = 5 显而易见,其中G节点与A节点形成的边的权值最小。
第二步:现在集合Vnew = {A,G},在集合V中除了A,G节点意外直接与这两个节点相连接的节点有B,C,E,B,F。 w(A,C) = 7,w(A,B) = 5,w(G,E) = 4,w(G,F) = 6,w(G,B) = 3,能够看出G节点与B节点形成的边权值最小。
第三步:现在集合Vnew = {A,G,B},满足条件的节点有C,E,F,D。w(A,C) = 7,w(G,E) = 4, w(G,F) = 6,w(B,D) = 9,这里G节点与E节点形成的边的权值最小。
第四步:现在集合Vnew = {A,G,B,E} , w(E,F) = 5 通过判断 E节点与F节点形成的边的权值最小。
第五步:集合Vnew = {A,G,B,E,F), w(F,D) = 4 最小
第六步:集合Vnew = {A,G,B,E,F,D), w(A,C) = 7 最小
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
第一步: 将G节点加入到集合Vnew中,w(A,G)边加入到集合Enew中
第二步:将B节点加入到集合Vnew中,w(G,B)边加入到集合Enew中
第三步:将E节点添加到集合Vnew中,w(G,E)边加入到集合Enew中
第四步:将F节点添加到集合Vnew中,w(E,F)边加入到集合Enew中
第五步:将D节点添加到集合Vnew中,w(F,D)边加入到集合Enew中
第六步:将A节点添加到集合Vnew中,w(A,C)边加入到集合Enew中
到目前为止:
Vnew = {A,G,B,E,F,D,A}
Enew = {(A,G),(G,B),(G,E),(E,F),(F,D),(F,D),(A,C)}
2.图文演示
3).重复下列操作,直到Vnew= V:
相等的意思是 原顶点V集合的全部节点经过下面你的操作全部加入到了新的节点集合Vnew中,这样每个节点找到的与之相连的节点形成的边满足权值最小。
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且
v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
第一步:刚开始集合Vnew中只有一个节点A,
现在我们要做的是,在V集合中寻找除了A节点以外的其他与A直接连接的节点(除A以外是因为节点A已经存在与Vnew集合之中),
满足条件的节点有节点B,C,G。
现在我们要对节点A与这三个节点形成的边的权值进行判断,选出权值最小的。w(A,C) = 7,w(A,G) = 2, w(A,B) = 5
显而易见,其中G节点与A节点形成的边的权值最小。
第二步:现在集合Vnew = {A,G},
在集合V中除了A,G节点意外直接与这两个节点相连接的节点有B,C,E,F。
w(A,C) = 7,w(A,B) = 5,w(G,E) = 4,w(G,F) = 6,w(G,B) = 3,
能够看出G节点与B节点形成的边权值最小。
第三步:现在集合Vnew = {A,G,B},
满足条件的节点有C,E,F,D。
w(A,C) = 7,w(G,E) = 4, w(G,F) = 6,w(B,D) = 9,这里G节点与E节点形成的边的权值最小。
第四步:现在集合Vnew = {A,G,B,E} ,
满足条件的节点有C,D,F
w(E,F) = 5 通过判断 E节点与F节点形成的边的权值最小。
第五步:集合Vnew = {A,G,B,E,F),
满足条件的节点有C,D
w(F,D) = 4 最小
第六步:集合Vnew = {A,G,B,E,F,D),
满足条件的节点有C
w(A,C) = 7 最小
第七步:
通过上述步骤 我们得到的Enew集合中含有六条边分别是(A,C),(A,G),(G,B),(G,E),(E,F),(F,D)
形成的最小二叉树如下图
整理过后如下
三、代码实现
1.节点代码
class MGraph {
int nodes; //节点的个数
char[] element; //节点的元素
int[][] side; //存放边,邻接矩阵
public MGraph(int nodes) { //构造器
this.nodes = nodes;
element = new char[nodes];
side = new int[nodes][nodes];
}
}
2.邻接矩阵
我们通过邻接矩阵来实现各个城市之间的连通图
int[][] side = {
{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}};
其中,数字10000代表两个城市之间不相直接连通
如图
3.将邻接矩阵形成连通图
//创建树
public void creatTree(MGraph mGraph, int nodes, char[] element, int[][] side) {
for (int i = 0; i < nodes; i++) {
mGraph.element[i] = element[i]; //添加每个顶点的名字
for (int j = 0; j < nodes; j++) {
mGraph.side[i][j] = side[i][j]; //添加各个城市之间直接相连的边的权值
}
}
}
4.Prime算法
//普利姆算法
public void prim(MGraph mGraph, int node) {
int[] nodes = new int[mGraph.nodes]; //用于标记遍历过的节点
nodes[node] = 1; //将当前节点标记为1 表示已访问
//用来记录最小路径两个节点的下标
int index1 = -1;
int index2 = -1;
int minSide = 10000; //遍历与之比较 找到最小的路径
for (int k = 1; k < mGraph.nodes; k++) { //若有n个节点 则有n - 1 条边
for (int i = 0; i < mGraph.nodes; i++) {
for (int j = 0; j < mGraph.nodes; j++) {
if (nodes[i] == 1 && nodes[j] == 0 && mGraph.side[i][j] < minSide) {
// 代表在已经访问过的节点中(即本博客上文写到已经加入到Vnew集合中的节点) 遍历 未访问过的节点(即满足条件的节点) 并 找到最小的路径 将其返回
minSide = mGraph.side[i][j]; //最短边上的权值
//最短边的两个顶点
index1 = i;
index2 = j;
}
}
}
//从第内层for循环出来后,表示找到一条最短边
System.out.println("边<" + mGraph.element[index1] + "," + mGraph.element[index2] + ">权值:" + minSide);
nodes[index2] = 1; //将最内层与已访问节点路径最短的节点设置为已访问 (将找出的节点添加到Vnew集合中)
minSide = 10000; //将minSide恢复初始值 开始下次遍历
}
}
5.完整代码
package com.xx.prime;
public class PrimAlgorithm {
public static void main(String[] args) {
char[] element = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
int nodes = element.length;
int[][] side = {
{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 = new MGraph(nodes);
MinTree minTree = new MinTree();
minTree.creatTree(mGraph, nodes, element, side);
minTree.showTree(mGraph);
minTree.prim(mGraph, 0);
}
}
class MinTree {
//创建树
public void creatTree(MGraph mGraph, int nodes, char[] element, int[][] side) {
for (int i = 0; i < nodes; i++) {
mGraph.element[i] = element[i];
for (int j = 0; j < nodes; j++) {
mGraph.side[i][j] = side[i][j];
}
}
}
//普利姆算法
public void prim(MGraph mGraph, int node) {
int[] nodes = new int[mGraph.nodes]; //用于标记遍历过的节点
nodes[node] = 1; //将当前节点标记为1 表示已访问
//用来记录最小路径两个节点的下标
int index1 = -1;
int index2 = -1;
int minSide = 10000; //遍历与之比较 找到最小的路径
for (int k = 1; k < mGraph.nodes; k++) { //若有n个节点 则有n - 1 条边
for (int i = 0; i < mGraph.nodes; i++) {
for (int j = 0; j < mGraph.nodes; j++) {
if (nodes[i] == 1 && nodes[j] == 0 && mGraph.side[i][j] < minSide) {
// 代表在已经访问过的节点中(即本博客上文写到已经加入到Vnew集合中的节点) 遍历 未访问过的节点(即满足条件的节点) 并 找到最小的路径 将其返回
minSide = mGraph.side[i][j]; //最短边上的权值
//最短边的两个顶点
index1 = i;
index2 = j;
}
}
}
//从第内层for循环出来后,表示找到一条最短边
System.out.println("边<" + mGraph.element[index1] + "," + mGraph.element[index2] + ">权值:" + minSide);
nodes[index2] = 1; //将最内层与已访问节点路径最短的节点设置为已访问 (将找出的节点添加到Vnew集合中)
minSide = 10000; //将minSide恢复初始值 开始下次遍历
}
}
//输出树
public void showTree(MGraph mGraph) {
for (int[] arr : mGraph.side) {
for (int element : arr) {
System.out.print(element + " ");
}
System.out.println();
}
}
}
class MGraph {
int nodes; //节点的个数
char[] element; //节点的元素
int[][] side; //存放边,邻接矩阵
public MGraph(int nodes) { //构造器
this.nodes = nodes;
element = new char[nodes];
side = new int[nodes][nodes];
}
}