算法导论学习笔记(17)——最小生成树

给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树.

求最小生成树的算法
(1) 克鲁斯卡尔算法
图的存贮结构采用边集数组,且权值相等的边在数组中排列次序可以是任意的.该方法对于边相对比较多的不是很实用,浪费时间.

(2) 普里姆算法
图的存贮结构采用邻接矩阵.此方法是按各个顶点连通的步骤进行,需要用一个顶点集合,开始为空集,以后将以连通的顶点陆续加入到集合中,全部顶点加入集合后就得到所需的最小生成树 . 

在Kruskal算法中,集合A是一个森林,加入集合A中的安全边总是图中连接两个不同连通分支的最小权边。

在Prim算法中,集合A仅形成单棵树,添加入集合A的安全边总是连接树与一个不在树中的顶点的最小权边。

 

Kruskal算法:

思想:该算法每次从所有未使用边中,找出权值最小的边,加入到生成树中,直到加入V-1条边(V是顶点数),构成一颗MST。

看图23-4,Kruskal的构造过程

具体实现(C/C++):

#include <iostream>
#include <algorithm>
using namespace std;
 
const int maxint = 999999;
 
typedef struct Road{
    int c1, c2;   // a到b
    int value;  // 权值
}Road;
 
int no;
int line;
Road road[100];
int node[101];
 
bool myCmp(const Road &a, const Road &b)
{
    if(a.value < b.value)
        return 1;
    return 0;
}
 
int Find_Set(int n)
{
    if(node[n] == -1)
        return n;
    return node[n] = Find_Set(node[n]);
}
 
bool Merge(int s1, int s2)
{
    int r1 = Find_Set(s1);
    int r2 = Find_Set(s2);
    if(r1 == r2)
        return 0;
    if(r1 < r2)
        node[r2] = r1;
    else
        node[r1] = r2;
    return 1;
}
 
int main()
{
    freopen("input.txt", "r", stdin);
 
    memset(node, -1, sizeof(node));
    cout << "Input the number of the node:";
    cin >> no;
    cout << "Input the number of the line:";
    cin >> line;
    cout << "Input the edge:";
    for(int i=0; i<line; ++i)
    {
        cin >> road[i].c1 >> road[i].c2 >> road[i].value;
    }
    sort(road, road+line, myCmp);
    int sum = 0, count = 0;  // sum是MST的值,count是记录已使用的点数
    for(int i=0; i<line; ++i)
    {
        if(Merge(road[i].c1, road[i].c2))
        {
            count ++;
            sum += road[i].value;
        }
        if(count == no-1)
            break;
    }
    cout << sum << endl;
}
 
 
/*
input.txt:
9
14
1 2 4
1 8 8
2 3 8
2 8 11
3 4 7
3 6 4
3 9 2
4 5 9
4 6 14
5 6 10
6 7 2
7 8 1
7 9 6
8 9 7
*/

Prim算法:

思想:可以通过Dijkstra算法来理解,因为两者的思想非常类似,假设顶点集S,其中集合A是已经加入到生成树中的点,集合B是剩余的点,每次从连接A,B的边中找出权值最小的边,作为生成树的边。

(Dijkstra的讲解见:http://www.wutianqi.com/?p=1890)

看图23-5,Prim的构造过程

具体实现(C/C++):

#include <iostream>
using namespace std;
const int INF=1001;
 
int no, line;
int arcs[101][101];
int dis[101], vis[101];
int _min;
 
int main()
{
    freopen("input.txt", "r", stdin);
 
 
    cout << "Input the number of the node:";
    cin >> no;
    cout << "Input the number of the line:";
    cin >> line;
 
    for(int i=0; i<no; ++i)
        for(int j=0; j<no; ++j)
            arcs[i][j] = INF;
    cout << "Input the edge:";
    int p, q, len;          // 输入p, q两点及其路径长度
    for(int i=0; i<line; ++i)
    {
        cin >> p >> q >> len;
        if(len < arcs[p-1][q-1])       // 有重边
        {
            arcs[p-1][q-1] = len;      // p指向q
            arcs[q-1][p-1] = len;      // q指向p,这样表示无向图
        }
    }
 
    memset(vis, 0, sizeof(vis));
    for(int i=1; i<no; ++i)
        dis[i] = arcs[0][i];
    vis[0] = 1;
    int sum = 0, rec = 0;
    dis[0] = 0;
    for(int i=1; i<no; ++i)
    {
        _min = INF;
        for(int j=0; j<no; ++j)
            if(!vis[j] && dis[j]<_min)
            {
                rec = j;
                _min = dis[j];
            }
        //cout << "min: " << _min << endl;
        sum += _min;
        vis[rec] = 1;
        for(int j=0; j<no; ++j)
            if(!vis[j] && arcs[rec][j] < dis[j])
                dis[j] = arcs[rec][j];
    }
    printf("%d\n", sum);
 
    return 0;
}
 
 
/*
input.txt:
9
14
1 2 4
1 8 8
2 3 8
2 8 11
3 4 7
3 6 4
3 9 2
4 5 9
4 6 14
5 6 10
6 7 2
7 8 1
7 9 6
8 9 7
*/

输入数据是根据P350的图23-5得到。

可以看到结果是37。

小结:

Kruskal算法和Prim算法的最大区别就是在找安全边时,对于安全边的选择不一样,不过同样都是基于贪心算法的。

Kruskal适合一维数组来管理数据,而Prim则适合用一个二维矩阵来管理边之间的关系,具体选择可以看实际数据的量。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值