最小生成树——POJ 1258 Agri-Net

    放了好久了,今天终于用Kruskal把它给过了,其实跟之前做到的那步相比,就加了一条语句。开始也是用Kruskal做得,结果怎么都不对,自己找了几组测试数据也没有发现什么问题,后面无奈之下,只得用Prim把它给过了。今天做之前的那个Friends的时候发现了这个问题,然后联想到曾经用Kruskal算法做得这个题目,于是便又把它翻出来,把它给做了。现在,趁着刚刚做完,把求最小生成树常用的Kruskal算法跟Prim算法作个小结。

    求最小生成树在图论中是经常会用到的操作,简单一点说,就是对给出的一些结点,用最短的路径和把这些点连接成一棵树。常用到的两种算法就是著名的Kruskal算法跟Prim算法,两个算法本质上都是贪心的思想。为了更好的说明,我选择结合我刚刚做过的POJ 1258这个题来说明,题目在http://acm.pku.edu.cn/JudgeOnline/problem?id=1258 大概的意思就是:给出N个顶点及N个顶点间的距离,然后求一棵最小生成树。先输入顶点数N,然后一个N*N的数组,用来描述各个顶点之间的距离。

    具体而言,对于Kruskal算法, 为使生成树上边的权值之和达到最小,则应当使生成树上每条边的权值尽可能的小,典型的贪心思想。具体的做法就是:先构造一个只含一个N个顶点的子图SG,然后从权值最小的边开始,若添加它不使SG中形成回路,则在SG中加上这条边,如此重复,直到加上N-1条边为止。具体到这个题目而言,就是先输入N的值,然后将N*N个距离的信息保存在一个一维数组town中,其中town是一个结构体类型,有三个成员变量,分别为该条边的起点、终点和这条边的长度,然后对这个数组按照长度dis进行升序排序,然后用Kruskal取最短边的时候就可以从左到右取过去,当然前面为0的不要了。然后一个问题就是判断加入的边不使SG形成回路,这个问题我觉得可以用类似于并查集的方法解决,也就是开始的时候开一个数组nodeSet,各个元素的值分别为对应的下标值。判断是否形成回路的话,就转化成判断边的两个端点i及j对应的nodeSet[i]与nodeSet[j]是否相等,如果相等,则可以说明加入这条边后会使SG中形成回路。另外,再用一个bool型的数组用来表示边的各个结点是否已经被加入到SG中的边连接。用Kruskal算法解决POJ 1258 Agri-Net的AC Code(Kruskal)

#include <iostream>
#include <algorithm>
using namespace std;

typedef struct Edge{
    int x, y, dis;
};
Edge town[10002];	//存放边的两端和长度信息
bool flag[102];		//标记新的结点是否加入
int nodeSet[102];	//标记编号对应的结点所属的集合

bool compare(Edge a, Edge b)
{
    return (a.dis < b.dis);
}

int main()
{
    int n, nodeTag;
    while(cin>>n){
        nodeTag = 1;
        for(int i = 1; i <= n; i++){
            nodeSet[i] = i;	//结点所在的set即为其编号
            for(int j = 1; j <= n; j++){	
                scanf("%d", &town[nodeTag].dis);
                town[nodeTag].x = i;
                town[nodeTag].y = j;
                nodeTag++;
            }
        }
        sort(town+1, town+nodeTag, compare);
        nodeTag = n+1;	//去掉前面的n个0,从最小的边开始
        int sum = 0, edgeSet = 0;
        while(edgeSet < n-1){//n-1条边时完全所有结点的连接,访问过的结点都会加入到一个set中
            if((!flag[town[nodeTag].x]) && (flag[town[nodeTag].y])){
                sum += town[nodeTag].dis;
                edgeSet++;
                flag[town[nodeTag].x] = true;
                nodeSet[town[nodeTag].x] = nodeSet[town[nodeTag].y];
            }else if((flag[town[nodeTag].x]) && (!flag[town[nodeTag].y])){
                sum += town[nodeTag].dis;
                edgeSet++;
                flag[town[nodeTag].y] = true;
                nodeSet[town[nodeTag].y] = nodeSet[town[nodeTag].x];
            }else if((!flag[town[nodeTag].x]) && (!flag[town[nodeTag].y])){
                sum += town[nodeTag].dis;
                edgeSet++;
                flag[town[nodeTag].x] = true;
                flag[town[nodeTag].y] = true;
                nodeSet[town[nodeTag].x] = nodeSet[town[nodeTag].y];
            }else {	//两端顶点都加入了set,判断新加入的边是否使结点形成了环
                if(nodeSet[town[nodeTag].x] != nodeSet[town[nodeTag].y]){
                    sum += town[nodeTag].dis;
                    edgeSet++;
                    int tmp = nodeSet[town[nodeTag].y];
                    for(int i = 1; i <= n; i++)	//将两个set中的元素合到一个set中
                        if(nodeSet[i] == tmp)
                            nodeSet[i] = nodeSet[town[nodeTag].x];
                }
            }
            nodeTag ++;
        }
        cout<<sum<<endl;
        memset(flag, 0, sizeof(flag));
    }
    return 0;
}


 

 


<!-- -->#include  < iostream >
#include 
< algorithm >
using   namespace  std;

typedef 
struct  Edge{
    
int  x, y, dis;
};
Edge town[
10002 ];     // 存放边的两端和长度信息
bool  flag[ 102 ];         // 标记新的结点是否加入
int  nodeSet[ 102 ];     // 标记编号对应的结点所属的集合

bool  compare(Edge a, Edge b)
{
    
return  (a.dis  <  b.dis);
}

int  main()
{
    
int  n, nodeTag;
    
while (cin >> n){
        nodeTag 
=   1 ;
        
for ( int  i  =   1 ; i  <=  n; i ++ ){
            nodeSet[i] 
=  i;     // 结点所在的set即为其编号
             for ( int  j  =   1 ; j  <=  n; j ++ ){    
                scanf(
" %d " & town[nodeTag].dis);
                town[nodeTag].x 
=  i;
                town[nodeTag].y 
=  j;
                nodeTag
++ ;
            }
        }
        sort(town
+ 1 , town + nodeTag, compare);
        nodeTag 
=  n + 1 ;     // 去掉前面的n个0,从最小的边开始
         int  sum  =   0 , edgeSet  =   0 ;
        
while (edgeSet  <  n - 1 ){ // n-1条边时完全所有结点的连接,访问过的结点都会加入到一个set中
             if (( ! flag[town[nodeTag].x])  &&  (flag[town[nodeTag].y])){
                sum 
+=  town[nodeTag].dis;
                edgeSet
++ ;
                flag[town[nodeTag].x] 
=   true ;
                nodeSet[town[nodeTag].x] 
=  nodeSet[town[nodeTag].y];
            }
else   if ((flag[town[nodeTag].x])  &&  ( ! flag[town[nodeTag].y])){
                sum 
+=  town[nodeTag].dis;
                edgeSet
++ ;
                flag[town[nodeTag].y] 
=   true ;
                nodeSet[town[nodeTag].y] 
=  nodeSet[town[nodeTag].x];
            }
else   if (( ! flag[town[nodeTag].x])  &&  ( ! flag[town[nodeTag].y])){
                sum 
+=  town[nodeTag].dis;
                edgeSet
++ ;
                flag[town[nodeTag].x] 
=   true ;
                flag[town[nodeTag].y] 
=   true ;
                nodeSet[town[nodeTag].x] 
=  nodeSet[town[nodeTag].y];
            }
else  {     // 两端顶点都加入了set,判断新加入的边是否使结点形成了环
                 if (nodeSet[town[nodeTag].x]  !=  nodeSet[town[nodeTag].y]){
                    sum 
+=  town[nodeTag].dis;
                    edgeSet
++ ;
                    
int  tmp  =  nodeSet[town[nodeTag].y];
                    
for ( int  i  =   1 ; i  <=  n; i ++ )     // 将两个set中的元素合到一个set中
                         if (nodeSet[i]  ==  tmp)
                            nodeSet[i] 
=  nodeSet[town[nodeTag].x];
                }
            }
            nodeTag 
++ ;
        }
        cout
<< sum << endl;
        memset(flag, 
0 sizeof (flag));
    }
    
return   0 ;
}

    然后就是用Prim算法来计算了,Prim算法相对而言要简单一点,但时间复杂度也要高一点。还是贪心的思想,在图中任意取一个顶点V作为生成树的根,之后往生成树上添加新的顶点W,在即将添加的顶点W和已经在生成树上的顶点V之间

必定存在一条边,并且该边的权值在所有连通顶点V和W之间的边中取值最小。之后,继续往生成树上添加顶点,直到生成树上含有N-1个顶点。具体的做法就是:用lowcost来记录从顶点W到V-W具有最小权值的边,对个每个求决策的顶点Vi(Vi在V-W中),lowcost[i]=min{cost(W,Vi)},即存储Vi到已决策过的顶点集的各边中权值最小的边。假若从顶点U开始,则初始化时,lowcost[i]即为从i顶点到U顶点的边的权值,通过循环,找到下一个要被加入的顶点,即lowcost中的最小值。每加入一个顶点后,判断它到V-U中各个顶点的距离是否小于那个顶点到之前加入的顶点之间的距离,如果是则修改相应的lowcost的值。

AC Code(Prim)

#include <iostream>
using namespace std;

const int INFINITY = 9999999;
const int MAXVEX = 102;	
int edge[MAXVEX][MAXVEX], lowcost[MAXVEX];
int vexNum;	 

int myPrim(int start);
int main()
{
    while(cin>>vexNum)
    {
        for(int i = 1; i <= vexNum; i++)
            for(int j = 1; j <= vexNum; j++)
                scanf("%d", &edge[i][j]);
        cout<<myPrim(1)<<endl;
    }
    return 0;
}

int myPrim(int start)
{	
    int nextVex, minEdge, sumPath = 0;
    for(int i = 1; i <= vexNum; i++)
        lowcost[i] = edge[start][i];
    for(int i = 1; i < vexNum; i++){
        minEdge = INFINITY;
        nextVex = 1;
        for(int j = 2; j <= vexNum; j++){
            if((lowcost[j] > 0) && (lowcost[j] < minEdge)){
                minEdge = lowcost[j];
                nextVex = j;
            }
        }
        sumPath += minEdge;
        lowcost[nextVex] = 0;
        for(int j = 1; j <= vexNum; j++){
            if((edge[nextVex][j] < lowcost[j]) && (lowcost[j] > 0)){
                lowcost[j] = edge[nextVex][j];
            }
        }
    }
    return sumPath;
}


  <!-- -->#include  < iostream >
using   namespace  std;

const   int  INFINITY  =   9999999 ;
const   int  MAXVEX  =   102 ;    
int  edge[MAXVEX][MAXVEX], lowcost[MAXVEX];
int  vexNum;     

int  myPrim( int  start);
int  main()
{
    
while (cin >> vexNum)
    {
        
for ( int  i  =   1 ; i  <=  vexNum; i ++ )
            
for ( int  j  =   1 ; j  <=  vexNum; j ++ )
                scanf(
" %d " & edge[i][j]);
        cout
<< myPrim( 1 ) << endl;
    }
    
return   0 ;
}

int  myPrim( int  start)
{    
    
int  nextVex, minEdge, sumPath  =   0 ;
    
for ( int  i  =   1 ; i  <=  vexNum; i ++ )
        lowcost[i] 
=  edge[start][i];
    
for ( int  i  =   1 ; i  <  vexNum; i ++ ){
        minEdge 
=  INFINITY;
        nextVex 
=   1 ;
        
for ( int  j  =   2 ; j  <=  vexNum; j ++ ){
            
if ((lowcost[j]  >   0 &&  (lowcost[j]  <  minEdge)){
                minEdge 
=  lowcost[j];
                nextVex 
=  j;
            }
        }
        sumPath 
+=  minEdge;
        lowcost[nextVex] 
=   0 ;
        
for ( int  j  =   1 ; j  <=  vexNum; j ++ ){
            
if ((edge[nextVex][j]  <  lowcost[j])  &&  (lowcost[j]  >   0 )){
                lowcost[j] 
=  edge[nextVex][j];
            }
        }
    }
    
return  sumPath;
}

提交情况:记不清多少次Wrong Answer,主要是在用Kruskal将两个树合成一棵树的时候出现了问题。

2次Accepted

收获:对用Kruskal跟Prim算法来求解最小生成树的问题有了一些理性的认识,同时也能够用他们来求解一些比较明显的最  重生成树问题。

经验: 上课的时候要认真,而不要局限于所谓的考试,我们当时就是因为这些东西几乎都不考,我也就忽悠了一回;另外,要小心,注意变量值的变化;考虑问题更加多元化。

你可能感兴趣的:(数据结构,算法,.net,J#)