给定一个有向图,图中可能包含有环,有向边用两个节点表示。第一个整数表示起始节点,第二个整数表示终止节点,如0 1
表示存在从0
到1
的路径。每个节点用正整数表示,求这个数据的头节点与尾节点,题目给的用例会是一个头节点,但可能存在多个尾节点。同时,图中可能含有环,如果图中含有环,返回-1
。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
说明:入度为0
是头节点,出度为0
是尾节点
第一行为后续输入的键值对数量N >= 0
,第二行为2N
个数字。每两个为一个起点,一个终点。
输出一行头节点和尾节点。如果有多个尾节点,按从大到小的顺序输出。
如果图有环,输出为-1
所有输入均合法,不会出现不配对的数据
4
1 2 1 3 2 4 3 4
1 4
该例子表示以下有向图。头节点为1
,尾节点为4
本题就是拓扑排序模板题。
假设输入确认没有环,那么题目的提示已经告诉了我们应该如何找到头节点和尾节点。
说明:入度为
0
是头节点,出度为0
是尾节点
由于进行拓扑排序之前需要同时构建邻接表和入度****哈希表,即
neighbor_dic = defaultdict(list)
indegree_dic = defaultdict(int)
for i in range(0, 2*n, n):
a, b = lst[i], lst[i+1]
indegree_dic[b] += 1
neighbor_dic[a].append(b)
则无需再额外构建出度数组。邻接表可以起到出度数组的作用。
如果某一个节点k
位于邻接表的key
中(说明存在以k
为起始节点的边),但其入度为0
(说明不存在以k
为终止节点的边),则说明k
是一个头节点。即
head_node = -1
for k in neighbor_dic.keys():
if k not in indegree_dic:
head_node = k
break
如果某一个节点k
入度不为0
(说明存在以k
为终止节点的边),但其不位于邻接表的key
中(说明不存在以k
为起始节点的边),则说明k
是一个尾节点。即
tail_nodes = list()
for k in indegree_dic.keys():
if k not in neighbor_dic:
tail_nodes.append(k)
头节点和尾节点非常容易直接通过邻接表和入度哈希表计算得到。但本题的核心在于判断该有向图是否存在环。环的判断可以直接使用拓扑排序来进行。如果
-1
。故设计函数check()
来检查所给定的有向图是否能够成功完成拓扑排序
def check(head_node, neighbor_dic, indegree_dic):
q = deque()
q.append(head_node)
while q:
cur_node = q.popleft()
for nxt_node in neighbor_dic[cur_node]:
indegree_dic[nxt_node] -= 1
if indegree_dic[nxt_node] == 0:
q.append(nxt_node)
return all(v == 0 for v in indegree_dic.values())
其中,完成拓扑排序的标准是:BFS结束之后,所有节点的入度均降为0
。
# 题目:【BFS】2023C-查找一个有向网络的头节点和尾节点
# 分值:200
# 作者:闭着眼睛学数理化
# 算法:BFS/拓扑排序
# 代码看不懂的地方,请直接在群上提问
from collections import deque, defaultdict
# 检查给定数据能够完成拓扑排序(是否存在环)的函数
def check(head_node, neighbor_dic, indegree_dic):
# 构建队列
q = deque()
q.append(head_node)
# 进行BFS
while q:
# 弹出当前队头节点cur_node
cur_node = q.popleft()
# 考虑cur_node的所有近邻节点nxt_node
for nxt_node in neighbor_dic[cur_node]:
# 近邻节点的入度-1
indegree_dic[nxt_node] -= 1
# 如果近邻节点的入度降为0,将其加入队列中
if indegree_dic[nxt_node] == 0:
q.append(nxt_node)
# 如果最终所有节点的入度均为0,即indegree_dic.values()均为0
# 则说明成功完成拓扑排序,有向图中不存在环
return all(v == 0 for v in indegree_dic.values())
# 输入边数n
n = int(input())
# 输入2n个数据,每2个节点一组,一共有n组数据
lst = list(map(int, input().split()))
# 构建邻接表
neighbor_dic = defaultdict(list)
# 构建入度哈希表(因为节点的个数不确定,因此用哈希表而不是数组)
indegree_dic = defaultdict(int)
# 每两个一组,取lst中的数据
for i in range(0, 2*n, 2):
# a为该条边的起始节点,b为该条边的终止节点
# 即存在a指向b
a, b = lst[i], lst[i+1]
# 节点b的入度+1
indegree_dic[b] += 1
# 节点a的邻接表延长
neighbor_dic[a].append(b)
head_node = -1
# 遍历邻接表中的所有起始节点k
# 如果k不存在于入度哈希表中
# 说明k的入度为0,即k是头节点
for k in neighbor_dic.keys():
if k not in indegree_dic:
# 由于题目明确只存在一个头节点
# 因此可以直接退出循环
head_node = k
break
# 构建尾节点序列
tail_nodes = list()
# 遍历入度哈希表中的所有节点k
# 如果k不存在于邻接表中
# 说明k不是任何一条边的起始节点,即k是一个尾节点
for k in indegree_dic.keys():
if k not in neighbor_dic:
tail_nodes.append(k)
# 尾节点数组进行排序
tail_nodes.sort()
# 调用check()函数,进行拓扑排序
# 如果能够顺利完成拓扑排序,则说明图中不存在环,直接输出头节点和若干尾节点
# 否则输出-1
print(head_node, *tail_nodes) if check(head_node, neighbor_dic, indegree_dic) else print(-1)
import java.util.*;
public class Main {
public static boolean check(int headNode, Map<Integer, List<Integer>> neighborDic, Map<Integer, Integer> indegreeDic) {
Queue<Integer> q = new LinkedList<>();
q.offer(headNode);
while (!q.isEmpty()) {
int curNode = q.poll();
for (int nxtNode : neighborDic.getOrDefault(curNode, new ArrayList<>())) {
indegreeDic.put(nxtNode, indegreeDic.get(nxtNode) - 1);
if (indegreeDic.get(nxtNode) == 0) {
q.offer(nxtNode);
}
}
}
for (int value : indegreeDic.values()) {
if (value > 0) {
return false;
}
}
return true;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] lst = new int[2 * n];
for (int i = 0; i < 2 * n; i++) {
lst[i] = scanner.nextInt();
}
Map<Integer, List<Integer>> neighborDic = new HashMap<>();
Map<Integer, Integer> indegreeDic = new HashMap<>();
for (int i = 0; i < 2 * n; i += 2) {
int a = lst[i], b = lst[i + 1];
indegreeDic.put(b, indegreeDic.getOrDefault(b, 0) + 1);
neighborDic.computeIfAbsent(a, k -> new ArrayList<>()).add(b);
}
int headNode = -1;
for (int node : neighborDic.keySet()) {
if (!indegreeDic.containsKey(node)) {
headNode = node;
break;
}
}
List<Integer> tailNodes = new ArrayList<>();
for (int node : indegreeDic.keySet()) {
if (!neighborDic.containsKey(node)) {
tailNodes.add(node);
}
}
Collections.sort(tailNodes);
if (check(headNode, neighborDic, indegreeDic)) {
System.out.print(headNode + " ");
for (int node : tailNodes) {
System.out.print(node + " ");
}
System.out.println();
} else {
System.out.println(-1);
}
}
}
#include
#include
#include
#include
#include
#include
using namespace std;
bool check(int headNode, unordered_map<int, vector<int>>& neighborDic, unordered_map<int, int>& indegreeDic) {
queue<int> q;
q.push(headNode);
while (!q.empty()) {
int curNode = q.front();
q.pop();
for (int nxtNode : neighborDic[curNode]) {
indegreeDic[nxtNode]--;
if (indegreeDic[nxtNode] == 0) {
q.push(nxtNode);
}
}
}
for (auto entry : indegreeDic) {
if (entry.second > 0) {
return false;
}
}
return true;
}
int main() {
int n;
cin >> n;
vector<int> lst(2 * n);
for (int i = 0; i < 2 * n; i++) {
cin >> lst[i];
}
unordered_map<int, vector<int>> neighborDic;
unordered_map<int, int> indegreeDic;
for (int i = 0; i < 2 * n; i += 2) {
int a = lst[i], b = lst[i + 1];
indegreeDic[b]++;
neighborDic[a].push_back(b);
}
int headNode = -1;
for (auto entry : neighborDic) {
if (indegreeDic.find(entry.first) == indegreeDic.end()) {
headNode = entry.first;
break;
}
}
vector<int> tailNodes;
for (auto entry : indegreeDic) {
if (neighborDic.find(entry.first) == neighborDic.end()) {
tailNodes.push_back(entry.first);
}
}
sort(tailNodes.begin(), tailNodes.end());
if (check(headNode, neighborDic, indegreeDic)) {
cout << headNode << " ";
for (int node : tailNodes) {
cout << node << " ";
}
cout << endl;
} else {
cout << -1 << endl;
}
return 0;
}
时间复杂度:O(N)
。
空间复杂度:O(N)
。
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
绿色聊天软件戳 od1336
了解更多