RainPPR 发表于 2023-1-1 18:16

最小生成树算法:普里姆算法(Prim算法)&克鲁斯卡尔算法(Kruskal算法)

学习笔记

# 最小生成树问题

常用的最小生成树一般是无向图。
一般不对边的负权作要求。

## 一、普里姆算法(Prim算法)

**普里姆算法**(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (graph theory)),且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克(英语:Vojtěch Jarník)发现;并在1957年由美国计算机科学家罗伯特·普里姆(英语:Robert C. Prim)独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。

### 1. 朴素版Prim算法

**适用情况**:稠密图
**时间复杂度**:O(n^2^)

#### 思路
1. 初始化距离(INF)
2. 迭代 n 次
   1. 找到不在连通块中的距离最小的点
   2. 用这个点更新其他点到**连通块**(注意与迪杰斯特拉算法的区别)的距离
   3. 加入连通块

#### 详解
见代码注释

#### 代码
```cpp
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 510;
const int INF = 0x3f3f3f3f;

int n, m;
int g;        // 稠密图用邻接矩阵存储
int dist;        // 每个点到连通块的距离
bool st;                // 这个点是否在连通块内

int prim()
{
        memset(dist, 0x3f, sizeof dist);

        int res = 0;        // 所有边的长度之和
        for (int i = 0 ; i < n ; ++i)
        {
                int t = -1;
                for (int j = 1 ; j <= n ; ++j)
                        if (!st && (t == -1 || dist > dist))
                                t = j;

                // 图不连通,没有最小生成树
                if (i && dist == INF)
                        return INF;
                if (i)                                // 如果不是第一个点
                        res += dist;        // 结果中加入边

                // 更新距离
                for (int j = 1 ; j <= n ; ++j)
                        dist = min(dist, g);

                // 加入连通块
                st = true;
        }

        return res;
}

int main()
{
        // 初始化距离为正无穷
        memset(g, 0x3f, sizeof g);

        // 输入
        scanf("%d %d", &n, &m);

        int a, b, c;
        while (m--)
        {
                scanf("%d %d %d", &a, &b, &c);
                g = g = min(g, c);
        }

        // Prim算法
        int t = prim();

        // 输出
        if (t == INF)
                printf("impossible\n");
        else
                printf("%d\n", t);
        return 0;
}
```

### 2. 堆优化Prim算法【不常用】

**适用情况**:稀疏图(不如下一个常用)
**时间复杂度**:O(m log n)

**优化与迪杰斯特拉的堆优化类似,此处略**

## 二、Kruskal(克鲁斯卡尔)算法

克鲁斯卡尔算法是求连通网的最小生成树的另一种方法。与普里姆算法不同,它的时间复杂度为O(e log e)(e为网中的边数),所以,适合于求边稀疏的网的最小生成树。

**适用情况**:稀疏图
**时间复杂度**:O(m log m)

#### 思路

1. 将所有边按权重从小到大排序(此处调用 `sort` 就可以)
2. 枚举每一条边
3. 如果这一条边两边不连通,加入这一条边

#### 详解
见代码注释

#### 代码
```cpp
#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010;

int n, m;
int p;

// 用结构体存储图
struct Edge
{
        int a, b, w;

        bool operator< (const Edge &W)const
        {
                return w < W.w;
        }
} edges;

// 并查集-查找祖宗
int find(int x)
{
        if (p != x)
                p = find(p);
        return p;
}

int main()
{
        // 输入
        scanf("%d %d", &n, &m);
        int a, b, w;
        for (int i = 0 ; i < m ; ++i)
        {
                scanf("%d %d %d", &a, &b, &w);
                edges = {a, b, w};
        }

        // 排序
        sort(edges, edges + m);

        // 初始化并查集
        for (int i = 1 ; i <= n ; ++i)
                p = i;

        // 从小到大枚举所有边
        int res = 0, cnt = 0;
        for (int i = 0 ; i < m ; ++i)
        {
                int a = edges.a, b = edges.b, w = edges.w;

                // 查找祖宗
                a = find(a), b = find(b);

                // 如果不连通
                if (a != b)
                {
                        res += w, ++cnt;
                        // 合并并查集
                        p = b;
                }
        }

        // 输出
        if (cnt < n - 1)
                printf("impossible\n");
        else
                printf("%d\n", res);
        return 0;
}
```

NO MORE, THANKS!

小丶白丶丶 发表于 2023-1-1 19:11

学习一下

kanxue2018 发表于 2023-1-1 20:07

好好学习,天天向上

yxrhy_ming 发表于 2023-1-1 20:43

你也是Y总的学生?{:1_929:}

陌语mua 发表于 2023-1-1 20:49

当年奥赛支配的恐惧.......

RainPPR 发表于 2023-1-2 08:42

yxrhy_ming 发表于 2023-1-1 20:43
你也是Y总的学生?

啊对对对!

学习使我快乐1 发表于 2023-1-2 13:00

不错不错,get到了

aimomoshen 发表于 2023-1-3 09:09

感谢分享,很实用的算法
页: [1]
查看完整版本: 最小生成树算法:普里姆算法(Prim算法)&克鲁斯卡尔算法(Kruskal算法)