最小生成树
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图联通的最少的边。
最小生成树
3条构造最小生成树的准则:
- 只能使用该网络中的边来构造最小生成树
- 只能使用恰好n-1条边来联结网络中的n个结点
- 选用的这个n-1条边不能构成回路。
MST性质
假设N=(V,{E})是一个连通网,U是顶点集合V的一个非空子集。若(u,v)是一条具有最小值(代价)的边,其中u属于U,v属于V-U(即U对立集合),那么必存在一颗包含边(u,v)的最小生成树。
注意prim和kruskal算法都是利用MST性质
如何利用MST性质来构造最小生成树?
我们知道权值最小的肯定在最小生成树中,权值次小的边也会被至少一颗生成树采用,关键第三小的边选择(由于第三条边可能出现回路,从而不满足树的定义)。由此我们可以使用不同策略来解决回路问题常见的算法有Prim算法和kruskal算法。(都用了贪心策略)
一 Prim算法
三种实现(from wiki)
邻接矩阵、搜索 O(V2)
二叉堆、邻接表 O((V + E) log(V)) = O(E log(V))
邻接矩阵实现, 时间复杂度O(V2)
/*
Prim算法
LowCost[i]:当前生成树(U集合)到顶点i的多条边中最短的一条边。
inc[i]:顶点i不在生成树中。1表示在里面,0反之。
pre[i]:保存顶点i的前驱结点。
MGraph用的邻接矩阵表示,其中用edges[i][j]来记录i-->j是否有关系。n表示顶点个数,e表示边的个数。
*/
const int INF = 10000000;
const int N = 2025;
int graph[N][N], LowCost[N], pre[N];
bool inc[N];
int n;
int Prim(int s)
{
int i, j, cmin;
for(i=0;i<n;i++)
{
LowCost[i] = graph[s][i];
inc[i] = 0;
pre[i] = 0;
}
inc[s] = 1;
/*------------------------------------------------我是分割线---------------------------------------------------------*/
for(i=1;i<n;i++)
{
cmin = INF;//表示无穷或者大于所有的权值就行
for(j=0;j<n;j++)
{
if(!inc[j]&&LowCost[j]<=cmin)
{
if(LowCost[j]==cmin&&pre[j]>=s)
continue;
cmin = LowCost[j];
s = j;
}
}
if(cmin==INF)//查找失败
break;
//找出LowCost的最短代价(权值)即找出最短边中的最短的一条。
inc[s] = 1;//将该条边加入生成树中
//加入一个顶点v之后,生成树到各个不在生成树的顶点的最小值也会变。
//即更新LowCost数组(只计算不在生成树的顶点且只要更新与新添进去的顶点相关的边)。
for(j=0;j<n;j++)
{
if(!inc[j]&&graph[s][j]<=LowCost[j])
{
if(LowCost[j]==graph[s][j]&&pre[j]<=s)
continue;
pre[j] = s;
LowCost[j] = graph[s][j];
}
}
}
return sum;
}
int main()
{
//FRE;
int x, y, t;
int i, j;
/*很重要的一点啊, 看清楚是无向图还是有向图!!!!*/
for(i=0;i<n;++i)
for(j=0;j<n;++j)
graph[i][j] = graph[j][i] = INF;//默认设置断开~
for(i=0;i<m;i++)
{
scanf("%d%d%d", &x, &y, &t);
x--;
y--;
if(graph[x][y]<t) //一般情况下要判断重边的 (>_<)
graph[x][y] = graph[y][x] = t;
}
}