最小生成树-Minimum Spanning Tree
- 一个重心:总权最小连通子图(遍历所有边和点)
- 两个基本点:
- Prim:由点入手找边
- Kruskal:以边入手找点
Prim
Prim通过逐点遍历搜索,找与现有最小生成树(初态仅有一个点,然后逐渐生成)最近的一个点,不断填充来生成MST。
int prim(vector<vector<int>> graph)
{
int pointnum = graph.size();
vector<int> lowcost(pointnum);
vector<int> mst(pointnum);
int minid, min, sum = 0;
for (int i = 0; i < pointnum; i++)
{
lowcost[i] = graph[0][i];
mst[i] = 1;
}
mst[0] = 0;
for (int i = 1; i < pointnum; i++)
{
minid = 0;
min = INT32_MAX;
for (int j = 1; j < pointnum; j++)
{
if (lowcost[j] < min && lowcost[j] != 0)
{
min = lowcost[j];
minid = j;
}
}
cout << "V" << mst[minid]+1 << "-V" << minid+1 << "=" << min << endl;
sum += min;
mst[minid] = 0;
lowcost[minid] = 0;
for (int j = 1; j < pointnum; j++)
{
if (lowcost[j] > graph[minid][j] && lowcost[j] != 0)
{
lowcost[j] = graph[minid][j];
mst[j] = minid;
}
}
}
return sum;
}
int main()
{
vector<vector<int>> weight = {
vector<int>{1,2,6},
vector<int>{1,3,1},
vector<int>{1,4,5},
vector<int>{2,3,5},
vector<int>{2,5,3},
vector<int>{3,4,5},
vector<int>{3,5,6},
vector<int>{3,6,4},
vector<int>{4,6,2},
vector<int>{5,6,6},
};
int pointnum = 6;
int edgenum = 10;
//初始化图G,权填充为最大值
vector<vector<int>> graph(pointnum);
for (int i = 0; i < pointnum; i++)
{
graph[i].resize(pointnum);
for (int j = 0; j < pointnum; j++)
{
graph[i][j] = INT32_MAX;
}
}
//构建图G
for (int k = 0; k < weight.size(); k++)
{
int i = weight[k][0]-1;
int j = weight[k][1]-1;
graph[i][j] = weight[k][2];
graph[j][i] = weight[k][2];
}
int cost = prim(graph);
return 0;
}
Kruskal
Kruskal通过找最短的不构成回路的边来生成树,算法实现有一点比较困扰的是迭代寻找路径起点,点明了之后就不难理解。
struct edge
{
//起点,终点,权
int fr, to, w;
bool operator < (const edge &a) const {
return w<a.w;
}
};
vector<edge> e;
vector<int> pre;
vector<edge> mst;
//赋值
void add(int fr, int to, int w)
{
edge tempedge;
tempedge.fr = fr;
tempedge.to = to;
tempedge.w = w;
e.push_back(tempedge);
}
//寻找路径起点
int fin(int x)
{
if (x == pre[x])
return x;
return pre[x] = fin(pre[x]);
}
void Kruskal()
{
int n = 6;
pre.resize(n + 1);
for (int i = 1; i <= n; ++i)
pre[i] = i;
sort(e.begin(), e.begin() +e.size() );
int ans = 0;
for (int i = 0; i<e.size(); ++i) {
int u = fin(e[i].fr);
int v = fin(e[i].to);
//判断是否加入一条边后,生成树中的两点是否有相同的起点
if (u != v) {
mst.push_back(e[i]);
ans += e[i].w;
pre[u] = v;
}
}
for (int k = 0; k < mst.size(); k++) {
cout << "V" << mst[k].fr << "-V" << mst[k].to << "=" << mst[k].w<< endl;
}
printf("%d\n", ans);
}
int main()
{
vector<vector<int>> weight = {
vector<int>{1,2,6},
vector<int>{1,3,1},
vector<int>{1,4,5},
//vector<int>{1,5,INT32_MAX},
//vector<int>{1,5,INT32_MAX},
vector<int>{2,3,5},
//vector<int>{2,4,INT32_MAX},
vector<int>{2,5,3},
//vector<int>{2,6,INT32_MAX},
vector<int>{3,4,5},
vector<int>{3,5,6},
vector<int>{3,6,4},
//vector<int>{4,5,INT32_MAX},
vector<int>{4,6,2},
vector<int>{5,6,6},
};
for (int k = 0; k < weight.size(); k++)
{
add(weight[k][0], weight[k][1], weight[k][2]);
add(weight[k][1], weight[k][0], weight[k][2]);
}
Kruskal();
return 0;
}
比较
由于算法复杂度的问题,在稠密连接的树中用Prim,稀疏的树中用Kruskal。