0
点赞
收藏
分享

微信扫一扫

POJ1258 最小生成树基础模板题通俗详细版(prim解法)

1kesou 2022-03-12 阅读 56

既然是基础题,就顺带详细一些讲一下算法吧

题目意思:就是输入一个N,代表有N个点,然后给你一个N*N的数组,假定数组为 map[N][N] 数组中的每个数map[i][j] 代表从 i j 所需要的时间,保证所有节点都连接的情况下,需要的最小花费

首先来看一下prim算法

一开始从所有边选择离出发点最小的一条加入,此时加入<v1,v3>权重为1的边

在我们加入<v1,v3>后,需要从与v1,v3相连的几条边中选出一条最小的边,如图中绿色标注边,可以看出<v3,v6>边的权值最小,所以加入<v3,v6>,同时要更新v2的权值,因为<v1,v2>权值为6,<v2,v3>权值为5

 

再从与v1,v3,v6相连的边中加入权值最小的边

 

如图所示,我们加入<v6,v4>,按照这个步骤,依次加入<v3,v2>,<v2,v5>

接下来对比代码看一下
开始我们输入Graph的时候用的是vector<node>G[maxn]的形式,以G[i][j].len表示i到j的长度(或者说所用时间),在这里我加入了一个结构体自动排序的函数,用来使用优先队列,在向队列里插入数据时可以把小的排在队头,大的排在队尾,当然也可以选择用二位数组存图,手动排序,可能会损失一些时间,但还是在题目时间限制内

struct node
{
    int v, len;
    node(int v = 0, int len = 0) :v(v), len(len) {}
    bool operator < (const node &a)const    // 加入队列的元素自动按距离从小到大排序
    {
        return len> a.len;
    }
};

然后是对输入进行预处理

void init()
{
    for (int i = 0; i<maxn; i++)    
    {
        G[i].clear();
        dis[i] = INF;      // dis[i]记录从开始节点到以i为节点的两点之间的最短路,初始化为无穷大
        vis[i] = false;    // vis[i]记录当我们进行选择时,该节点是否已经加入最短路
    }
}

下面是最重要的prim算法过程,prim算法需要使用队列,这里我们使用了一个优先队列,不懂优先队列的可以自己去查一下

优先队列Q第一步加入点v1,此时起始点为v1,再加入起始点与当前点的最短路,因为第一步起始点和当前点都是v1,所以它们之间的距离是0 ,ans记录的是当前最短路的长度,每次加入一条边,ans就会加上这条边的权值

进入prim后,取出队列头结点,然后找到和头节点相连的权值最小的边,把这条边加入队列

第一步取出v1,标记v1已经加入最短路vis[v1]=1,找到与v1相连接的边是<v1,v2>,<v1,v3>,<v1,v4>加入队列,把v2,v3,v4以及<v1,v2>,<v1,v3>, <v1,v4>的权值加入队列加入队列,此时队列中的情况为<v3,1>,<v4,5>,<v2,6>每个节点后的数字代表他们与出发点之间最短路的权重

第二步,取出v3,标记v3已经加入最短路vis[v3]=1,找到与v3相连接的边,此时<v6,4>,<v2,5>,<v5,6>,入队,ans=1

 第三步,取出v6,标记v6已经加入最短路vis[v6]=1,找到与v6相连接的边,此时<v4,2>,入队,ans=5

第四步,取出v4,标记v4已经加入最短路vis[v4]=1,找到与v4相连接的边,此时发现与v4相连的边都已经加入最短路,ans=7

接下来按照这样的步骤依次加入v2和v5,构建完成。

int Prim(int s)
{
    priority_queue<node>Q; // 定义优先队列
    int ans = 0;
    Q.push(node(s,0));  // 起点加入队列
    while (!Q.empty())
    {

        node now = Q.top();
        Q.pop();  // 取出距离最小的点
        int v = now.v;
        if (vis[v]) continue;  // 同一个节点,可能会推入2次或2次以上队列,这样第一个被标记后,剩下的需要直接跳过。
        vis[v] = true;  // 标记一下
        ans += now.len;
        for (int i = 0; i<G[v].size(); i++)    // 开始更新
        {
            int v2 = G[v][i].v;
            int len = G[v][i].len;
            if (!vis[v2] && dis[v2] > len)
            {
                dis[v2] = len;
                Q.push(node(v2, dis[v2]));  // 更新的点加入队列并排序
            }
        }
    }
    return ans;
}

 下面的是完整代码

#include<stdio.h>
#include<string.h>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
#define maxn 200
#define INF 0x3f3f3f3f
struct node
{
    int v, len;
    node(int v = 0, int len = 0) :v(v), len(len) {}
    bool operator < (const node &a)const    // 加入队列的元素自动按距离从小到大排序
    {
        return len> a.len;
    }
};

vector<node> G[maxn];
int vis[maxn];
int dis[maxn];

void init()
{
    for (int i = 0; i<maxn; i++)
    {
        G[i].clear();
        dis[i] = INF;
        vis[i] = false;
    }
}
int Prim(int s)
{
    priority_queue<node>Q; // 定义优先队列
    int ans = 0;
    Q.push(node(s,0));  // 起点加入队列
    while (!Q.empty())
    {

        node now = Q.top();
        Q.pop();  // 取出距离最小的点
        int v = now.v;
        if (vis[v]) continue;  // 同一个节点,可能会推入2次或2次以上队列,这样第一个被标记后,剩下的需要直接跳过。
        vis[v] = true;  // 标记一下
        ans += now.len;
        for (int i = 0; i<G[v].size(); i++)    // 开始更新
        {
            int v2 = G[v][i].v;
            int len = G[v][i].len;
            if (!vis[v2] && dis[v2] > len)
            {
                dis[v2] = len;
                Q.push(node(v2, dis[v2]));  // 更新的点加入队列并排序
            }
        }
    }
    return ans;
}

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        init();
        int x;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<n; j++)
            {
                G[i].push_back(j);
                scanf("%d",&x);
                G[i][j].len = x;
            }
        }
        printf("%d\n",Prim(0));
    }

}

举报

相关推荐

0 条评论