DS图—最小生成树

130c72bac5b24a09ac9b35ba1fcb3ca2.png

Description

根据输入创建无向网。分别用Prim算法和Kruskal算法构建最小生成树。(假设:输入数据的最小生成树唯一。)

Input

顶点数n

n个顶点

边数m

m条边信息,格式为:顶点1 顶点2 权值

Prim算法的起点v

Output

输出最小生成树的权值之和

对两种算法,按树的生长顺序,输出边信息(Kruskal中边顶点按数组序号升序输出)

Input

Copy

6
v1 v2 v3 v4 v5 v6 
10
v1 v2 6
v1 v3 1
v1 v4 5
v2 v3 5
v2 v5 3
v3 v4 5
v3 v5 6
v3 v6 4
v4 v6 2
v5 v6 6
v1

Output

Copy

15
prim:
v1 v3 1
v3 v6 4
v6 v4 2
v3 v2 5
v2 v5 3
kruskal:
v1 v3 1
v4 v6 2
v2 v5 3
v3 v6 4
v2 v3 5

Prim算法:

ac21b0800a934535a37fe9a7aa51fff9.png

图片来源于知乎@夏虫,图片与样例无关

我个人感觉有点类似广度搜索

我们建立一个动态数组ss,第一个节点是0,放进去

ss:0
假设我们从0节点出发,以0为节点广度搜索与2的边是最短的,所以第一条边为0到2长度为1

把2放进ss里
ss:0 2 

然后我们把0,2分别为中心都扫一遍没有进入树里面的节点,找到距离最短的边,就是0,2之外的节点,因为如果不除去0,2这个已经在树里的节点,那0为中心的时候扫到2就是最短的了。

然后我们找到2到5距离为1的边。将5,放入ss里
ss:0 2 5
然后重复上述步骤
0 2 1
2 5 4
5 3 2
2 1 5
1 4 3

这几条边生成最小生成树

kruskal算法:

这个算法很好想。首先你的会并查集,并查集是用来判断两条边是否有联系的,
假设1 2有关系,你可以把1的父节点当作2,然后2跟3有关系,那2的父节点是3。
1,2,3是不是都有关系,然后说难听点按上面的父亲关系来看,是不是1是3的孙子,2是3的儿子。
可以通过压缩路径的算法直接让1跟3有联系,这样判断1,2是否有关系的时候判断1,2的父节点是否相等就可以了。

实际算法实现。首先我们要按照边的长度去排序,拍好序之后是不是都是短的边在前面,这样方便我们选择。
然后判断这条边的两个节点是否已经有联系了,如果有就代表已经连接了,那就不能选,选了就重复了,成环了,如果没有就可以选,而且你从左往右数组遍历都是小的边在前。

如;

首先这个是无向图

34e917a730fc43a9a7a061d776df7e00.png

可以得出6条边

1 2 1 a边
2 1 1 b边
2 3 2 c边
3 2 2 d边
1 3 3 e边
3 1 3 f边
 

我这已经将他排好序了,按照距离短的在前,但是距离相等的我按照字典序排了
我们选第一条a边
1 2 1
然后第二条边看b,但是1 2已经有边了,再选不就成环了吗,所以b不选
然后看c
2 3 2 
2跟3没有联系,所以可以选,而且是能选中的最短了的是不是,
然后d边一样,都是构成环了,不选

后的1 3跟3 1的边都不选,因为1通过a c两条边已经让1 3建立联系了。

已经生成最小生成树了,n个节点用了n-1条边

是不是期间你不用考虑边长因为已经排好序了,只需要考虑两个点之间是否已经存在连接关系了。

代码如下:

希望如果要理解能粘贴在ide内,然后先不看函数体,看最开始的创建的变量,基本都带有解析,然后看main函数,然后遇到相关函数的时候往回找到函数,这样子方便理解。

#include <iostream>
#include <queue>
#include <vector>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn = 1e3 + 10;
int n, m;
int sum;///用于计算最小权值和
vector<string>ss;
string start;从哪个点开始

struct edge
{
    int l, r;
    int dis;
    bool operator<(const edge& p)
    {
        if (dis < p.dis)
            return true;
        else if (dis > p.dis)
            return false;
        else
        {
            if (l < p.l)
                return true;
            else if (l > p.l)
                return false;
            else
            {
                if (r <= p.r)
                    return true;
                else 
                    return false;
            }
        }
    }
};
edge shuzu[maxn];///kruskal算法的数组
int primtu[maxn][maxn];///prim算法的邻接矩阵
int visit[maxn];///prim算法判断节点的是否进树
int fa[maxn];///kruskal要用到并查集要用到的父节点

///prim图的初始化,还有k算法的父节点fa[]数组初始化
void irit()
{
    for (int i = 0; i < n; i++)
    {
        visit[i] = 0;///prim算法的判断·这个点是否进树
        fa[i] = i;//父节点都初始化为自己
        for (int j = 0; j < n; j++)
        {
            primtu[i][j] = 0x3f3f3f;
        }
    }
}

//找对应下标对应的字符串
int findstr(string ch)
{
    int len = ss.size();
    for (int i = 0; i < len; i++)
    {
        if (ch == ss[i])
            return i;
    }
}

//prim算法实现
vector<edge>out;//因为要先计算权值和在输出,所以把输出都先存储
void prim()
{
    int sindex=findstr(start);///startindex,开始的下标
    vector<int>intree;///有点类似层次遍历
    intree.push_back(sindex);///开始的节点已经进树了
    visit[sindex] = 1;
    while (out.size() < n-1)
    {
        int minm = 0x3f3f3f;
        int len = intree.size();
        int a=0, b=0;
        for (int i = 0; i < len; i++)///找到相邻权值最小的那个节点
        {
            //行是intree[i],列是0到n
            int hang = intree[i];
            for (int j = 0; j < n; j++)
            {
                if (minm > primtu[hang][j]&&visit[j]==0)//j这个节点也要没进树里
                {
                    a = hang;
                    b = j;
                    minm = primtu[hang][j];
                }
            }
        }
        //最后得出的就是相邻最小的节点
        visit[b] = 1;
        sum += minm;
        edge nod;
        nod.l = a, nod.r = b, nod.dis = minm;
        out.push_back(nod);
        intree.push_back(b);
    }
}

/// <krusual>
int find_(int x)//并查集的查找
{
    if (x == fa[x])
        return x;
    return fa[x] = find_(fa[x]);//压缩路径
}
void emerge_(int x, int y)///并查集的并,节点连接
{
    int fx = find_(x), fy = find_(y);
    if (fx != fy)
        fa[fx] = fy;
}
void kruskal()
{
    sort(shuzu, shuzu + 2*m);///按边长从小到大排序
    int cnt = 0;
    int i = 0;
    while (cnt < n - 1)///n个点需要n-1条边
    {
        int a = shuzu[i].l, b = shuzu[i].r;
        if (find_(a) != find_(b))//两个节点没有连接
        {
            emerge_(a, b);///那把两个节点连接
            cout << ss[a] << " " << ss[b] << " " << shuzu[i].dis << endl;
            cnt++;
        }
        i++;//i用来遍历数组的
    }
}
/// </summary>

int main()
{
    cin >> n;
    irit();
    for (int i = 0; i < n; i++)
    {
        string ch;
        cin >> ch;
        ss.push_back(ch);///这里是将一个下标对应一个字符串,这样可以在构建邻接矩阵的时候可以方便一些
    }
    cin >> m;
    int k = 0;
    for (int i = 0; i < m; i++)
    {
        string a, b;
        int dis;
        cin >> a >> b >> dis;
        int indexa = findstr(a);//找出字符串对应下标
        int indexb = findstr(b);
        shuzu[k].l = indexa;//k算法要用的数组,l是左节点,r是右节点。dis是距离
        shuzu[k].r = indexb;
        shuzu[k].dis = dis;
        k++;
        shuzu[k].l = indexb;///这里因为是无向图,所以都创建一遍
        shuzu[k].r = indexa;
        shuzu[k].dis = dis;
        k++;
        primtu[indexa][indexb] = dis;///prim算法的无向图处理
        primtu[indexb][indexa] = dis;无向图处理
    }
    cin >> start;
    prim();///prim算法的实现
    cout << sum << endl;//最小权值和
    cout << "prim:" << endl;
    int len = out.size();
    for (int i = 0; i < len; i++)///因为是先输出权值和再输出节点,所以要用一个数组存着
    {
        cout << ss[out[i].l] << " " << ss[out[i].r] << " " << out[i].dis << endl;
    }
    cout << "kruskal:" << endl;
    kruskal();///krusukal算法的实现
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值