详解次小生成树以及相关的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日

相关文章

  • JS实现的排列组合算法示例

    下面我将详细讲解一下JS实现的排列组合算法示例的完整攻略。 算法原理 JS实现的排列组合算法主要基于数学组合学,其核心思想是将需要进行排列组合的数据按照一定规则进行排列组合,得到所有可能的排列组合方式。这里我们首先介绍排列与组合的概念: 排列:从n个不同元素中取出m个元素进行排列,按照一定的顺序排列的所有可能的情况被称为排列。其中,n>m。 组合:从n…

    算法与数据结构 2023年5月19日
    00
  • 希尔排序算法的C语言实现示例

    下面是“希尔排序算法的C语言实现示例”完整攻略。 希尔排序算法简介 希尔排序是通过将整个待排序数组分割成多个子序列,对每个子序列进行插入排序,然后逐步减少子序列长度,最终使整个序列有序的一种算法。 希尔排序算法的流程 按照一定的间隔将待排序数组分成若干个子序列; 对每个子序列进行插入排序,使其中的元素可以快速有序; 缩小排序间隔,重复执行步骤1和2; 直至排…

    算法与数据结构 2023年5月19日
    00
  • JS折半插入排序算法实例

    下面是介绍JS折半插入排序算法的完整攻略。 什么是折半插入排序算法? 折半插入排序是插入排序的一种改进算法,它的基本思路是利用二分查找找到某个待排元素在已排序序列中插入位置。 折半插入排序算法的时间复杂度为 O(nlogn),比普通插入排序 O(n^2)快。 折半插入排序算法实现步骤 折半插入排序算法的实现步骤如下: 从第二个元素开始,将整个序列分为已排序区…

    算法与数据结构 2023年5月19日
    00
  • c语言冒泡排序和选择排序的使用代码

    下面是冒泡排序和选择排序的使用代码攻略。 冒泡排序和选择排序的使用代码 在C语言中,冒泡排序和选择排序都是经典的排序算法。本文将分别介绍它们的使用代码,以供参考。 冒泡排序 冒泡排序的基本思路是,相邻的元素两两比较,大的往后移,小的往前移,最终实现升序或降序排列的算法。 下面是一个简单的C语言冒泡排序的代码示例: #include <stdio.h&g…

    算法与数据结构 2023年5月19日
    00
  • 排序算法图解之Java插入排序

    首先要了解什么是插入排序,插入排序是排序算法中简单直观的一种,其原理是将未排序的元素一个一个插入到已经排好序的元素中,最终得到一个有序的序列。那么下面我将用Java代码来演示插入排序的实现过程,并且提供详细的注释帮助读者理解。 算法步骤 从第一个元素开始,认为第一个元素是已经排好序的,取第二个元素和已排序的元素进行比较,如果第二个元素比已排序的元素小,则交换…

    算法与数据结构 2023年5月19日
    00
  • 图解Java排序算法之快速排序的三数取中法

    图解Java排序算法之快速排序的三数取中法 什么是快速排序 快速排序是一种常见的排序方法,它的特点是在待排序的记录序列中,通过一趟排序将待排序的记录分割成独立的两部分,其中一部分的记录关键字均比另一部分的关键字小。 快速排序的基本流程 快速排序的基本流程如下: 从数列中挑出一个元素,称为“基准”(pivot)。 对数列重新排序,将比基准值小的元素放在基准前面…

    算法与数据结构 2023年5月19日
    00
  • C#选择排序法实例分析

    C#选择排序法实例分析 介绍 在本文中,我们将会讲解如何使用C#编写选择排序算法。选择排序是一种简单直观的排序算法,其思想是找到未排序部分中的最小值,然后将其放置在已排序部分的最后。该算法选择数组中的第一个元素作为已排序部分的起点,然后在未排序部分中查找最小值,将其放在已排序部分的末尾。这个过程会不断重复,直到整个数组都被排序。 程序示例 下面是一个选择排序…

    算法与数据结构 2023年5月19日
    00
  • JS前端面试必备——基本排序算法原理与实现方法详解【插入/选择/归并/冒泡/快速排序】

    JS前端面试必备——基本排序算法原理与实现方法详解 在前端面试中,算法是一个必考的考点,掌握一些基本的排序算法对于一个前端工程师来说是非常重要的。 排序算法的分类 排序算法可以按照许多不同的标准进行分类: 平均时间复杂度 空间复杂度 稳定性 内部排序和外部排序 在这篇文章中,我们将按照时间复杂度从小到大的顺序介绍以下五个基本的排序算法:插入排序、选择排序、归…

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