MST(Minimum Spanning Tree,最小生成树)
所谓的最小生成树,即在一个有n个结点的连通图中,n个结点之间有若干条边相连,这些边有不同的权值,要求找到使这些点连通并且所得到的权值总和最小,这样的一棵树被叫做最小生成树。从一张图中找出最小生成树的算法有两种,prim算法与 kruskal算法。
prim算法
思路:prim算法的代码类似于dijkstra算法,每次从源点出发,更新周围与其相连的点。但这里的dis[j]并不是到源点的最小距离,而是从相邻的点到达该点的最小距离。每次,我们会选择一条花费最小的边加入生成树,加入这条边后,则更新对应的终点延伸出去的那些点的dis,以便下次选择花费最小的边。 这样不断选择,直到所有的点都被标记为vis之后,我们得到的树就是最小生成树。
int prim()
{
int i,j,ans=0;
for(i=1;i<=n;i++) dis[i]=inf;
memset(vis,0,sizeof(vis));
dis[1]=0;//1为源点
for(i=1;i<=n;i++)
{
int temp=inf,k=1;
for(j=1;j<=n;j++)
{
if(!vis[j]&&temp>dis[j])
{
k=j;
temp=dis[j];
}
}
if(temp==inf) break;
vis[k]=1;
ans+=temp;//找到该边之后加上它的权值,并把该点终点标记为vis,表示其已经加入生成树中
for(j=1;j<=n;j++)
if(!vis[j]&&dis[j]>map[k][j])
dis[j]=map[k][j];
}
return ans;
}
Kruskal算法
思路:Kruskal算法,该算法基于贪心思想,先对边权从小到大排序,每次都选择最小边权的边,如果这条边的两个点之间未连通,则把这条边加入到生成树中。否则考虑下一个点。 不断地重复这个操作,基于贪心的思想,最后我们将得到总边权最小的一颗生成树。关于如何判断点与点之间是否在同一个集合里,需要用到并查集。
并查集主要的思想就是,维护一个set数组,set[i]的值代表i的祖先,一开始初始化为本身。如果合并两个点f1和f2,则设置set[f1]=f2。我们可以通过判断两个点的祖先是否相同来判断他们是否在同一个集合。如果只是简单的set[f1]=f2的话,最后这颗树有可能很深,因此可以采用路径压缩,将树压缩成只有两层。具体请看代码理解。
struct edge
{
int from;
int to;
int cost;
}
int cmp(edge a,edge b)
{
return a.cost<b.cost;
}
int find(int k)
{
if(set[k]==k)return k;
set[k]=find(set[k]);//并查集路径压缩
return set[k];
}
void union(int f1,int f2)
{
if(find(f1)!=find(f2))
set[f1]=f2;
}
int Kruskal()
{
sort(edge,edge+n,cmp);
int i,ans=0,count=1,f1,f2;
for(i=0;i<edge_num;i++)
{
if(count==n) break;
f1=find(edge[i].from);
f2=find(edge[i].to);
if(f1==f2)continue;
ans+=edge[i].cost;
union(f1,f2);
count++;
}
}