白书写到最小生成树了,突然发现自己忘得差不多,简单回忆一下
最小生成树问题
给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫做生成树(Spanning Tree)如果边上有权值,那么使得边权和最小的生成树叫做最小生成树( MST, Minimum Spanning Tree )
最常见的问题,多个村子修路,使得道路建设费用最小。注意如果图不连通,肯定不存在生成树。
Prim 算法-给定初始点的最小生成树
我们假设有一棵只包含一个顶点
v
v
v的树
T
T
T,然后贪心地选取
T
T
T和其他顶点之间相连的最小权值的边,并把它加到
T
T
T中。不断进行这个操作,就可以得到一棵生成树了。具体的证明就算啦
算法实现的难点在于:如何查找树T与其他顶点之间的最小权值的边,
(1)维护一个
u
s
e
d
used
used数组,
u
s
e
d
[
i
]
used[i]
used[i]表示
i
i
i是否已经在生成树中
(2)维护一个
m
i
n
c
o
s
t
mincost
mincost数组,
m
i
n
c
o
s
t
[
i
]
mincost[i]
mincost[i]表示从已选生成树出发到每个顶点的最小权值。
每次遍历
m
i
n
c
o
s
t
mincost
mincost数组,选没有
u
s
e
use
use的而且cost最小的那个点
v
v
v,将顶点
v
v
v加入生成树中,最后,更新
m
i
n
c
o
s
t
mincost
mincost数组的所有距离(因为新加入一个点
v
v
v,更新所有距离为
m
i
n
c
o
s
t
[
i
]
=
m
i
n
(
m
i
n
c
o
s
t
[
i
]
,
d
i
s
[
v
,
i
]
]
)
mincost[i]=min(mincost[i], dis[v,i]])
mincost[i]=min(mincost[i],dis[v,i]])。
#define MAX 1555
#define inf 1e9
int cost[MAX][MAX]; // cost[u][v]表示边e=(u,v)的权值( 不存在的情况下设为INF)
int mincost[MAX]; // 从集合X出发的边到每个顶点的最小权值
bool used[MAX]; // 顶点i是否包含在集合X中
int V; // 顶点数
int prim(int s) {
//1:初始化
memset(used, 0, sizeof(used));
fill(mincost, mincost + MAX, inf);
mincost[s] = 0; int res = 0;
while (true) {
int v = -1;
//2:找到没使用过的,而且当前距离T集合最近的
for (int i = 0; i < V; i++) {
if (!used[i] && (v == -1 || mincost[i] < mincost[v]))
v = i;
}
if (v == -1)break;//找不到就结束
used[v] = 1;
res += mincost[v];
//3:从这里开始再更新一遍最短距离
for (int i = 0; i < V; i++)
mincost[i] = min(mincost[i], cost[v][i]);
}
}
Kruskal算法
Kruskal算法按照边的权值的顺序从小到大査看一遍,如果不产生圈
(重边等也算在内 ),就把当前这条边加人到生成树中。至于这个算法为什么是正确的,其实和Prim算法证明的思路基本相同,在此就不详细说明了。
这里的重点问题在于产生圈如何判断,此时使用到了基础的并查集,每次加入一个点就将点与生成树点集中任何一个点进行union,如果两个节点已经同根了,那么就会产生圈。
#define inf 10000000
#define MAX 2005 //边的最大数目
#define p pair<int,int>
struct edge {
int from, to, cost;//只需要存储边的关系
bool operator<(const edge & e) { return cost < e.cost; }
};
vector<edge>G;
int V, E;//顶点数目与边的数目
int root[MAX];
int find(int x) {
if (x == root[x])return x;
else return root[x] = find(root[x]);
}
void unite(int x, int y) {
root[x = find(x)] = root[find(y)];//双跟合并
}
int Kruskal() {
int res = 0;
sort(G.begin(), G.end());
for (int i = 0; i <= V; i++)root[i] = i;//并查集的初始化
for (unsigned i = 0; i < G.size(); i++) {
edge e = G[i];
if (find(e.from) != find(e.to)) {//两个点不在一个集合中,加上该边不会导致环
unite(e.from, e.to);//合并到一个集合
res += e.cost;
}
}
return res;
}