首页 > 编程语言 >「代码随想录算法训练营」第四十九天 | 图论 part7

「代码随想录算法训练营」第四十九天 | 图论 part7

时间:2024-08-30 16:38:40浏览次数:9  
标签:val int 随想录 第四十九 生成 minDist 最小 part7 节点

目录

最小生成树的解题

最小生成树类型的题目主要用于解决所有节点的最小连通子图的问题,即:以最小的成本(边的权值)将图中所有节点链接到一起。

最小生成树可以使用prim算法,也可以使用kruskal算法计算出来。

prim算法

prim算法的核心有三步:

  1. 第一步,选距离生成树最近节点。
  2. 第二步,最近节点加入生成树。
  3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

其中,minDist数组最为重要,用来记录每一个节点距离最小生成树的最近距离

举例说明(来自代码随想录)

下面是一个无向有权图:

image

1. minDist初始化:

minDist数组里的数值初始化为最大数,本例中最大值不超过10000,所以初始化最大数为10001就可以。

image

2. 最初随机选择一个节点加入生成树(这里选择节点1),并更新minDist数组:

选择距离最小生成树最近的节点,加入到最小生成树,刚开始还没有最小生成树,所以随便选一个节点加入就好(因为每一个节点一定会在最小生成树里,所以随便选一个就好),那我们选择节点1 (符合遍历数组的习惯,第一个遍历的也是节点1)

image

3. 在minDist随机选择一个最小距离的节点加入生成树(这里选择节点2),并更新minDist数组:

image

4. 继续选择最小距离的节点加入生成树(这里选择节点3),并更新minDist数组:

image

5. 继续选择最小的加入生成树(这里选择节点4),并更新minDist数组,注意,此时节点5的距离改变了:

image

6. 重复上述,加入节点5,继续:

image

7. 继续,加入节点6:

image

8. 继续,加入节点7:

image

9. 最后把minDist数组的值累加,得到总距离,当然,也可以输出路径。

题目:53. 寻宝

题目链接:https://kamacoder.com/problempage.php?pid=1053
文章讲解:https://www.programmercarl.com/kamacoder/0053.寻宝-prim.html
题目状态:看题解

思路:

prim算法,看上面。

代码:

#include <iostream>
#include <vector>
#include <climits>

using namespace std;

int main()
{
    int v, e;
    int x, y, k;
    cin >> v >> e;
    // 默认最大值
    vector<vector<int>> grid(v + 1, vector<int>(v + 1, 10001));
    while(e--)
    {
        cin >> x >> y >> k;
        // 因为是双向图,两个方向都要填上
        grid[x][y] = k;
        grid[y][x] = k;
    }
    // 所有节点到最小生成树的最小距离
    vector<int> minDist(v + 1, 10001);
    // 这个节点是否在树里
    vector<bool> isInTree(v + 1, false);

    // 初始化,用来记录边的连接
    vector<int> parent(v + 1, -1);

    // 循环n-1次,建立n-1条边,将n个节点连在一起
    for(int i = 1; i < v; ++i)
    {
        // 第一步:选距离生成树最近的节点
        int cur = -1; // 选择节点加入生成树
        int minVal = INT_MAX;
        for(int j = 1; j <= v; ++j)
        {
            // 选取条件
            // 1. 不在最小生成树里
            // 2. 距离最小生成树最近的节点
            if(!isInTree[j] && minDist[j] < minVal)
            {
                minVal = minDist[j];
                cur = j;
            }
        }
        // 第二步:最近节点加入生成树
        isInTree[cur] = true;
        // 第三步:更新非生成树节点到生成树的距离
        for(int j = 1; j <= v; ++j)
        {
            if(!isInTree[j] && grid[cur][j] < minDist[j])
            {
                minDist[j] = grid[cur][j];

                // 记录边
                parent[j] = cur;
            }
        }
    }

    // 统计结果
    int result = 0;
    for(int i = 2; i <= v; ++i) result += minDist[i];
    cout << result << endl;

    // 输出最小生成树边的连接情况
    for(int i = 1; i <= v; ++i) cout << i << "->" << parent[i] << endl;
}

Kruskal算法

Kruskal算法是维护边的集合。思路如下:

  • 边的权重排序,因为要有限选最小的边加入到生成树里。
  • 遍历排序后的边。
    • 如果边首尾的两个节点在同一个集合,说明如果连上这条边图中会出现环。
    • 如果边首尾的两个节点不在同一个结合,加入到最小生成树,并把两个节点加入同一个集合。

举例说明(来自代码随想录)

image

排序后的边顺序为[(1,2) (4,5) (1,3) (2,6) (3,4) (6,7) (5,7) (1,5) (3,2) (2,4) (5,6)]

使用并查集来将两个节点加入同一个集合,并且判断两个节点是否在同一个集合中。

流程如下:

image

image

image

image

image

image

题目:53. 寻宝

题目链接:https://kamacoder.com/problempage.php?pid=1053
文章讲解:https://www.programmercarl.com/kamacoder/0053.寻宝-prim.html
题目状态:看题解

思路:

Kruskal算法,看上面。

代码:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// l、r为两边的节点,val为边的权值
struct Edge
{
    int l, r, val;
};

// 节点数量
int n = 10001;
// 并查集标记节点关系的数组
vector<int> father(n, -1); // 节点编号是从1开始的,n要大一些

// 并查集初始化
void init()
{
    for(int i = 0; i < n; ++i)
    {
        father[i] = i;
    }
}

// 并查集的查找操作
int find(int u)
{
    return u == father[u] ? u : father[u] = find(father[u]);
    // 路径压缩
}

// 并查集的加入集合
void join(int u, int v)
{
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if(u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
}

int main()
{
    int v, e;
    int v1, v2, val;
    vector<Edge> edges;
    int result_val = 0;
    cin >> v >> e;
    while(e--)
    {
        cin >> v1 >> v2 >> val;
        edges.push_back({v1, v2, val});
    }

    // 执行kruskal算法
    // 按边的权限对边进行从小到大排序
    sort(edges.begin(), edges.end(), [](const Edge &a, const Edge &b) {
        return a.val < b.val;
    });

    vector<Edge> result; // 存储最小生成树的边

    // 并查集初始化
    init();

    // 从头开始遍历边
    for(Edge &edge : edges)
    {
        // 并查集,搜出两个节点的祖先
        int x = find(edge.l);
        int y = find(edge.r);

        // 如果祖先不同,则不在同一个集合
        if(x != y)
        {
            result.push_back(edge); // 保存最小生成树的边
            result_val += edge.val; // 这条边可以作为生成树的边
            join(x, y); // 两个节点加入到同一个集合
        }
    }
    cout << result_val << endl;
    
    // 打印最小生成树的边
    for(auto &edge : result)
    {
        cout << edge.l << "-" << edge.r << " : " << edge.val << endl;
    }
    return 0;
}

Kruskal与prim的关键区别在于,prim维护的是节点的集合,而Kruskal维护的是边的集合。 如果一个图中,节点多,但边相对较少,那么使用Kruskal更优。

标签:val,int,随想录,第四十九,生成,minDist,最小,part7,节点
From: https://www.cnblogs.com/frontpen/p/18388678

相关文章

  • 代码随想录day45 || 115 不同子序列, 583 两个字符串删除操作, 72 编辑距离
    115不同子序列funcnumDistinct(sstring,tstring)int{ //动态规划,思考一下判断连续和不连续的区别,如果相等都是左上角+1,如果不等,连续情况就是直接等于左上角,不连续情况直接归零 //dp[i][j]表示s[i]中存在t[j]结尾的的个数 //递推公式,不要求连续字串,所以,如果s[i......
  • 代码随想录算法day3 - 链表1
    题目1203.移除链表元素给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val==val的节点,并返回新的头节点。示例1:输入:head=[1,2,6,3,4,5,6],val=6输出:[1,2,3,4,5]示例2:输入:head=[],val=1输出:[]示例3:输入:head=[7,7,7,7],v......
  • 代码随想录算法训练营,29日 | 704. 二分查找,27. 移除元素,977.有序数组的平方,209.长度最
    数组基础文档讲解︰代码随想录(programmercarl.com)1.连续空间、相同类型元素2.元素只能覆盖3.二维数组的地址连续吗(C++连续,Java不连续)704.二分查找题目链接:704.二分查找文档讲解︰代码随想录(programmercarl.com)视频讲解︰二分查找日期:2024-08-29思路:第一反应是想到二分查......
  • 代码随想录算法day2-数组2
    题目1209.长度最小的子数组给定一个含有n个正整数的数组和一个正整数target。找出该数组中满足其总和大于等于target的长度最小的子数组[numsl,numsl+1,...,numsr-1,numsr],并返回其长度。如果不存在符合条件的子数组,返回0。示例1:输入:target=7,nums=[2,......
  • 代码随想录day44 || 1143 最长公共子序列, 1035 不相交的线, 53 最大子序列和, 392 判
    1143最长公共子序列funclongestCommonSubsequence(text1string,text2string)int{ //思路和判断最长公共数组一样 //dp[i][j]表示以text1[i],text2[j]为结尾的最长公共子序列的长度 //iftext1[i]==text2[j]{dp[i+1][j+1]=dp[i][j]+1}else{dp[i+1][j+1]=......
  • 代码随想录算法训练营第四十三天 | 300.最长递增子序列 , 674. 最长连续递增序列 , 718.
    目录300.最长递增子序列 思路1.dp[i]的定义2.状态转移方程3.dp[i]的初始化4.确定遍历顺序 5.举例推导dp数组方法一:动态规划方法二:贪心心得收获 674.最长连续递增序列思路动态规划1.确定dp数组(dptable)以及下标的含义2.确定递推公式3.dp数组如何初始化4.......
  • 代码随想录算法训练营第二十九天(贪心 三)
    力扣题部分:134.加油站题目链接:.-力扣(LeetCode)题面:在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为......
  • 代码随想录 -- 哈希表 -- 四数之和
    18.四数之和-力扣(LeetCode)思路:(与三数之和类似,在外面加一层循环)    1. 先将nums 按升序排序    2. 初始状态:k=0,i= k+1,left= i+1,right= len(nums)-1        3. 进入第一层for循环:        如果 nums[k]>0andtarget>0 ......
  • 代码随想录算法day1-数组1
    题目1704.二分查找题目描述:给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。示例1:输入:nums=[-1,0,3,5,9,12],target=9输出:4解释:9出现在nums中并且下标为4示例2:......
  • 代码随想录算法训练营第一天 | 数组part01:数组理论基础,704. 二分查找,27. 移除元素 97
    数组理论基础数组是存放在连续内存空间上的相同类型数据的集合数组徐璈注意的是:数组的下标都是从0开始的数组内存空间是的地址是连续的正因为舒适的内存空间是连续的,所以在删除和增添元素的时候,需要移动其他元素的地址。在c++中,vector的底层实现是array,严格来说,vector是容......