生成树:如果连通图G的一个子图是一棵包含G的所有顶点的树,则该子图称为G的生成树。生成树不唯一,不同起点遍历所得生成树不同。
最小生成树:边权最小的生成树。
最小边原则:图中权值最小的边(如果唯一的话)一定在最小生成树上。
唯一性:一棵生成树上,如果各边的权都不相同,则最小生成树是唯一的。反之不然。
生成树一定是无向图。
Prim
基础是两个属性:
环属性:一棵生成树上,增加一条边e,再删除e所在环上的最大边,会得到另一棵“更好”的生成树(如果e不是最大边)
剪切属性:在图中,剪切将顶点划分成两个不相交集合。交叉边为这些顶点在两个不同集合的边。对于任何一个剪切,各条最小的交叉边都属于某个MST,且每个MST中都包含一条最小交叉边。
简述:MST_Prim(G, r)
(1)将G剪切成两个集合A、B,A中只有一个点r
(2)取最小权的交叉边(x,y),x∈A, y∈B
(3)将y加入A并更新
(4)如果已经加了n-1条边,结束。否则,转 (3)
示例:
模版:
int flag[maxm]={};//是否在A中。
int dis[maxm];//集合A与i点的交叉边权值,如果flag[i]==1,那么dis[i]无意义。
int sum=0;//MST边权总和。
void prim()
{
flag[1]=1;//从任意一点出发均可,此处用第一个点。第一个点已被选入集合A。
for(int i=1;i<=n;i++) dis[i]=a[1][i];//初始化。
dis[1]=0; //已在A中,无交叉边。
for(int i=1;i<n;i++)
{
int minn=1000000;
int k=0;
for(int j=1;j<=n;j++)
if(!flag[j]&&dis[j]<minn) minn=dis[j],k=j;//在现有的交叉边中找到最小的。
flag[k]=1;//将其加入A。
sum+=dis[k];
if(k==0) break;//同dijkstra,非联通图的情况。
for(int j=1;j<=n;j++)
if(!flag[j]&&dis[j]>a[k][j]) dis[j]=a[k][j];//利用新加入的点更新集合A与B的交叉边。
}
return;
}
基于点的迭代,稳定O(n^2)。
堆优化后:O((|E|+|V|)*log|V|) ,适合稀疏图。
Kruskal
简述:MST_Kruskal(G)
(1)将G所有条边按权从小到大排序;图mst开始为空
(2)从小到大次序取边(x,y)
(3)若加入边(x,y),mst就有环,则放弃此边,转(2)
(4)将边(x,y)加入mst,如果已经加了n-1条边,结束。否则,转 (2)
判环即利用并查集,改日另写。
int n,m,summ=0;
struct edge
{
int q,w,v;
}e[maxm];//边表。
int fa[maxn]={};//根节点。
sort(e+1,e+m+1,mycmp);//升序排序。
for(int i=1;i<=n;++i) fa[i]=i;//根节点初始化。
inline int find(int x)
{
if(fa[x]==x) return x;
fa[x]=find(fa[x]);
return fa[x];
}//找爸爸。
/*或者另一种装13的找爸爸写法:
inline int find(int x)
{
return(fa[x]==x?x:fa[x]=find(fa[x]));
}*/
void kru()
{
int flag=0;//边数计数器。
for(int i=1;i<=m;i++)
{
int x=find(e[i].q);
int y=find(e[i].w);
if(x!=y)//如果它们不是同一个爸爸生的,即加入它们不会形成一个环。
{
fa[x]=y;//合并。
flag++;//计数。
summ+=e[i].v;
if(flag==n-1) return;//如果边已达到n-1条,结束。
}
}
return;
}
基于边的迭代,适合稀疏图,O(|E|*log|E| +|N|*A(|V|))。O(|E|*log|E| )用于排序。
最大生成树可把数据全部取负再操作。注意初始化问题。