Prim和Kruskal算法之C++实现

最近好长时间都困惑在这两个算法中,其实也不难,就是写的时候比较费劲。现在总结一下。
首先说一下两个算法是干嘛呢?
都是求解一个无向图G的最小生成树(minimum spanning tree),就是由该图的那些连接G的所有顶点的边构成的树,其总值最低。这里很重要一点就是要求G是连通的。
克鲁斯卡尔(Kruskal)算法因为只与边相关,则适合求稀疏图的最小生成树。而prime算法因为只与顶点有关,所以适合求稠密图的最小生成树。

克鲁斯卡尔(Kruskal)算法因为只与边相关,则适合求稀疏图的最小生成树。而prime算法因为只与顶点有关,所以适合求稠密图的最小生成树。

Prim

设图G顶点集合为U,首先任意选择图G中的一点作为起始点a,将该点加入集合V,再从集合U-V中找到另一点b使得点b到V中任意一点的权值最小,此时将b点也加入集合V;以此类推,现在的集合V={a,b},再从集合U-V中找到另一点c使得点c到V中任意一点的权值最小,此时将c点加入集合V,直至所有顶点全部被加入V,此时就构建出了一颗MST。因为有N个顶点,所以该MST就有N-1条边,每一次向集合V中加入一个点,就意味着找到一条MST的边。

算法要维持两个数组:
lowcost[i]:表示以i为终点的边的最小权值,当lowcost[i]=0说明以i为终点的边的最小权值=0,也就是表示i点加入了MST。
mst[i]: 表示对应lowcost[i]的起点,即说明边(mst[i],i)是MST的一条边,当mst[i]=0表示起点i加入MST。
具体过程详:http://blog.csdn.net/yeruby/article/details/38615045

代码

//最小生成树的prim算法
//http://blog.csdn.net/yeruby/article/details/38615045
#include<iostream>
#include<fstream>
using  namespace std;

#define  MAX 100
#define MAXCOST 0x7fffffff
int graph[MAX][MAX];

int prim(int graph[][MAX], int n,int begin)//begin作为初始顶点,n总的顶点数目
{
    int lowcost[MAX];
    int mst[MAX];
    int sum = 0;
    for (int i = 1;i <= n;i++) //初始化
    {
        if (i == begin)
            continue;
        lowcost[i] = graph[begin][i];//顶点1到其他定点的代价
        mst[i] = begin;//初始都指向顶点begin
    }
    mst[begin] = 0;//顶点begin在MST集合里
    lowcost[begin] = 0;
    for (int i = 1;i <= n;i++)
    {
        if (i == begin)
            continue;
        int min = MAXCOST;
        int minid = 0;
        for (int j = 1;j <= n;j++)//寻找lowcost中最小的边
        {
            if (j == begin)
                continue;
            if (lowcost[j] < min&&lowcost[j] != 0)
            {
                min = lowcost[j];
                minid = j;
            }
        }
        lowcost[minid] = 0;

        cout << "V" << mst[minid] << "-V" << minid << "=" << min << endl;//输出找到的最小一条边
        sum = sum + min;

        for (int j = 1;j <= n;j++)//更新其他定点(主要变化的就是以minid为起始的边)
        {
            if (j == begin)
                continue;
            if (graph[minid][j] < lowcost[j])
            {
                lowcost[j] = graph[minid][j];
                mst[j] = minid;
            }
        }
    }
    return sum;

    }

int main()
{
    ifstream in("input.txt");
    int m, n;
    int begin = 2;
    in >> m >> n;
    for (int i = 1;i <= m;i++)
    {
        for (int j = 1;j <= m;j++)
            graph[i][j] = MAXCOST;
    }
    for (int k = 1;k <= n;k++)
    {
        int i, j, cost;
        in >> i >> j >> cost;
        graph[i][j] = cost;
        graph[j][i] = cost;
    }

    int cost = prim(graph, m, begin);
    cout << "最小权值和=" << cost << endl;
    system("pause");
    return 0;
}

Kruskal

算法过程:
1.将图各边按照权值进行排序
2.将图遍历一次,找出权值最小的边,(条件:此次找出的边不能和已加入最小生成树集合的边构成环),若符合条件,则加入最小生成树的集合中。不符合条件则继续遍历图,寻找下一个最小权值的边。
3.递归重复步骤1,直到找出n-1条边为止(设图有n个结点,则最小生成树的边数应为n-1条),算法结束。得到的就是此图的最小生成树。

代码

//Kruskal 算法
#include<iostream>
#include<algorithm>
#include<fstream>
using namespace std;
#define MAX 1000

int father[MAX], son[MAX];//father[i]表示i的祖先(根),son[i]表示i的儿子数目,如果没有记为1,如果有一个记为2...

typedef struct Edge
{
    int a;
    int b;
    int value;
};

bool cmp(Edge &a, Edge &b)
{
    return a.value < b.value;
}

int unionsearch(int x)
{
    return x == father[x] ? x : unionsearch(father[x]);
}

bool join(int x, int y)//合并x与y
{
    int root1, root2;
    root1 = unionsearch(x);//每个数据都有一个根(祖先)
    root2 = unionsearch(y);
    if (root1 == root2) //两个的祖先一样,说明已经属于同一颗树了,不用再合并了
        return false;
    else if (son[root1] >= son[root2]) //如果root1的儿子数目比较多,则把root2的祖先定为root1,同时root1的儿子数目更新
    {
        father[root2] = root1;
        son[root1]= son[root1] + son[root2];
    }
    else
    {
        father[root1] = root2;
        son[root2]= son[root1] + son[root2];
    }
}
int main()
{
    ifstream in("input.txt");
    Edge edge[MAX];
    int v, l;
    bool  flag=0;
    int addedge_num=0, sum=0;
    in >> v >> l;
    for (int i = 1; i <= v; i++)
    {
        father[i] = i;
        son[i] = 1; //初始时,每个顶点的儿子数为1,即本身自己
    }
    for (int i = 1; i <= l; i++)
    {
        in >> edge[i].a >> edge[i].b >> edge[i].value;
    }
    sort(edge + 1, edge + 1 + l, cmp);//按照升序排列
    for (int i = 1; i < l; i++)
    {
        if (join(edge[i].a, edge[i].b))//说明能合并
        {
            addedge_num++;
            sum = sum + edge[i].value;
            cout<< edge[i].a << "->" << edge[i].b << endl;
        }
        if (addedge_num == v - 1)
        {
            flag = 1;
            break;
        }
    }
    if (flag)
    {
        cout << sum << endl;
    }
    else
    {
        cout << "Data error!" << endl;
    }
    system("pause");
    return 0;
}

注意里面涉及到并查集合的巧妙运用,仔细体会。

阅读更多
版权声明:转载标明出处,点一个顶即可:) https://blog.csdn.net/starstar1992/article/details/52939669
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭