最小生成树之kruskal算法和Prim算法

连同n个点最少需要n - 1条边,但是它给你的边数大于n - 1 呢你怎么才能连出一个最短的路呢,这就是最小生成树可以解决的问题,你要获得所有边的关系以及边的长度,接下来可以有两种思路一种事kruskal算法

是从最小的便开始连点,连完两个点后用并查集将这两个点视为连同状态,接着去找下一条最短的边,判断两个人不是一个老大的话,就再连接起来。这样最终就能找到一个最短的生成树了,且绝对没有环(因为你的并查集)

先来看看常用的并查集代码(找老大,判老大,连老大)

int Find(int x)
{
    if(x == father[x])return x;
    return father[x] = Find(father[x]);//一定得带回去
}
void join(int x,int y)
{
    int u = Find(x);
    int v = Find(y);
    if(u == v)return ;
    father[u] = v;
}
bool same(int x,int y)
{
    return Find(x) == Find(y);
}

接下来长贵的输之后开始调用kruskal算法

 long long res = kruscar(len);
进入kruscal算法首先第一步是不是要排序(因为我的结构体李存放了所有的边,而kruscal是从找最小边为开始的,所以排序后,我会一条一条的找和连)

sort(edge + 1, edge + 1 + len,cmp);
接下来,对于每一条边的起点和终点我都会进行是否在一个集合中的判断(是不是同一个老大)

只有不是同一个老大,也就是这两个点并没有互通互联,才能够被连接

if( !same(edge[i].from,edge[i].to))
            {
                //cout<<"cs"<<endl;
                join(edge[i].from,edge[i].to);
                //cout<<edge[i].from<<" "<<edge[i].to<<endl;
                res += edge[i].cost;
            }
至于在循环之中还要做什么,就得看相应的题目要求来进行优化了,比如有的题目会要求输出最短路,那就定义一个pre[u] = v

数值表示u的点前连v这个点

一下是全码

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define Max 1000
struct node{
    int from,to,cost;
}edge[Max * Max];
int father[Max];

bool cmp(node a,node b)
{
    return a.cost < b.cost;
}
void init()
{
    for(int i = 1;i <= Max;i++)
    {
        father[i] = i;
    }
}
int Find(int x)
{
    if(x == father[x])return x;
    return father[x] = Find(father[x]);//一定得带回去
}
void join(int x,int y)
{
    int u = Find(x);
    int v = Find(y);
    if(u == v)return ;
    father[u] = v;
}
bool same(int x,int y)
{
    return Find(x) == Find(y);
}
long long kruscar(int len)
{
    long long res = 0;
    sort(edge + 1, edge + 1 + len,cmp);
        for(int i = 1;i <= len;i++)
        {
            if( !same(edge[i].from,edge[i].to))
            {
                //cout<<"cs"<<endl;
                join(edge[i].from,edge[i].to);
                //cout<<edge[i].from<<" "<<edge[i].to<<endl;
                res += edge[i].cost;
            }
        }
    return res;
}
int main()
{
    int t;
    while(scanf("%d",&t) != EOF)
    {
        getchar();
        if(t == 0)break;
        int len = (t * t - t) / 2;
        init();
        for(int i = 1; i <= len;i++)
        {
            scanf("%d %d %d",&edge[i].from,&edge[i].to,&edge[i].cost);
            getchar();
        }
        long long res = kruscar(len);
        cout << res <<endl;

    }
    return 0;
}

然后Prim算法是针对的点,因为是对点寻边,那么,将一个点所外连的边联系起来,就有了选择,比较好理解的就是用二维动态结构体数组vector<node> v[Max];
v[i][j] ——对于第i点一共有v[i].size()条边,这样for遍历一下,第i点所有的边都会进入优先队列之中

struct edge{
    int to;
    int cost;
    edge (int ito,int icost) : to(ito),cost(icost){}
    edge(){};
    friend bool operator < (edge a,edge b)
    {
        return a.cost > b.cost;//小的优先级高
    }
};
vector<edge> E[Max];
这里提一下优先队列,虽然树对点寻边,但是出队列的肯定还是最短的边,因为我们要做的是最小生成树啊~~,所以优先对列就很好用了

然后对于结构体内置的edge 函数,这个我目前也不大明白,所知道的就是便于对动态二维结构体数组赋值

for(int i = 1;i <= len;i++)
        {
            int from,to,cost;
            scanf("%d %d %d",&from,&to,&cost);
            E[from].push_back(edge(to,cost));//利用结构体内部的函数赋值;否则只能用E[i].push_back(结构体来赋值)
            E[to].push_back(edge(from,cost));
        }
不用你去新定义一个结构体遍量,然后赋初值,在push_back()到结构体数组中去了

那么就开始prim算法啦

先找第一个你想找的随机点spoint

   for(int i = 0;i < E[spoint].size();i++)
    {
        //cout<<E[spoint][i].to<<"  "<<E[spoint][i].cost<<endl;
        q.push(E[spoint][i]);
    }
把与第一个点spoint有关的边全push到优先队列中去,然后出队列,入队列 ……
while(!q.empty())
    {
        edge edgenow = q.top();
        q.pop();
        if(!vis[edgenow.to])
        {
            vis[edgenow.to] = 1;
            //cout<<edgenow.to <<" cost = "<<edgenow.cost<<endl;
            n++;
            res += edgenow.cost;
            if(n == t)break;
            for(int i = 0;i < E[edgenow.to].size();i++)
            {
                //cout<<E[edgenow.to][i].to<<"  "<<E[edgenow.to][i].cost<<endl;
                q.push(E[edgenow.to][i]);
            }
        }
    }
这之间不要忘记优化你的vis数组,这个vis[]就相当于kruskal算法中并查集判断有没有连通的方法

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <queue>
#include <vector>
using namespace std;
#define Max 1000
int t;
struct edge{
    int to;
    int cost;
    edge (int ito,int icost) : to(ito),cost(icost){}
    edge(){};
    friend bool operator < (edge a,edge b)
    {
        return a.cost > b.cost;//小的优先级高
    }
};
vector<edge> E[Max];
int vis[Max];
priority_queue<edge> q;

void init()
{
    memset(vis,0,sizeof(vis));
    for(int i = 1;i <= t;i++)
    {
        E[i].clear();
    }
    while(q.size())q.pop();
}
long long prim(int spoint)
{
    long long res = 0;
    for(int i = 0;i < E[spoint].size();i++)
    {
        //cout<<E[spoint][i].to<<"  "<<E[spoint][i].cost<<endl;
        q.push(E[spoint][i]);
    }
    vis[spoint] = 1;
    int n = 1;
    while(!q.empty())
    {
        edge edgenow = q.top();
        q.pop();
        if(!vis[edgenow.to])
        {
            vis[edgenow.to] = 1;
            //cout<<edgenow.to <<" cost = "<<edgenow.cost<<endl;
            n++;
            res += edgenow.cost;
            if(n == t)break;
            for(int i = 0;i < E[edgenow.to].size();i++)
            {
                //cout<<E[edgenow.to][i].to<<"  "<<E[edgenow.to][i].cost<<endl;
                q.push(E[edgenow.to][i]);
            }
        }
    }
    return res;
}
int main()
{
    while(scanf("%d",&t) != EOF && t)
    {
        int len = (t * t - t) / 2;
        init();
        for(int i = 1;i <= len;i++)
        {
            int from,to,cost;
            scanf("%d %d %d",&from,&to,&cost);
            E[from].push_back(edge(to,cost));//利用结构体内部的函数赋值;否则只能用E[i].push_back(结构体来赋值)
            E[to].push_back(edge(from,cost));
        }
        long long res = prim(1);
        printf("%lld\n",res);
    }
    return 0;
}
第二个方法是用两个一位数组实现二维动态结构体数组——链式前向星

别管这个可怕的名字了

先来看看第一个一维结构体数组

struct edge{
    int to;
    int cost;
    int reid;
    friend bool operator < (edge a,edge b)
    {
        return a.cost > b.cost;
    }
}e[Max];
可以看出来,prim算法是要用优先队列的(因为这个很方便);这是一维结构体数组

里面定义的to和cost都很好说,那么reid是干什么用的呢,不如先来看看第二个一位数组

int cnt,id[Max];
很明显,这两个之间有关系,在赋值的时候就能体现出来

int init(int len)
{
    cnt = 0;
    for(int i = 0;i < Max;i++)
    {
        id[i] = -1;
    }
    while(q.size())q.pop();
    memset(vis,0,sizeof(vis));
    memset(e,0,sizeof(e));
}
void add(int from,int to,int cost)
{
    e[cnt].cost = cost;
    e[cnt].to = to;
    e[cnt].reid = id[from];
    id[from] = cnt++;
}
for(int i = 0;i < len ;i++)
        {
            int from,to,cost;
            scanf("%d %d %d",&from,&to,&cost);
            add(from,to,cost);
            add(to,from,cost);
        }
初始化的时候一位数组id中的各个项都被赋值为-1——这是一个结束标志

然后cnt赋值为0,在add函数中你可以看出,我输入的每一条边的下标都是cnt且是递增的,那么我怎么才能把起点都为1的边联系起来的,那就是id数组的作用了

add函数中id数组的下标都是from也就是根据起始点来进行分类id[from] = cnt++;如果这一个不好懂的话,那就拆开来看id[from] = cnt ; 
cnt++;
你可以做一个假设,输入了很多from都是1的边,这是最后一条了,后面还会有输入,但是from都不是1了,所以当前这最后一条起始点为1的边的下标就被保存到了id[from]数组中了,也就是id[i]是一个目录索引,通过id你可以找到有关1这个点的边的链表的head坐标,其实我觉得链式前向行真的就类似多个链表~~
id[i]的作用可以帮你找到起点,那么中间各个边的关系链就是结构体数组中的reid的作用了

 e[cnt].reid = id[from];

这里的id【from】还没有更新,所以代表的是这个点的链表还没有往下加结点前的head下标,记录了这个下标,连上了节点,然后更新了id[from]的head…………

总之我是这么去理解链式前向星的(链式),肯定会有更多更好的理解方法,也欢迎大家交流互进

接下来开始找第一个点的所有相关边

for(int i = id[point];~i;i = e[i].reid)//-1取反是0就结束了
    {
        q.push(e[i]);
    }
先得到head下标 i = id[point]  然后push进去,再通过head往下找i = e[i].reid,e[i].reid是中间的链子,最后结束的时候就是连向-1的时候了

连完第一个点的后续push入队出队也就不难了

 while(sum < n)
    {
        while(q.size())
        {
            edge now = q.top();
            q.pop();
            if(!vis[now.to])
            {
                vis[now.to] = 1;
                res += now.cost;
                sum++;
                for(int i = id[now.to]; ~i;i = e[i].reid)
                {
                    q.push(e[i]);
                }
            }
        }
    }

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int t;
const int Max = 1000;
int vis[Max];
struct edge{
    int to;
    int cost;
    int reid;
    friend bool operator < (edge a,edge b)
    {
        return a.cost > b.cost;
    }
}e[Max * Max];
priority_queue<edge>q;
int cnt,id[Max];
int init(int len)
{
    cnt = 0;
    for(int i = 0;i < Max;i++)//更新的话要确保全部!!!
    {
        id[i] = -1;
    }
    while(q.size())q.pop();
    memset(vis,0,sizeof(vis));
    memset(e,0,sizeof(e));
}
void add(int from,int to,int cost)
{
    e[cnt].cost = cost;
    e[cnt].to = to;
    e[cnt].reid = id[from];
    id[from] = cnt++;
}
long long prim(int point,int n)
{
    int sum = 1;
    long long res = 0;
    for(int i = id[point]; ~i ;i = e[i].reid)//-1取反是0就结束了
    {
        q.push(e[i]);
    }
    vis[point] = 1;
    while(sum < n)
    {
        while(q.size())
        {
            edge now = q.top();
            q.pop();
            if(!vis[now.to])
            {
                vis[now.to] = 1;
                res += now.cost;
                sum++;
                for(int i = id[now.to]; i != -1;i = e[i].reid)
                {
                    q.push(e[i]);
                }
            }
        }
    }
    return res;
}
int main()
{
    while(scanf("%d",&t) != EOF && t)
    {
        getchar();
        int len = (t * t - t) / 2;
        init(t);
        for(int i = 0;i < len ;i++)
        {
            int from,to,cost;
            scanf("%d %d %d",&from,&to,&cost);
            getchar();
            add(from,to,cost);
            add(to,from,cost);
        }
        long long res = prim(1,t);
        printf("%lld\n",res);
    }
    return 0;//这个竟然忘了!!!terminate called after throwing an instance of 'std::bad_alloc
}








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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值