生成树:连通图包含全部顶点的一个极小连通子图
最小生成树:对于带权无向连通图 G=(V, E),G的所有生成树当中边的权值之和最小的生成树为 G 的最小生成树(MST)
Prim 算法适用于稠密图,Kruskal算法适用于稀疏图
# 伪代码
void Prim(G, T){
T = 空集;
U = {w};
while(V-U)!=空集){
找到(u,v)使 u∈U 与 v∈(V-U),且权值最小的边;
T=T∪{(u,v)};
U=U∪{v};
}
}
C++ 代码
void MST_Prim(Graph G){
int min_weight[G.vexnum]; //存储最小权重 索引代表节点
int adjvex[G.vexnum]; //存储边的节点 u
for(int i=0;i<G.vexnum; i++){
min_weight[i]=G.Edge[0][i];
adjvex[i] = 0;
}
int min_arc; //当前挑选的最小的边
int min_vex; //挑选的最小的权重边的节点 v
for(int i=1; i<G.vexnum; i++){
min_arc = MAX;
for(int j=1; j<G.vexnum; j++)
if(min_weight[j] != 0 && min_weight[j] < min_arc){
min_arc = min_weight[j];
min_vex = j;
}
min_weight[min_vex] = 0;
for(int j=0; j<G.vexnum; j++){
if(min_weight[j] != 0 && G.Edge[min_vex][j] < min_weight[j]){
min_weight[j] = G.Edge[min_vex][j];
adjvex[j] = min_vex;
}
}
}
}
Python 代码
def mst_prim(graph):
vex_num = len(graph)
min_weight = [] # 存储最小权重 索引代表节点
adjvex = [] # 存储边的节点 u
for i in graph[0]:
min_weight.append(i)
adjvex.append(0)
min_vex = 0
for i in range(1, vex_num):
min_arc = MAX
for j in range(vex_num):
if min_weight[j] != 0 and min_weight[j] < min_arc:
min_arc = min_weight[j]
min_vex = j
min_weight[min_vex] = 0
for k in range(vex_num):
if min_weight[k] != 0 and min_weight[k] > graph[min_vex][k]:
min_weight[k] = graph[min_vex][k]
adjvex[k] = min_vex
return adjvex
以这个图为例
Python示例
MAX = 10e6
if __name__ == '__main__':
# 上图的邻接矩阵表示法, A-E 分别用索引 0-4 表示
graph = [
[0, 3, 1, MAX, 4],
[3, 0, 2, MAX, MAX],
[1, 2, 0, 5, 6],
[MAX, MAX, 5, 0, MAX],
[4, MAX, 6, MAX, 0]
]
adjvex = mst_prim(graph)
print(adjvex)
'''
结果:
[0, 2, 0, 2, 0]
'''
结果表示最小生成树的边有:1-2, 2-0, 3-2, 4-0 即 (B-C, C-A, D-C, E-A)即下图
时间复杂度 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) 适用于稠密图
# 伪代码
void Kruskal(V, T){
T=V;
nums=n;
while(nums>1){
从E中取出权值最小的边(v, u);
if(v和u属于T中不同的连通分量){
T=T∪{(v,u)};
nums--;
}
}
}
使用排序+并查集 (文末) 的方法(排序可以使用快排或堆排序)
C++ 代码
#define SIZE 100
int UFSets[SIZE];
void Initial(int S[]){
for(int i=0;i<SIZE;i++)
S[i]=-1;
}
int Find(int s[], int x){
while(S[x]>=0)
x=S[x];
return x
}
void Union(int S[], int Root1, int Root2){
S[Root2] = Root1;
}
typedef struct Edge{
int a,b;
int weight;
};
void MST_Kruskal(Graph G, Edge* edges, int* parent){
heap_sort(edges);
Initial(parent);
for(int i=0, i<G.arcnum; i++){
int a_root = Find(parent, edges[i].a);
int b_root = Find(parent, edges[i].b);
if(a_root != b_root)
Union(parent, a_root, b_root);
}
}
class UFSet:
def __init__(self, length):
self.set = []
for i in range(length):
self.set.append(-1)
def find(self, x):
while self.set[x] >= 0:
x = self.set[x]
return x
def union(self, root1, root2):
self.set[root2] = root1
def mst_kruskal(graph):
res = [] # 存储最终选择的边
nums = len(graph)
edge_list = [] # 存储所有的边 [(节点,节点,权重)]
# 对所有的边存储排序
for i in range(nums):
for j in range(i, nums):
if graph[i][j] < MAX:
edge_list.append((i, j, graph[i][j]))
# 使用排序对边排序(可以自己使用快速排序,堆排序)
edge_list = sorted(edge_list, key=lambda x:x[2])
parent = UFSet(nums) # 并查集初始化
for i in range(len(edge_list)):
a_root = parent.find(edge_list[i][0])
b_root = parent.find(edge_list[i][1])
if a_root != b_root:
parent.union(a_root, b_root)
res.append(edge_list[i])
return res
还是以上个图为例
if __name__ == '__main__':
graph = [
[0, 3, 1, MAX, 4],
[3, 0, 2, MAX, MAX],
[1, 2, 0, 5, 6],
[MAX, MAX, 5, 0, MAX],
[4, MAX, 6, MAX, 0]
]
res = mst_kruskal(graph)
print(res)
'''
结果:
[(0, 2, 1), (1, 2, 2), (0, 4, 4), (2, 3, 5)]
'''
时间复杂度 O ( ∣ E ∣ log ∣ E ∣ ) O(|E| \log|E|) O(∣E∣log∣E∣) 适用于稀疏图
一种简单的集合表示。通常用树的双亲表示法作为并查集的存储结构。通常用数组元素的下标代表元素名,用根节点的下标代表集合名,根节点的双亲节点为负数。
Initial(S) 将集合 S 中的每个元素都初始化为只有一个单元素的子集合
Union(S, Root1, Root2) 把集合 S 中的子集合(互不相交)Root2 并入子集合 Root1
Find(S, x) 查找集合 S 中单元素 x 所在子集合,并返回该子集合的名字
#define SIZE 100
int UFSets[SIZE];
void Initial(int S[]){
for(int i=0;i<SIZE;i++)
S[i]=-1;
}
int Find(int s[], int x){
while(S[x]>=0)
x=S[x];
return x
}
void Union(int S[], int Root1, int Root2){
S[Root2] = Root1;
}