算法源码地点:https://github.com/wudashan/longest-path-problem
媒介
最近产物线举行了一个软件编程大赛,题目很是的有趣,就是在一个9 × 9的格子里,你要和另一个仇人PK,在PK的进程中,你可以吃格子里的果实来晋升进攻力。每次可以往正上、正下、正左、正右、左上、左下、右上、右下八个偏向走。每次要么持续吃果实要么持续走空缺区域,且不能走反复的位置。初始状态如下图所示:

为了晋升进攻力,我们需要尽大概地一次吃最多的果实,所以蹊径可以这样筹划:

至此,我们可以对这个问题举办描写:已知空缺区域不能走,每次可以往正上、正下、正左、正右、左上、左下、右上、右下八个偏向走,走过的位置不能再走,求能吃最多果实的蹊径(最长路径问题)?
前置条件
舆图暗示
首先我们将上面的舆图利用布尔范例的二维数组暗示,个中true暗示可以行走的格子,false暗示不能行走的格子:
boolean[][] simpleMap = new boolean[][] {
{false, false, false, false, false, false, false, false, false},
{false, false, false, false, false, false, true , true , false},
{false, false, false, true , false, false, true , true , false},
{false, false, true , false, false, false, false, false, false},
{false, false, true , false, false, false, false, false, false},
{false, false, true , false, false, false, false, false, false},
{false, false, false, true , false, true , false, false, false},
{false, false, false, false, true , true , false, false, false},
{false, false, false, false, false, false, false, false, false}
};
格子暗示
对付舆图上的每一个格子,我们用一个简朴类来暗示:
public class Pos {
private int x; // 横坐标
private int y; // 纵坐标
// get、set、construct要领省略
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Pos{");
sb.append("x=").append(x);
sb.append(", y=").append(y);
sb.append('}');
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pos pos = (Pos) o;
if (x != pos.x) return false;
return y == pos.y;
}
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
由于我们是利用横纵坐标而不是几行几列来暗示一个格子(没错,我就是这么傲娇),那么我们就需要给舆图界说横纵坐标偏向。偏向如下图所示:

那么起点上方的果实坐标就是[3, 2](横坐标为3,纵坐标为2),可是对应着二维数组为map[2][3](第二行,第三列),软件开发,即横坐标对应着二维数组的列,纵坐标对应着二维数组的行。
移动暗示
为了措施简捷,我们给八个偏向的移动界说对应的偏移量,这样每次行走只要对偏移量数组举办for轮回就可以了。
Pos[] moveOffset = new Pos[] {
new Pos(-1, 0), // 向左移动
new Pos(-1, -1), // 向左上移动
new Pos( 0, -1), // 向上移动
new Pos( 1, -1), // 向右上移动
new Pos( 1, 0), // 向右移动
new Pos( 1, 1), // 向右下移动
new Pos( 0, 1), // 向下移动
new Pos(-1, 1) // 向左下移动
};
深度优先搜索算法
算法思想
拿到这道题,脑壳里第一个想到的就是深度优先搜索算法,其思想为每次往八个偏向递归,当不能继承走下去的时候生存最长路径,软件开发,并回退到能继承行走的格子,继承递归直到竣事。
代码示例
接下来就是我们的深度优先搜索代码:
/**
* 通过深度优先搜索算法获取最长路径
* @param map 舆图
* @param start 起点
* @param moveOffset 移动偏移量
* @return 最长路径
*/
public static List<Pos> getLongestPathByDFS(boolean[][] map, Pos start, Pos[] moveOffset) {
List<Pos> longestPath = new ArrayList<>();
dfs(start, map, new ArrayList<>(), longestPath, moveOffset);
return longestPath;
}
/**
* 递归实现深度优先搜索
*/
private static void dfs(Pos pos, boolean[][] map, List<Pos> path, List<Pos> result, Pos[] moveOffset) {
// 记录当前位置向周围格子移动的记录
List<Pos> visited = new ArrayList<>();
// 生存当前位置的周围格子
Pos[] neighbours = new Pos[moveOffset.length];
// 依次向周围移动
for (int i = 0; i < moveOffset.length; i++) {
Pos next = new Pos(pos.getX() + moveOffset[i].getX(), pos.getY() + moveOffset[i].getY());
neighbours[i] = next;
if (inMap(map, next) && !path.contains(next) && map[next.getY()][next.getX()]) {
path.add(next);
visited.add(next);
dfs(next, map, path, result, moveOffset);
}
}
// 若在当前位置下,没有向周围的格子移动过期,生存最长路径
if (visited.isEmpty()) {
if (path.size() > result.size()) {
result.clear();
result.addAll(path);
}
}
// 周围的格子都不行以移动时回退到上一格子
for (Pos neighbour : neighbours) {
if (canPath(map, path, neighbour, visited)) {
return;
}
}
path.remove(pos);
}
/**
* 判定格子是否可以移动
*/
private static boolean canPath(boolean[][] map, List<Pos> path, Pos pos, List<Pos> visited) {
// 不在舆图里,不能移动
if (!inMap(map, pos)) {
return false;
}
// 空缺格子,不能移动
if (!map[pos.getY()][pos.getX()]) {
return false;
}
// 已经在路径中或颠末,不能移动
if (path.contains(pos) || visited.contains(pos)) {
return false;
}
return true;
}
/**
* 判定格子是否在舆图内
*/
private static boolean inMap(boolean[][] map, Pos pos) {
if (pos.getY() < 0 || pos.getY() >= map.length) {
return false;
}
if (pos.getX() < 0 || pos.getX() >= map[0].length) {
return false;
}
return true;
}