详解次小生成树以及相关的C++求解方法

详解次小生成树以及相关的C++求解方法

什么是次小生成树

在普通的生成树中,每个节点只有一条边与其相连。而次小生成树则是指,在所有的生成树中,除了最小生成树之外,权值和第二小的生成树。

求解方法

Kruskal算法

Kruskal算法是一种贪心算法,也是求解最小生成树的常用算法。我们可以对Kruskal算法做一些修改,使其求出次小生成树。

一般情况下,我们需要两个并查集$fa1$和$fa2$,分别表示这个点在第一最小生成树中的父亲和第二最小生成树中的父亲。如果一个点和它的父亲在两颗生成树中不相交,那么这个联通块中一定会有一个是用第一大生成树拼接的,另一个是第二小生成树。在做Kruskal的过程中,如果我们将边加入当前的生成树后,发现边的两个端点$u$和$v$在$fa1$和$fa2$中的根节点不同,那么说明我们已经找到了新的一条边,可以将当前的生成树的权值和记录为此时的最小边权和第二小边权(前者是用于求解最小生成树,后者是用于求解次小生成树)。

以下是对应的C++代码实现:

const int maxn = ...;
int fa1[maxn], fa2[maxn];

struct edge {
    int u, v, w;
    bool operator < (const edge e) const {
        return w < e.w;
    }
} edges[maxn];

int find_fa(int *fa, int u) {
    if (u == fa[u]) return u;
    return fa[u] = find_fa(fa, fa[u]);
}

int kruskal() {
    int ans1 = 0, ans2 = 0;
    int cnt = 0;
    sort(edges, edges + n);
    for (int i = 1; i <= n; ++i) {
        fa1[i] = i; fa2[i] = i;
    }
    for (int i = 0; i < m; ++i) {
        int u = edges[i].u, v = edges[i].v, w = edges[i].w;
        int fu1 = find_fa(fa1, u), fv1 = find_fa(fa1, v);
        int fu2 = find_fa(fa2, u), fv2 = find_fa(fa2, v);
        if (fu1 != fv1 && fu2 != fv2) {
            fa1[fu1] = fv1, fa2[fu2] = fv2;
            ans1 += w;
            if (++cnt == n - 1) break;
        }
    }
    return ans1;
}

Prim算法

Prim算法同样可以用于求解次小生成树。在Prim算法中,我们需要用两个数组$lowcost1$和$lowcost2$存储每个节点和当前最小生成树以及次小生成树中的最短边,分别对应着最小的权值和次小的权值。而在Prim算法的每一轮中,我们需要维护两个堆$heap1$和$heap2$,分别对应着已经加入最小生成树和次小生成树的节点。在加入一个点$u$的时候,我们需要判断这个点加入最小生成树时的边是否成环,并且将另一条边更新到次小生成树中。具体的实现细节可参见以下的C++代码:

const int maxn = ...;
const int inf = ...;
int lowcost1[maxn], lowcost2[maxn], vis[maxn];
int heap1[maxn], heap2[maxn];

struct edge {
    int to, w;
};

vector<edge> G[maxn];

void prim(int start) {
    memset(lowcost1, inf, sizeof(lowcost1));
    memset(lowcost2, inf, sizeof(lowcost2));
    memset(vis, 0, sizeof(vis));
    memset(heap1, 0, sizeof(heap1));
    memset(heap2, 0, sizeof(heap2));
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q1, q2;

    lowcost1[start] = 0;
    q1.push(make_pair(0, start));
    while (!q1.empty()) {
        pair<int, int> p = q1.top();
        q1.pop();
        int u = p.second;
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i = 0; i < G[u].size(); ++i) {
            int v = G[u][i].to, w = G[u][i].w;
            if (!vis[v] && lowcost1[v] > w) {
                lowcost2[v] = lowcost1[v];
                lowcost1[v] = w;
                heap2[v] = heap1[v];
                heap1[v] = u;
                q2.push(make_pair(lowcost2[v], v));
                q1.push(make_pair(lowcost1[v], v));
            } else if (vis[v] && heap1[v] != u && lowcost2[v] > w) {
                lowcost2[v] = w;
                heap2[v] = u;
                q2.push(make_pair(lowcost2[v], v));
            }
        }
    }
}

示例说明

案例一

下列是一个有$n=5$个节点,$m=7$条边的无向图,每条边都有对应的正整数权值:

1 ----- 2 ----- 5 
|       | 4     / |  
| 3     |/     /2 |  
|/      3     /   |
3 ----- 4 ----- 5

我们可以用Kruskal算法和Prim算法求解这个无向图的次小生成树。首先,我们需要存储每条边的信息,例如:

struct edge {
    int u, v, w;
}

则上图对应的边可以描述如下:

edge es[] = {
    { 1, 2, 3 },
    { 1, 3, 4 },
    { 2, 4, 4 },
    { 2, 5, 2 },
    { 3, 4, 3 },
    { 4, 5, 5 },
    { 3, 5, 2 }
};

Kruskal算法的代码详见上述部分,这里不再赘述。使用Kruskal算法求解这个无向图的次小生成树的结果如下:

1 ---3--- 2 ---4--- 5
         | 2
         |
         4
         |
         3

次小生成树的权值和为$14$。

Prim算法的代码详见上述部分,这里不再赘述。使用Prim算法求解这个无向图的次小生成树的结果如下:

1 --3-- 2 --2-- 5
        | 4
        4
        | 3
        3

次小生成树的权值和为$14$。

案例二

接着我们考虑下列这个比较复杂的案例,在这个案例中,我们有$n=6$个节点,$m=10$条边:

1 ----- 2 ----- 5 --15-- 6
|       | 5     / |  7  /  | 
| 3     |/     /2 |  /9  |  
|/      3     /   | /   \ |
3 ----- 4 ----- 5 --24-- 7

同样,我们可以使用Kruskal算法和Prim算法求解这个无向图的次小生成树。求解的过程和上述案例一样,这里不再赘述。得到的结果如下:

Kruskal算法:

1 ---3--- 3 ---4--- 5 ---7--- 6
         | 5    | 2    | 7
         |      |      |  
         2      3     /  |
         |      |   24   |
         | 4    |/      |
         6 ---15--- 4   /
             \        |9
              \       |
               1 ---  5

次小生成树的权值和为$65$。

Prim算法:

1 --3-- 3 --5-- 5 --7-- 6
        | 2   | 7  /  |
        |     |   /   |
        4     3 /   \ |
        |     |24     9
        | 5   |/     /
        2 --15-- 4  --
              \   | 
               \  |
                1 5

次小生成树的权值和为$65$。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解次小生成树以及相关的C++求解方法 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • JavaScript实现数组全排列、去重及求最大值算法示例

    JavaScript实现数组全排列、去重及求最大值算法示例 实现数组全排列 数组的全排列即为将数组中所有元素进行全排列的结果。实现数组全排列的常用方法为回溯法。 回溯法的思想是从第一个元素开始,固定第一个元素,对于剩下的元素进行全排列,得到结果后将第一个元素与第二个元素交换,并对第二个元素之后的元素进行全排列,以此类推,直到最后一个元素,此时将所有的结果返回…

    算法与数据结构 2023年5月19日
    00
  • 算法系列15天速成 第六天 五大经典查找【下】

    算法系列15天速成 第六天 五大经典查找【下】- 完整攻略 简介 本篇文章是算法系列15天速成中的第六天内容,主要是介绍五大经典查找的后三种查找算法:插值查找、斐波那契查找以及分块查找。在介绍每一种查找算法时都会包含具体的思路、复杂度和应用场景等内容。 插值查找 思路 插值查找是在二分查找的基础上优化的一种查找算法,它不是通过数组的中间元素进行查找,而是通过…

    算法与数据结构 2023年5月19日
    00
  • JavaScript求解最长回文子串的方法分享

    JS求解最长回文子串的方法分享: 一、前置知识 在学习JS求解最长回文子串之前,你需要掌握以下知识: 严格模式 回文字符串 动态规划 二、什么是回文字符串? 回文字符串是指正着读和倒着读都一样的字符串。例如,’level’、’racecar’、’rotor’ 都是回文字符串。 三、求解最长回文子串的方法 对于字符串中的每一个字符,判断它和它往前的字符组成的子…

    算法与数据结构 2023年5月19日
    00
  • 利用JavaScript实现的10种排序算法总结

    作为“利用JavaScript实现的10种排序算法总结”的作者,首先需要明确以下内容: 熟悉10种排序算法的原理与流程 理解JavaScript作为一门编程语言的特点和应用场景 知道如何将算法的流程用JavaScript代码实现 针对以上内容,可以采取以下步骤: 梳理10种排序算法的流程和实现方式,用markdown文本形式编写对应的标题和文本,例如: 插入…

    算法与数据结构 2023年5月19日
    00
  • Java编程实现汉字按字母顺序排序的方法示例

    下面是关于”Java编程实现汉字按字母顺序排序的方法示例”的详细攻略,包含以下步骤: 一、理解题意及需求 题目要求实现汉字按字母顺序排序,我们需要用到汉字拼音转换工具包,如pinyin4j。同时,我们已知的数据是一个汉字数组,需要对这些汉字进行排序并输出结果。因此,我们需要进行以下步骤: 导入pinyin4j包 对汉字进行拼音转换 对转换结果进行排序 输出结…

    算法与数据结构 2023年5月19日
    00
  • C#实现希尔排序

    C#实现希尔排序攻略 简介 希尔排序(Shell Sort)是插入排序的一种改进版本,也称为缩小增量排序(Diminishing Increment Sorting)。希尔排序首先将要排序的序列分成若干个子序列,分别进行插入排序,待子序列基本有序时,再对全体记录进行一次直接插入排序。其算法主要思想是将原序列按一定间隔分为若干子序列,对每个子序列分别进行插入排…

    算法与数据结构 2023年5月19日
    00
  • Python实现选择排序

    当我们需要对一个列表或数组进行排序时,选择排序是一种简单易懂的方法。Python是一种非常流行的编程语言,它可以轻松实现选择排序。 以下是Python实现选择排序的攻略: 选择排序的原理 选择排序是一种简单直观的排序算法,其基本思想是每次选择出最小的元素,放到已经排好序的部分的末尾。 实现步骤 找到未排序部分的最小元素; 将其放在已排序部分的末尾; 重复步骤…

    算法与数据结构 2023年5月19日
    00
  • C++实现归并排序

    C++实现归并排序 什么是归并排序 归并排序是一种分治策略的排序算法,将待排序的序列切分为若干个子序列,递归地对子序列排序,并将各子序列的排序结果合并成最终有序序列。归并排序的时间复杂度为O(nlogn),是一种高效的排序算法。 归并排序的实现 递归实现 归并排序的递归实现比较容易理解。我们可以将待排序的序列不断切分为更小的子序列,直到子序列长度为1,此时子…

    算法与数据结构 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部