06图论算法练习题解析

图论练习题及参考代码

01课程表

问题描述

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 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] 中的所有课程对 互不相同

输入描述

第一行输入两个整数分别表示 numCoursesprerequisites.length

接下来 prerequisites.length 行,每行两个由空格分隔的整数,分别代表 aibi

输出描述

如果可以完成所有课程的学习,输出 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 个节点的树,标记为 0n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 aibi 之间存在一条无向边。

可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树

请你找到所有的 最小高度树 并按 从小到大的顺序 返回它们的根节点标签列表。

树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。

示例 1:

img

输入: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 行,每行两个由空格分隔的整数,分别代表 aibi

输出描述

从小到大的顺序 返回所有的 最小高度树 的根节点标签,由空格分隔。

输入样例

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 个人,按从 1n 的顺序编号。传言称,这些人中有一个暗地里是小镇法官。

如果小镇法官真的存在,那么:

  1. 小镇法官不会信任任何人。
  2. 每个人(除了小镇法官)都信任这位小镇法官。
  3. 只有一个人同时满足属性 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

输入描述

第一行输入两个整数分别表示 ntrust.length

接下来 trust.length 行,每行两个由空格分隔的整数,分别代表 aibi

输出描述

请输出该法官的编号;否则,输出 -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 个编号从 1n 的节点组成。星型图有一个 中心 节点,并且恰有 n - 1 条边将中心节点与其他每个节点连接起来。

给你一个二维整数数组 edges ,其中 edges[i] = [ui, vi] 表示在节点 uivi 之间存在一条边。请你找出并返回 edges 所表示星型图的中心节点。

示例 1:

img

  • 3 <= n <= 10^5
  • edges.length == n - 1
  • edges[i].length == 2
  • 1 <= ui, vi <= n
  • ui != vi
  • 题目数据给出的 edges 表示一个有效的星型图

输入描述

第一行输入一个整数表示 n

接下来 n-1 行,每行两个由空格分隔的整数,分别代表 uivi

输出描述

输出星型图的中心节点编号

输入样例

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]xiyi 表示第 i 个炸弹的 X 和 Y 坐标,ri 表示爆炸范围的 半径

你需要选择引爆 一个 炸弹。当这个炸弹被引爆时,所有 在它爆炸范围内的炸弹都会被引爆,这些炸弹会进一步将它们爆炸范围内的其他炸弹引爆。

给你数组 bombs ,请你返回在引爆 一个 炸弹的前提下,最多 能引爆的炸弹数目。

示例 1:

img

输入: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 行,每行三个由空格分隔的整数,分别代表 xiyiri

输出描述

输出最多能引爆的炸弹数目

输入样例

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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuelanghanbao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值