Java递归实现二维数组路径查找问题求助
递归路径查找问题的解决思路
嘿,我来帮你捋捋这个递归路径查找的问题~你的核心困扰主要在递归逻辑混乱、不必要的while循环,以及递归终止的处理上,咱们一步步拆解:
1. 递归里完全不需要while循环!
这是你当前代码最核心的问题之一。递归本身就是通过函数调用栈实现深度优先遍历+回溯的,while循环会让当前节点反复检查四个方向,不仅打乱递归的自然流程,还可能导致死循环或者重复处理,完全是画蛇添足。
2. 递归的终止条件要明确
递归的终止逻辑其实很清晰,分两种情况:
- 成功终止:当前节点就是目标终点,直接返回
true,同时把当前节点加入路径 - 失败终止:当前节点超出数组边界、是陷阱、已经被访问过,直接返回
false
3. 现有代码的几个关键bug
先帮你指出几个明显的错误,这些也是导致你逻辑混乱的原因:
- 边界判断错误:比如
(i-1)>0应该是(i-1)>=0(不然第一行的节点无法向上移动),(j-1)>10完全写错了,应该是(j-1)>=0 - 访问标记时机不对:应该在进入当前节点时就标记为
visited,而不是在递归调用之后 - 路径收集顺序错误:递归是深度优先,找到终点后会从终点往回收集节点,最后需要反转路径才能得到从起点到终点的顺序
- 查找节点坐标的效率低:每次递归都遍历整个数组找
start的位置,不如直接把坐标(row, col)作为递归参数传入,既高效又避免了equals判断的潜在问题(你的Field类没重写equals,默认是引用比较,虽然当前场景可能没问题,但传坐标更直接)
4. 重构后的递归思路示例
给你一个简化的、可运行的递归实现思路,核心是用坐标代替Field对象作为参数:
public class Gitter { Field[][] gitter = new Field[10][10]; List<Field> path = new ArrayList<Field>(); // 初始化二维数组 public Gitter() { for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { gitter[i][j] = new Field(); } } } // 修改getStartAndGoal,返回起点和终点的坐标(这里用数组返回四个int:startRow, startCol, goalRow, goalCol) public int[] getStartAndGoalCoords() { boolean notFound = true; Random rn = new Random(); // 只创建一个Random实例更高效 int[] coords = new int[4]; while (notFound) { int row0 = rn.nextInt(10); int line0 = rn.nextInt(10); int row1 = rn.nextInt(10); int line1 = rn.nextInt(10); int distance = Math.abs(row1 - row0) + Math.abs(line1 - line0); if (distance > 2 && !gitter[row0][line0].isTrap() && !gitter[row1][line1].isTrap()) { coords[0] = row0; coords[1] = line0; coords[2] = row1; coords[3] = line1; notFound = false; } } return coords; } // 递归查找路径的核心方法,参数是当前坐标和目标坐标 public boolean findPath(int currentRow, int currentCol, int goalRow, int goalCol) { // 失败终止条件:越界、是陷阱、已访问 if (currentRow < 0 || currentRow >= 10 || currentCol < 0 || currentCol >= 10 || gitter[currentRow][currentCol].isTrap() || gitter[currentRow][currentCol].visited) { return false; } // 成功终止条件:到达终点 if (currentRow == goalRow && currentCol == goalCol) { path.add(gitter[currentRow][currentCol]); return true; } // 标记当前节点为已访问,避免重复走 gitter[currentRow][currentCol].visited = true; // 尝试四个方向:下、上、右、左(顺序不影响,只要覆盖四个方向即可) if (findPath(currentRow + 1, currentCol, goalRow, goalCol) || findPath(currentRow - 1, currentCol, goalRow, goalCol) || findPath(currentRow, currentCol + 1, goalRow, goalCol) || findPath(currentRow, currentCol - 1, goalRow, goalCol)) { // 如果某个方向找到了路径,把当前节点加入路径(回溯时收集) path.add(gitter[currentRow][currentCol]); return true; } // 可选:如果需要多次查找路径,这里要取消标记(回溯),否则可以省略 // gitter[currentRow][currentCol].visited = false; return false; } // 测试用的方法 public static void main(String[] args) { Gitter g = new Gitter(); int[] coords = g.getStartAndGoalCoords(); boolean found = g.findPath(coords[0], coords[1], coords[2], coords[3]); if (found) { // 因为路径是从终点到起点收集的,反转后得到起点到终点的顺序 Collections.reverse(g.path); System.out.println("找到路径啦!"); for (Field f : g.path) { System.out.print(f.number + " -> "); } } else { System.out.println("没有找到可行路径"); } } } // 你的Field类可以保留,建议重写equals和hashCode class Field { int number; boolean visited; Random rn = new Random(); // 可以把Random实例提升为成员变量,避免每次创建 Field() { this.number = rn.nextInt(900) + 100; // 简化写法:100-999的随机数 this.visited = false; } boolean isTrap() { String str = String.valueOf(number); return str.contains("2") || str.contains("3") || str.contains("5") || str.contains("7"); } // 可选:重写equals和hashCode @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Field field = (Field) o; return number == field.number; } @Override public int hashCode() { return Objects.hash(number); } }
5. 关键逻辑说明
- 递归流程:每次进入一个节点,先判断是否符合终止条件,符合就返回;否则标记为已访问,尝试四个方向的递归调用。如果某个方向返回
true,说明该方向能到终点,就把当前节点加入路径,再返回true给上一层。 - 路径反转:因为递归是从终点往回收集节点,所以最后需要用
Collections.reverse(path)来得到从起点到终点的正确顺序。 - 回溯标记:如果需要多次查找不同路径,记得在四个方向都失败后,把当前节点的
visited改回false,这样下次查找时还能访问这个节点。
6. 其他小优化
- Field类里的Random可以提升为成员变量,不用每次创建新实例,更高效
getStartAndGoal里的Random也只需要创建一个- 边界判断统一写成
currentRow >=0 && currentRow <10,更清晰易懂
按照这个思路调整后,你的递归逻辑会变得清晰很多,也能正确终止递归并收集路径啦~
内容的提问来源于stack exchange,提问作者jo87casi
相关产品推荐
相关产品推荐

