Java 中的高级递归
我只是无法掌握递归的窍门,尤其是对于复杂的例子。如果有人花一些时间来解释它,我将非常感激。我实际上有 4 张纸,上面写满了我对这个函数的追踪,但我不知道如何将它们组合在一起。
public static String shortestPath(int x, int y, int tX, int tY,boolean blocked[][]) {
if(x>blocked.length-1 || y>blocked[0].length-1 || x<0 || y<0 )
return null;
if(blocked[x][y]==true)
return null;
if(x==tX && y==tY)
return "";
String paths[]=new String[4];
blocked[x][y]=true; //this just means this coordinate is blocked, so dont use it
paths[0]=shortestPath(x, y+1, tX, tY, blocked);
paths[1]=shortestPath(x, y-1, tX, tY, blocked);
paths[2]=shortestPath(x+1, y, tX, tY, blocked);
paths[3]=shortestPath(x-1, y, tX, tY, blocked);
blocked[x][y] = false;
int result=findShortestString(paths, 0, 3);
//findShortestString just takes an array of strings,
//with 0 being the lo index and 3 being the hi,
//and returns the index that contains the string with the shortest length.
//5
if(paths[result]==null)
return null;
else{
if(result==0)
return 'N' + paths[result];
if(result==1)
return 'S' + paths[result];
if(result==2)
return 'E' + paths[result];
if(result==3)
return 'W' + paths[result];}
return paths[result];
因此,这段代码的作用是,给定 x 和 y 参数,它会告诉您必须进行的最短移动组合(NSWE 表示北、南、西、东)才能达到 tX 和 tY 参数。该代码完美运行,但我不知道如何运行。
当我尝试跟踪 paths[0] 计算的内容时,它总是返回 null,因为 y 将始终保持递增,直到它超出范围,此时它返回 null。 paths[1][2]和[3]也是如此,它们都返回null,不是吗?那么这个功能到底是如何工作的呢?
I just can't get the hang of recursion, especially with complicated examples. I would really appreciate it if someone would take some time to explain it. I literally have 4 pieces of paper all filled with me tracing this function, but I have no idea how to put it together.
public static String shortestPath(int x, int y, int tX, int tY,boolean blocked[][]) {
if(x>blocked.length-1 || y>blocked[0].length-1 || x<0 || y<0 )
return null;
if(blocked[x][y]==true)
return null;
if(x==tX && y==tY)
return "";
String paths[]=new String[4];
blocked[x][y]=true; //this just means this coordinate is blocked, so dont use it
paths[0]=shortestPath(x, y+1, tX, tY, blocked);
paths[1]=shortestPath(x, y-1, tX, tY, blocked);
paths[2]=shortestPath(x+1, y, tX, tY, blocked);
paths[3]=shortestPath(x-1, y, tX, tY, blocked);
blocked[x][y] = false;
int result=findShortestString(paths, 0, 3);
//findShortestString just takes an array of strings,
//with 0 being the lo index and 3 being the hi,
//and returns the index that contains the string with the shortest length.
//5
if(paths[result]==null)
return null;
else{
if(result==0)
return 'N' + paths[result];
if(result==1)
return 'S' + paths[result];
if(result==2)
return 'E' + paths[result];
if(result==3)
return 'W' + paths[result];}
return paths[result];
So what this code does is, given an x and y parameter, it tells you the shortest combination of moves you would have to make (NSWE for north, south, west, east) in order to reach the tX and tY parameters. The code works perfectly, but I have no idea how.
When I try to trace what paths[0] computes, it always comes out to null, because y will always keep incrementing until it goes out of bounds, in which it returns null. This is the same case for paths[1] [2] and [3], they all return to null, don't they? So then how the heck is this function working?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
首先尝试一个非常小的例子 - 一个没有阻塞单元的 1x1 网格。正如预期的那样,没有采取任何行动。
x == tX
和y == tY
因此您返回空字符串。到目前为止还不错。现在看一个 2x2 网格,您位于西北角,目的地是东北角。
当您尝试向东走并设置
paths[0]
时,它会调用shortestPath
,封锁您当前的单元格并将您的新位置设置为您下方的单元格。现在,在该调用中,它将尝试向 N、W、S 和 E 方向移动。为简单起见,请忽略向东发生在向西之前的情况,因此我们可以立即完成其他 3 个方向 - 它们都会再次调用
ShortestPath
位于无效位置(2 个超出范围,1 个您去过)并立即返回null
。您将向东行驶,并获得一个新的网格和位置,如下所示:同样,其中 3 个方向返回
null
。只有北方才能给你你想要的最终结果。当您尝试去那里时,您再次调用shortestPath
,它立即返回空字符串,因为板现在看起来像这样:现在我们要结束调用堆栈:
paths[1]
是空字符串,其他 3 个是null
,result
是 1(我假设这就是你的字符串函数的工作原理)。因此,您将"N"
返回到上一个调用。paths[0] == null
、paths[1] == null
、paths[3] == null
code> 但关键的是paths[2]
是"N"
。因此结果
将为2,并且您将向之前的调用返回"EN"
。现在您将返回到
shortestPath
的第一次调用,这完成了我们所做的第一个选择 - 从起始位置向南。但我们还有1个选择——向东走。所以你顺着那棵树出来,它就是""
。然后是最后一步,您可以看到哪个字符串较小,并发现
""
当然小于"EN"
。因此result
为 2,因此您在字符串中添加"E"
前缀,"E"
就是您的最终答案。现在用它来找出更大的例子。它有助于绘制决策树和每个节点的棋盘状态。当到达叶子时,绘制返回代表返回值的父节点的箭头。这将有很大帮助。
First try it with a trivially small example - a 1x1 grid with no blocked cells. As expected, there are no moves to be made.
x == tX
andy == tY
so you return empty string. Good so far.Now look at a 2x2 grid where you are in the NW corner and the destination is NE.
When you try to go east and set
paths[0]
it invokesshortestPath
, blocking off your current cell and setting your new location to the one below you. Now you haveIn that invocation, it's going to try to go N, W, S and E. Ignore for simplicity that going east happens before west, so we can finish up the other 3 directions right away - they all invoke again
shortestPath
on an invalid location (2 out of bounds, 1 you've been to) and returnnull
immediately. You are left going east with a new grid and location like this:Again, 3 of the directions return
null
. Only north will give you the end result you want. When you try to go there, you once again invokeshortestPath
which immediately returns empty string because the board now looks like this:Now we get to wrap up the call stack:
paths[1]
was empty string and the other 3 werenull
,result
is 1 (I assume that's how your string function works). So you return"N"
to the previous call.paths[0] == null
,paths[1] == null
,paths[3] == null
but criticallypaths[2]
is"N"
. Thereforeresult
will be 2, and you will return"EN"
to the earlier call.Since now you are returning to the very first invocation of
shortestPath
, that wraps up the first choice we made - going south from the start position. But we also had 1 more choice - go east. So you follow that tree out and it is simply""
.Then comes the final step, where you see which string is smaller and get that
""
is of course smaller than"EN"
. Soresult
is 2, and therefore you prefix the string with"E"
, and"E"
is your final answer.Now use that to figure out the larger examples. It helps to draw a decision tree and the state of the board at each node. And as you get to the leaves, draw arrows going back up to the parent node representing return values. That will help immensely.
尝试猜测您在想什么 -
您可能会想象 4 个执行路径:
路径 0:最短路径(x, y+1, tX, tY, 阻塞) -> ShortestPath(x, y+1, tX, tY, 阻塞) ->...
路径 1: ShortestPath(x, y-1, tX, tY, 阻塞) -> ShortestPath(x, y-1, tX, tY, 阻塞) ->...
路径 2: ShortestPath(x+1, y, tX, tY, 阻塞) -> ShortestPath(x+1, y, tX, tY, 阻塞) ->...
路径 3: ShortestPath(x-1, y, tX, tY, 阻塞) -> ShortestPath(x-1, y, tX, tY, Blocked) ->...
实际上,执行路径构成一棵树。每次对 ShortestPath 的调用都会再产生 4 次对 ShortestPath 的调用,即“path0”调用、“path1”调用、“path2”调用和“path3”调用。
因此,您将得到一个执行路径:path0、path0、path0...,该路径将返回 null。
但是,大多数路径将是不同调用的组合。
当递归返回到第一个shortestPath调用时,paths[0]将包含第一个移动为shortestPath(x,y+1,tX,tY,blocked)的最短路径,path[1]将包含第一个移动为shortestPath的最短路径(x、y-1、tX、tY、阻止)等。
Trying to guess what you're thinking -
You might be picturing 4 execution paths:
path 0: shortestPath(x, y+1, tX, tY, blocked) -> shortestPath(x, y+1, tX, tY, blocked) ->...
path 1: shortestPath(x, y-1, tX, tY, blocked) -> shortestPath(x, y-1, tX, tY, blocked) ->...
path 2: shortestPath(x+1, y, tX, tY, blocked) -> shortestPath(x+1, y, tX, tY, blocked) ->...
path 3: shortestPath(x-1, y, tX, tY, blocked) -> shortestPath(x-1, y, tX, tY, blocked) ->...
In reality, the execution paths make a tree. Each call to shortestPath spawns 4 more calls to shortestPath, a "path0" call, a "path1" call, a "path2" call, and a "path3" call.
So, you will get one execution path that is path0, path0, path0... that will return null.
But, most of the paths will be a combination of different calls.
When the recursion returns to the first shortestPath call, paths[0] will contain the shortest path whose FIRST move was shortestPath(x, y+1, tX, tY, blocked), path[1] the shortest path whose FIRST move was shortestPath(x, y-1, tX, tY, blocked), etc.
事情没那么复杂。
这部分检查 x 或 y 参数是否有效(在边界内或未阻塞)
这里检查位置是否到达目的地
现在到递归部分,该函数递归到其他四个函数,每个函数对应一个可用的 NSWE 方向相对值到当前位置:
然后比较递归函数找到的每条路线,以找到可用的最短路径/方向串。
如果每个字符串都为 null,则 findShortestString() 可能会返回 null,因此无法从该递归的起始位置到达目的地。
递归的当前位置被标记为阻塞,因此算法不会返回到之前访问过的位置。
这会检查 findShortestString 是否未找到任何有效路径。
最后,相对于递归中当前位置找到的路径将附加到找到最短路径的递归调用的方向。
例子:
假设一张地图只有一条到达目的地的有效路径,所有其他位置都被封锁。起始位置为[0][0],目的地为[1][1](x+1 = N, y+1 = E)
MAP:
第一次调用:
RECURSION:(
回到第一次迭代)
由于 paths[2] 具有最短的字符串,因此结果是 'E'+'N'。这是正确的。
由于 y = y+1 总是首先被调用,所以递归会一直运行直到超出边界。然后它将测试最后一个位置周围的其他位置,依此类推。
It's not that complicated.
This part checked if x or y parameters are valid(either in boundary or not blocked)
Here it is checked if the position arrived at the destination
Now to the recursive part, this function recurses into four other functions, each for an available NSWE direction relative to the current position:
Each of the route found by the recursed functions is then compared to find the shortest path/string of directions available.
findShortestString() probably returns null if every string is null, so the destination can not be reached from the originating position of that recursion.
The current position of the recursion is marked as blocked so the algorithm does not go back to a position visited before.
This checks if findShortestString did not find any valid path.
At the end the path found relative to the current position in the recursion is appended to the direction of the recursed call which found the shortest path.
Example:
Lets say that a map has only one valid path to a destination, all other positions are bocked. The starting position is [0][0] and the destination is [1][1](x+1 = N, y+1 = E)
MAP:
First call:
RECURSION:
(back to the first iteration)
Since paths[2] has the shortest string, the result is 'E'+'N'. Which is correct.
Since the y = y+1 is always called first, the recursion runs until it goes out of boundary. Then it will test the other positions around the last position and so forth.