既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
🔥前言
本专栏收录的均为牛客网的算法题目,内含链表、双指针、递归、动态规划、基本数据结构等算法思想的具体运用。牛客网不仅有大量的经典算法题目,也有大厂的面试真题,面试、找工作完全可以来这里找机会。此外,网站内的编码主题多样化,调试功能可运用性强,可谓是非常注重用户体验。这么好的免费刷题网站还不快入手吗,快去注册开启算法百炼成神之路吧!
1、AB13 【模板】拓扑排序
学会使用邻接表解决图论问题,巧妙利用vector
容器
题目链接:拓朴排序
1.1、解题思路
解决拓扑排序之前要先认识什么是拓扑排序:
对一个有向无环图(Directed Acyclic Graph简称
DAG
)图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u
和v
,若边<u,v>∈E(G)
,则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
解决步骤:
- 使用邻接表将顶点联系起来,辅助数组
inDegree
表示每个顶点的入度。 - 借助队列和计数器变量来判断该有向图是否有环:
- 将入度为零的顶点入队(也就是拓扑图第一个顶点)
- 取队首,遍历与之相邻的顶点,若该顶点入度减一后为零就将其入队
- 只要队列非空就循环操作,计算器循环加一,与顶点数比较是否相等
- 本题末尾也不能输出空格,因此输出拓扑序列时要加限制条件
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
算法:
- 将二维数组
cost
按权升序排序,那么cost[0][2]
就是最小的一个权值 - 将连接这条边的两个顶点放入
unordered_set
容器:unordered_set
容器的元素是无序的,可以用来给顶点去重- 内置的
find
方法也很好用
- 接下来遍历
cost
二维数组,直到所有顶点全部放入容器中,遍历结束 - 将遍历中权值的和返回,程序结束
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;
}
};
![img](https://img-blog.csdnimg.cn/img_convert/ca8a8ff7d89a56f99c7fc0b998f38513.png)
![img](https://img-blog.csdnimg.cn/img_convert/acd408c9adb114e239f79e62e8ad368d.png)
![img](https://img-blog.csdnimg.cn/img_convert/bbb18e11d6e8e0d3d77610ee3c8f0d11.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
WX01Hs-1715371051947)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**