图论基础 :: labuladong的算法小抄 #986
Replies: 32 comments 6 replies
-
这句话:注意 Java 的语言特性,向 res 中添加 path 时需要拷贝一个新的列表,否则最终 res 中的列表都是空的。 |
Beta Was this translation helpful? Give feedback.
-
已从回溯的公众号知道了答案!! |
Beta Was this translation helpful? Give feedback.
-
@moustacheyummy 是的,经典的传值和传引用的问题。 |
Beta Was this translation helpful? Give feedback.
-
小菜鸡放一下自己照着写的c++版本 class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites)
{
buildGraph(numCourses,prerequisites);
visited.resize(numCourses,0);
onpath.resize(numCourses,0);
for(int i=0;i<numCourses;i++)
traverse(i);
return !hasCycle;
}
void traverse(int i)
// 这个地方不能用参数传递
// 该函数会被多次递归调用,如果采用参数传递的话,每次值传递时的拷贝时间会过长
// 最终会报超时错误
// 出错原因:当时参照的是labuladong的java代码,
// java中 当参数类型为基本类型时,传递值进去。当参数为对象时,传递对象的引用地址值进去。
{
if(onpath[i])
hasCycle=true;
if(visited[i]||hasCycle)
return;
visited[i]=true;
onpath[i]=true;
for(int t:graph[i])
traverse(t);
onpath[i]=false;
}
void buildGraph(int numCourses, vector<vector<int>>& prerequisites)
{
graph.resize(numCourses);
for(auto& edge:prerequisites)
{
int from=edge[1],to=edge[0];
graph[from].push_back(to);
}
}
private:
vector<vector<int>> graph;
vector<bool> visited{0};
vector<bool> onpath{0};
bool hasCycle=0;
}; |
Beta Was this translation helpful? Give feedback.
-
啊发错位置了,上面对应-->207 课程表 |
Beta Was this translation helpful? Give feedback.
-
leetcode那道题,图遍历函数那个结束条件为什么是n-1结束呢??有的图节点并不会指向n-1, 有点没想明白。 然后结束的if操作中, 为什么也要remove一次呢?? 好抽象,二叉树遍历root == null结束比较形象,这里有点迷糊。。 |
Beta Was this translation helpful? Give feedback.
-
@Jackwong0716 你好,我的理解是这样的,希望可以帮到你。1.traverse()函数返回值是void,所以不存在什么结束条件哦,这个if(s==n-1)判断的真正目的是当遍历到目的结点n-1时,将整个路径加到res中。2.没错,有的图节点不会指向n-1,这个时候就是for循环执行完,再执行函数最后一行的path.removeLast()就退出以该节点(此处该节点就是指,那些没指向n-1的图节点)的traverse函数,路径里面不需要该节点,因为他们无法到达n-1。3.如果if(s==n-1)判断里不path.removeLast(),即不把n-1节点从当前路径中删除,那么返回上层节点的时候,路径的末尾仍带着n-1节点,那么接下来新构造的路径就会出错,因为这是个递归的过程。 |
Beta Was this translation helpful? Give feedback.
-
@Jackwong0716 我的理解是,因为题中让你找到n - 1 的路径,其实不加 path.remove和 return也可以,因为n - 1没有后继节点了,n -1 这个点,没有for循环的选择了, 对于n -1 来说不会进入递归。 而这种写法是可以处理target还有后序节点的,直接跳出,不需要再次进入递归加入不相干的后继节点。 然后回溯是为了,如果有另一条路径能到达n - 1,比如1有两条路径能到达4, 1 - > 2 - > 4, 回溯 1 - > 2 - > 3 - > 4,这样进行回溯,就可以把两条路径都找出来。 |
Beta Was this translation helpful? Give feedback.
-
@lee666-lee @KaihuaS 你们理解的没问题,这道题给定的图中不存在环,所以省去很多麻烦。如果在找到目标节点时 return,则需要正确维护 |
Beta Was this translation helpful? Give feedback.
-
感觉onPath 在遍历中没起作用啊,有人可以给个解释码 |
Beta Was this translation helpful? Give feedback.
-
@https://github.com/Yuanheng-glitch 记录经过的路径 |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
内存超出了,有大佬帮我看看为什么吗? class Solution {
public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
List<Integer> sup = new ArrayList<>();
sup.add(0);
getPath(0,graph,sup);
return res;
}
List<List<Integer>> res=new ArrayList<>();
public void getPath(int point,int[][] graph,List<Integer> sup){
boolean hasRount=false;
for (int i = 0; i < graph[point].length; i++) {
if(graph[point][i]!=0){
hasRount=true;
List<Integer> temp=new ArrayList<>(sup);
temp.add(i);
getPath(i,graph,temp);
}
}
if(!hasRount){
res.add(sup);
}
}
} |
Beta Was this translation helpful? Give feedback.
-
老师写的太好了 看了将近三分之二的内容 收获甚多 感谢! |
Beta Was this translation helpful? Give feedback.
-
打卡,感谢楼主! |
Beta Was this translation helpful? Give feedback.
-
谢谢,有了干迪杰斯特拉算法的信心 |
Beta Was this translation helpful? Give feedback.
-
C++版本 class Solution {
private:
vector<vector<int>> rvalue;
vector<int> path;
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
/*
解法一:
return AllThePath(graph,0);
解法二:
vector<int> path;
DFS(graph,path,0);
return rvalue;
*/
//解法三:
DFS2(graph,0);
return rvalue;
}
//递归解法:定义一个函数,可以返回从VertexStart→n-1的所有可能路径(递归解法)
vector<vector<int>> AllThePath(vector<vector<int>>& graph,int VextexStart){
vector<vector<int>> result;
if(VextexStart==graph.size()-1){
vector<vector<int>> res;
vector<int> k(1,VextexStart);
res.push_back(k);
return res;
}
for(int i=0;i<graph[VextexStart].size();i++){
vector<vector<int>> re=AllThePath(graph,graph[VextexStart][i]);
for(int i=0;i<re.size();i++){
re[i].insert(re[i].begin(),VextexStart);
result.push_back(re[i]);
}
}
return result;
}
//遍历解法---用形参path记录当前路径,空间复杂度较高
void DFS1(vector<vector<int>>& graph,vector<int> path,int s){
path.push_back(s);
if(s==graph.size()-1) rvalue.push_back(path);
for(int v:graph[s]){
DFS1(graph,path,v);
}
}
//遍历解法------不用形参记录路径,直接用全局变量path,记录当前路径,需要正确的进行维护
void DFS2(vector<vector<int>>& graph,int s){
path.push_back(s);
if(s==graph.size()-1) rvalue.push_back(path);
for(int v:graph[s]){
DFS2(graph,v);
}
path.pop_back();
}
}; |
Beta Was this translation helpful? Give feedback.
-
发现弹栈的时机有两种,本文和力扣官方解答分别用到了这两种。 class Solution:
def dfs(self, node, graph, path, endnote):
path += [node]
if path and path[-1] == endnote:
self.ans.append(path[:])
return
for nodeNext in graph[node]:
self.dfs(nodeNext, graph, path, endnote)
path.pop()
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
path = list()
self.ans = list()
self.dfs(0,graph,path, len(graph)-1)
return self.ans 被调用者自己弹栈: class Solution:
def dfs(self, node, graph, path, endnote):
path += [node]
if path and path[-1] == endnote:
self.ans.append(path[:])
path.pop()
return
for nodeNext in graph[node]:
self.dfs(nodeNext, graph, path, endnote)
path.pop()
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
path = list()
self.ans = list()
self.dfs(0,graph,path, len(graph)-1)
return self.ans |
Beta Was this translation helpful? Give feedback.
-
C++ class Solution {
public:
int n;
vector<vector<int>> paths;
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
n = graph.size();
vector<int> path;
traverse(graph, 0, path);
return paths;
}
void traverse(vector<vector<int>>& graph, int s, vector<int>& path) {
path.push_back(s);
if(s == n-1) {
paths.push_back(path);
path.erase(path.end()-1);
return;
}
for(auto node: graph[s]) {
traverse(graph, node, path);
}
path.erase(path.end()-1);
}
}; |
Beta Was this translation helpful? Give feedback.
-
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
int n = 0;
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
n = graph.size();
//用DFS还是回溯都是可以的!
//backtrack(graph, 0);
traverse(graph, 0);
return res;
}
void backtrack(vector<vector<int>>& graph, int s){
if(s == n - 1){
path.push_back(s);
res.push_back(path);
path.pop_back();
return;
}
for (int v : graph[s]) {
path.push_back(s);
backtrack(graph, v);
path.pop_back();
}
}
void traverse(vector<vector<int>>& graph, int s){
path.push_back(s);
if(s == n - 1){
res.push_back(path);
path.pop_back();
return;
}
for (int v : graph[s]) {
traverse(graph, v);
}
path.pop_back();
}
}; |
Beta Was this translation helpful? Give feedback.
-
东哥, 另外onPath会回撤,从而可以判断有无环。visited并没有回撤啊。请指教。谢谢 |
Beta Was this translation helpful? Give feedback.
-
@xiaopeiyi0704 我和你想的一样, 如果说图是有环的情况下,就只需要定义一个onepath数组,因为onepath里边只是代表这一条路径,不要visited数组,因为要visited的话,最后得到的路径是不全的,如果我说的有错 请纠正我 |
Beta Was this translation helpful? Give feedback.
-
@Yyhan-1998 我上面的留言只想想针对东哥那句话产生了歧义。我现在理解东哥想表达的有没有环并不是指当前路径上的环,而是说整幅图的。整幅图上的环会导致做重复的事,一条路径上有没有环是为了得到答案。(假设问题是问有没有环) 。换句话说如果一道题求的不是问有没有环,而就是让你遍历图从而求得一些其他的值,他们如果整幅图有环,重复遍历不就stackoverflow了。 另外到底需不需要visited?- 因题而异。你提到“因为要visited的话,最后得到的路径是不全的” 那有没有情况可以不用visited也能提交成功的题? 一定有。我记得我之前做过一道题有没有visited对最后效率影响不大。但我发现其实还是有重复的,只是非常少量的重复不太影响结果。但是面试的时候还是加上好。 我也刚刚开始学习刷题,所说的都是个人理解,甚至都不一定对。如有错误,请指出,并多包涵。请多指教。 |
Beta Was this translation helpful? Give feedback.
-
@labuladong 感谢回答,我有个问题, 比如力扣中的示例2,我的想法是,无论有环没环,加上visited数组都会导致结果不全。 |
Beta Was this translation helpful? Give feedback.
-
克隆图 class Solution:
def cloneGraph(self, node: 'Node') -> 'Node':
visited = {}
def traverse(node):
if not node:
return
if node in visited:
return
cloned = Node(node.val)
visited[node] = cloned
for neighbor in node.neighbors:
traverse(neighbor)
cloned.neighbors.append(visited[neighbor])
return cloned
return traverse(node) |
Beta Was this translation helpful? Give feedback.
-
打卡 func allPathsSourceTarget(graph [][]int) [][]int {
var res [][]int
var traverse func(onPath []int, i int)
traverse = func(onPath []int, i int) {
n := len(graph)
onPath = append(onPath, i)
if i == n-1 {
res = append(res, append([]int{}, onPath...))
//这里因为是有向无环图,不会出现无限递归,无需return
//onPath = onPath[:len(onPath)-1]
//return
}
for _, j := range graph[i] {
traverse(onPath, j)
}
}
traverse([]int{}, 0)
return res
} |
Beta Was this translation helpful? Give feedback.
-
为什么在这道题中 path.addLast(s) 这个做选择的动作被放在判断结束的条件if (s == n - 1)之前, 而非放在递归每个相邻节点的前一行。我这么理解是因为在课程表里,OnPath这个做选择的动作是放在判断结束条件之后的。有点想不清楚。 |
Beta Was this translation helpful? Give feedback.
-
捉个虫,“他们的区别可以这样反应到代码上”中的“反应”可改为“反映” |
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:图论基础
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions