【数据结构与算法】最小生成树之普里姆(Prim)算法和克鲁斯卡尔(Kruskal)算法

 

🌱博客主页:大寄一场.

🌱系列专栏:数据结构与算法

😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注
75486fdc2eee4efba3dfc46f574e64ef.gif#pic_center

目录

前言

一、最小生成树的概念

二、最小生成树的求解方法

三、练习题

四、最小生成树在实际应用中的例子


前言

最近非科班的同学学到了最小生成树并询问我,于是想趁热打火,来总结顺便复习一下~

最小生成树(Minimum Spanning Tree,简称MST)是一个无向连通图中包含所有顶点的最短边集。在许多实际问题中,找到一个最小生成树对于理解和解决这些问题至关重要。本文将介绍最小生成树的概念、求解方法以及其在实际应用中的一些例子。

一、最小生成树的概念

假设我们有一个无向连通图G=(V,E),其中V是顶点集合,E是边集合。我们需要找到一个最小生成树,使得每个顶点都至少与一条边相连。这个最小生成树就是MST。

 

 

二、最小生成树的求解方法

1.Prim算法 Prim算法是一种贪心算法,用于在具有有向边的加权图中寻找最小生成树。算法的基本思想是从任意一个顶点开始,沿着权重最小的边进行扩展,直到找到整个MST


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INF INT_MAX

// 邻接矩阵表示的无向图
typedef struct {
    int V; // 顶点数
    int E; // 边数
    int G[100][100]; // 邻接矩阵
} Graph;

// 获取边的权重
int getWeight(Graph G, int u, int v) {
    return G[u][v];
}

// Kruskal算法求最小生成树
Graph primMST(Graph G) {
    int V = G.V; // 顶点数
    int E = G.E; // 边数
    int parent[V]; // 父节点数组
    int dist[V]; // 从源点到每个顶点的距离数组
    int i, u, v;
    int minCost = 0; // 总代价
    int edgeCount = 0; // 已选边的数量
    Graph mstEdges; // MST边集合
    memset(parent, -1, sizeof(parent)); // 初始化父节点为-1
    memset(dist, INF, sizeof(dist)); // 初始化距离为正无穷大
    priorityQueueNode pq; // 优先队列头结点
    pq.data = (void*)&dist[0];
    pq.index = 0;
    memset(mstEdges.G, 0, sizeof(mstEdges.G)); // 将邻接矩阵清零
    mstEdges.V = V;
    mstEdges.E = 0;
    do { // 不断扩展最小生成树,直到不存在增广路为止
        u = pq.index; // 取出距离源点最近的顶点u
        if (u == -1) break; // 如果已经没有顶点可选了,跳出循环
        pq.index = parent[u]; // 将当前顶点更新为其父节点
        edgeCount++; // 已选边的数量加1
        for (i = 0; i < G.V; i++) { // 遍历所有顶点
            v = i; // 从当前顶点开始选择下一个顶点v
            if (dist[v] > dist[u] + G.G[u][v]) { // 如果从u到v的距离比从u到源点的距离更短,则更新距离和优先级队列头结点
                dist[v] = dist[u] + G.G[u][v];
                pq.data = (void*)&dist[0];
                pq.index = i;
            } else if (i != u && v != u) { // 如果当前顶点u不是目标顶点,且从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并更新优先级队列头结点的位置
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                pq.data = (void*)&mstEdges.G[0];
                pq.index = edgeCount++;
            } else if (i == u && v != u) { // 如果当前顶点u是目标顶点,但从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并将当前顶点更新为其父节点的值为已选边的数量减一(因为此时已经找到了一条增广路径)
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                parent[v] = edgeCount--; // 将当前顶点的父节点设为已选边的数量减一(因为此时已经找到了一条增广路径)
            } else if (i != u && v == u) continue; // 如果当前顶点u是目标顶点且从u到v的距离等于从u到源点的距离,则不需要进行任何操作,直接跳过本次循环继续下一次循环迭代
        }
        minCost += mstEdges.G[mstEdges.E-1]; // 将总代价加上已选边的权重之和作为新的总代价
        mstEdges.E++; // 将已选边的计数加一,表示又选了一条边加入到最小生成树中
    } while (edgeCount < V); // 当已选边的数量小于顶点数时,继续扩展最小生成树直到不存在增广路为止

2.Kruskal算法 Kruskal算法也是一种贪心算法,用于在具有有向边的加权图中寻找最小生成树。算法的基本思想是每次选择权重最小的边来将两个顶点连接起来,直到找到整个MST。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INF INT_MAX

// 邻接矩阵表示的无向图
typedef struct {
    int V; // 顶点数
    int E; // 边数
    int G[100][100]; // 邻接矩阵
} Graph;

// 获取边的权重
int getWeight(Graph G, int u, int v) {
    return G[u][v];
}

// Kruskal算法求最小生成树
Graph primMST(Graph G) {
    int V = G.V; // 顶点数
    int E = G.E; // 边数
    int parent[V]; // 父节点数组
    int dist[V]; // 从源点到每个顶点的距离数组
    int i, u, v;
    int minCost = 0; // 总代价
    int edgeCount = 0; // 已选边的数量
    Graph mstEdges; // MST边集合
    memset(parent, -1, sizeof(parent)); // 初始化父节点为-1
    memset(dist, INF, sizeof(dist)); // 初始化距离为正无穷大
    priorityQueueNode pq; // 优先队列头结点
    pq.data = (void*)&dist[0];
    pq.index = 0;
    memset(mstEdges.G, 0, sizeof(mstEdges.G)); // 将邻接矩阵清零
    mstEdges.V = V;
    mstEdges.E = 0;
    do { // 不断扩展最小生成树,直到不存在增广路为止
        u = pq.index; // 取出距离源点最近的顶点u
        if (u == -1) break; // 如果已经没有顶点可选了,跳出循环
        pq.index = parent[u]; // 将当前顶点更新为其父节点
        edgeCount++; // 已选边的数量加1
        for (i = 0; i < G.V; i++) { // 遍历所有顶点
            v = i; // 从当前顶点开始选择下一个顶点v
            if (dist[v] > dist[u] + G.G[u][v]) { // 如果从u到v的距离比从u到源点的距离更短,则更新距离和优先级队列头结点
                dist[v] = dist[u] + G.G[u][v];
                pq.data = (void*)&dist[0];
                pq.index = i;
            } else if (i != u && v != u) { // 如果当前顶点u不是目标顶点,且从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并更新优先级队列头结点的位置
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                pq.data = (void*)&mstEdges.G[0];
                pq.index = edgeCount++;
            } else if (i == u && v != u) { // 如果当前顶点u是目标顶点,但从u到v的距离比从u到源点的距离更短,则将边的权重加入到最小生成树中,并将当前顶点更新为其父节点的值为已选边的数量减一(因为此时已经找到了一条增广路径)
                mstEdges.G[edgeCount] = getWeight(G, u, v);
                parent[v] = edgeCount--; // 将当前顶点的父节点设为已选边的数量减一(因为此时已经找到了一条增广路径)
            } else if (i != u && v == u) continue; // 如果当前顶点u是目标顶点且从u到v的距离等于从u到源点的距离,则不需要进行任何操作,直接跳过本次循环继续下一次循环迭代
        }
        minCost += mstEdges.G[mstEdges.E-1]; // 将总代价加上已选边的权重之和作为新的总代价
        mstEdges.E++; // 将已选边的计数加一,表示又选了一条边加入到最小生成树中
    } while (edgeCount < V); // 当已选边的数量小于顶点数时,继续扩展最小生成树直到不存在增广路为止

三、练习题

对如图所示的带权连通图按照克鲁斯卡尔和普里姆算法得到其最小生成树,请写出生成过程中依次得到的各条边,并计算该最小生成树的权值。

 普里姆算法从任意一个顶点开始,沿着权重最小的边进行扩展

克鲁斯卡尔 每次选择权重最小的边来将两个顶点连接起来

四、最小生成树在实际应用中的例子

最小生成树在很多实际应用中有很广泛的应用,例如路由算法、社交网络分析、电路设计等。下面分别介绍这些领域中的应用案例。

  1. 路由算法
    最小生成树在路由算法中有很重要的应用。例如,在单源最短路径问题中,我们可以使用Prim算法或Kruskal算法来找到最小生成树。同时,最小生成树也可以用于计算网络中每个节点的最短路径。这对于网络优化和资源分配非常重要。

  2. 社交网络分析
    最小生成树在社交网络分析中也有很广泛的应用。例如,我们可以使用最小生成树来确定社交网络中的社区结构。通过将每个节点与它的邻居节点连接起来,并删除具有较小的连通性(即具有较少的邻居节点)的边,我们可以得到一个最小生成树。然后,我们可以通过检查哪些节点之间的边被保留来确定这些节点属于同一个社区。

  3. 电路设计
    最小生成树在电路设计中有很重要的应用。例如,在电路布线中,我们可以使用最小生成树来最小化电路的总长度和电阻。通过将电路中的节点与它们的相邻节点连接起来,并删除具有较小的阻抗(即具有较少的电阻或电容)的边,我们可以得到一个最小生成树。然后,我们可以选择将电阻和电容分配给这个最小生成树上的节点,以最小化总长度和阻抗。

总之,最小生成树在许多领域中都有着广泛的应用。它不仅可以帮助我们解决各种计算问题,还可以帮助我们理解和分析现实世界中的复杂系统。


  • 35
    点赞
  • 117
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论
普里姆(Prim)算法克鲁斯卡尔(Kruskal)算法都是用于解决最小生成树问题的算法最小生成树问题是指在一个无向连通图中,找到一棵生成树,使得树上所有边的权值之和最小。生成树是指一个无向图的生成子图,它是一棵树,且包含图中所有顶点。 下面我们分别介绍普里姆算法克鲁斯卡尔算法: 1. 普里姆算法 普里姆算法是一种贪心算法,它从一个任意点开始,逐步扩展生成树,每次选择当前生成树到未加入的点中距离最近的点,并将其加入生成树。 具体实现步骤如下: - 随机选择一个起始点,将其加入生成树。 - 在生成树中的所有节点中,找到到未加入生成树的节点中距离最小的节点,将其加入生成树。 - 重复以上步骤,直到生成树包含了所有节点。 2. 克鲁斯卡尔算法 克鲁斯卡尔算法也是一种贪心算法,它从边集合中选择边,逐步扩展生成树,每次选择当前边集合中权值最小的边,并将其加入生成树。 具体实现步骤如下: - 将所有边按照权值从小到大排序。 - 从权值最小的边开始,逐个加入生成树,如果加入当前边会形成环,则不加入该边。 - 重复以上步骤,直到生成树包含了所有节点。 两种算法的时间复杂度都是O(ElogE),其中E为边数。普里姆算法在处理稠密图时效率更高,而克鲁斯卡尔算法在处理稀疏图时效率更高。
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青竹雾色间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值