【算法入门&图论】【模板】拓扑排序 【模板】单源最短路2 最小生成树_ab15 【模板

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

学会使用邻接表解决图论问题,巧妙利用vector容器

题目链接:拓朴排序

在这里插入图片描述

1.1、解题思路

解决拓扑排序之前要先认识什么是拓扑排序:

对一个有向无环图(Directed Acyclic Graph简称DAG)图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点uv,若边<u,v>∈E(G)则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序

解决步骤:

  1. 使用邻接表将顶点联系起来,辅助数组inDegree表示每个顶点的入度。
  2. 借助队列和计数器变量来判断该有向图是否有环:
    • 将入度为零的顶点入队(也就是拓扑图第一个顶点)
    • 取队首,遍历与之相邻的顶点,若该顶点入度减一后为零就将其入队
    • 只要队列非空就循环操作,计算器循环加一,与顶点数比较是否相等
  3. 本题末尾也不能输出空格,因此输出拓扑序列时要加限制条件

1.2、代码实现与注释

本题源码:

#include<iostream>
#include<vector>
#include<queue>
#define M 200001
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<int> adjList[M]; // 模拟邻接表
    int inDegree[M] = { 0 };// 记录每个顶点的入度
    int a, b;
    for (int i = 0; i < m; i++) {
        cin >> a >> b;
        adjList[a].push\_back(b);
        inDegree[b]++;
    }
    queue<int> que; // 将初始入度为零的顶点入队
    for (int i = 1; i <= n; i++) {
        if (inDegree[i] == 0)
            que.push(i);
    }

    int cnt = 0; // 用来计数,判断改图是否有环
    vector<int> res; // 用来输出顶点序列
    while (!que.empty()) {
        int u = que.front();
        que.pop();
        res.push\_back(u);
        for (int i = 0; i < adjList[u].size(); i++) { // 遍历u的相邻顶点
            int v = adjList[u][i];
            if (--inDegree[v] == 0)
                que.push(v);
        }
        cnt++;
    }
    // 若计数器与顶点数相同则图无环,存在拓扑排序
    if (cnt == n) {
        for (int i = 0; i < res.size(); i++) {
            cout << res[i];
            // 限制输出空格的条件
            if (i != res.size() - 1) {
                cout << " ";
            }
        }
    } else {
        cout << -1;
    }
    return 0;
}

重要注释:

  • adjList数组是vector类型的,用来模拟邻接表
    • 使用每个元素为一个数组的vector容器模拟邻接表进行建图
    • vector[a]所对应的数组中存储着该顶点所指向的其他顶点
    • inDegree数组代表每一个顶点的入度情况
  • 使用一个队列,初始时将所有入度为0的顶点全部入队,之后采用BFS的思想:
    • 依次取出队头元素并存入结果数组中,然后在邻接表中遍历该队头元素所指向的其他顶点
    • 将这些顶点的入度全部减一,若减一后某顶点的入度变为0,则将该顶点进行入队操作,
      重复此步骤直至队列为空为止。
  • 设置一个用于判断图中是否存在环(是否可以得到拓扑序列)的计数器,在弹出队头元素后要将计数器加一,最后队列为空后,若计数器的值与顶点数相同,则说明图不存在环,可以得到拓扑序列。

2、AB14 最小生成树

题目链接:最小生成树

在这里插入图片描述

2.1、解题思路

本题要求在最小花费下将 n 户人家连接起来,很显然是最小生成树的问题,我采用prim算法:

  1. 将二维数组cost按权升序排序,那么cost[0][2]就是最小的一个权值
  2. 将连接这条边的两个顶点放入unordered_set容器:
    • unordered_set 容器的元素是无序的,可以用来给顶点去重
    • 内置的 find 方法也很好用
  3. 接下来遍历cost二维数组,直到所有顶点全部放入容器中,遍历结束
  4. 将遍历中权值的和返回,程序结束

2.2、代码实现与注释

本题源码:

class Solution {
  public:
    /\*\*
 \* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 \*
 \* 返回最小的花费代价使得这n户人家连接起来
 \* @param n int n户人家的村庄
 \* @param m int m条路
 \* @param cost intvector<vector<>> 一维3个参数,表示连接1个村庄到另外1个村庄的花费的代价
 \* @return int
 \*/

    // 自定义排序规则:按权递增
    static bool cmp(vector<int>& x, vector<int>& y) {
        return x[2] < y[2];
    }
    int miniSpanningTree(int n, int m, vector<vector<int> >& cost) {
        unordered_set<int> points; // 记录不重复的点
        int res = 0;
        sort(cost.begin(), cost.end(), cmp);
        res += cost[0][2]; // 此时res 为最小权值
        // 将最小边加入
        points.insert(cost[0][0]);
        points.insert(cost[0][1]);
        while (1) {
            if (points.size() == n)
                break; // 所有的点连同后退出循环
            // 遍历剩余的边
            for (auto it = cost.begin(); it != cost.end(); it++) {
                // 如果边仅有一个点在集合内就加入
                if ((points.find((\*it)[0]) != points.end() &&
                    points.find((\*it)[1]) == points.end()) ||
                    (points.find((\*it)[1]) != points.end() &&
                    points.find((\*it)[0]) == points.end()))
                    {
                        res += (\*it)[2];
                        points.insert((\*it)[0]);
                        points.insert((\*it)[1]);
                        cost.erase(it); // 删除该边
                        break;
                    }
            }
        }
        return res;
    }
};

重要注释:

  • cmp 是自定义的一个按权递增的排序函数,配合sort函数来将cost排序
  • auto关键字可以自动推导表达式类型,在这里就相当于vector<vector<int>>::iterator
  • if的条件很长,但其实就是将有且仅有一个顶点在points中的边找到:
    • 获取该边的权值并求和,将另一顶点插入到points
    • 将该边删除,重新遍历cost,直到全部顶点被插入到points
  • 最终的 res 就是该题的结果,即最小花费。

3、AB15 单源最短路2

题目链接:单源最短路2

在这里插入图片描述在这里插入图片描述

3.1、解题思路

使用Dijkstra算法(即不断从未处理集合中找当前距离源点最近的顶点以添加到已处理集合中,直至未处理集合为空的算法思想)

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

gntGeR-1715371088082)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化资料的朋友,可以戳这里获取

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值