题目要求
给定一个有 n 个节点的有向无环图,节点编号从 1 到 n。请编写一个函数,找出并返回所有从节点 1 到节点 n 的路径。每条路径应以节点编号的列表形式表示。
输入描述
第一行包含两个整数 N,M,表示图中拥有 N 个节点,M 条边
后续 M 行,每行包含两个整数 s 和 t,表示图中的 s 节点与 t 节点中有一条路径
输出描述
输出所有的可达路径,路径中所有节点之间空格隔开,每条路径独占一行,存在多条路径,路径输出的顺序可任意。如果不存在任何一条路径,则输出 -1。
注意输出的序列中,最后一个节点后面没有空格! 例如正确的答案是 `1 3 5`,而不是 `1 3 5 `, 5后面没有空格!
输入示例
5 5
1 3
3 5
1 2
2 4
4 5
输出示例
1 3 5
1 2 4 5
思路
首先要知道图应该怎么存储,然后是通过搜索的方法找出所有的边。
图的存储
邻接矩阵
本题有n个节点,节点标号从1开始,为了对其标号和下标,我们申请n+1*n+1这么大的二维数组。
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
输入m个边,构造方式如下:
while(m--) {
cin >> s >> t;
// 使用邻接矩阵,1表示节点s指向节点t
graph[s][t] = 1;
}
邻接表
采用数组+链表的形式来表示。
vector<list<int>> graph(n + 1); // 邻接表,list为C++里的链表,初始时没有申请空间,随用随取
// 输入m个边
while(m--) {
cin >> s >> t;
// 使用邻接表,表示s —> t是相连的
graph[s].push_back(t);
}
深度优先搜索
深搜三部曲:1.确认递归函数、参数, 2.确认终止条件,3.处理目前搜索节点出发的路径
1. 确认递归函数、参数
首先dfs函数一定要存一个图,用来遍历,需要存一个目前我们遍历的节点,定义为x。
还需要存储终点位置n,当x==n时表示到达了终点。
至于 单一路径 和 路径集合(答案)可以放在全局变量。
vector<vector<int>> result; // 收集符合条件的路径
vector<int> path; // 0节点到终点的路径
// x:目前遍历的节点
// graph:存当前的图
// n:终点
void dfs (const vector<vector<int>>& graph, int x, int n) {
2. 确认终止条件
当目前遍历的节点为最后一个节点n的时候,就找到了一条从出发点到终止点的路径。
// 当前遍历的节点x,到达节点n
if (x == n) { // 找到符合条件的一条路径
result.push_back(path);
return;
}
3. 处理目前搜索节点出发的路径
接下来时走当前遍历节点x的下一个节点。
// 找到节点x指向哪些节点
for (int i = 1; i <= n; ++i) { // 遍历节点x链接的所有节点
if (graph[x][i] == 1) { // 找到x指向的节点就是i
// 将x所指向的节点加入到单一路径中
path.push_back(i);
// 进入下一层递归
dfs(graph, i, n);
// 回溯过程,撤销本次添加节点的操作
path.pop_back();
}
}
打印结果
注意结尾没有空格
// 输出结果
if (result.size() == 0) cout << -1 << endl;
for (const vector<int> &pa : result) {
for (int i = 0; i < pa.size() - 1; ++i) { // 打印到倒数第二个
cout << pa[i] << " ";
}
cout << pa[pa.size() - 1] << endl;
}
本题代码
C++邻接矩阵写法
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> result;
vector<int> path;
void dfs(const vector<vector<int>>& graph, int x, int n) {
if (x == n) {
result.push_back(path);
return;
}
for (int i = 1; i <= n; ++i) {
if (graph[x][i] == 1) {
path.push_back(i);
dfs(graph, i, n);
path.pop_back();
}
}
}
int main() {
int n, m, s, t;
cin >> n >> m;
vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i) {
cin >> s >> t;
graph[s][t] = 1;
}
path.push_back(1); // 注意:无论什么路径都是从节点0(1)出发
dfs(graph, 1, n);
if (result.size() == 0) cout << -1 << endl;
for (const vector<int> &pa : result) {
for (int i = 0; i < pa.size() - 1; ++i) {
cout << pa[i] << " ";
}
cout << pa[pa.size() - 1] << endl;
}
}
C++邻接表写法
#include <iostream>
#include <vector>
#include <list>
using namespace std;
vector<vector<int>> result;
vector<int> path;
void dfs(const vector<list<int>>& graph, int x, int n) {
if (x == n) {
result.push_back(path);
return;
}
for (int i : graph[x]) {
path.push_back(i);
dfs(graph, i, n);
path.pop_back();
}
}
int main() {
int n, m, s, t;
cin >> n >> m;
vector<list<int>> graph(n + 1);
while (m--) {
cin >> s >> t;
graph[s].push_back(t);
}
path.push_back(1); // 注意:无论什么路径都是从节点0(1)出发
dfs(graph, 1, n);
if (result.size() == 0) cout << -1 << endl;
for (const vector<int> &pa : result) {
for (int i = 0; i < pa.size() - 1; ++i) {
cout << pa[i] << " ";
}
cout << pa[pa.size() - 1] << endl;
}
}
Python邻接矩阵写法
def dfs(graph, x, n, path, result):
if x == n:
result.append(path.copy())
return
for i in range(1, n+1):
if graph[x][i] == 1:
path.append(i)
dfs(graph, i, n, path, result)
path.pop()
def main():
n, m = map(int, input().split())
graph = [[0] * (n+1) for _ in range(n+1)]
for _ in range(m):
s, t = map(int, input().split())
graph[s][t] = 1
result = []
dfs(graph, 1, n, [1], result)
if not result:
print(-1)
else:
for path in result:
print(' '.join(map(str, path)))
if __name__ == "__main__":
main()
Python邻接表写法(注意collections.defaultdict的使用)
from collections import defaultdict
result = []
path = []
def dfs(graph, x, n, path, result):
if x == n:
result.append(path.copy())
return
for i in graph[x]:
path.append(i)
dfs(graph, i, n, path, result)
path.pop()
def main():
n, m = map(int, input().split())
graph = defaultdict(list)
for _ in range(m):
s, t = map(int, input().split())
graph[s].append(t)
result = []
dfs(graph, 1, n, [1], result)
if not result:
print(-1)
else:
for pa in result:
print(' '.join(map(str, pa)))
if __name__ == "__main__":
main()
Leetcode 797. All Paths From Source to Target
题目也是一道有向无环的图的求所有起点到终点的路径问题,区别在于不需要调整输入输出。(题目中的图已经给出了邻接表的存储形式)
C++
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
void dfs(const vector<vector<int>>& graph, int x, int n) {
if (x == n) {
result.push_back(path);
return;
}
for (int i : graph[x]) {
path.push_back(i);
dfs(graph, i, n);
path.pop_back();
}
}
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
path.push_back(0);
dfs(graph, 0, graph.size() - 1);
return result;
}
};
Python (注意列表是引用类型,后续修改会影响已经添加的路径,所以要用.copy())
class Solution:
def dfs(self, graph, x, n, path, result):
if (x == n):
result.append(path.copy())
return
for i in graph[x]:
path.append(i)
self.dfs(graph, i, n, path, result)
path.pop()
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
path = []
result = []
path.append(0)
self.dfs(graph, 0, len(graph) - 1, path, result)
return result