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

「代码随想录算法训练营」第五十天 | 图论 part8

时间:2024-08-31 18:27:38浏览次数:14  
标签:图论 cur int 随想录 dijkstra minDist 权值 part8 节点

目录

拓扑排序

拓扑排序概括来说就是给出一个有向无环图,把这个有向无环图转成线性的排序,就叫拓扑排序。

使用广度优先搜索(BFS)即可。

image

如上图,当我们做拓扑排序的时候,优先找入度为0的节点,只有入度为0,它才是出发节点。

拓扑排序的过程:

  1. 找到入度为0的节点,加入结果集。
  2. 将该节点从图中移除。

循环以上两步,直到所有节点都在图中被移除了。

结果集的顺序,就是我们想要的拓扑排序顺序(结果集里顺序可能不唯一)

模拟过程:

image

image

image

image

image

image

判断有环:

image

这个图,我们只能将入度为0的节点0接入结果集。

之后,节点1、2、3、4形成了环,找不到入度为0的节点了,所以此时结果集里只有一个元素。

那么如果我们发现结果集元素个数不等于图中节点个数,我们就可以认定图中一定有有向环!

题目:117. 软件构建

题目链接:https://kamacoder.com/problempage.php?pid=1191
文章讲解:https://www.programmercarl.com/kamacoder/0117.软件构建.html
题目状态:看题解

思路:

使用拓扑排序。

代码:

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>

using namespace std;

int main()
{
    int m, n, s, t;
    cin >> n >> m;
    vector<int> isDegree(n, 0); // 记录每个文件的入度
    unordered_map<int, vector<int>> umap; // 记录文件依赖关系
    vector<int> result; // 结果集

    while(m--)
    {
        // s->t,先有s才能有t
        cin >> s >> t;
        isDegree[t]++; // t的入度加一
        umap[s].push_back(t); // 记录s指向哪些文件
    }
    queue<int> que;
    for(int i = 0; i < n; ++i)
    {
        // 入度为0的文件,可以作为开头,先加入队列
        if(isDegree[i] == 0) que.push(i);
    }

    while(que.size())
    {
        int cur = que.front(); // 当前选中的文件
        que.pop();
        result.push_back(cur);
        vector<int> files = umap[cur]; // 获取该文件指向的文件
        if(files.size()) // cur有后续文件
        {
            for(int i = 0; i < files.size(); ++i)
            {
                isDegree[files[i]]--; // cur的指向的文件入度-1
                if(isDegree[files[i]] == 0) que.push(files[i]);
            }
        }
    }

    if(result.size() == n)
    {
        for(int i = 0; i < n - 1; ++i) cout << result[i] << " ";
        cout << result[n - 1];
    }
    else
        cout << -1 << endl;
    return 0;
}

dijkstra(朴素版)

dijkstra算法:在有权图(权值非负数)中求从起点到其他节点的最短路径算法。

需要注意两点:

  • dijkstra算法可以同时求起点到所有节点的最短路径。
  • 权值不能为负数。

dijkstra算法和prim算法的计算过程非常类似:

  1. 第一步,选源点到哪个节点近且该节点未被访问过
  2. 第二步,该最近节点被标记访问过
  3. 第三步,更新非访问节点到源点的距离(即更新minDist数组)

其中minDist数组用来记录每一个节点距离源点的最小距离。

模拟过程:

image

image

image

image

image

image

image

image

image

题目:47. 参加科学大会

题目链接:https://kamacoder.com/problempage.php?pid=1047
文章讲解:https://www.programmercarl.com/kamacoder/0047.参会dijkstra朴素.html
题目状态:看题解

思路:

dijkstra算法。

代码:

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

using namespace std;

int main()
{
    int n, m, p1, p2, val;
    cin >> n >> m;

    vector<vector<int>> grid(n + 1, vector<int>(n + 1, INT_MAX));
    for(int i = 0; i < m; ++i)
    {
        cin >> p1 >> p2 >> val;
        grid[p1][p2] = val;
    }

    int start = 1;
    int end = n;

    // 存储从源点到每个节点的最短距离
    vector<int> minDist(n + 1, INT_MAX);

    // 记录顶点是否被访问过
    vector<bool> visited(n + 1, false);

    minDist[start] = 0; // 起始点到自身的距离为0

    // 初始化路径数组
    vector<int> parent(n + 1, -1);

    // 遍历所有节点
    for(int i = 1; i <= n; ++i)
    {
        int minVal = INT_MAX;
        int cur = 1;

        // 1.选距离源点最近且未访问过的节点
        for(int v = 1; v <= n; ++v)
        {
            if(!visited[v] && minDist[v] < minVal)
            {
                minVal = minDist[v];
                cur = v;
            }
        }

        // 2.标记该节点已被访问
        visited[cur] = true;

        // 3.更新非访问节点到源点的距离(即更新minDist数组)
        for(int v = 1; v <= n; ++v)
        {
            if(!visited[v] && grid[cur][v] != INT_MAX && minDist[cur] + grid[cur][v] < minDist[v])
            {
                minDist[v] = minDist[cur] + grid[cur][v];
                parent[v] = cur; // 记录边
            }
        }
    }

    // 不能到达终点
    if(minDist[end] == INT_MAX) cout << -1 << endl;
    // 到达终点最短路径
    else cout << minDist[end] << endl;

    // 输出最短情况
    for(int i = 1; i <= n; ++i) cout << parent[i] << "->" << i << endl;

    return 0;
}

dijkstra算法和prim算法的区别

  • prim是求非访问节点到最小生成树的最小距离
  • dijkstra是求非访问节点到源点的最小距离

dijkstra(堆优化版)

上一节的dijkstra算法是从节点的角度来遍历的,这一节的dijkstra算法我们从边的角度来遍历分析,且采用邻接表来存储图。邻接表表示一个有向有权图如下:

image

其中:

  • 节点1指向节点3,权值为1
  • 节点1指向节点5,权值为2
  • 节点2指向节点4,权值为7
  • 节点2指向节点3,权值为6
  • 节点2指向节点5,权值为3
  • 节点3指向节点4,权值为3
  • 节点5指向节点1,权值为10

dijkstra算法思路三部曲:

  1. 第一步,选源点到哪个节点近且该节点未被访问过
  2. 第二步,该最近节点被标记访问过
  3. 第三步,更新非访问节点到源点的距离(即更新minDist数组)

其中第一步我们要选择距离源点近的节点(即:该边的权值最小),所以我们需要一个小顶堆来帮我们对边的权值排序,每次从小顶堆堆顶去边就是权值最小的边。

因此在堆优化版本的dijkstra算法的三部曲中:

  1. 第一步:不用for循环去遍历,直接取堆顶元素
  2. 第二步:将节点做访问标记
  3. 第三步:和朴素dijkstra算法一样

题目:47. 参加科学大会

题目链接:https://kamacoder.com/problempage.php?pid=1047
文章讲解:https://www.programmercarl.com/kamacoder/0047.参会dijkstra朴素.html
题目状态:看题解

思路:

使用堆优化版本的dijkstra算法。

代码:

#include <iostream>
#include <vector>
#include <list>
#include <queue>
#include <climits>

using namespace std;

// 小顶堆
class mycomparison
{
public:
    bool operator()(const pair<int, int> &lhs, const pair<int, int> &rhs)
    {
        return lhs.second > rhs.second;
    }
};

// 定义一个结构体来表示带权重的边
struct Edge
{
    int to; // 邻接顶点
    int val; // 边的权重
    Edge(int t, int w): to(t), val(w) {} // 构造函数
};

int main()
{
    int n, m, p1, p2, val;
    cin >> n >> m;

    vector<list<Edge>> grid(n + 1);

    for(int i = 0; i < m; ++i)
    {
        cin >> p1 >> p2 >> val;
        // p1指向p2,权值为val
        grid[p1].push_back(Edge(p2, val));
    }

    int start = 1; // 起点
    int end = n; // 终点

    // 存储从源点到每个节点的最短距离
    vector<int> minDist(n + 1, INT_MAX);

    // 记录顶点是否被访问过
    vector<bool> visited(n + 1, false);

    // 优先级队列中存放pair<节点, 源点到该节点的权值>
    priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pq;

    // 初始化队列,源点到源点的距离为0,所以初始为0
    pq.push(pair<int, int>(start, 0));

    // 起始点到自身的距离为0
    minDist[start] = 0;

    while(!pq.empty())
    {
        // 1.第一步,选源点到哪个节点近且该节点未被访问过(通过优先级队列来实现)
        // <节点, 源点到该节点的距离>
        pair<int, int> cur = pq.top();
        pq.pop();

        if(visited[cur.first]) continue;

        // 2.第二步,该最近节点被标记访问过
        visited[cur.first] = true;

        // 3.第三步,更新非访问节点到源点的距离(即更新minDist数组)
        for(Edge &edge : grid[cur.first])
        {
            // 遍历cur指向的节点,cur指向的节点为edge
            // cur指向的节点edge.to,这条边的权值为edge.val
            if(!visited[edge.to] && minDist[cur.first] + edge.val < minDist[edge.to])
            {
                // 更新minDist
                minDist[edge.to] = minDist[cur.first] + edge.val;
                pq.push(pair<int, int>(edge.to, minDist[edge.to]));
            }
        }
    }
    // 不能到达终点
    if(minDist[end] == INT_MAX) cout << -1 << endl;
    // 到达终点最短路径
    else cout << minDist[end] << endl;
}

标签:图论,cur,int,随想录,dijkstra,minDist,权值,part8,节点
From: https://www.cnblogs.com/frontpen/p/18389808

相关文章

  • 代码随想录算法训练营,8月31日 | 24. 两两交换链表中的节点,19.删除链表的倒数第N个节点
    24.两两交换链表中的节点题目链接:24.两两交换链表中的节点文档讲解︰代码随想录(programmercarl.com)视频讲解︰两两交换链表中的节点日期:2024-08-31做前思路:用上虚拟头指针,从头开始,先指向2再到1,再到3,但要注意保留原本的结点。Java代码如下:classSolution{publicListN......
  • 图论学习笔记
    最短路割边最短路约定\(1\rightsquigarrowx\)表示\(T(1\rightsquigarrowx)\)表示最短路树\(T\)上\(1\rightsquigarrowx\)的经过的边的集合。思路问题:给定一张无向正权图,对图上的每条边,求删去该边后\(1\rightsquigarrown\)的最短路。首先求出\(1\rightsqu......
  • 代码随想录day46 || 647 回文子串, 516 最长回文子序列
    647回文字串funccountSubstrings(sstring)int{ //动规五部曲 //dp[i][j]表示s[i:j+1]区间是否是一个回文 //ifs[i]==s[j]{ifi-j<=1||dp[i+1][j-1]==true{dp[i][j]==true}} //初始化为false //从下往上,从左往右 //print varcountint var......
  • [Python办公]一文入门图论Graphs,轻松处理最短路径等问题!
            [Python办公]一文入门图论Graphs,轻松处理最短路径等问题!        图论是研究图这种数学结构的性质和应用的学科。图(Graphs)由节点(或顶点)和连接这些节点的边组成,它是一种强大的数据结构,广泛应用于各种领域。以下举例用最短距离来入门图论。入门问题: ......
  • 代码随想录算法day4 - 链表2
    题目124.两两交换链表中的节点给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。示例1:输入:head=[1,2,3,4]输出:[2,1,4,3]示例2:输入:head=[]输出:[]示例3:输入:head=[1]输出:[1]......
  • 代码随想录算法训练营,8月30日 | 203.移除链表元素, 707.设计链表, 206.反转链表
    链表理论基础1.单链表:数据域和指针域(指向下一个结点的位置)组成,头结点,结尾为空指针;双链表:多了一个指向前一个结点的指针;循环链表:结尾指向头结点。2.链表在内存中的存储不是顺序的,跟数组不同,要找一个数据只能通过前一个数据来找,所有这就导致链表的查询比数组麻烦,但是插入删除数据......
  • 「代码随想录算法训练营」第四十九天 | 图论 part7
    目录最小生成树的解题prim算法举例说明(来自代码随想录)题目:53.寻宝Kruskal算法举例说明(来自代码随想录)题目:53.寻宝最小生成树的解题最小生成树类型的题目主要用于解决所有节点的最小连通子图的问题,即:以最小的成本(边的权值)将图中所有节点链接到一起。最小生成树可以使用prim算......
  • 代码随想录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......
  • 图论-色数
    设\(G\)是一个图,\(G\)的色数定义为为顶点染色需要的最小颜色数,使得任意相邻的两个顶点颜色不同,一般记作\(\chi(G)\)常用的色数是二部图色数为\(2\)。我们有\(Brooks\)定理:(弱化版)设\(G\)是图,则\(\chi(G)\le\Delta(G)+1\)任意染色,给\(v_i\)着色时,至多\(\Delta(G......