最小生成树

来源:互联网 发布:小米手环2 知乎 编辑:程序博客网 时间:2024/05/22 16:49

算法一:Prim算法(可称为加点法)

   点集记为v。 定义一个集合U,来存储已经在生成树中的点。v-u是未在生成树中的点。定义一个closedge数组,分为两个区域,一个存储最小边在u中的顶点,另一个存储最小边的权值。将初始顶点加入U中,初始化closedge数组。选择最小边closedge[k],将K加入集合U,输出此边。更新closedge[]的值,选择集合V-U中点到k和u之间较小的哪个权值存到clodedge数组中。重复此步骤,直到所有的顶点都加入u中。

用邻接矩阵存储图,用一维数组存储现已有的树到其他定点的最小权值,用另一个一维数组存储某个点是否在生成树中。

#include<cstdio>#include<iostream>#include<cstring>#include<algorithm>using namespace std;#define Maxn 110#define INF 9999999int maz[Maxn][Maxn],lowcase[Maxn],flag[Maxn];//maz[i][j]表示i到j的距离,注意无向图和重边!lowcase数组存的是现已在树里的点所能到达的点(Maxn)的最小权值//flag数组是用来标记某个点(Maxn)是否已经在树里面int prim(int n){    int i,j,ans = 0,pos,mi;    memset(flag,0,sizeof(flag));//起初所有点都不在生成树中    for (i = 2; i <= n; ++i)        lowcase[i] = maz[1][i];//先以1作为根节点,更新lowcase数组    lowcase[1] = 0;//为了数组里的值全(自己到自己的距离是0)    flag[1] = 1;//1这个点(初始点)标记为已在树里面    for (i = 0; i < n-1; ++i)//循环n-1次,每次找一个点纳入到树里面,加上1根节点总共就是n个点    {        mi = INF;//mi为每次在lowcase数组里找到的最小值,所以刚开始赋值成最大值        for (j = 1; j <= n; ++j)//寻找最小权值的点        {            if (!flag[j] && lowcase[j] < mi)//j点不在树里面并且权值小            {                mi = lowcase[j];                pos = j;//记录一下这个将要进入生成树的点            }        }        ans += mi;//记录最小生成树的总权值        flag[pos] = 1;//将此次寻找的点标记为已经加入生成树        for (j = 1; j <= n; ++j)//用新的节点更新lowcase数组        {            if (!flag[j] && maz[pos][j] < lowcase[j])//判断新加入的点到其他所有未加入的点的距离是否比前一个点的小            {                lowcase[j] = maz[pos][j];//如果小的话更新数组的值            }        }    }    return ans;//返回最小生成树的总权值}int main(){    int n,i,j;    while (~scanf("%d",&n) && n)//顶点个数    {        for (i = 1; i <= n; ++i)        {            for (j = 1; j <= n; ++j)                maz[i][j] = i == j ? 0 : INF;//用邻接矩阵存储图        }//刚开始得把数组里的数存成无穷大//本身到本身的距离是0//点和点之间的距离是无穷大        int a,b,c;        for (i = 0; i < n*(n-1)/2; ++i)//最大边数        {            scanf("%d%d%d",&a,&b,&c);            if (c < maz[a][b])                maz[a][b] = maz[b][a] = c;//无向图+重边        }        int ans = prim(n);//n个点的最小生成树的总长度        printf("%d\n",ans);    }    return 0;}

算法二:克鲁斯卡尔(加边法)

将所有边的权值排序。

1)初始状态看为只有顶点没有边,每一个顶点看作一个连通分量。

2)选择最小权值的边,如果该边连接了不同连通分量,则将该边加入集合T,否则舍去该边,继续寻找下一条权值最小的边。

3)重复2)直到所有顶点都在同一个连通分量中。

#include<cstdio>#include<iostream>#include<cstring>#include<algorithm>using namespace std;    #define Maxn 1100    int p[Maxn];    int find(int x)    {        return p[x] == x ? x : p[x] = find(p[x]);    }//并查集    struct node    {        int u,v,w;    };//每条边的情况,u,v是边的端点,w是边的权值    struct node q[Maxn*500];  //定义边的个数    int cmp(node a,node b)    {        return a.w < b.w;    }//sort将边按权值排序    int main()    {        int n,m,sum,num,i,x,y;        while (~scanf("%d",&n) && n)        {            for (i = 1; i <= n; ++i)                p[i] = i;//初始化每个顶点是一个连通分量            m = n * (n-1) / 2;  //最大边的数            for (i = 0; i < n*(n-1)/2; ++i)            {                scanf("%d%d%d",&q[i].u,&q[i].v,&q[i].w);//****不需要考虑重边的情况,因为重边的存在并不覆盖                //之前的边,重边参与排序,选出重边中最小的,就算遍历到了重边中较大的,此时端点已经在同                //一棵树中了,所以重边的存在不会有影响            }            sort(q,q+m,cmp);//将边按照权值排序            sum = num = 0;            for (i = 0; i < m; ++i)//m条边,从其中选出****n-1条边,然后跳出循环            {                x = find(q[i].u);                y = find(q[i].v); //判断两个顶点的边是否在同一个连通分量中                if (x != y)  //不在同一个连通分量中                {                    if (x > y)                        p[x] = y;                    else                        p[y] = x;//连通分量标志合并到哪个较小的中去                    sum += q[i].w; //记录生成树的总权值                    ++num; //记录加入生成树中的边数                }                if (num == n-1)  //当足够了n-1条边的时候停止                    break;            }            printf("%d\n",sum);  //输出总权值        }        return 0;    }



0 0
原创粉丝点击