最小生成树之prim算法

最小生成树的Prim算法也是贪心算法的一大经典应用。Prim算法的特点是时刻维护一棵树,算法不断加边,加的过程始终是一棵树。
 

Prim算法过程:

一条边一条边地加, 维护一棵树。


初始 E = {}空集合, V = {任意节点}

循环(n – 1)次,每次选择一条边(v1,v2), 满足:v1属于V , v2不属于V。且(v1,v2)权值最小。

E = E + (v1,v2)
V = V + v2

最终E中的边是一棵最小生成树, V包含了全部节点。

以下图为例介绍Prim算法的执行过程。

Prim算法的过程从A开始 V = {A}, E = {}

选中边AF , V = {A, F}, E = {(A,F)} 

选中边FB, V = {A, F, B}, E = {(A,F), (F,B)}

选中边BD, V = {A, B, F, D},   E = {(A,F), (F,B), (B,D)}

选中边DE, V = {A, B, F, D, E},   E = {(A,F), (F,B), (B,D), (D,E)}
 
选中边BC, V = {A, B, F, D, E, c},   E = {(A,F), (F,B), (B,D), (D,E), (B,C)}, 算法结束。

Prim算法的证明:假设Prim算法得到一棵树P,有一棵最小生成树T。假设P和T不同,我们假设Prim算法进行到第(K – 1)步时选择的边都在T中,这时Prim算法的树是P’, 第K步时,Prim算法选择了一条边e = (u, v)不在T中。假设u在P’中,而v不在。

因为T是树,所以T中必然有一条u到v的路径,我们考虑这条路径上第一个点u在P’中,最后一个点v不在P’中,则路径上一定有一条边f = (x,y),x在P’中,而且y不在P’中。
我们考虑f和e的边权w(f)与w(e)的关系:


若w(f) > w(e),在T中用e换掉f (T中加上e去掉f),得到一个权值和更小的生成树,与T是最小生成树矛盾。
若w(f) < w(e), Prim算法在第K步时应该考虑加边f,而不是e,矛盾。

因此只有w(f) = w(e),我们在T中用e换掉f,这样Prim算法在前K步选择的边在T中了,有限步之后把T变成P,而树权值和不变, 从而Prim算法是正确的。
请仔细理解Prim算法——时刻维护一棵生成树。

最后,我们来提供输入输出数据,由你来写一段程序,实现这个算法,只有写出了正确的程序,才能继续后面的课程。

 

输入

 

第1行:2个数N,M中间用空格分隔,N为点的数量,M为边的数量。(2 <= N <= 1000, 1 <= M <= 50000)
第2 - M + 1行:每行3个数S E W,分别表示M条边的2个顶点及权值。(1 <= S, E <= N,1 <= W <= 10000)

 

输出

 

输出最小生成树的所有边的权值之和。

 

输入示例

 

9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8


输出示例

 

37
//program 2-6
#include <iostream>
using namespace std;

const int INF = 0x3fffffff;
const int N = 1005;
bool s[N];
int closest[N];
int lowcost[N];
void Prim(int n, int u0, int c[N][N])
{    //顶点个数n、开始顶点u0、带权邻接矩阵C[n][n]
    //如果s[i]=true,说明顶点i已加入最小生成树
    //的顶点集合U;否则顶点i属于集合V-U
    //将最后的相关的最小权值传递到数组lowcost
    s[u0]=true; //初始时,集合中U只有一个元素,即顶点u0
    int i;
    int j;
    for(i=1; i<=n; i++)
    {
        if(i!=u0)
        {
            lowcost[i]=c[u0][i];
            closest[i]=u0;
            s[i]=false;
        }
        else
            lowcost[i]=0;
    }

    for(i=1; i<=n;i++) //在集合中V-u中寻找距离集合U最近的顶点t
    {
        int temp=INF;
        int t=u0;
        for(j=1;j<=n;j++)
        {
            if((!s[j])&&(lowcost[j]<temp))
            {
                t=j;
                temp=lowcost[j];
            }
        }
        if(t==u0)
            break;       //找不到t,跳出循环

        s[t]=true;     //否则,讲t加入集合U
        for(j=1; j<=n;j++) //更新lowcost和closest
        {
            if((!s[j])&&(c[t][j]<lowcost[j]))
            {
                lowcost[j]=c[t][j];
                closest[j]=t;
            }
        }
    }
}

int main()
{

        int n, c[N][N], m, u, v, w;
        int u0;
        //cout<<"输入结点数n和边数m:"<<endl;
        cin>>n>>m;
        int sumcost=0;
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                c[i][j]=INF;
        //cout <<"输入结点数u,v和边值w:"<<endl;
        for(int i=1; i<=m; i++)
        {
            cin>>u>>v>>w;
            c[u][v]=c[v][u]=w;
        }
        //cout <<"输入任一结点u0:"<<endl;
        //cin >> u0 ;
        //计算最后的lowcos的总和,即为最后要求的最小的费用之和
        u0=1;
        Prim(n, u0, c);
        //cout <<"数组lowcost的内容为"<<endl;
        //for(int i = 1; i <= n; i++)
            //cout << lowcost[i] << " ";
        //cout << endl;
        for(int i = 1; i <= n; i++)
           sumcost += lowcost[i];
        cout <<sumcost<<endl;
    return 0;
}

 

posted @ 2018-09-25 16:03  浮生惘语  阅读(291)  评论(0编辑  收藏  举报