经过算法优化后得到这个树
所以,这种问题的解决方法通俗的有两种:
普里姆(prim)算法
克鲁斯卡尔(Kruskal)算法
我们首先来入手Kruskal算法
Kruskal
Kruskal算法的基本思想:从边入手找顶点(贪心法)
步骤:
1.先构造一个只含 n 个顶点的子图 SG;
2.然后从权值最小的边开始,若它的添加不使SG 中产生回路,则在 SG 上加上这条边
3.反复执行第2步,直至加上 n-1 条边为止。
(下图中浅色的就是舍弃掉的边,根据上面的描述很快能理解这个图)
下面的算法描述中,适用并查集来做
/*
构造非连通图 ST=( V,{ } );
k = i = 0; // k 计选中的边数
while (k<n-1)
{
++i;
检查边集 E 中第 i 条权值最小的边(u,v);
若(u,v)加入ST后不使ST中产生回路,则 输出边(u,v); 且 k++;
}
*/
还没学到并查集。。等学到了再补充(听说要用到路径压缩?!)
先放大佬博客最小生成树(kruskal算法)_Superb_day-CSDN博客
更新了,并查集由某位大佬指点其实并不难
洛谷也有这一道题,很棒的思路,怕自己写的代码不自信可以oj一下
P3366 【模板】最小生成树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
/*
算法思想是:如果顶点x x->f[x]->f[f[x]]==z; y->f[y]->f[f[x]]->f[f[f[y]]]==z; 若
若存在顶点z=f[z],即说明x和y在同一个集合当中!
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
int n,m,i,j,u,v,total;
struct edge{
int from; //边的起点
int to; //终点
int val; //边的权值
}bian[2000005]; //默认边的存放数量最大
int f[100000]; //并查集数组
long long ans; //最小生成树的各边的长度之和
int find(int x) //并查集部分,find()函数主要找x顶点的
{
if (f[x]==x)
return x;
else
{
f[x]=find(f[x]); //一直往复查找,递归查找
return f[x];
}
}
bool cmp(edge a,edge b) //结构体快排时用到的,STL的sort()函数
{
return a.val<b.val;
}
void kruskal(edge bian[],int m,int n)//最小生成树
{
int total=0;
for(int i=1;i<=m;i++)
{
u=find(bian[i].from);
v=find(bian[i].to); //找到
if(u==v)
continue;//判断在不在同一个并查集里面,在就下一个循环
ans+=bian[i].val;//不在,就加上,或者是输出
f[u]=v; //将u,v两点链接的并查集都加入到同一个并查集中!
total++; //成功遍历了图的一个顶点,total加一
if(total==n-1)
break;//当形成了最小生成树后,退出(之后做的也没用了)
}
}
int main()
{
scanf("%d%d",&n,&m); //输入点和边的个数
for(i=1;i<=n;i++)
f[i]=i; //分成了n个集合,每个顶点相当于枢纽,区分出find()操作结束的终点!
for(i=1;i<=m;i++)
{
cin>>bian[i].from>>bian[i].to>>bian[i].val;
}
sort(bian+1,bian+m+1,cmp);//cmp为规则,stl快排
kruskal();
printf("%d",ans);
return 0;
}
上面的代码不理解?下面有推导过程,画的有点丑,但是很容易get到这个算法的点
路径压缩
在上面find操作上修改一下即可,在下一次操作更快地查找一个集合内的元素,更快找
//这个是查找,顺带把路径压缩了
int find(int x)
{
int temp=x;
while(x!=f[x])
x=f[x];
f[temp]=x;
return x;
}
Prim
普里姆算法的基本思想: 从顶点入手找边 [贪心法 ]
实施步骤:
1.分组,出发点为第一组,其余结点为第二组。
2.在一端属于第一组和另一端属于第二组的边中选择一条权值最小的一条。
3.把原属于第二组的结点放入第一组中。
4.反复2,3两步,直到第二组为空为止
图示手工
这两张图联合在一起看,很好理解的
实现代码
#include<iostream.h>
#define M 20
int G[M][M];
int n; //图所有结点
void Prim()
{
int temp[M]; //存放已经加入的结点
int size; //已加入的结点个数
int i,j,k;
int curnode,pos1,pos2;
int min;
temp[0]=0;
size=1;
G[0][0]=1;
for( i=0;i<n-1;i++)
{
min=32767; // 极大值
for ( j=0;j<size;j++)
{
curnode=temp[j];
for( k=0;k<n;k++)
if ( G[curnode][k]<min && G[k][k]==0){
min=G[curnode][k];
pos1=curnode;
pos2=k;
}
}
cout<<"edge "<<size<<" ( "<<pos1<<" -----> "<<pos2<<" ):" <<G[pos1][pos2]<<endl;
G[pos2][pos2]=1;
temp[size]=pos2;
size++;
}
}
void Input()
{
cin>>n;
for(i=0;i<n;i++)
for (j=0;j<n;j++)
cin>>G[i][j];
}
void main()
{
cout<<"please input the data of graph:"<<endl;
Input();
Prim();
}
上面的有点难懂?
其实
除了这个迭代公式计算和代表意义不一样,prim跟dijkstra其他均一样,算法实现基本一样。 只是松弛策略不一样。
dijistra
void Dijkstra(typec cost[][MAXN],typec lowcost[],int n,int beg) {
for(int i=0;i<n;i++) { lowcost[i]=INF;vis[i]=false;pre[i]=-1; }
lowcost[beg]=0;
for(int j=0;j<n;j++) {
int k=-1;
long long Min=INF; //改下模板的int类型 以匹配最大long long
for(int i=0;i<n;i++) //找出最小的未访问的最小顶点k
if(!vis[i]&&lowcost[i]<Min)
{ Min=lowcost[i]; k=i; }
if(k==-1)break;
vis[k]=true;
for(int i=0;i<n;i++)
if(!vis[i]&&lowcost[k]+cost[k][i]<lowcost[i]) //松弛
{
lowcost[i]=lowcost[k]+cost[k][i];
pre[i]=k;
}
}
}
prim
void Prim(typec cost[][MAXN],typec lowcost[],int n,int beg) { //名字改prim
for(int i=0;i<n;i++)
{
lowcost[i]=INF;
vis[i]=false;
pre[i]=-1;
}
lowcost[beg]=0;
for(int j=0;j<n;j++) {
int k=-1;
long long Min=INF; //改下模板的int类型 以匹配最大long long
for(int i=0;i<n;i++) //找出最小的未访问的lowcost[i]值最小顶点k
if(!vis[i]&&lowcost[i]<Min)
{ Min=lowcost[i]; k=i; }
if(k==-1)break;
vis[k]=true;
for(int i=0;i<n;i++)
if(!vis[i]&&cost[k][i]<lowcost[i]) {
//松弛策略改为i到原来的S的最小值大于i到新加入的点边权值
lowcost[i]=cost[k][i];
pre[i]=k;
}
}
}