本文实现代码地址:
https://github.com/helloWorldchn/DataStructure
一、图的最小生成树
最小生成树(Minimum spanning tree,MST)是最小权重生成树(Minimum weight spanning tree)的简称,是一个连通加权无向图中一棵权值最小的生成树。
在一给定的无向图 G = ( V , E ) G = (V, E) G=(V,E) 中 ( u , v ) (u,v) (u,v) 代表连接顶点 u 与顶点 v 的边 E = { ( u , v ) ∣ u ∈ V , v ∈ V } E = \{ (u, v) | u \in V, v \in V \} E={(u,v)∣u∈V,v∈V},而 w ( u , v ) ) w(u,v)) w(u,v))代表此边的权重,若存在 T T T 为 E E E 的子集(即 T ⊆ E {\displaystyle T\subseteq E} T⊆E)且 ( V , T ) (V, T) (V,T) 为树,使得:
w ( T ) = ∑ ( u , v ) ∈ T w ( u , v ) {\displaystyle w(T)=\sum _{(u,v)\in T}w(u,v)} w(T)=(u,v)∈T∑w(u,v)
的 w ( T ) w(T) w(T) 最小,则此 T 为 G 的最小生成树。
一个连通图可能有多个生成树。当图中的边具有权值时,总会有一个生成树的边的权值之和小于或者等于其它生成树的边的权值之和。广义上而言,对于非连通无向图来说,它的每一连通分量同样有最小生成树,它们的并被称为最小生成森林。
- 最小生成树在一些情况下可能会有多个。例如,当图的每一条边的权值都相同时,该图的所有生成树都是最小生成树。
- 如果图的每一条边的权值都互不相同,那么最小生成树将只有一个。
如下加权无向连通图
其最小成生树为:
和
求取一张无向图的最小生成树的算法主要有普里姆(Prim)算法个克鲁斯克尔(Kruskal)算法。
二、普里姆(Prim)算法
普里姆算法(Prim Algorithm)是图论中的一种贪心算法,可在一个加权连通图中找到其最小生成树。
1. 算法简介
该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(Robert C. Prim)独立发现;1959年,艾兹赫尔·韦伯·迪杰斯特拉(Edsger W. Dijkstra)再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。
2. 算法思想与步骤
算法思想:从某⼀个顶点开始构建⽣成树;每次将代价最⼩的新顶点纳⼊⽣成树,直到所有顶点都纳⼊为⽌。
算法步骤:
输入:一个加权连通图,其中顶点集合为
V
{\displaystyle V}
V,边集合为
E
{\displaystyle E}
E;
输出:使用集合
V
new
{\displaystyle V_{\text{new}}}
Vnew 和
E
new
{\displaystyle E_{\text{new}}}
Enew 来描述所得到的最小生成树。
初始化:
V
new
=
{
x
}
{\displaystyle V_{\text{new}}=\{x\}}
Vnew={x},其中
x
{\displaystyle x}
x 为集合
V
{\displaystyle V}
V 中的任一节点(起始点),
E
new
=
{
}
{\displaystyle E_{\text{new}}=\{\}}
Enew={}。
- 在集合 E {\displaystyle E} E中选取权值最小的边 ( u , v ) {\displaystyle (u,v)} (u,v),其中 u {\displaystyle u} u为集合 V new {\displaystyle V_{\text{new}}} Vnew中的元素,而 v {\displaystyle v} v则是 V {\displaystyle V} V中没有加入 V new {\displaystyle V_{\text{new}}} Vnew的顶点(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
- 将 v {\displaystyle v} v加入集合 V new {\displaystyle V_{\text{new}}} Vnew中,将 ( u , v ) {\displaystyle (u,v)} (u,v)加入集合 E new {\displaystyle E_{\text{new}}} Enew中;
- 重复步骤1到步骤2,直到 V new = V {\displaystyle V_{\text{new}}=V} Vnew=V。
下图为普里姆(Prim)算法的一个例子:
上图是一个
V
=
{
v
0
,
v
1
,
v
2
,
v
3
,
v
4
,
v
5
}
V=\{v_0,v_1,v_2,v_3,v_4,v_5\}
V={v0,v1,v2,v3,v4,v5} 的无向图,使用Prim算法计算最小生成树。
-
首先进行初始化操作,任选一个顶点作为初始顶点,本例子中选择 v 0 v_0 v0,即:将 v 0 {v_0} v0加入到 V n e w V_{new} Vnew,此时 V n e w = { v 0 } {V_{new}=\{v_0\}} Vnew={v0};
-
其次以 V n e w = { v 0 } {V_{new}=\{v_0\}} Vnew={v0}为基准,寻找与 v 0 v_0 v0 相邻,边的权值最小的顶点,可以发现边 ( v 0 , v 3 ) (v_0, v_3) (v0,v3) 的权值 w 03 w_{03} w03 为1,是最小的,所以连通 v 0 v_0 v0 与 v 3 v_3 v3,即:将 v 3 {v_3} v3加入到 V n e w V_{new} Vnew并将 ( v 0 , v 3 ) (v_0, v_3) (v0,v3)加入到 E n e w E_{new} Enew此时 V n e w = { v 0 , v 3 } {V_{new}=\{v_0,v_3\}} Vnew={v0,v3}, E n e w = { ( v 0 , v 3 ) } {E_{new}=\{(v_0, v_3)\}} Enew={(v0,v3)};
-
然后以 V n e w = { v 0 , v 3 } {V_{new}=\{v_0,v_3\}} Vnew={v0,v3} 中的两个顶点一起作为基准,发现相邻的顶点中边 ( v 2 , v 3 ) (v_2, v_3) (v2,v3) 的权值 w 23 w_{23} w23为4,是最小的,所以连通 v 2 v_2 v2 与 v 3 v_3 v3 ,即:将 v 2 {v_2} v2加入到 V n e w V_{new} Vnew并将 ( v 2 , v 3 ) (v_2, v_3) (v2,v3)加入到 E n e w E_{new} Enew,此时 V n e w = { v 0 , v 2 , v 3 } {V_{new}=\{v_0,v_2,v_3\}} Vnew={v0,v2,v3}, E n e w = { ( v 0 , v 3 ) , ( v 2 , v 3 ) } {E_{new}=\{(v_0, v_3),(v_2, v_3)\}} Enew={(v0,v3),(v2,v3)};
-
接下来以 V n e w = { v 0 , v 2 , v 3 } {V_{new}=\{v_0,v_2,v_3\}} Vnew={v0,v2,v3}作为基准,权值 w 25 w_{25} w25 为2,是最小的一条边,连通边 ( v 2 , v 5 ) (v_2, v_5) (v2,v5) ,即:将 v 5 {v_5} v5 加入到 V n e w V_{new} Vnew 并将 ( v 2 , v 5 ) (v_2, v_5) (v2,v5) 加入到 E n e w E_{new} Enew,此时 V n e w = { v 0 , v 2 , v 3 , v 5 } {V_{new}=\{v_0,v_2,v_3, v_5\}} Vnew={v0,v2,v3,v5}, E n e w = { ( v 0 , v 3 ) , ( v 2 , v 3 ) , ( v 2 , v 5 ) } {E_{new}=\{(v_0, v_3), (v_2, v_3),(v_2, v_5)\}} Enew={(v0,v3),(v2,v3),(v2,v5)};
-
以 V n e w = { v 0 , v 2 , v 3 , v 5 } {V_{new}=\{v_0,v_2,v_3, v_5\}} Vnew={v0,v2,v3,v5} 作为基准, w 13 w_{13} w13为5,连通边 ( v 1 , v 3 ) (v_1, v_3) (v1,v3) ,即:将 v 1 {v_1} v1 加入到 V n e w V_{new} Vnew 并将 ( v 1 , v 3 ) (v_1, v_3) (v1,v3) 加入到 E n e w E_{new} Enew,此时 V n e w = { v 0 , v 1 , v 2 , v 3 , v 5 } {V_{new}=\{v_0,v_1,v_2,v_3, v_5\}} Vnew={v0,v1,v2,v3,v5}, E n e w = { ( v 0 , v 3 ) , ( v 2 , v 3 ) , ( v 2 , v 5 ) , ( v 1 , v 3 ) } {E_{new}=\{(v_0, v_3), (v_2, v_3),(v_2, v_5),(v_1, v_3)\}} Enew={(v0,v3),(v2,v3),(v2,v5),(v1,v3)};
-
最后连通权值为4的边 ( v 1 , v 4 ) (v_1, v_4) (v1,v4) ,即:将 v 4 {v_4} v4 加入到 V n e w V_{new} Vnew 并将 ( v 1 , v 4 ) (v_1, v_4) (v1,v4) 加入到 E n e w E_{new} Enew,此时 V n e w = { v 0 , v 1 , v 2 , v 3 , v 4 , v 5 } {V_{new}=\{v_0,v_1,v_2,v_3,v_4, v_5\}} Vnew={v0,v1,v2,v3,v4,v5}, E n e w = { ( v 0 , v 3 ) , ( v 2 , v 3 ) , ( v 2 , v 5 ) , ( v 1 , v 3 ) , ( v 1 , v 4 ) } {E_{new}=\{(v_0, v_3), (v_2, v_3),(v_2, v_5),(v_1, v_3),(v_1, v_4)\}} Enew={(v0,v3),(v2,v3),(v2,v5),(v1,v3),(v1,v4)}
-
对比发现此时 V n e w = V {V_{new}=V} Vnew=V,即说明所有节点都已经连通了,最小生成树计算完毕。
经过以上步骤,通过Prim算法得到了无向图 V = { v 0 , v 1 , v 2 , v 3 , v 4 , v 5 } V=\{v_0,v_1,v_2,v_3,v_4,v_5\} V={v0,v1,v2,v3,v4,v5} 的最小生成树。
3. 代码实现(Java)
代码测试的图如下
(1)邻接矩阵存储方式:
private static final int INF = Integer.MAX_VALUE;
/**
* @descript 最小生成树算法:prim算法
* @param: graph
*/
public void prim(int[][] graph) {
int vertices = graph.length;
int[] parent = new int[vertices]; // 用于存储最小生成树的父节点
int[] key = new int[vertices]; // 用于存储顶点与最小生成树的最小权重
boolean[] mstSet = new boolean[vertices]; // 用于记录顶点是否已加入最小生成树
Arrays.fill(key, INF); // 初始化所有顶点的权重为无穷大
Arrays.fill(mstSet, false); // 初始化所有顶点均未加入最小生成树
key[0] = 0; // 初始顶点的权重为0,即将其作为起始节点
parent[0] = -1; // 初始节点没有父节点
// 依次加入(n-1)个顶点到最小生成树中
for (int i = 0; i < vertices - 1; i++) {
int u = minKey(key, mstSet, vertices); // 选择权重最小的顶点u加入最小生成树
mstSet[u] = true;
// 更新相邻顶点的权重和父节点信息
for (int v = 0; v < vertices; v++) {
if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {
parent[v] = u;
key[v] = graph[u][v];
}
}
}
// 打印最小生成树的边和权重
System.out.println("Edge" + "\t" + "Weight");
for (int i = 1; i < graph.length; i++) {
if (parent[i] != -1) { // 避免打印起始节点的边
System.out.println(vertexList.get(parent[i]) + " -> " + vertexList.get(i) + "\t" + graph[parent[i]][i]);
}
}
}
// 寻找权重最小的未加入最小生成树的顶点
private int minKey(int[] key, boolean[] mstSet, int vertices) {
int min = INF, minIndex = -1;
for (int v = 0; v < vertices; v++) {
if (!mstSet[v] && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
邻接矩阵如下:
邻接矩阵Prim算法结果如下:
(2)邻接表存储方式:
/**
* @descript 最小生成树算法:prim算法
* @param: graph
*/
public void prim() {
int V = vertexNumber;
boolean[] mstSet = new boolean[V]; // 存储顶点是否已加入最小生成树的集合
int[] key = new int[V]; // 存储顶点到最小生成树的最小权重
int[] parent = new int[V]; // 存储最小生成树的边
Arrays.fill(key, Integer.MAX_VALUE);
key[0] = 0; // 将起始顶点的key值设为0
parent[0] = -1; // 将起始顶点的父节点设为-1
for (int count = 0; count < vertexNumber - 1; count++) {
int u = minKey(key, mstSet, vertexNumber); // 选择key值最小的顶点u
mstSet[u] = true; // 将顶点u标记为已访问
// 更新与顶点u相邻的顶点的key值和parent信息
EdgeNode current = headNode[u].firstEdge;
while (current != null) {
int v = vertexList.indexOf(current.vertex);
int weight = current.weight;
if (!mstSet[v] && weight < key[v]) {
parent[v] = u;
key[v] = weight;
}
current = current.next;
}
}
// 输出最小生成树的边和权重
System.out.println("Edge" + "\t" + "Weight");
for (int i = 1; i < vertexNumber; i++) {
if (headNode[i].firstEdge.vertex == vertexList.get(parent[i])) {
// 不是头结点,直接确定对应权值
System.out.println(vertexList.get(parent[i]) + " -> " + vertexList.get(i) + "\t" + headNode[i].firstEdge.weight);
} else {
// 不是头结点,顺着链表遍历,寻找对应权值
EdgeNode currentEdge = headNode[i].firstEdge;
while (currentEdge!=null) {
if (currentEdge.vertex == vertexList.get(parent[i])) {
System.out.println(vertexList.get(parent[i]) + " -> " + vertexList.get(i) + "\t" + currentEdge.weight);
break;
}
currentEdge = currentEdge.next;
}
}
}
}
// 选择key值最小的顶点
private int minKey(int[] key, boolean[] mstSet, int V) {
int min = Integer.MAX_VALUE, minIndex = -1;
for (int v = 0; v < V; v++) {
if (!mstSet[v] && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
邻接表如下:
邻接表存储方式下Prim算法结果如下:
三、克鲁斯克尔(Kruskal)算法
克鲁斯克尔算法(英语:Kruskal algorithm)是一种用来查找最小生成树的算法,是贪心算法的应用。
1. 算法简介
克鲁斯克尔算法由美国数学家约瑟夫·克鲁斯克尔(Joseph Bernard Kruskal, Jr.)在1956年发表。贪心算法的应用,克鲁斯克尔算法在图中存在相同权值的边时也有效。
2. 算法思想与步骤
算法思想:每次选择⼀条权值最⼩的边,使这条边的两头连通(原本已经连通的就不选),直到所有结点都连通。
算法步骤:
- 新建图 G {\displaystyle G} G, G {\displaystyle G} G中拥有原图中相同的节点,但没有边;
- 将原图中所有的边按权值从小到大排序 ;
- 从权值最小的边开始,如果这条边连接的两个节点于图 G {\displaystyle G} G中不在同一个连通分量中,则添加这条边到图 G {\displaystyle G} G中;
- 重复3,直至图 G {\displaystyle G} G中所有的节点都在同一个连通分量中。
下图为克鲁斯克尔(Kruskal)算法的一个例子:
上图是一个
V
=
{
v
0
,
v
1
,
v
2
,
v
3
,
v
4
,
v
5
}
V=\{v_0,v_1,v_2,v_3,v_4,v_5\}
V={v0,v1,v2,v3,v4,v5} 的无向图,使用Kruskal算法计算最小生成树。
-
首先按照权值将所有的边进行排序,按照顺序的集合 E o r d e r = { ( v 0 , v 3 ) , ( v 2 , v 5 ) , ( v 1 , v 4 ) , ( v 2 , v 3 ) , ( v 3 , v 5 ) , ( v 0 , v 2 ) , ( v 1 , v 3 ) , ( v 0 , v 1 ) , ( v 3 , v 4 ) , ( v 4 , v 5 ) } E_{order} =\{(v_0, v_3),(v_2, v_5),(v_1, v_4),(v_2, v_3),(v_3, v_5),(v_0, v_2),(v_1, v_3),(v_0, v_1),(v_3, v_4),(v_4, v_5)\} Eorder={(v0,v3),(v2,v5),(v1,v4),(v2,v3),(v3,v5),(v0,v2),(v1,v3),(v0,v1),(v3,v4),(v4,v5)},将集合中按照权值排序好点按照顺序一次检查是否符合连通条件。
-
首先检查发现加入顶点 v 0 v_0 v0 和 v 3 v_3 v3 的边 ( v 0 , v 3 ) (v_0, v_3) (v0,v3) 后不会构成回路,即当前顶点 v 0 v_0 v0 和 v 3 v_3 v3 不在同一个连通分量中,则可以将顶点 v 0 v_0 v0 和 v 3 v_3 v3 连起来,即 E n e w = { ( v 0 , v 3 ) } E_{new} =\{(v_0, v_3)\} Enew={(v0,v3)};
-
依次检查发现加入顶点 v 2 v_2 v2 和 v 3 v_3 v3 的边 ( v 2 , v 3 ) (v_2, v_3) (v2,v3) 后不会构成回路,即当前顶点 v 2 v_2 v2 和 v 3 v_3 v3 不在同一个连通分量中,则可以将顶点 v 2 v_2 v2 和 v 3 v_3 v3 连起来,即 E n e w = { ( v 0 , v 3 ) , ( v 2 , v 5 ) } E_{new} =\{(v_0, v_3),(v_2, v_5)\} Enew={(v0,v3),(v2,v5)};
-
依次检查发现加入顶点 v 1 v_1 v1 和 v 4 v_4 v4 的边 ( v 1 , v 4 ) (v_1, v_4) (v1,v4) 后不会构成回路,即当前顶点 v 1 v_1 v1 和 v 4 v_4 v4 不在同一个连通分量中,则可以将顶点 v 1 v_1 v1 和 v 4 v_4 v4 连起来,即 E n e w = { ( v 0 , v 3 ) , ( v 2 , v 5 ) , ( v 1 , v 4 ) } E_{new} =\{(v_0, v_3),(v_2, v_5),(v_1, v_4)\} Enew={(v0,v3),(v2,v5),(v1,v4)};
-
依次检查发现加入顶点 v 2 v_2 v2 和 v 3 v_3 v3 的边 ( v 2 , v 3 ) (v_2, v_3) (v2,v3) 后不会构成回路,即当前顶点 v 2 v_2 v2 和 v 3 v_3 v3 不在同一个连通分量中,则可以将顶点 v 2 v_2 v2 和 v 3 v_3 v3 连起来,即 E n e w = { ( v 0 , v 3 ) , ( v 2 , v 5 ) , ( v 1 , v 4 ) , ( v 2 , v 3 ) } E_{new} =\{(v_0, v_3),(v_2, v_5),(v_1, v_4),(v_2, v_3)\} Enew={(v0,v3),(v2,v5),(v1,v4),(v2,v3)}
-
依次检查发现加入顶点 v 3 v_3 v3 和 v 5 v_5 v5 的边 ( v 3 , v 5 ) (v_3, v_5) (v3,v5) 后会构成回路,即当前顶点 v 3 v_3 v3 和 v 5 v_5 v5 在同一个连通分量中,则不能将顶点 v 3 v_3 v3 和 v 5 v_5 v5 连起来;
-
依次检查发现加入顶点 v 0 v_0 v0 和 v 2 v_2 v2 的边 ( v 0 , v 2 ) (v_0, v_2) (v0,v2) 后会构成回路,即当前顶点 v 0 v_0 v0 和 v 2 v_2 v2 在同一个连通分量中,则不能将顶点 v 0 v_0 v0 和 v 2 v_2 v2 连起来;
-
依次检查发现加入顶点 v 1 v_1 v1 和 v 3 v_3 v3 的边 ( v 1 , v 3 ) (v_1, v_3) (v1,v3) 后不会构成回路,即当前顶点 v 1 v_1 v1 和 v 3 v_3 v3 不在同一个连通分量中,则可以将顶点 v 1 v_1 v1 和 v 3 v_3 v3 连起来,即 E n e w = { ( v 0 , v 3 ) , ( v 2 , v 5 ) , ( v 1 , v 4 ) , ( v 2 , v 3 ) , ( v 1 , v 3 ) } E_{new} =\{(v_0, v_3),(v_2, v_5),(v_1, v_4),(v_2, v_3),(v_1, v_3)\} Enew={(v0,v3),(v2,v5),(v1,v4),(v2,v3),(v1,v3)}
-
此时所有顶点均已处于同一个连通分量中,再连上任意一条边都会形成回路,所以最小生成树计算完毕!
3.代码实现(Java)
代码测试的图如下
(1)邻接矩阵存储方式:
/**
* @descript 最小生成树算法:kruskal算法
* @param graph
*/
public void kruskal(int[][] graph) {
int vertices = graph.length;
List<Edge> edges = new ArrayList<>(); // 用于存储图中的所有边
// 将图的边存储在edges列表中
for (int i = 0; i < vertices; i++) {
for (int j = 0; j < vertices; j++) {
if (graph[i][j] != 0) {
Edge edge = new Edge(i, j, graph[i][j]); // 边的起点、终点和权值
edges.add(edge);
}
}
}
// 按权重对边进行排序
Collections.sort(edges, Comparator.comparingInt(e -> e.weight));
int[] parent = new int[vertices]; // 用于存储每个顶点的父节点
Arrays.fill(parent, -1);
List<Edge> mst = new ArrayList<>(); // 用于存储最小生成树的边
int edgeCount = 0;
// 遍历所有边
for (Edge edge : edges) {
int x = find(parent, edge.src); // 查找边的起点的根节点
int y = find(parent, edge.dest); // 查找边的终点的根节点
// 如果边的两个顶点不在同一个连通分量中,则加入最小生成树
if (x != y) {
mst.add(edge); // 将边加入最小生成树
union(parent, x, y); // 合并两个顶点的连通分量
edgeCount++; // 增加边的数量
if (edgeCount == vertices - 1) { // 已找到n-1条边,则退出循环
break;
}
}
}
// 输出最小生成树的边和权重
System.out.println("Edge" + "\t" + "Weight");
for (Edge edge : mst) {
System.out.println(vertexList.get(edge.src) + " -> " + vertexList.get(edge.dest) + "\t" + edge.weight);
}
}
// 查找顶点i的根节点
private int find(int[] parent, int i) {
if (parent[i] == -1) {
return i; // 如果顶点i的父节点为-1,表示i是根节点
}
return find(parent, parent[i]); // 递归查找i的根节点
}
// 合并两个顶点x和y的连通分量
private void union(int[] parent, int x, int y) {
int rootX = find(parent, x);// 查找顶点x的根节点
int rootY = find(parent, y);// 查找顶点y的根节点
parent[rootX] = rootY; // 将顶点x的根节点的父节点设为顶点y的根节点
}
// 辅助类表示边的信息
class Edge {
int src;
int dest;
int weight;
public Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
}
邻接矩阵如下:
邻接矩阵kruskal算法结果如下:
(2)邻接表存储方式:
/**
* @descript 最小生成树算法:kruskal算法
*/
public void kruskal() {
List<Edge> edges = new ArrayList<>();// 用于存储图中的所有边
// 将图的边存储在edges列表中
for (int i = 0; i < vertexNumber; i++) {
EdgeNode current = headNode[i].firstEdge;
while (current != null) {
Edge edge = new Edge(i, vertexList.indexOf(current.vertex), current.weight);
edges.add(edge);
current = current.next;
}
}
// 将图的边存储在edges列表中
Collections.sort(edges, Comparator.comparingInt(e -> e.weight));
int[] parent = new int[vertexNumber]; // 用于存储每个顶点的父节点
Arrays.fill(parent, -1);
List<Edge> mst = new ArrayList<>(); // 用于存储最小生成树的边
int edgeCount = 0;
// 遍历所有边
for (Edge edge : edges) {
int x = find(parent, edge.src);
int y = find(parent, edge.dest);
// 如果边的两个顶点不在同一个连通分量中,则加入最小生成树
if (x != y) {
mst.add(edge); // 将边加入最小生成树
union(parent, x, y); // 合并两个顶点的连通分量
edgeCount++; // 增加边的数量
if (edgeCount == vertexNumber - 1) {// 已找到n-1条边,则退出循环
break;
}
}
}
// 输出最小生成树的边和权重
System.out.println("Edges in the Minimum Spanning Tree:");
for (Edge edge : mst) {
System.out.println(vertexList.get(edge.src) + " - " + vertexList.get(edge.dest) + " : " + edge.weight);
}
}
// 查找顶点i的根节点
private int find(int[] parent, int i) {
if (parent[i] == -1) {
return i; // 如果顶点i的父节点为-1,表示i是根节点
}
return find(parent, parent[i]); // 递归查找i的根节点
}
// 合并两个顶点x和y的连通分量
private void union(int[] parent, int x, int y) {
int rootX = find(parent, x);// 查找顶点x的根节点
int rootY = find(parent, y);// 查找顶点y的根节点
parent[rootX] = rootY; // 将顶点x的根节点的父节点设为顶点y的根节点
}
// 辅助类表示边的信息
class Edge {
int src;
int dest;
int weight;
public Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
}
邻接表如下:
邻接表存储方式下Kruskal算法结果如下:
四、普里姆(Prim)算法对比克鲁斯克尔(Kruskal)算法
- 算法思想:
- 普里姆(Prim)算法:从某⼀个顶点开始构建⽣成树;每次将代价最⼩的新顶点纳⼊⽣成树,直到所有顶点都纳⼊为⽌。
- 克鲁斯克尔(Kruskal)算法:每次选择⼀条权值最⼩的边,使这条边的两头连通(原本已经连通的就不选)直到所有结点都连通
- 时间复杂度:
- 普里姆(Prim)算法: O ( ∣ V ∣ 2 ) O(|V|^2 ) O(∣V∣2)
- 克鲁斯克尔(Kruskal)算法: O ( ∣ E ∣ l o g 2 ∣ E ∣ ) O( |E|log_2 |E| ) O(∣E∣log2∣E∣)
- 适用范围:
- 普里姆(Prim)算法:适合⽤于边稠密图
- 克鲁斯克尔(Kruskal)算法:适合⽤于边稀疏图
五、普里姆(Prim)算法和克鲁斯克尔(Kruskal)算法Java完整代码
本文实现代码地址:
https://github.com/helloWorldchn/DataStructure
代码测试的图如下
1.邻接矩阵(Adjacency Matrix)存储方式
package graph;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
// 邻接矩阵
public class AdjacencyMatrixGraph {
ArrayList<String> vertexList; //存储顶点集合
int[][] arcs; // 邻接矩阵
int vertexNumber; // 图的当前顶点的数目
int edgeNumber; // 图的当前边的数目
// 初始化
public AdjacencyMatrixGraph(int maxVertex) {
vertexList = new ArrayList<String>(maxVertex);
arcs = new int[maxVertex][maxVertex];
vertexNumber = 0;
edgeNumber = 0;
for (int i = 0; i < maxVertex; i++) {
for (int j = 0; j < maxVertex; j++) {
arcs[i][j] = 0;
}
}
}
//插入顶点
public void insertVertex(String vertex) {
vertexList.add(vertex); // 把要插入的值添加到vertexList中即可。
vertexNumber ++;
System.out.println(vertex + " has been entered!");
}
/**
* 添加边(a->b的边)
* @param a
* @param b
* @param weight 权重(不带权的图即weight=1)
*/
public void insertEdge(int a, int b, int weight) {
if(a < vertexNumber && b < vertexNumber) {
if (arcs[a][b] == 0) {
arcs[a][b] = weight; // 将权重添加到arcs[a][b]中即可
System.out.println(vertexList.get(a)+"->"+vertexList.get(b)+" connect edge!");
}
edgeNumber ++;
}
}
// 得到index顶点的第一个邻接结点的下标
public int getFirstNeighbor(int index) {
for (int j = 0; j < vertexNumber; j++) {
if (arcs[index][j] > 0) { // arcs[index][j]即为第一个邻接点的权重
// System.out.println(vertexList.get(j) + " is the first neighbor");
return j;
}
}
return -1;
}
//根据前一个邻接结点的下标来获取下一个邻接结点
public int getNextNeighbor (int a, int b) {
for (int j = b+1; j < vertexNumber; j++) {
if (arcs[a][j] > 0) { // arcs[a][j]即为下一个邻接点的权重
// System.out.println(vertexList.get(j) + " is the next neighbor");
return j;
}
}
return -1;
}
// 返回结点对应的顶点值 0:"A" 1:"B" 2:"C" 3:"D" 4:"E"
public String getValueByIndex(int index) {
return vertexList.get(index);
}
// 获取a->b的权值
public int getWeight(int a, int b) {
return arcs[a][b];
}
/**
* Deep First Search Algorithm: 深度优先搜索算法(dfs)
* @return: java.util.List<java.lang.String>
*/
public List<Object> deepFirstSearch() {
return deepFirstSearch(0);
}
public List<Object> deepFirstSearch(int startIndex) {
List<Object> dfsList = new ArrayList<Object>();
boolean[] isVisited = new boolean[vertexList.size()]; // 定义给数组boolean[], 记录某个结点是否被访问
//遍历所有的结点,进行dfs[回溯]
for (int i = startIndex; i < vertexNumber+startIndex; i++) {
int index = i%vertexNumber;
if (!isVisited[index]) {
deepFirstSearch(isVisited, index ,dfsList);
}
}
return dfsList;
}
public void deepFirstSearch(boolean[] isVisited, int index, List<Object> dfsList) {
dfsList.add(vertexList.get(index)); // 首先我们访问该结点,输出
// System.out.print(vertexList.get(index) + " "); // 首先我们访问该结点,输出
isVisited[index] = true; // index已经在上一行被访问过了,所以变为true
// 深度优先搜索,重复找第一个邻接节点,直到找不到了为止
int w = getFirstNeighbor(index); // 得到index顶点的第一个邻接结点的下标
while (w != -1) { // 如果有邻接顶点就循换,直到没有了为止
if (!isVisited[w]) { // 如果w没被访问过
deepFirstSearch(isVisited, w, dfsList);
}
// 如果w结点已经被访问过,说明一轮深度搜索已完成!寻找下一个邻接顶点
w = getNextNeighbor(index, w);
}
}
/**
* Breadth First Search Algorithm: 广度优先搜索算法(bfs)
* @return: java.util.List<java.lang.Object>
*/
public List<Object> breadthFirstSearch() {
return breadthFirstSearch(0);
}
public List<Object> breadthFirstSearch(int startIndex) {
List<Object> bfsList = new ArrayList<Object>();
boolean[] isVisited = new boolean[vertexList.size()]; // 定义给数组boolean[], 记录某个结点是否被访问
isVisited = new boolean[vertexList.size()];
//遍历所有的结点,依次进行bfs
for (int i = startIndex; i < vertexNumber+startIndex; i++) {
int index = i%vertexNumber;
if (!isVisited[index]) {
breadthFirstSearch(isVisited, index, bfsList);
}
}
return bfsList;
}
public void breadthFirstSearch(boolean[] isVisited, int index, List<Object> bfsList) {
int u; // u代表队列的头结点对应的下标。
int w; // w代表邻接节点
Queue<Integer> queue = new ArrayDeque<>(); // 辅助队列
bfsList.add(vertexList.get(index));
// System.out.print(vertexList.get(index) + " "); // 首先我们访问该结点,输出
isVisited[index] = true; // index已经在上一行被访问过了,所以变为true
queue.add(index); // 使A进入队列
// 对队列中的几个元素依次执行广度遍历
while (!queue.isEmpty()) {
u = queue.poll();// 取出队列的头
w = getFirstNeighbor(u); // 得到第一个邻接点的下标
while (w != -1) { // 如果有邻接顶点就循换,直到没有了为止
if (!isVisited[w]) { // 如果w没被访问过
//如果第一个邻接点未被访问则访问第一个邻接节点
bfsList.add(getValueByIndex(w));
// System.out.print(getValueByIndex(w) + " ");
isVisited[w] = true;
queue.add(w);
}
// 如果w结点已经被访问过,寻找u的下一个邻接顶点。实现广度遍历
w = getNextNeighbor(u, w);
}
}
}
// 显示图对应的矩阵
public void display() {
for (int i = 0; i < vertexNumber; i++) {
System.out.print(vertexList.get(i)+"\t");
}
System.out.println();
for (int i = 0; i < vertexNumber; i++) {
for (int j = 0; j < vertexNumber; j++) {
System.out.print(arcs[i][j] + "\t");
}
System.out.println();
}
}
private static final int INF = Integer.MAX_VALUE;
/**
* @descript 最小生成树算法:prim算法
* @param: graph
*/
public void prim(int[][] graph) {
int vertices = graph.length;
int[] parent = new int[vertices]; // 用于存储最小生成树的父节点
int[] key = new int[vertices]; // 用于存储顶点与最小生成树的最小权重
boolean[] mstSet = new boolean[vertices]; // 用于记录顶点是否已加入最小生成树
Arrays.fill(key, INF); // 初始化所有顶点的权重为无穷大
Arrays.fill(mstSet, false); // 初始化所有顶点均未加入最小生成树
key[0] = 0; // 初始顶点的权重为0,即将其作为起始节点
parent[0] = -1; // 初始节点没有父节点
// 依次加入(n-1)个顶点到最小生成树中
for (int i = 0; i < vertices - 1; i++) {
int u = minKey(key, mstSet, vertices); // 选择权重最小的顶点u加入最小生成树
mstSet[u] = true;
// 更新相邻顶点的权重和父节点信息
for (int v = 0; v < vertices; v++) {
if (graph[u][v] != 0 && !mstSet[v] && graph[u][v] < key[v]) {
parent[v] = u;
key[v] = graph[u][v];
}
}
}
// 打印最小生成树的边和权重
System.out.println("Edge" + "\t" + "Weight");
for (int i = 1; i < graph.length; i++) {
if (parent[i] != -1) { // 避免打印起始节点的边
System.out.println(vertexList.get(parent[i]) + " -> " + vertexList.get(i) + "\t" + graph[parent[i]][i]);
}
}
}
// 寻找权重最小的未加入最小生成树的顶点
private int minKey(int[] key, boolean[] mstSet, int vertices) {
int min = INF, minIndex = -1;
for (int v = 0; v < vertices; v++) {
if (!mstSet[v] && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
/**
* @descript 最小生成树算法:kruskal算法
* @param graph
*/
public void kruskal(int[][] graph) {
int vertices = graph.length;
List<Edge> edges = new ArrayList<>(); // 用于存储图中的所有边
// 将图的边存储在edges列表中
for (int i = 0; i < vertices; i++) {
for (int j = 0; j < vertices; j++) {
if (graph[i][j] != 0) {
Edge edge = new Edge(i, j, graph[i][j]); // 边的起点、终点和权值
edges.add(edge);
}
}
}
// 按权重对边进行排序
Collections.sort(edges, Comparator.comparingInt(e -> e.weight));
int[] parent = new int[vertices]; // 用于存储每个顶点的父节点
Arrays.fill(parent, -1);
List<Edge> mst = new ArrayList<>(); // 用于存储最小生成树的边
int edgeCount = 0;
// 遍历所有边
for (Edge edge : edges) {
int x = find(parent, edge.src); // 查找边的起点的根节点
int y = find(parent, edge.dest); // 查找边的终点的根节点
// 如果边的两个顶点不在同一个连通分量中,则加入最小生成树
if (x != y) {
mst.add(edge); // 将边加入最小生成树
union(parent, x, y); // 合并两个顶点的连通分量
edgeCount++; // 增加边的数量
if (edgeCount == vertices - 1) { // 已找到n-1条边,则退出循环
break;
}
}
}
// 输出最小生成树的边和权重
System.out.println("Edge" + "\t" + "Weight");
for (Edge edge : mst) {
System.out.println(vertexList.get(edge.src) + " -> " + vertexList.get(edge.dest) + "\t" + edge.weight);
}
}
// 查找顶点i的根节点
private int find(int[] parent, int i) {
if (parent[i] == -1) {
return i; // 如果顶点i的父节点为-1,表示i是根节点
}
return find(parent, parent[i]); // 递归查找i的根节点
}
// 合并两个顶点x和y的连通分量
private void union(int[] parent, int x, int y) {
int rootX = find(parent, x);// 查找顶点x的根节点
int rootY = find(parent, y);// 查找顶点y的根节点
parent[rootX] = rootY; // 将顶点x的根节点的父节点设为顶点y的根节点
}
// 辅助类表示边的信息
class Edge {
int src;
int dest;
int weight;
public Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
}
public static void main(String[] args) {
AdjacencyMatrixGraph adjacencyMatrixGraph = new AdjacencyMatrixGraph(5);
adjacencyMatrixGraph.insertVertex("A");
adjacencyMatrixGraph.insertVertex("B");
adjacencyMatrixGraph.insertVertex("C");
adjacencyMatrixGraph.insertVertex("D");
adjacencyMatrixGraph.insertVertex("E");
adjacencyMatrixGraph.insertEdge(0, 1, 15);
adjacencyMatrixGraph.insertEdge(0, 4, 9);
adjacencyMatrixGraph.insertEdge(1, 2, 3);
adjacencyMatrixGraph.insertEdge(2, 3, 2);
adjacencyMatrixGraph.insertEdge(3, 0, 11);
adjacencyMatrixGraph.insertEdge(3, 1, 7);
adjacencyMatrixGraph.insertEdge(4, 2, 21);
adjacencyMatrixGraph.insertEdge(1, 0, 15);
adjacencyMatrixGraph.insertEdge(4, 0, 9);
adjacencyMatrixGraph.insertEdge(2, 1, 3);
adjacencyMatrixGraph.insertEdge(3, 2, 2);
adjacencyMatrixGraph.insertEdge(0, 3, 11);
adjacencyMatrixGraph.insertEdge(1, 3, 7);
adjacencyMatrixGraph.insertEdge(2, 4, 21);
System.out.println("==========Adjacency Matrix==========");
adjacencyMatrixGraph.display();
// adjacencyMatrixGraph.getFirstNeighbor(1);
// adjacencyMatrixGraph.getNextNeighbor(3, 0);
System.out.println("============Deep First Search============");
List<Object> dfsList = adjacencyMatrixGraph.deepFirstSearch(); // A B C D E
System.out.println(dfsList);
System.out.println("==========Breadth First Search===========");
List<Object> bfsList = adjacencyMatrixGraph.breadthFirstSearch(); // A B E C D
System.out.println(bfsList);
System.out.println("============Prim============");
adjacencyMatrixGraph.prim(adjacencyMatrixGraph.arcs);
System.out.println("============Kruskal============");
adjacencyMatrixGraph.kruskal(adjacencyMatrixGraph.arcs);
/*
* A B C D E
* A 0 15 0 11 9
* B 15 0 3 7 0
* C 0 3 0 2 21
* D 11 7 2 0 0
* E 9 0 21 0 0
*/
}
}
运行结果如下:
2.邻接表(Adjacency List)存储方式
package graph;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
//邻接表
public class AdjacencyListGraph {
// 邻接表中表对应的链表的顶点
public class EdgeNode{
String vertex; // 顶点值
int weight; // 以该顶点为终点的边的权值
int adjvex; // 邻接点域,存储该顶点对应的下标
EdgeNode next; // 指向下一个边的地址域
EdgeNode() {
}
EdgeNode(String vertex) {
this.vertex = vertex;
}
EdgeNode(String vertex, EdgeNode next) {
this.vertex = vertex;
this.next = next;
}
EdgeNode(String vertex, int adjvex, int weight) {
this.vertex = vertex;
this.adjvex = adjvex;
this.weight = weight;
}
EdgeNode(String vertex, int adjvex, int weight, EdgeNode next) {
this.vertex = vertex;
this.adjvex = adjvex;
this.weight = weight;
this.next = next;
}
}
// 作为某个点的邻接点的顶点信息
public class VertexNode{
String data; // 顶点的值
EdgeNode firstEdge; // 指向第一条依附该顶点的弧
VertexNode() {
}
VertexNode(String data) {
this.data = data;
}
}
ArrayList<String> vertexList; //存储顶点集合
VertexNode[] headNode; // 邻接表中左侧所有节点,每一行链表的头结点
int vertexNumber = 0; // 图的当前顶点的数目
int edgeNumber = 0; // 图的当前边的数目
// 初始化
public AdjacencyListGraph(int maxVertex) {
vertexList = new ArrayList<String>(maxVertex);
headNode = new VertexNode[maxVertex];
}
//插入顶点
public void insertVertex(String vertex) {
insertVertex(vertex, 1); // 默认权值为1
}
public void insertVertex(String vertex, int weight) {
VertexNode graphNode = new VertexNode(vertex);
// 需要将顶点值添加到adjacencyListNode数组中,但是不知道数组空位在哪里,所以需要循换
for (int i = 0; i < headNode.length; i++){
if(headNode[i] == null){ // 如果第i个位置是null。说明添加到该位置中
headNode[i] = graphNode;// 添加到邻接表中左侧
vertexList.add(vertex); // 把要插入的顶点值添加到vertexList中。
vertexNumber ++;
System.out.println(vertex + " has been entered!");
break;
}
}
}
/**
* @descript 创建一条firstNode与secondNode相连的边,其权重为weight
* @param firstNode 第一个顶点的名称
* @param secondNode 第二个顶点的名称
* @param weight 两点之间的边的权重
*/
public void insertEdge(String firstNode, String secondNode) {
insertEdge(firstNode, secondNode, 1);
}
public void insertEdge(String firstNode, String secondNode, int weight) {
boolean isContainsFirst = vertexList.contains(firstNode);
boolean isContainsSecond = vertexList.contains(secondNode);
if (isContainsFirst && isContainsSecond) { // 要添加的两个点存在于顶点集合里
for (int i = 0; i < headNode.length; i++) { // 遍历所有的头结点
if (headNode[i] != null && headNode[i].data.equals(firstNode)) {
VertexNode vertexNode = headNode[i]; // 要对哪个头结点操作,先标记出来
boolean isExist = false; // 标记要添加的边是否存在
int adjvex = -1;
for (int j = 0; j < headNode.length; j++) { // 遍历所有的头结点
if (headNode[j].data.equals(secondNode)) {
adjvex = j;
}
}
if(vertexNode.firstEdge == null) {
EdgeNode edgeNode = new EdgeNode();
edgeNode.vertex = secondNode;
edgeNode.weight = weight;
edgeNode.adjvex = adjvex;
vertexNode.firstEdge = edgeNode;
System.out.println(firstNode+"->"+secondNode+" have added edges!");
continue;
}
EdgeNode edgeNode = vertexNode.firstEdge;
// 以此寻找graphNode为头结点的链表所有节点,看是否有和secondNode相同的
// 如果和secondNode名字相同,说明两个节点间的边已经存在
while (edgeNode.next != null) {
if(edgeNode.vertex.equals(secondNode)) {
isExist = true; // 两个节点间的边已经存在
break;
}
edgeNode = edgeNode.next;
}
// 两个节点之间的边不存在,那么在链表中添加信息
if (!isExist) {
EdgeNode newEdgeNode = new EdgeNode();
newEdgeNode.vertex = secondNode;
newEdgeNode.weight = weight;
newEdgeNode.adjvex = adjvex;
edgeNode.next = newEdgeNode;
System.out.println(firstNode+"->"+secondNode+" have added edges!");
}
break;
}
}
}
}
//打印
public void display(){
for (int i = 0; i < headNode.length; i++) {
EdgeNode edgeNode = headNode[i].firstEdge;
System.out.print(headNode[i].data);
if (edgeNode != null) {
System.out.print("-->"+"\t"+ "[" + headNode[edgeNode.adjvex].data + "|" + edgeNode.weight + "]");
EdgeNode temp = edgeNode.next;
while (temp != null){
System.out.print("-->"+"\t"+ "[" + headNode[temp.adjvex].data + "|" + temp.weight + "]");
temp = temp.next;
}
System.out.println();
} else {
break;
}
}
}
/**
* Deep First Search Algorithm: 深度优先搜索算法(dfs)
* @return: java.util.List<java.lang.String>
*/
public List<Object> deepFirstSearch() {
return deepFirstSearch(0) ; // 默认遍历起始节点为0
}
public List<Object> deepFirstSearch(int startIndex) {
List<Object> dfsList = new ArrayList<>(); // 存放遍历结果
int i;
boolean[] visited = new boolean[vertexNumber]; // 记录每个顶点是否被访问过
for (i = startIndex; i < vertexNumber+startIndex; i++) {
int index = i%vertexNumber;
if (!visited[index]) {
deepFirstSearch(index, visited, dfsList);
}
}
return dfsList;
}
public void deepFirstSearch(int index, boolean[] visited, List<Object> dfsList) {
dfsList.add(headNode[index].data); // 遍历到第index节点
EdgeNode edgeNode = headNode[index].firstEdge; // 此顶点的第一条边
visited[index] = true;
while (edgeNode != null) {
if (!visited[edgeNode.adjvex]) {
deepFirstSearch(edgeNode.adjvex, visited, dfsList);
}
edgeNode = edgeNode.next;
}
}
/**
* Breadth First Search Algorithm: 广度优先搜索算法(bfs)
* @return: java.util.List<java.lang.Object>
*/
public List<Object> breadthFirstSearch() {
return breadthFirstSearch(0);
}
public List<Object> breadthFirstSearch(int startIndex) {
List<Object> bfsList = new ArrayList<Object>();
boolean[] isVisited = new boolean[vertexList.size()]; // 定义给数组boolean[], 记录某个结点是否被访问
isVisited = new boolean[vertexList.size()];
//遍历所有的结点,依次进行bfs
for (int i = startIndex; i < vertexNumber+startIndex; i++) {
int index = i%vertexNumber;
if (!isVisited[index]) {
breadthFirstSearch(isVisited, index, bfsList);
}
}
return bfsList;
}
public void breadthFirstSearch(boolean[] isVisited, int index, List<Object> bfsList) {
Queue<Integer> queue = new ArrayDeque<>(); // 辅助队列
if (!isVisited[index]) {
isVisited[index] = true;
bfsList.add(headNode[index].data);
queue.add(index); // 进入队列
}
isVisited[index] = true; // index已经在上一行被访问过了,所以变为true
// 对队列中的几个元素依次执行广度遍历
while (!queue.isEmpty()) {
int j = queue.poll();// 取出队列的头
EdgeNode edgeNode = headNode[j].firstEdge;
while (edgeNode != null) {
int k = edgeNode.adjvex;
if (!isVisited[k])
{
isVisited[k] = true;
bfsList.add(headNode[k].data);
queue.add(k);
}
edgeNode = edgeNode.next;
}
}
}
/**
* @descript 最小生成树算法:prim算法
* @param: graph
*/
public void prim() {
int V = vertexNumber;
boolean[] mstSet = new boolean[V]; // 存储顶点是否已加入最小生成树的集合
int[] key = new int[V]; // 存储顶点到最小生成树的最小权重
int[] parent = new int[V]; // 存储最小生成树的边
Arrays.fill(key, Integer.MAX_VALUE);
key[0] = 0; // 将起始顶点的key值设为0
parent[0] = -1; // 将起始顶点的父节点设为-1
for (int count = 0; count < vertexNumber - 1; count++) {
int u = minKey(key, mstSet, vertexNumber); // 选择key值最小的顶点u
mstSet[u] = true; // 将顶点u标记为已访问
// 更新与顶点u相邻的顶点的key值和parent信息
EdgeNode current = headNode[u].firstEdge;
while (current != null) {
int v = vertexList.indexOf(current.vertex);
int weight = current.weight;
if (!mstSet[v] && weight < key[v]) {
parent[v] = u;
key[v] = weight;
}
current = current.next;
}
}
// 输出最小生成树的边和权重
System.out.println("Edge" + "\t" + "Weight");
for (int i = 1; i < vertexNumber; i++) {
if (headNode[i].firstEdge.vertex == vertexList.get(parent[i])) {
// 不是头结点,直接确定对应权值
System.out.println(vertexList.get(parent[i]) + " -> " + vertexList.get(i) + "\t" + headNode[i].firstEdge.weight);
} else {
// 不是头结点,顺着链表遍历,寻找对应权值
EdgeNode currentEdge = headNode[i].firstEdge;
while (currentEdge!=null) {
if (currentEdge.vertex == vertexList.get(parent[i])) {
System.out.println(vertexList.get(parent[i]) + " -> " + vertexList.get(i) + "\t" + currentEdge.weight);
break;
}
currentEdge = currentEdge.next;
}
}
}
}
// 选择key值最小的顶点
private int minKey(int[] key, boolean[] mstSet, int V) {
int min = Integer.MAX_VALUE, minIndex = -1;
for (int v = 0; v < V; v++) {
if (!mstSet[v] && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
/**
* @descript 最小生成树算法:kruskal算法
*/
public void kruskal() {
List<Edge> edges = new ArrayList<>();// 用于存储图中的所有边
// 将图的边存储在edges列表中
for (int i = 0; i < vertexNumber; i++) {
EdgeNode current = headNode[i].firstEdge;
while (current != null) {
Edge edge = new Edge(i, vertexList.indexOf(current.vertex), current.weight);
edges.add(edge);
current = current.next;
}
}
// 将图的边存储在edges列表中
Collections.sort(edges, Comparator.comparingInt(e -> e.weight));
int[] parent = new int[vertexNumber]; // 用于存储每个顶点的父节点
Arrays.fill(parent, -1);
List<Edge> mst = new ArrayList<>(); // 用于存储最小生成树的边
int edgeCount = 0;
// 遍历所有边
for (Edge edge : edges) {
int x = find(parent, edge.src);
int y = find(parent, edge.dest);
// 如果边的两个顶点不在同一个连通分量中,则加入最小生成树
if (x != y) {
mst.add(edge); // 将边加入最小生成树
union(parent, x, y); // 合并两个顶点的连通分量
edgeCount++; // 增加边的数量
if (edgeCount == vertexNumber - 1) {// 已找到n-1条边,则退出循环
break;
}
}
}
// 输出最小生成树的边和权重
for (Edge edge : mst) {
System.out.println(vertexList.get(edge.src) + " - " + vertexList.get(edge.dest) + " : " + edge.weight);
}
}
// 查找顶点i的根节点
private int find(int[] parent, int i) {
if (parent[i] == -1) {
return i; // 如果顶点i的父节点为-1,表示i是根节点
}
return find(parent, parent[i]); // 递归查找i的根节点
}
// 合并两个顶点x和y的连通分量
private void union(int[] parent, int x, int y) {
int rootX = find(parent, x);// 查找顶点x的根节点
int rootY = find(parent, y);// 查找顶点y的根节点
parent[rootX] = rootY; // 将顶点x的根节点的父节点设为顶点y的根节点
}
// 辅助类表示边的信息
class Edge {
int src;
int dest;
int weight;
public Edge(int src, int dest, int weight) {
this.src = src;
this.dest = dest;
this.weight = weight;
}
}
public static void main(String[] args) {
AdjacencyListGraph adjacencyListGraph = new AdjacencyListGraph(5);
adjacencyListGraph.insertVertex("A");
adjacencyListGraph.insertVertex("B");
adjacencyListGraph.insertVertex("C");
adjacencyListGraph.insertVertex("D");
adjacencyListGraph.insertVertex("E");
adjacencyListGraph.insertEdge("A", "B", 15);
adjacencyListGraph.insertEdge("A", "E", 9);
adjacencyListGraph.insertEdge("B", "C", 3);
adjacencyListGraph.insertEdge("C", "D", 2);
adjacencyListGraph.insertEdge("D", "A", 11);
adjacencyListGraph.insertEdge("D", "B", 7);
adjacencyListGraph.insertEdge("E", "C", 21);
adjacencyListGraph.insertEdge("B", "A", 15);
adjacencyListGraph.insertEdge("E", "A", 9);
adjacencyListGraph.insertEdge("C", "B", 3);
adjacencyListGraph.insertEdge("D", "C", 2);
adjacencyListGraph.insertEdge("A", "D", 11);
adjacencyListGraph.insertEdge("B", "D", 7);
adjacencyListGraph.insertEdge("C", "E", 21);
System.out.println("==========Adjacency List==========");
adjacencyListGraph.display();
System.out.println("==========Deep First Search==========");
List<Object> dfsList = adjacencyListGraph.deepFirstSearch();
System.out.println(dfsList);
System.out.println("==========Breadth First Search==========");
List<Object> bfsList = adjacencyListGraph.breadthFirstSearch();
System.out.println(bfsList);
System.out.println("============Prim============");
adjacencyListGraph.prim();
System.out.println("============Kruskal============");
adjacencyListGraph.kruskal();
/*
* A--> [B|15]--> [E|9]--> [D|11]
* B--> [C|3]--> [A|15]--> [D|7]
* C--> [D|2]--> [B|3]--> [E|21]
* D--> [A|11]--> [B|7]--> [C|2]
* E--> [C|21]--> [A|9]
*/
}
}
运行结果如下: