数据结构之图的应用(1)——最小生成树

在学完图的存储结构和两种遍历后,相信大家对于图也有了自己的理解了吧,下面我们继续来讲讲图的应用方面吧,应用方面其实有很多很多,但课本好像只列举四种——最小生成树,最短路径,拓扑排序和关键路径。下面我们就一一来分享给大家吧。今天这期文章主要分享一下最小生成树。

最小生成树。

生成树:是由n个顶点,n-1条边,将一个连通图连接起来,不形成回路

可以分为深度优先生成树和广度优先生成树。

概念:最小生成树(minimum spanning tree)是由n个顶点,n-1条边,将一个连通图连接起来,不形成回路,且使权值最小的结构。

最小生成树的构造:先拓展一下。

 我们主要学的最小生成树的算法是Prim(普里姆)算法或kruskal(克鲁斯卡尔)算法。此外还可以用bfs和dfs生成,分别叫bfs生成树和dfs生成树。

1. Prim(普里姆)算法

核心思路:与前面的MST性质有关哦 

 首先就是从图中的一个起点a开始,把a加入U集合,然后,寻找从与a有关联的边中,权重最小的那条边并且该边的终点b在顶点集合:(V-U)中,我们也把b加入到集合U中,并且输出边(a,b)的信息,这样我们的集合U就有:{a,b},然后,我们寻找与a关联和b关联的边中,权重最小的那条边并且该边的终点在集合:(V-U)中,我们把c加入到集合U中,并且输出对应的那条边的信息,这样我们的集合U就有:{a,b,c}这三个元素了,一次类推,直到所有顶点都加入到了集合U。
最后,我们发现六个点都已经加入到集合U了,我们的最小生成树建立完成。

普里姆算法的实现:

代码(这段代码也是借鉴某位大佬写的,特地感谢):

#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;
}

程序执行图:

 2.kruskal(克鲁斯卡尔)算法:

 基本思想 :按照权值从小到大的顺序选择 n-1 条边,并保证这 n-1 条边不构成回路

 具体做法 :首先构造一个只含 n 个顶点的森林,然后依权值从小到大从连通网中选择边加入到森林中,并使森林中不产生回路,直至森林变成一棵树为止。

 但这种情况下构造出来的最小生成树不是唯一的,因为有些支路权值是一样的。

kruskal(克鲁斯卡尔)算法:

#include <stdio.h>
#define MAXE 100
#define MAXV 100
typedef struct

{
    int vex1;                     //边的起始顶点
    int vex2;                      //边的终止顶点
    int weight;                    //边的权值
}Edge;
void kruskal(Edge E[], int n, int e)
{
    int i, j, m1, m2, sn1, sn2, k, sum = 0;
    int vset[n + 1];
    for (i = 1; i <= n; i++)        //初始化辅助数组
        vset[i] = i;
    k = 1;//表示当前构造最小生成树的第k条边,初值为1
    j = 0;//E中边的下标,初值为0
    while (k < e)//生成的边数小于e时继续循环
    {
        m1 = E[j].vex1;
        m2 = E[j].vex2;//取一条边的两个邻接点
        sn1 = vset[m1];
        sn2 = vset[m2];
        //分别得到两个顶点所属的集合编号
        if (sn1 != sn2)//两顶点分属于不同的集合,该边是最小生成树的一条边
        {//防止出现闭合回路 
            printf("V%d-V%d=%d\n", m1, m2, E[j].weight);
            sum += E[j].weight;
            k++;                //生成边数增加 
            if (k >= n)//没有边或者找到的边>顶点数减1都退出循环
                break;
            for (i = 1; i <= n; i++)    //两个集合统一编号
                if (vset[i] == sn2)  //集合编号为sn2的改为sn1
                    vset[i] = sn1;
        }
        j++;                  //无论上一条边是否被收入最下生成树,都要扫描下一条边
                              //k++与j++的位置不同,k++在循环内部(只有满足条件才能被收入最小生成树),j++在循环外部
    }
    printf("the lowest weight=%d\n", sum);
}
void swap(Edge arr[], int low, int high)
{
    Edge temp;
    temp = arr[low];
    arr[low] = arr[high];
    arr[high] = temp;

}
int fun(Edge arr[], int low, int high)
{
    int key;
    Edge lowx;
    lowx = arr[low];
    key = arr[low].weight;
    while (low < high)
    {
        while (low < high && arr[high].weight >= key)
            high--;
        if (low < high)
            swap(arr, low, high);
        //arr[low++]=arr[high];

        while (low < high && arr[low].weight <= key)
            low++;
        if (low < high)
            swap(arr, low, high);
        //arr[high--]=arr[low];
    }
    arr[low] = lowx;
    return low;
}
void quick_sort(Edge arr[], int start, int end)
{
    int pos;
    if (start < end)
    {
        pos = fun(arr, start, end);
        quick_sort(arr, start, pos - 1);
        quick_sort(arr, pos + 1, end);
    }
}
void gen(Edge E[], int vertex, int edge)
{
    for (int i = 0; i < edge; i++)
        scanf_s("%d%d%d", &E[i].vex1, &E[i].vex2, &E[i].weight);
}
int main()
{
    Edge E[MAXE];
    int vertex, edge;
    //freopen("1.txt","r",stdin);//文件输入
    printf("please intput the vertexs and edges:\n");
    scanf_s("%d%d", &vertex, &edge);
    gen(E, vertex, edge);
    quick_sort(E, 0, edge - 1);
    kruskal(E, vertex, edge);
}

两种算法比较:

 好啦,关于这两种算法的介绍就到这啦,其实这两种算法都有各自的优缺点,理解起来其实不算难,代码可能难看懂一些。以后的日常应用中要根据实际情况来选择用哪种算法合适吧。

本贴为博主亲手整理。如有错误,请评论区指出,一起进步。谢谢大家的浏览.

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最小生成是一种数据结构,它具有以下三个性质: (1) 最小生成是一棵,因此它的边数等于顶点数减1,且内不会有环。 (2) 对于给定的G(V, E),最小生成可以有多个,但它们的边权之和是唯一的。 (3) 最小生成是在无向生成的,因此它的根节点可以是上的任意一个节点。 构造最小生成有两种常用的算法:Prim算法和Kruskal算法。这两种算法都利用了最小生成的MST性质:对于一个连通网N=(V, E)和顶点集V的一个非空子集U,如果(u, v)是一条具有最小权值的边,其中u属于U,v属于V-U,则必定存在一棵包含边(u, v)的最小生成。 Prim算法是一种贪心算法,从一个初始节点开始,逐步扩展的规模,每次选择连接和非节点的边中权值最小的边,直到包含了所有的顶点。Prim算法的时间复杂度为O(n^2),其中n为顶点的数量。 Kruskal算法也是一种贪心算法,它先将中的所有边按照权值从小到大排序,然后逐个加入最小生成中,如果加入的边不会导致形成环,则将其加入,直到包含了所有的顶点。Kruskal算法的时间复杂度主要取决于排序边的时间复杂度,通常为O(mlogm),其中m为边的数量。 综上所述,最小生成是一种数据结构,可以通过Prim算法或Kruskal算法来构造。Prim算法的时间复杂度为O(n^2),而Kruskal算法的时间复杂度为O(mlogm)。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [数据结构之——最小生成(prim算法和kruskal算法)](https://blog.csdn.net/xiaoyong5854/article/details/106563790)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [数据结构--最小生成详解与实现](https://blog.csdn.net/u011630575/article/details/79833703)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值