返回目录
题目描述
给定一个有向图,图中可能包含有环,图使用二维矩阵表示,每一行的第一列表示起始节点,第二列表示终止节点,如 [0, 1] 表示从 0 到 1 的路径。
每个节点用正整数表示。求这个数据的首节点与尾节点,题目给的用例会是一个首节点,但可能存在多个尾节点。同时图中可能含有环。如果图中含有环,返回 [-1]。
说明:入度为0是首节点,出度为0是尾节点。
输入描述
第一行为后续输入的键值对数量N(N ≥ 0)
第二行为2N个数字。每两个为一个起点,一个终点.
输出描述
输出一行头节点和尾节点。如果有多个尾节点,按从小到大的顺序输出
备注
- 如果图有环,输出为 -1
- 所有输入均合法,不会出现不配对的数据
示例:
输入 | 4 0 1 0 2 1 2 2 3 |
---|---|
输出 | 0 3 |
说明 |
解题思路
环的检测
DFS实现:
- 为了进行DFS,首先构建了图的邻接表表示。这是通过将边信息转换成每个节点的邻接节点列表来完成的。
- 然后,对每个节点进行DFS遍历。
在遍历过程中,维护两个集合:
visited
和recStack
。visited
用于记录已经访问过的节点。recStack
用于记录当前递归调用栈中的节点。
检测环的逻辑:
- 在DFS遍历中,如果当前节点已在
recStack
中,则表示找到了一个环,因为这意味着在遍历过程中回到了正在访问的节点。 - 如果节点在
visited
中但不在recStack
中,这意味着节点已被访问且没有形成环。 - 对每个邻接节点重复这个过程。
- 在DFS遍历中,如果当前节点已在
结束条件:
- 如果在任何节点的DFS遍历中检测到环,立即返回
true
,表示图中存在环。 - 如果所有节点都被遍历过,且没有检测到环,则返回
false
。
- 如果在任何节点的DFS遍历中检测到环,立即返回
确定起点和终点
起点和终点的定义:
- 起点是指没有入度的节点,即没有其他节点指向它的节点。
- 终点是指没有出度的节点,即它不指向任何其他节点的节点。
查找方法:
- 遍历所有节点,检查它们的入度和出度。
- 对于每个节点,如果它在
inDegree
映射中没有记录,那么它就是起点。 - 如果它在
outDegree
映射中没有记录,那么它就是终点。
存储结果:
- 结果数组首先存储起点,然后是所有终点。
- 这个数组最后返回作为方法的输出。
Python算法源码
def detect_cycle(graph, node, visited, rec_stack):
# 如果节点在递归栈中,表示存在环
if node in rec_stack:
return True
# 如果节点已经被访问过,说明该路径无环
if node in visited:
return False
visited.add(node)
rec_stack.add(node)
# 遍历节点的邻居
for neighbor in graph.get(node, []):
if detect_cycle(graph, neighbor, visited, rec_stack):
return True
rec_stack.remove(node)
return False
def has_cycle(nodes, edges):
# 构建图的邻接表
graph = {}
for node in nodes:
graph[node] = []
for i in range(0, len(edges), 2):
graph[edges[i]].append(edges[i + 1])
visited = set()
rec_stack = set()
# 检测图中是否存在环
for node in nodes:
if detect_cycle(graph, node, visited, rec_stack):
return True
return False
def find_start_and_end_nodes(edges):
in_degree = {} # 入度映射
out_degree = {} # 出度映射
nodes = set() # 节点集合
# 构建入度和出度映射
for i in range(0, len(edges), 2):
start, end = edges[i], edges[i + 1]
nodes.add(start)
nodes.add(end)
in_degree[end] = in_degree.get(end, 0) + 1
out_degree[start] = out_degree.get(start, 0) + 1
# 如果图中存在环,返回-1
if has_cycle(nodes, edges):
return [-1]
end_nodes = [] # 终点节点列表
start_node = -1 # 起点节点
# 寻找起点和终点
for node in nodes:
if node not in in_degree:
start_node = node
if node not in out_degree:
end_nodes.append(node)
return [start_node] + end_nodes
def main():
input() # 读取并丢弃第一行
edges = list(map(int, input().split()))
result = find_start_and_end_nodes(edges)
print(*result)
if __name__ == "__main__":
main()
C算法源码
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100005
// 定义图节点结构体
struct Node {
int value;
struct Node* next;
};
// 定义队列结构体
struct Queue {
int front, rear;
int capacity;
int* array;
};
// 创建新节点
struct Node* createNode(int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->value = value;
newNode->next = NULL;
return newNode;
}
// 创建队列
struct Queue* createQueue(int capacity) {
struct Queue* queue = (struct Queue*)malloc(sizeof(struct Queue));
queue->capacity = capacity;
queue->front = queue->rear = -1;
queue->array = (int*)malloc(queue->capacity * sizeof(int));
return queue;
}
// 判断队列是否为空
int isEmpty(struct Queue* queue) {
return queue->front == -1;
}
// 入队
void enqueue(struct Queue* queue, int value) {
queue->array[++queue->rear] = value;
if (queue->front == -1)
++queue->front;
}
// 出队
int dequeue(struct Queue* queue) {
int value = queue->array[queue->front++];
if (queue->front > queue->rear)
queue->front = queue->rear = -1;
return value;
}
// 算法入口函数
void getResult(int n, int tmp[]) {
// 记录每个点的入度
int inDegree[MAX_SIZE] = {0};
// 记录每个点的后继点集合
struct Node* nxt[MAX_SIZE] = {NULL};
// 记录图中点
int points[MAX_SIZE] = {0};
for (int i = 0; i < 2 * n; i += 2) {
// 从 a 到 b 的路径
int a = tmp[i];
int b = tmp[i + 1];
// 收集图中所有节点
points[a] = points[b] = 1;
// b点入度+1
inDegree[b]++;
// a点的后继点集合纳入b
struct Node* newNode = createNode(b);
if (nxt[a] == NULL)
nxt[a] = newNode;
else {
newNode->next = nxt[a];
nxt[a] = newNode;
}
}
// 图中总共total个节点
int total = 0;
for (int i = 0; i < MAX_SIZE; ++i) {
if (points[i] == 1)
total++;
}
// head记录图的头节点
int head = 0;
// 队列记录入度为0的点
struct Queue* queue = createQueue(MAX_SIZE);
for (int i = 0; i < MAX_SIZE; ++i) {
// 题目描述中说图中只有一个首节点,首节点是入度为0的节点,因此如果某节点p没有入度,则为头节点
if (points[i] == 1 && inDegree[i] == 0) {
head = i;
enqueue(queue, i);
break;
}
}
// tails记录所有尾节点
int tails[MAX_SIZE], tailIndex = 0;
// count记录已被剥去的点个数,如果图中存在环,则必然最终count < total
int count = 0;
while (!isEmpty(queue)) {
// 剥离入度为0的点
int fa = dequeue(queue);
count++;
// 如果fa没有后继点,即fa没有出度,则fa是尾节点
if (nxt[fa] == NULL) {
tails[tailIndex++] = fa;
continue;
}
// 如果fa有后继点,则其所有后继点入度-1
struct Node* current = nxt[fa];
while (current != NULL) {
int ch = current->value;
inDegree[ch]--;
// 如果ch点入度变为0,则加入队列
if (inDegree[ch] == 0)
enqueue(queue, ch);
current = current->next;
}
}
if (count != total) {
// 如果存在环,则必然count < total
printf("-1\n");
} else {
// 如果不存在环,则打印头节点和尾节点
// 注意本题描述存在冲突(用例2输出的尾节点是从小到大排序的,而题目输出描述是要求尾节点从大到小排序),这里以用例为准
printf("%d", head);
for (int i = tailIndex - 1; i >= 0; --i)
printf(" %d", tails[i]);
printf("\n");
}
}
int main() {
int n;
scanf("%d", &n);
int tmp[2 * MAX_SIZE];
for (int i = 0; i < 2 * n; ++i)
scanf("%d", &tmp[i]);
getResult(n, tmp);
return 0;
}
Java算法源码
import java.util.*;
public class Main {
// 递归检测图中是否有环
public static boolean detectCycle(Map<Integer, List<Integer>> graph, int node, Set<Integer> visited, Set<Integer> recStack) {
if (recStack.contains(node)) {
return true;
}
if (visited.contains(node)) {
return false;
}
visited.add(node);
recStack.add(node);
List<Integer> neighbors = graph.get(node);
if (neighbors != null) {
for (int neighbor : neighbors) {
if (detectCycle(graph, neighbor, visited, recStack)) {
return true;
}
}
}
recStack.remove(node);
return false;
}
// 检测图中是否有环
public static boolean hasCycle(Set<Integer> nodes, List<Integer> edges) {
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int node : nodes) {
graph.put(node, new ArrayList<>());
}
for (int i = 0; i < edges.size(); i += 2) {
int start = edges.get(i);
int end = edges.get(i + 1);
graph.get(start).add(end);
}
Set<Integer> visited = new HashSet<>();
Set<Integer> recStack = new HashSet<>();
for (int node : nodes) {
if (detectCycle(graph, node, visited, recStack)) {
return true;
}
}
return false;
}
// 寻找起点和终点节点
public static List<Integer> findStartAndEndNodes(List<Integer> edges) {
Map<Integer, Integer> inDegree = new HashMap<>();
Map<Integer, Integer> outDegree = new HashMap<>();
Set<Integer> nodes = new HashSet<>();
// 构建入度和出度的映射
for (int i = 0; i < edges.size(); i += 2) {
int start = edges.get(i);
int end = edges.get(i + 1);
nodes.add(start);
nodes.add(end);
inDegree.put(end, inDegree.getOrDefault(end, 0) + 1);
outDegree.put(start, outDegree.getOrDefault(start, 0) + 1);
}
// 检测是否有环
if (hasCycle(nodes, edges)) {
return Collections.singletonList(-1);
}
// 寻找起点和终点
List<Integer> endNodes = new ArrayList<>();
int startNode = -1;
for (int node : nodes) {
if (!inDegree.containsKey(node)) {
startNode = node;
}
if (!outDegree.containsKey(node)) {
endNodes.add(node);
}
}
List<Integer> result = new ArrayList<>();
result.add(startNode);
result.addAll(endNodes);
return result;
}
// 主函数
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
scanner.nextLine(); // 读取N,但在这个程序中不使用
String line = scanner.nextLine(); // 读取边的数据
String[] edgeTokens = line.split(" ");
List<Integer> edges = new ArrayList<>();
for (String token : edgeTokens) {
edges.add(Integer.parseInt(token));
}
List<Integer> result = findStartAndEndNodes(edges);
for (int num : result) {
System.out.print(num + " ");
}
System.out.println();
}
}
2 条评论
[...]2024 C卷 100分序号题目知 识 点难易程度1密码输入检测数据结构/栈☆☆☆2分配土地几何问题☆☆☆3找座位逻辑分析☆☆☆4智能成绩表动态条件分析☆☆☆5内存冷热标记多条件排序☆☆☆6螺旋数字矩阵逻辑分析☆☆☆7围棋的气逻辑分析☆☆☆8分割平衡字符串逻辑分析☆☆☆9机器人搬砖二分法☆☆☆10转盘寿司数据结构/栈/单调栈☆☆☆11小明找位置二分法☆☆☆12提取字符串的最长合法简单数学表达式双指[...]
[...]2024 C卷 100分序号题目知 识 点难易程度1密码输入检测数据结构/栈☆☆☆2分配土地几何问题☆☆☆3找座位逻辑分析☆☆☆4智能成绩表动态条件分析☆☆☆5内存冷热标记多条件排序☆☆☆6螺旋数字矩阵逻辑分析☆☆☆7围棋的气逻辑分析☆☆☆8分割平衡字符串逻辑分析☆☆☆9机器人搬砖二分法☆☆☆10转盘寿司数据结构/栈/单调栈☆☆☆11小明找位置二分法☆☆☆12提取字符串的最长合法简单数学表达式双指[...]