图论练习题及参考代码
01课程表
问题描述
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,输出 true
;否则,输出 false
。
1 <= numCourses <= 10^5
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
输入描述
第一行输入两个整数分别表示 numCourses
和 prerequisites.length
接下来 prerequisites.length
行,每行两个由空格分隔的整数,分别代表 ai
和 bi
输出描述
如果可以完成所有课程的学习,输出 true
;否则,输出 false
输入样例
2 2
1 0
0 1
输出样例
false
参考代码
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int>> edges;
vector<int> visited;
bool valid = true;
void dfs(int u) {
visited[u] = 1;
for (int v: edges[u]) {
if (visited[v] == 0) {
dfs(v);
if (!valid) {
return;
}
}
else if (visited[v] == 1) {
valid = false;
return;
}
}
visited[u] = 2;
}
int main(){
int numCourses, len;
cin>>numCourses>>len;
vector<vector<int>> prerequisites(len, vector<int>(2));
for(int i=0;i<len;i++){
cin>>prerequisites[i][0]>>prerequisites[i][1];
}
edges.resize(numCourses);
visited.resize(numCourses);
for (const auto& info: prerequisites) {
edges[info[1]].push_back(info[0]);
}
for (int i = 0; i < numCourses && valid; ++i) {
if (!visited[i]) {
dfs(i);
}
}
if(valid) cout<<"true";
else cout<<"false";
return 0;
}
02最小高度树
问题描述
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 n
个节点的树,标记为 0
到 n - 1
。给定数字 n
和一个有 n - 1
条无向边的 edges
列表(每一个边都是一对标签),其中 edges[i] = [ai, bi]
表示树中节点 ai
和 bi
之间存在一条无向边。
可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h
。在所有可能的树中,具有最小高度的树(即,min(h)
)被称为 最小高度树 。
请你找到所有的 最小高度树 并按 从小到大的顺序 返回它们的根节点标签列表。
树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。
示例 1:
输入:n = 4, edges = [[1,0],[1,2],[1,3]]
输出:[1]
解释:如图所示,当根是标签为 1 的节点时,树的高度是 1 ,这是唯一的最小高度树。
1 <= n <= 2 * 10^4
edges.length == n - 1
0 <= ai, bi < n
ai != bi
- 所有
(ai, bi)
互不相同 - 给定的输入 保证 是一棵树,并且 不会有重复的边
输入描述
第一行输入一个整数表示 n
接下来 n-1
行,每行两个由空格分隔的整数,分别代表 ai
和 bi
输出描述
从小到大的顺序 返回所有的 最小高度树 的根节点标签,由空格分隔。
输入样例
4
1 0
1 2
1 3
输出样例
1
参考代码
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int main(){
int n;
cin>>n;
vector<vector<int>> edges(n-1, vector<int>(2));
for(int i=0;i<n-1;i++){
cin>>edges[i][0]>>edges[i][1];
}
vector<int> degree(n);
vector<vector<int>> adj(n);
for (auto & edge : edges){
adj[edge[0]].emplace_back(edge[1]);
adj[edge[1]].emplace_back(edge[0]);
degree[edge[0]]++;
degree[edge[1]]++;
}
queue<int> qu;
vector<int> ans;
for (int i = 0; i < n; i++) {
if (degree[i] == 1) {
qu.emplace(i);
}
}
int remainNodes = n;
while (remainNodes > 2) {
int sz = qu.size();
remainNodes -= sz;
for (int i = 0; i < sz; i++) {
int curr = qu.front();
qu.pop();
for (auto & v : adj[curr]) {
if (--degree[v] == 1) {
qu.emplace(v);
}
}
}
}
while (!qu.empty()) {
ans.emplace_back(qu.front());
qu.pop();
}
for(int i=0;i<ans.size();i++){
cout<<ans[i]<<" ";
}
return 0;
}
03找到小镇的法官
问题描述
小镇里有 n
个人,按从 1
到 n
的顺序编号。传言称,这些人中有一个暗地里是小镇法官。
如果小镇法官真的存在,那么:
- 小镇法官不会信任任何人。
- 每个人(除了小镇法官)都信任这位小镇法官。
- 只有一个人同时满足属性 1 和属性 2 。
给你一个数组 trust
,其中 trust[i] = [ai, bi]
表示编号为 ai
的人信任编号为 bi
的人。
如果小镇法官存在并且可以确定他的身份,请返回该法官的编号;否则,返回 -1
。
1 <= n <= 1000
0 <= trust.length <= 10^4
trust[i].length == 2
trust
中的所有trust[i] = [ai, bi]
互不相同ai != bi
1 <= ai, bi <= n
输入描述
第一行输入两个整数分别表示 n
和 trust.length
接下来 trust.length
行,每行两个由空格分隔的整数,分别代表 ai
和 bi
输出描述
请输出该法官的编号;否则,输出 -1
输入样例
3 2
1 3
2 3
输出样例
3
参考代码
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n, len;
cin>>n>>len;
vector<int> inDegrees(n + 1);
vector<int> outDegrees(n + 1);
for(int i=0;i<len;i++){
int x, y;
cin>>x>>y;
++inDegrees[y];
++outDegrees[x];
}
for (int i = 1; i <= n; ++i) {
if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {
cout<<i;
return 0;
}
}
cout<<-1;
return 0;
}
04找出星型图的中心节点
问题描述
有一个无向的 星型 图,由 n
个编号从 1
到 n
的节点组成。星型图有一个 中心 节点,并且恰有 n - 1
条边将中心节点与其他每个节点连接起来。
给你一个二维整数数组 edges
,其中 edges[i] = [ui, vi]
表示在节点 ui
和 vi
之间存在一条边。请你找出并返回 edges
所表示星型图的中心节点。
示例 1:
3 <= n <= 10^5
edges.length == n - 1
edges[i].length == 2
1 <= ui, vi <= n
ui != vi
- 题目数据给出的
edges
表示一个有效的星型图
输入描述
第一行输入一个整数表示 n
接下来 n-1
行,每行两个由空格分隔的整数,分别代表 ui
和 vi
输出描述
输出星型图的中心节点编号
输入样例
4
1 2
2 3
2 4
输出样例
2
参考代码
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n, e00, e01, e10, e11, result;
cin>>n>>e00>>e01>>e10>>e11;
e00 == e10 || e00 == e11 ? result=e00 : result=e01;
cout<<result;
return 0;
}
05引爆最多的炸弹
问题描述
给你一个炸弹列表。一个炸弹的 爆炸范围 定义为以炸弹为圆心的一个圆。
炸弹用一个下标从 0
开始的二维整数数组 bombs
表示,其中 bombs[i] = [xi, yi, ri]
。xi
和 yi
表示第 i
个炸弹的 X 和 Y 坐标,ri
表示爆炸范围的 半径 。
你需要选择引爆 一个 炸弹。当这个炸弹被引爆时,所有 在它爆炸范围内的炸弹都会被引爆,这些炸弹会进一步将它们爆炸范围内的其他炸弹引爆。
给你数组 bombs
,请你返回在引爆 一个 炸弹的前提下,最多 能引爆的炸弹数目。
示例 1:
输入:bombs = [[2,1,3],[6,1,4]]
输出:2
解释:
上图展示了 2 个炸弹的位置和爆炸范围。
如果我们引爆左边的炸弹,右边的炸弹不会被影响。
但如果我们引爆右边的炸弹,两个炸弹都会爆炸。
所以最多能引爆的炸弹数目是 max(1, 2) = 2 。
1 <= bombs.length <= 100
bombs[i].length == 3
1 <= xi, yi, ri <= 10^5
输入描述
第一行输入一个整数表示 bombs.length
接下来 bombs.length
行,每行三个由空格分隔的整数,分别代表 xi
、yi
和 ri
输出描述
输出最多能引爆的炸弹数目
输入样例
2
2 1 3
6 1 4
输出样例
2
参考代码
#include<iostream>
#include<vector>
#include<unordered_map>
#include<queue>
using namespace std;
int main(){
int n;
cin>>n;
vector<vector<int>> bombs(n, vector<int>(3));
for(int i=0;i<n;i++){
cin>>bombs[i][0]>>bombs[i][1]>>bombs[i][2];
}
// 判断炸弹 u 能否引爆炸弹 v
auto isConnected = [&](int u, int v) -> bool {
long long dx = bombs[u][0] - bombs[v][0];
long long dy = bombs[u][1] - bombs[v][1];
return (long long)bombs[u][2] * bombs[u][2] >= dx * dx + dy * dy;
};
// 维护引爆关系有向图
unordered_map<int, vector<int>> edges;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i != j && isConnected(i, j)) {
edges[i].push_back(j);
}
}
}
int res = 0; // 最多引爆数量
for (int i = 0; i < n; ++i) {
// 遍历每个炸弹,广度优先搜索计算该炸弹可引爆的数量,并维护最大值
vector<int> visited(n);
int cnt = 1;
queue<int> q;
q.push(i);
visited[i] = 1;
while (!q.empty()) {
int cidx = q.front();
q.pop();
for (const int nidx: edges[cidx]) {
if (visited[nidx]) {
continue;
}
++cnt;
q.push(nidx);
visited[nidx] = 1;
}
}
res = max(res, cnt);
}
cout<<res;
return 0;
}