最小生成树(Prim算法)

算法思想

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

图的所有顶点集合为V;初始令集合u={s}, v=V-u;
在两个集合u,v能够组成的边中,选择一条代价最小的边(u0,v0),加入到最小生成树中,并把v0并入到集合u中。
重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组d,用来维护集合v中每个顶点与集合u中最小代价边信息。

复杂度分析

我们假设顶点数量是n,边的数量是m。

最小生成树,要连接到每个顶点,因此要找到n个点,这是无法再做优化的。另外的怎样找到最小权值的边,复杂度和存图方式有关,也可以进行优化。

邻接矩阵存图

邻接矩阵存图,空间复杂度显然是 nn。
时间复杂度和找最小权值的安全边效率有关,如果时用最朴素的方式查找,显然每个点要找n次,因此,时间复杂度是O(n
n)。当然可以用堆进行优化,可以将时间复杂度降到O(n*logn)。但是一般邻接矩阵不优化,也不会超时,因为如果超时,首先空间都会爆了,优化的意义不大。

邻接表

如果n比较大时,可能会爆空间,因此可以用邻接表存图。一般会用一个结构体存边。
空间复杂度是V(m+n),m是m条变数,n是顶点数,每个顶点都要开辟一个点,一般而言,m>n,基本可以认为空间复杂度是V(m)。
时间复杂度和邻接矩阵类似,最朴素的方式查找,每个点加入生成树后,要更新新的权值,最坏的情况是要更新m次,因此朴素的Prim算法,时间复杂度是O(nm)。如果我们用堆优化,可以讲时间复杂度将到O(nlogm)。


问题

题目来源

[hihocoder#1097] : 最小生成树一·Prim算法 http://hihocoder.com/problemset/problem/1097
[hihocoder#1109] : 最小生成树三·堆优化的Prim算法 http://hihocoder.com/problemset/problem/1109
两题区别在于数据n,m范围不同,其他一样。



描述
最近,小Hi很喜欢玩的一款游戏模拟城市开放出了新Mod,在这个Mod中,玩家可以拥有不止一个城市了!

但是,问题也接踵而来——小Hi现在手上拥有N座城市,且已知这N座城市中任意两座城市之间建造道路所需要的费用,小Hi希望知道,最少花费多少就可以使得任意两座城市都可以通过所建造的道路互相到达(假设有A、B、C三座城市,只需要在AB之间和BC之间建造道路,那么AC之间也是可以通过这两条道路连通的)。

提示:不知道为什么Prim算法和Dijstra算法很像呢Σ(っ °Д °;)っ 。
输入
每个测试点(输入文件)有且仅有一组测试数据。

在一组测试数据中:

第1行为1个整数N,表示小Hi拥有的城市数量。

接下来的N行,为一个N*N的矩阵A,描述任意两座城市之间建造道路所需要的费用,其中第i行第j个数为Aij,表示第i座城市和第j座城市之间建造道路所需要的费用。

对于100%的数据,满足N<=10^3,对于任意i,满足Aii=0,对于任意i, j满足Aij=Aji, 0<Aij<10^4.

输出
对于每组测试数据,输出1个整数Ans,表示为了使任意两座城市都可以通过所建造的道路互相到达至少需要的建造费用。

样例输入
5
0 1005 6963 392 1182
1005 0 1599 4213 1451
6963 1599 0 9780 2789
392 4213 9780 0 5236
1182 1451 2789 5236 0
样例输出
4178


下面给出Prim 算法的邻接矩阵、邻接矩阵堆优化、邻接表堆优化三种参考代码。

// Prim 邻接矩阵 朴素版 
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3+1;
const int INF = 1e8;
int g[maxn][maxn];
int n, d[maxn], ans;
bool vis[maxn];

void rd(){ // 邻接矩阵读入 
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            scanf("%d", &g[i][j]);
}

void prim(int s){
    memset(vis, false, sizeof(vis));
    fill(d, d+maxn, INF);
    d[s] = 0;
    for(int i=1; i<=n; i++){
        int k, minx = INF;
        for(int j=1; j<=n; j++){ // 找最小权值的顶点 
            if(!vis[j] && d[j]<minx){
                k = j;
                minx = d[j];
            }
        }
        vis[k] = true;  // 标记 
        ans += d[k];    // 最小权值累加 
        for(int j=1; j<=n; j++){ // 新增结点后,更新权值 
            if(!vis[j] && d[j]>g[k][j]) // 无向图g[k][j]=g[j][k] 
                d[j] = g[k][j];
        }
    }
}

int main(){
    scanf("%d", &n);
    rd();
    prim(1);
    printf("%d", ans);
    return 0;
} 
// Prim 邻接矩阵 堆优化版 
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3+1;
const int INF = 1e8;
int g[maxn][maxn];
int n, d[maxn], ans;
bool vis[maxn];
typedef pair<int, int> P;

void rd(){ // 邻接矩阵读入 
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            scanf("%d", &g[i][j]);
}

void prim(int s){
    priority_queue <P, vector<P>, greater<P> > q;  // 小根堆,d[i]越小排越前
    memset(vis, false, sizeof(vis));
    fill(d, d+maxn, INF);
    d[s] = 0;
    q.push((P){d[s], s});
    int cnt = 0;                // 统计加入最小生成树的点个数
    while(!q.empty() && cnt<n){ 
        P u = q.top();
        q.pop();
        int v = u.second;
        if(vis[v]) continue;
        vis[v] = true;
        ans += u.first;
        cnt++;
        for(int i=1; i<=n; i++){  // 更新权值
            if(!vis[i] && d[i]>g[v][i]){
                d[i] = g[v][i];
                q.push((P){d[i], i});
            }
        }
    }
}

int main(){
    scanf("%d", &n);
    rd();
    prim(1);
    printf("%d", ans);
    return 0;
} 
// Prim 邻接表 堆优化版 
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+1;
const int INF = 1e8+1;
struct edge{
    int from, to, cost;
};
vector<edge> G[maxn];
int n, m, d[maxn], ans;
bool vis[maxn];
typedef pair<int, int> P;

void rd(){ // 邻接表读入 
    scanf("%d %d", &n, &m);
    for(int i=1; i<=m; i++){
        int x, y, z;
        scanf("%d %d %d", &x, &y, &z);
        G[x].push_back((edge){x, y, z});
        G[y].push_back((edge){y, x, z});
    }
}

void prim(int s){
    priority_queue <P, vector<P>, greater<P> > q;
    memset(vis, false, sizeof(vis));
    fill(d, d+maxn, INF);
    d[s] = 0;
    q.push((P){d[s], s});
    int cnt = 0; 
    while(!q.empty() && cnt<n){
        P u = q.top();
        q.pop();
        int v = u.second;
        if(vis[v]) continue;
        vis[v] = true;
        ans += u.first;
        cnt++;
        for(int i=0; i<G[v].size(); i++){ // upload
            edge e = G[v][i];
            if(!vis[e.to] && d[e.to]>e.cost){
                d[e.to] = e.cost;
                q.push((P){d[e.to], e.to});
            }
        }
    }
}

int main(){
    rd();
    prim(1);
    printf("%d", ans);
    return 0;
} 

转载于:https://www.cnblogs.com/gdgzliu/p/11547513.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值