n个节点最少n-1条边能构成连通图(n个节点,其中任意两个节点都连通的图),最多n(n-1)/2边。树是没有环路的连通图
n个节点,n-1条边组成的权值最小的树为最小生成树。
prim算法
最小生成树算法,时间复杂度T(n)=O(V^2) V为顶点数目,受边的影响不大,适合于边比较多的图(稠密图),在稀疏图时,Kruscal复杂度更低,我们可以使用堆优化的prim算法达到与Kruscal一样的复杂度。
prim算法设计思路(O(N^2)):
1.任意选定一点s(通常选择第一个点),设集合S={s}
2.从不在集合S的点中选出一个点j使得其与S内的某点的距离最短,则(i,j)就是生成树上的一条边,同时将j点加入S
3.转到2继续进行,直至所有点都已加入S集合
#include<stdio.h>
#define MAXN 100
#define INF 100000;
int G[MAXN][MAXN];
void prim(int n)
{
int closeset[n]={0}; //记录不在点集s中的节点在s中的最近节点
int lowcost[n],used[n]; //记录不在s中的节点到s的最短距离,即到最近节点的权值
int result=0; //标记节点是否被访问过
//首先将节点0放入点集s
for(int i=0;i<n;i++)
{ //closeset[2]=1表示节点2(点集s外)的在s中的最近节点为节点1(在s中)
closeset[i]=0; //将所有s外的节点在s中的最近节点设为节点0(包括节点0)
lowcost[i]=G[0][i]; //所有s外的节点到s的最短距离设置为节点0到它们的距离
used[i]=0; //所有节点均设置为未被访问过
}
used[0]=1;//将第1个点加入s中
for(int i=1;i<n;i++)
{
int j=0;
for(int k=0;k<n;k++)
{
if(!used[k] && (lowcost[k]<lowcost[j]))
{
j=k;
}
}
used[j]=1;
for(int k=0;k<n;k++)
{
if((!used[k])&&(G[j][k]<lowcost[k]))
{
lowcost[k]=G[j][k];
closeset[k]=j;
}
}
}
for(int i=1;i<n;i++)
{
printf("node:%d->%d distance:%d\n",closeset[i],i,lowcost[i]);
result+=lowcost[i];
}
printf("sum:%d\n",result);
}
int main()
{
int n,m; //n为节点个数 m为边数
int x,y,dis; //节点x到节点y的距离dis
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
G[i][j]=INF;
}
}
for(int j=0;j<m;j++)
{
scanf("%d %d %d",&x,&y,&dis);
G[x][y]=dis;
G[y][x]=dis;
}
prim(n);
}
执行结果:
堆优化的prim算法(T(n)=O(nlogn)):
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#include <vector>
#define N 100
#define INF 1<<10
using namespace std;
int G[N][N]={0};//距离矩阵,G[i][j]表示顶点i和顶点j的距离
int visited[N]={0};//visited[i]=1表示某个顶点已经放入顶点集s中
int lowcost[N];//lowcost[i]表示顶点i与顶点集s(s中的顶点)的最短距离
int closeset[N];//closeset[i]=j表示顶点i在顶点集s中的最近顶点为顶点j
int flag=0;//最终判断整个图是否连通
int result=0;
int n,m;//n表示顶点个数,m表示边数
struct node
{
int pos;//顶点的位置
int len;//以顶点为终点的边长
};
vector<node> adjacent[N];//adjacent[i]表示顶点i的邻接节点
//排序方式为小顶堆(升序)
struct cmp
{
bool operator()(node a,node b)
{
return a.len>b.len;
}
};
void prim()
{
int s;
priority_queue<node,vector<node>,cmp> buf;//优先队列priority_queue<type,container,functional>
lowcost[0]=0;//将0号顶点放入s中,则0号顶点到点集s(中的0号顶点)的距离为0
closeset[0]=0;//定义0号顶点的最近顶点为0号顶点
node temp={0,0};//0号顶点,距离为0
buf.push(temp);//将0号顶点放入s中
while(!buf.empty())
{
s=buf.top().pos;
buf.pop();
visited[s]=1;//标记访问过0号顶点
int NodeNum=adjacent[s].size();//位置为s处顶点的临近顶点数目
for(int i=0;i<NodeNum;i++)
{
int p=adjacent[s][i].pos;//依次访问与位置s处顶点的所有临近顶点的位置
if(visited[p]) continue;
int templen=adjacent[s][i].len;//访问s出顶点与这些临近顶点的边
if(templen<lowcost[p])//找到最小边长
{
lowcost[p]=templen;//更新位置p与点集s的最短距离
closeset[p]=s;
temp.len=lowcost[p];
temp.pos=p;
buf.push(temp);
}
}
}
for(int i=1;i<n;i++)
{
if(closeset[i]==-1)
{
flag=1;
break;
}
result+=lowcost[i];
}
}
int main()
{
cin>>n>>m;
int x,y,dis;//顶点x和顶点y之间的距离为dis
node a;
for(int i=0;i<m;i++)
{
cin>>x>>y>>dis;
G[x-1][y-1]=dis;//输入时从1开始编码的顶点i和顶点j从0开始编码则为i-1和j-1
a.pos=y-1;
a.len=dis;
adjacent[x-1].push_back(a);//保存顶点x-1的邻接节点
G[y-1][x-1]=dis;
a.pos=x-1;
a.len=dis;
adjacent[y-1].push_back(a);
}
for(int i=0;i<n;i++)
{
closeset[i]=-1;
lowcost[i]=INF;
}
prim();
if(flag) cout<<"orz"<<endl;
else cout<<result<<endl;
}
执行结果如下:
kruskal算法(简单来说就是n个孤立的节点,每次都选择最短边,如果选择的边不会使原来的图成环就加入(即连接两个点))
(1)设一个有n个顶点的连通网络为G(V,E),最初先构造一个只有n个顶点,没有边的非连通图T{V,Ø},图中每个顶点自成一个连通分量;
(2)当在E中选择一条具有最小权值的边时,若该边的两个顶点落在不同的连通分量上,则将此边加入到T中;否则,即这条边的两个顶点落在同一个连通分量上,则将此边舍去(此后永不选用这条边),重新选择一条权值最小的边;
(3)如此重复下去,直到所有顶点在同一个连通分量上为止。
#include<iostream>
#include<algorithm>
#define N 1000
using namespace std;
int n,m;//n为顶点个数 m为边数
int counter=0;//已经连接了的边数
int result=0;
int ConnectedNode[N];//求某个节点的相连节点
//通过每次选择最短边
struct edge
{
int sNode,eNode,dis;//边有起点、终点、边长属性
};
bool cmp(edge &e1,edge &e2)//按照边长的升序排序规则
{
return e1.dis<e2.dis;
}
int FindConnect(int x)//寻找与某个节点相连的节点
{
if(ConnectedNode[x]!=x)
{
return FindConnect(ConnectedNode[x]);
}
else
{
return x;
}
}
void AddSet(int x,int y)//如果两个节点不相连,将这两个节点相连(加入同一个集合中)
{
ConnectedNode[FindConnect(y)]=FindConnect(x);
}
int main()
{
cin>>n>>m;
edge e[m+1];
for(int i=1;i<=m;i++)
{
cin>>e[i].sNode>>e[i].eNode>>e[i].dis;
}
for(int i=1;i<=n;i++)//初始化每个节点的相连节点为自己
{
ConnectedNode[i]=i;
}
sort(e+1,e+1+m,cmp);//按照边长升序排列
for(int i=1;i<=m;i++)
{
if(counter==n-1) break;//最小生成树n个节点n-1条边,当计数器=n-1时退出循环
if(FindConnect(e[i].sNode)!=FindConnect(e[i].eNode))//判断两个节点是否在同一个集合中
{
AddSet(e[i].sNode,e[i].eNode);
result+=e[i].dis;
counter++;
}
}
cout<<result<<endl;
}