最小生成树,prim算法,kruskal (克鲁斯卡尔算法)

最小生成树

哈哈写代码,不知道为啥想送大家一首诗,哈哈哈哈写的不好勿喷
在这里插入图片描述

1、什么是最小生成树
现在假设有一个很实际的问题:我们要在n个城市中建立一个通信网络,则连通这n个城市需要布置n-1一条通信线路,这个时候我们需要考虑如何在成本最低的情况下建立这个通信网?
于是我们就可以引入连通图来解决我们遇到的问题,n个城市就是图上的n个顶点,然后,边表示两个城市的通信线路,每条边上的权重就是我们搭建这条线路所需要的成本,所以现在我们有n个顶点的连通网可以建立不同的生成树,每一颗生成树都可以作为一个通信网,当我们构造这个连通网所花的成本最小时,搭建该连通网的生成树,就称为最小生成树。

prim算法

  • 定义:

    (1)初始化U={v}。v到其他顶点的所有边为候选边;
    (2)重复以下步骤n-1次,使得其他n-1个顶点被加入到U中:

    ​ 从候选边中挑选权值最小的边输出,设该边在V-U中的顶点是k,将k加入U中;

    考察当前V-U中的所有顶点j,修改候选边:若(j,k)的权值小于原来和顶点k关联的候选边,则用(k,j)取代后者作为候选边。

    直接看定义比较抽象,这里我们直接用例子证明。

实例

这里我们以0为起点,求解下图最小生成树

我们用 U U U 表示选择的点(初始为空), V V V 表示所有的, V − U V-U VU表示没有选择的点

第一步:将0当成起点,加入 U U U

在这里插入图片描述

第二步:将跟 U U U集合中所有的点(0点)与 V − U V-U VU集合中相连的最小权值的点加入 U U U
在这里插入图片描述

第三步:重复第二步将跟 U U U集合中所有的点(0点,5点)与 V − U V-U VU集合中相连的最小权值的点加入 U U U

  • 注意这里是将 U U U集合中所有的点与 V − U V-U VU集合中的点的连线最短的一个添加进去,上图中有<0,1>=28,<5,4>=25这两条边。

第----步:继续重复不断更新 U U U集合,直到 V − U V-U VU为空
在这里插入图片描述

代码

#include<iostream>
#include<string>
#include<vector>
using  namespace std;

//首先是使用邻接矩阵完成Prim算法
struct Graph {
    int vexnum;  //顶点个数
    int edge;   //边的条数
    int ** arc; //邻接矩阵
    string *information; //记录每个顶点名称
};

//创建图
void createGraph(Graph & g) {
    cout << "请输入顶点数:输入边的条数" << endl;
    cin >> g.vexnum;
    cin >> g.edge;  //输入边的条数

    g.information = new string[g.vexnum];
    g.arc = new int*[g.vexnum];
    int i = 0;

    //开辟空间的同时,进行名称的初始化
    for (i = 0; i < g.vexnum; i++) {
        g.arc[i] = new int[g.vexnum];
        g.information[i]="v"+ std::to_string(i+1);//对每个顶点进行命名
        for (int k = 0; k < g.vexnum; k++) {
            g.arc[i][k] = INT_MAX;          //初始化我们的邻接矩阵
        }
    }

    cout << "请输入每条边之间的顶点编号(顶点编号从1开始),以及该边的权重:" << endl;
    for (i = 0; i < g.edge; i++) {
        int start;
        int end;
        cin >> start;   //输入每条边的起点
        cin >> end;     //输入每条边的终点
        int weight;
        cin >> weight;
        g.arc[start-1][end-1]=weight;//无向图的边是相反的
        g.arc[end-1][start-1] = weight;
    }
}

//打印图
void print(Graph g) {
    int i;
    for (i = 0; i < g.vexnum; i++) {
        //cout << g.information[i] << " ";
        for (int j = 0; j < g.vexnum; j++) {
            if (g.arc[i][j] == INT_MAX)
                cout << "∞" << " ";
            else
            cout << g.arc[i][j] << " ";
        }
        cout << endl;
    }
}

//作为记录边的信息,这些边都是达到end的所有边中,权重最小的那个
struct Assis_array {
    int start; //边的终点
    int end;  //边的起点
    int weight;  //边的权重
};
//进行prim算法实现,使用的邻接矩阵的方法实现。
void Prim(Graph g,int begin) {

    //close_edge这个数组记录到达某个顶点的各个边中的权重最大的那个边
    Assis_array *close_edge=new Assis_array[g.vexnum];

    int j;

    //进行close_edge的初始化,更加开始起点进行初始化
    for (j = 0; j < g.vexnum; j++) {
        if (j != begin - 1) {
            close_edge[j].start = begin-1;
            close_edge[j].end = j;
            close_edge[j].weight = g.arc[begin - 1][j];
        }
    }
    //把起点的close_edge中的值设置为-1,代表已经加入到集合U了
    close_edge[begin - 1].weight = -1;
    //访问剩下的顶点,并加入依次加入到集合U
    for (j = 1; j < g.vexnum; j++) {

        int min = INT_MAX;
        int k;
        int index;
        //寻找数组close_edge中权重最小的那个边
        for (k = 0; k < g.vexnum; k++) {
            if (close_edge[k].weight != -1) {  
                if (close_edge[k].weight < min) {
                    min = close_edge[k].weight;
                    index = k;
                }
            }
        }
        //将权重最小的那条边的终点也加入到集合U
        close_edge[index].weight = -1;
        //输出对应的边的信息
        cout << g.information[close_edge[index].start] 
            << "-----" 
            << g.information[close_edge[index].end]
            << "="
            <<g.arc[close_edge[index].start][close_edge[index].end]
            <<endl;

        //更新我们的close_edge数组。
        for (k = 0; k < g.vexnum; k++) {
            if (g.arc[close_edge[index].end][k] <close_edge[k].weight) {
                close_edge[k].weight = g.arc[close_edge[index].end][k];
                close_edge[k].start = close_edge[index].end;
                close_edge[k].end = k;
            }
        }
    }
}



int main()
{
    Graph g;
    createGraph(g);//基本都是无向网图,所以我们只实现了无向网图
    print(g);
    Prim(g, 1);
    system("pause");
    return 0;
}

克鲁斯卡尔算法

  • 定义

(1)置U的初值等于V(即包含有G中的全部顶点),TE的初值为空集(即图T中每一个顶点都构成一个连通分量)。

(2)将图G中的边按权值从小到大的顺序依次选取:
若选取的边未使生成树T形成回路,则加入TE;
否则舍弃,直到TE中包含(n-1)条边为止。

简单点来说:

将图的边值进行排序 − > -> >以此选择最短的边添加 − > -> >如果构成回路就舍弃这条边

还是一样定义太抽象我们直接看实例

实例

T E = TE= TE={ }表示加入的边,初始为空

在这里插入图片描述

在这里插入图片描述

第一步:对边值进行排序

在这里插入图片描述

第二步:选取最小(除开已经加入的边,和已经放弃的边)的边并且加入的边不能构成回路,加入 T E = TE= TE={ }中

在这里插入图片描述

第三步:选取最小(除开已经加入的边,和已经放弃的边)的边并且加入的边不能构成回路,加入 T E = TE= TE={ }中

在这里插入图片描述

第四步:选取最小(除开已经加入的边,和已经放弃的边)的边并且加入的边不能构成回路,加入 T E = TE= TE={ }中
在这里插入图片描述

第五步:选取最小(除开已经加入的边,和已经放弃的边)的边并且加入的边不能构成回路,加入 T E = TE= TE={ }中

在这里插入图片描述

第六步:选取最小(除开已经加入的边,和已经放弃的边)的边并且加入的边不能构成回路,加入 T E = TE= TE={ }中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j073MyzG-1638165054017)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20211129134145152.png)]

这时最小的边应该是:❤️,6>,但是如果加上这个边,就会形成回路,所以放弃掉这个边

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPf5x2A7-1638164708151)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20211129133719061.png)]
第-----步:选取最小(除开已经加入的边,和已经放弃的边)的边并且加入的边不能构成回路,加入 T E = TE= TE={ }中,直到所有的点都连接上

代码:

#include<iostream>
#include<algorithm>
#include<string>
using namespace std;

//检验输入边数和顶点数的值是否有效,可以自己推算为啥:
//顶点数和边数的关系是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum,int edge) {
    if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
        return false;
    return true;
}

//判断我们每次输入的的边的信息是否合法
//顶点从1开始编号
bool check_edge(int Vexnum, int start ,int end, int weight) {
    if (start<1 || end<1 || start>Vexnum || end>Vexnum || weight < 0) {
        return false;
    }
    return true;
}

//边集结构,用于保存每条边的信息
typedef struct edge_tag {
    bool visit; //判断这条边是否加入到了最小生成树中
    int start;   //该边的起点
    int end;   //该边的终点
    int weight; //该边的权重
}Edge;

//创建一个图,但是图是使用边集结构来保存
void createGraph(Edge * &e,int Vexnum, int edge) {
    e = new Edge[edge];//为每条边集开辟空间
    int start = 0;
    int end = 0;
    int weight = 0;

    int i = 0;
    cout << "输入每条边的起点、终点和权重:" << endl;
    while (i != edge)
    {
        cin >> start >> end >> weight;
        while (!check_edge(Vexnum, start, end, weight)) {
            cout << "输入的值不合法,请重新输入每条边的起点、终点和权重:" << endl;
            cin >> start >> end >> weight;
        }
        e[i].start = start;
        e[i].end = end;
        e[i].weight = weight;
        e[i].visit = false; //每条边都还没被初始化
        ++i;

    }
}

//我们需要对边集进行排序,排序是按照每条边的权重,从小到大排序。
int cmp(const void*  first, const void * second) {
    return ((Edge *)first)->weight - ((Edge *)second)->weight;
}

//好了,我们现在需要做的是通过一定的方式来判断
//如果我们把当前的边加入到生成树中是否会有环出现。
//通过我们之前学习树的知识,我们可以知道如果很多棵树就组成一个森林,而且
//如果同一颗树的两个结点在连上一条边,那么就会出现环,
//所以我们就通过这个方式来判断加入了一个新的边后,是否会产生环,
//开始我们让我们的图的每个顶点都是一颗独立的树,通过不断的组合,把这个森林变
//成来源于同一颗顶点的树
//如果不理解,画个图就明白了,

//首先是找根节点的函数,
//其中parent代表顶点所在子树的根结点
//child代表每个顶点孩子结点的个数
int find_root(int child, int * parent) {

    //此时已经找到了该顶点所在树的根节点了
    if (parent[child] == child) {
        return child;
    }
    //往前递归,寻找它父亲的所在子树的根结点
    parent[child] = find_root(parent[child], parent);
    return parent[child];
}

//合并两个子树
bool union_tree(Edge  e, int * parent, int * child) {
    //先找出改边所在子树的根节点
    int root1;
    int root2;
    //记住我们顶点从1开始的,所以要减1
    root1 = find_root(e.start-1, parent);
    root2 = find_root(e.end-1, parent);
    //只有两个顶点不在同一颗子树上,才可以把两棵树并未一颗树
    if (root1 != root2) {
        //小树合并到大树中,看他们的孩子个数
        if (child[root1] > child[root2]) {
            parent[root2] = root1;
            //大树的孩子数量是小树的孩子数量加上
            //大树的孩子数量在加上小树根节点自己
            child[root1] += child[root2] + 1;
        }
        else {
            parent[root1] = root2;
            child[root2] += child[root1] + 1;
        }
        return true;
    }
    return false;
}

//克鲁斯卡算法的实现
void Kruskal() {
    int Vexnum = 0;
    int edge = 0;
    cout << "请输入图的顶点数和边数:" << endl;
    cin >> Vexnum >> edge;
    while (!check(Vexnum, edge)) {
        cout << "你输入的图的顶点数和边数不合法,请重新输入:" << endl;
        cin >> Vexnum >> edge;
    }

    //声明一个边集数组
    Edge * edge_tag;
    //输入每条边的信息
    createGraph(edge_tag, Vexnum, edge);

    int * parent = new int[Vexnum]; //记录每个顶点所在子树的根节点下标
    int * child = new int[Vexnum]; //记录每个顶点为根节点时,其有的孩子节点的个数
    int i;
    for (i = 0; i < Vexnum; i++) {
        parent[i] = i;
        child[i] = 0;
    }
    //对边集数组进行排序,按照权重从小到达排序
    qsort(edge_tag, edge, sizeof(Edge), cmp);
    int count_vex; //记录输出的边的条数

    count_vex = i = 0;
    while (i != edge) {
        //如果两颗树可以组合在一起,说明该边是生成树的一条边
        if (union_tree(edge_tag[i], parent, child)) {
            cout << ("v" + std::to_string(edge_tag[i].start))
                << "-----"
                << ("v" + std::to_string(edge_tag[i].end))
                <<"="
                << edge_tag[i].weight
                << endl;
            edge_tag[i].visit = true;
            ++count_vex; //生成树的边加1
        }
        //这里表示所有的边都已经加入成功
        if (count_vex == Vexnum - 1) {
            break;
        }
        ++i;
    }

    if (count_vex != Vexnum - 1) {
        cout << "此图为非连通图!无法构成最小生成树。" << endl;
    }
    delete [] edge_tag;
    delete [] parent;
    delete [] child;
}

int main() {
    Kruskal();
    system("pause");
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值