最小生成树(prim算法,kruskal算法)

来源:互联网 发布:php生成二维码代码 编辑:程序博客网 时间:2024/06/05 05:35
对于边上有权的图来说,边权值和最小的生成树叫做这个图的最小生成树。如果一个图的边权值都不相同那么最小生成树是唯一的。
求无向图的最小生成树主要有prim算法和kruskal算法两个。
prim算法思想:设有两个点的集合:U和T,整个过程就是把T中的点不断放进U中,但是次序影响了树的形成,最佳的次序就是最小生成树的产生基础。从U选一个点q1,从T中选一个点q2,使得形成的边的权最小,将q2并入U,重复此过程,直到形成n-1条边。如果在形成N-1条边之前就找不到边相连了,那么则不能产生最小生成树。
kruskal算法思想:把原图中的所有边按权值进行从小到大的排序,然后依次选边,如果选入的边让新的图形成了环那么就舍弃,再选择其他的点并入U,最后的N-1的边(关系集)就是最小生成树。

例子:

图:

生成树:

输入:

7 10
1    2    50
2    5    40
5    6    70
5    4    50
2    4    65
1    3    60
3    4    52
3    7    45
4    6    30
4    7    42

prim算法(其实很多的无向图用邻接矩阵写prim算法即可,这里举的例子不是很好~):

#include <iostream>#include<cstdio> #include<cstring>using namespace std;const int maxn1=100,maxn2=140,mmax=0x3f3f3f3f;struct node{int to,w,next;}edge[maxn2];int head[maxn1],n,m; //n points, m edges.bool p[maxn1];  //p=1 means chosen, p=0 means not chosen int dist[maxn2]; //dist means shortest distance between tow points which one has been chosen but other one not  void prim(){int i,j;for(i=head[1];i>0;i=edge[i].next){dist[edge[i].to]=edge[i].w;}dist[1]=0;  p[1]=1; cout<<"chosen: 1"<<endl;for(i=1;i<n;i++){int mmin=mmax,k=0;for(j=1;j<=n;j++){if(!p[j]&&mmin>dist[j]){mmin=dist[j];k=j;}}if(k==0)return ;p[k]=1;cout<<"chosen: "<<k<<endl;for(j=head[k];j>0;j=edge[j].next)//更新选中的点和未选中区域的最短距离。   if(!p[edge[j].to]&&edge[j].w<dist[edge[j].to]){      dist[edge[j].to]=edge[j].w;   }}}void initaldis(){for(int i=1;i<=n;i++)dist[i]=mmax;}void travel(){for(int i=1;i<=n;i++){for(int j=head[i];j>0;j=edge[j].next){printf("%d %d %d\n",i,edge[j].to,edge[j].w);}}}int main(int argc, char *argv[]) {freopen("cin.txt","r",stdin);int i;while(cin>>n>>m){memset(head,0,sizeof(head));memset(edge,0,sizeof(edge));memset(p,0,sizeof(p));initaldis();for(i=1;i<=m;i++){int a,b,c;scanf("%d%d%d",&a,&b,&c);edge[i].to=b;edge[i].w=c;edge[i].next=head[a];head[a]=i; //head dex means start peak point, head val means edges.}travel();prim();}return 0;}
输出:

1 3 60
1 2 50
2 4 65
2 5 40
3 7 45
3 4 52
4 7 42
4 6 30
5 4 50
5 6 70
chosen: 1
chosen: 2
chosen: 5
chosen: 4
chosen: 6
chosen: 7
chosen: 3

prim算法用链式前向星写表面是一次选择了一个点,实际是选择了以该点为后继端点的边。牢记head的下标是点,head本身是边,edge[i].next也是边,edge[i].to是点(联系前向星的结构特征理解)。在无向图中,同样的边仅仅输入一次,所以不用考虑因为链式前向星的特殊结构而把一条边输出两次的问题。
kruskal算法:

#include <iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int maxn1=100,maxn2=140;struct node{int from,to,w;}edge[maxn2];int n,m;int father[maxn1];  //two sets are different collections(wards), set[i]=0 means point in A,set[i]=-1 means point in B.bool cmp(node a,node b){  //由于要交换边排序,所以不能用链式前向星return a.w<b.w;}int find(int a){return father[a]==a?a:find(father[a]);}void kruskal(){int i,j;for(i=1;i<=n;i++){        father[i]=i;}int top=0;  //write down amount of chosen edgesfor(i=0;i<m;i++){if(top==n-1)break;int q1=find(edge[i].from),q2=find(edge[i].to);if(q1!=q2){father[q1]=q2;printf("(%d,%d) ",edge[i].from,edge[i].to);top++;}}}void travel(){for(int i=0;i<m;i++){printf("%d %d %d\n",edge[i].from,edge[i].to,edge[i].w);}}int main(int argc, char *argv[]) {freopen("cin.txt","r",stdin);freopen("cout.txt","w",stdout);int i;while(cin>>n>>m){for(i=0;i<m;i++){int a,b,c;scanf("%d%d%d",&a,&b,&c);edge[i].to=b;edge[i].w=c;edge[i].from=a;}sort(edge,edge+m,cmp);travel();kruskal();}return 0;}


输出:

4 6 30
2 5 40
4 7 42
3 7 45
1 2 50
5 4 50
3 4 52
1 3 60
2 4 65
5 6 70
(4,6) (2,5) (4,7) (3,7) (1,2) (5,4) 

kruskal算法不能简单的设置两个不同值set={0,1}来区分选中的和未选中的,因为例如假设本例中先选择了4--6,再选中3--7,最后的4--7就不能选了,因为4和7都被标记了。

0 0
原创粉丝点击