最小生成树的构建本质
本质:
是一个换边的操作。我们考虑原来的一个 nnn个点,mmm条边的图,首先我们不管边权直接构建一个生成树,接着枚举剩下的边,我们发现如果添加上这个边(假如没有重边)一定会形成一个环。
根据最小生成树的定义,我们必须要将这个环上的边权最大的边删去,一直重复这个过程枚举完所有边一定就会构建出一个最小生成树。
假如 uuu到 vvv之间只有一条路径,那么所求就是 uuu到 vvv之间的最小边权。
接着我们考虑什么时候u到v存在两条及以上路径——uuu和 vvv在环上或 uuu到vvv之间存在环。
再考虑上文所说的,最小生成树的基本过程是每次构建一个环,接着拆掉最大边权的过程,也就是说原图任意一个环上的最大的边权全都被拆除了,同时因为这个拆除操作会导致 uuu到 vvv的路径唯一。
最小生成树
定义:包含连通图上所有节点的各边权值总和最小的树
**求法:**将所有点边按权值大小从小到大排列,每次都从中选取最小权值的边(u,v),
并把它添加到正在生长的森林中。森林中的树不断的合并,直到将所有点同属于一棵树为止。(贪心 + 并查集)
1.概述
图的生成树:是它的一颗含有其所有顶点的无环连通子图,一幅加权图的最小生成树(MST)是它的一颗权值(树中的所有边的权值之和)最小的生成树.
2.原理
1.图的一种切分是将图的所有顶点分为两个非空且不重叠的两个集合.
横切边是连接两个属于不同集合的顶点的边.
2.**切分定理:**在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于最小生成树.
PRIM算法:边数较多时使用
(找一点为起点,使所有点串联所连线的权值最小)
#include<iostream>
using namespace std;
#define MAX 100
#define MAXCOST 0x7fffffff
int graph[MAX][MAX];
int Prim(int graph[][MAX], int n)//二维数组作为参数如何使用?
{
int sta[MAX];//存放某一条边的起点值
int lowcost[MAX];//存放以i为终点的的边的最小的权值
int min,minid,sum=0;
//min用来存放最小权值,minid用来存放权值最小的边所对应的终点
for(int i=2;i<=n;i++)
{
lowcost[i]=graph[1][i];
//初始化lowcost[i],并把他们的初始值都看作是从节点1出发到i的权值
sta[i]=1;//起点赋值为1
}
sta[1]=0;//节点1进入最小生成树
for(int h=2;h<=n;h++)
{
min=MAXCOST;//找到最小的,先来个较大值
for(int j=2;j<=n;j++)
{
if(lowcost[j]<min&&lowcost[j]!=0)
//如果找到权值较小的就赋值给min,并把终点j赋值给minid。
{ min=lowcost[j]; minid=j;}
}
lowcost[minid]=0;//这条边已经进入最小生成树,所以把值置为0
if(min!=MAXCPST)
sum+=min;
for(int s=2;s<=n;s++)
{
if(lowcost[s]>graph[minid][s])
//如果原先的lowcost[j]的值大于以minid为起点到终点j的权值,
//则更新它,并把起点更新为minid
{
lowcost[s]=graph[minid][s];
sta[s]=minid;
}
}
}
return sum;
}
int main()
{
int m,n,x,y,cost;
cout<<"请输入节点数目和边的数目:"<<endl;
cin>>m>>n;
for(int i=1;i<=m;i++)
for(int j=1;j<=m;j++)
graph[i][j]=MAXCOST;
for(int k=1;k<=n;k++)
{
cin>>x>>y>>cost;
graph[x][y]=graph[y][x]=cost;
}
cost= Prim(graph,n);
cout<<cost<<endl;
return 0;
}
PRIM算法(邻接表版)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
struct Edge{
int to,dist;
Edge(int t,int d):to(t),dist(d){}
bool operator<(const Edge& e)const{
return dist>e.dist;
}
};
int n,m;
bool vis[maxn];
vector<Edge> g[maxn];
priority_queue<Edge> que;
void prim(){
memset(vis,0,sizeof(vis));
while(que.size()) que.pop();
for(int i=0;i<g[0].size();++i) que.push(g[0][i]);
vis[0]=true;
int ans=0;
for(int cnt=1;cnt<n;++cnt){
while(que.size() && vis[que.top().to]) que.pop();
Edge e=que.top();
ans+=e.dist;
int v=e.to;
vis[v]=true;
for(int i=0;i<g[v].size();++i){
if(!vis[g[v][i].to]) que.push(g[v][i]);
}
}
printf("%d\n",ans);
}
int main(){
while(scanf("%d%d",&n,&m)==2){
for(int i=0;i<n;++i) g[i].clear();
for(int i=0;i<m;++i){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
g[u-1].push_back(Edge(v-1,w));
g[v-1].push_back(Edge(u-1,w));
}
prim();
}
return 0;
}
KRUSKAL算法
(贪心+并查集)
struct edge
{
int u,v;
ll val;int cnt;
bool operator< (const edge & rhs)const{return val<rhs.val;}
}E[N];
struct Kruskal
{
int fa[N],cnt=0,n;ll ans=0;
void add(int uu,int vv,ll vall){E[++cnt].u=uu,E[cnt].v=vv,E[cnt].val=vall;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void init(int nn){ for(int i=0;i<=nn;i++)fa[i]=i; n=nn;}
void kru(){
sort(E+1,E+1+cnt);
int pp=0;
for(int i=1;i<=cnt;i++){
int u=find(E[i].u),v=find(E[i].v);
if(u!=v){
fa[u]=v,ans+=E[i].val;
if(++pp>=n-1)return ;
}
}
}
}kru;
#include <iostream>
#include<algorithm>
using namespace std;
#define MAX 100
struct edge
{
int x,y;
int w;
}e[MAX];
int fa[MAX];
int rank[MAX];
int sum;
int cmp(edge a,edge b)//排序函数
{
if(a.w!=b.w)
return a.w<b.w;
else
return a.x<b.x;
}
void make_set(int x)//初始化节点
{
fa[x]=x; rank[x]=0;
}
int find(int x)//查找父节点
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void union_set(int x,int y,int w)//合并节点
{
if(rank[x]>rank[y])
fa[y]=x;
else if(rank[x]<rank[y])
fa[x]=y;
else
rank[x]++,fa[y]=x;
sum+=w;//总权值加上w
}
int main()
{
int x,y,w;
int m,n;//n是点,m是边
cin>>n>>m;
for(int i=0;i<m;i++)
{
cin>>x>>y>>w;
e[i].x=x;e[i].y=y;e[i].w=w;
make_set(x); make_set(y);
}
sort(e,e+m,cmp);
sum=0;
for(int i=0;i<m;i++)
{
x=find(e[i].x); y=find(e[i].y);
w=e[i].w;
if(x!=y)
union_set(x,y,w);
}
cout<<sum<<endl;
return 0;
}