原题链接:http://poj.org/problem?id=3009

这道题目中文的描述大概是有一冰球,可以前后左右击打它,不能出边、不能一开始就碰壁、不能斜着走,极大后除非遇到边才能停下来,停下来后墙壁裂开(这个地方应该是为了防止重复运动),直到遇到终点,终点是一个摩擦力巨大的位置,所以只要行进轨道中存在该终点则游戏结束,不可高于十次击打。求最少的到达终点的击打次数。

部分配图如下,这是题目给出的例图,S为起点,G为终点

                                  

 

这里讲一下我们的思路

寻找最小的路径来通往终点,首先我们定义一个地图,0为空地、1为墙壁、2为起点、3为终点,在算法开始前,先将该起点位置保存,并将该位置置为空地,getStartFlag和getEndFlag用于提前结束寻找起点与终点循环,函数isLegal用于判断现在的情况下的棋子位置是否合法。深度优先遍历的逻辑如下:结束判断为:如果现在的起点位置正好是终点位置,那么说明现在正好是找到了最终点,那么退出;而如果此时的步数answerStep大于十或大于另设的最大阈值了,那么也退出。每次对下一步进行循环的时候,都要做一件事情=>对四个方向进行遍历,所以这里我们用一个向量数组表示我们位移的方向。那么对单一方向进行位移的过程中,我们用now对象来储存当前的位置,一直循环直到碰壁或接触到终点,碰壁则退出循环,终点则结束。退出循环后当然还要进行一次判断,判断该次位移是否只“移动了一位”,这里判断只移动了一位的原因是,在题目中不能往紧贴墙的位置击打,同时我们由于碰壁退出循环的条件是当前的map[now.x][now.y] == 1,所以若只位移了一次,说明这次的位移是“撞碎紧贴墙壁”造成的位移,所以如果是这样的话,我们就要进行下一个方向的位移阶段了。那么若是不是“撞碎紧贴墙壁”造成的位移,那么这次位移我们可以认为是OK的,我们将这里的map置为0,让start位移到这里,并减去一个单位的d向量(因为其实我们现在是在碎墙上),继续下一次的查找。每次函数退出,会将刚才的地图置为1,步数减一,视为悔步。

所以我们的代码如下,使用的是JS的语法,但是用其他语言也是OK的,没有什么需要特别注意的地方,大家如果有什么不懂的地方可以在评论区询问我哦

// poj3009
let map = [
    [1, 0, 0, 2, 1, 0],
    [1, 1, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 3],
    [0, 0, 0, 0, 0, 0],
    [1, 0, 0, 0, 0, 1],
    [0, 1, 1, 1, 1, 1],
];
let width = 6;
let height = 6;

//起始
let start = {};
//目标
let end = {};
// 简化调试步骤
let getStartFlag = false;
let getEndFlag = false;
// 移动的向量
let d = [[-1, 0], [1, 0], [0, -1], [0, 1]];
let result = 11;

for (let i = 0; i < height; i++) {
    for (let j = 0; j < width; j++) {
        if (getStartFlag && getEndFlag) {
            break;
        }
        if (map[i][j] == 2) {
            start.x = i;
            start.y = j;
            map[i][j] = 0;
            getStartFlag = true
        }
        if (map[i][j] == 3) {
            end.x = i;
            end.y = j;
            getEndFlag = true
        }
    }
}

dfs(start, 0);
console.log(`${result == 11 ? '-1' : result}`);

function dfs(start, answerStep) {
    // 该函数判断该now位置是否合法
    let isLegal = now => (0 <= now.x && 0 <= now.y && now.x < height && now.y < width);
    // 结束判断
    if (start.x == end.x && start.y == end.y) {
        if (result > answerStep) {
            result = answerStep;
        }
        return;
    }
    // 结束判断
    if (answerStep == 10 || answerStep >= result) {
        return;
    }
    // 对四个方向进行循环
    for (let i = 0; i < 4; i++) {
        let now = {}
        now.x = start.x + d[i][0];
        now.y = start.y + d[i][1];
        // 对单一方向的深入
        while (isLegal(now) && map[now.x][now.y] != 1) {
            if (now.x == end.x && now.y == end.y) {
                answerStep++;
                if (result > answerStep) {
                    result = answerStep;
                }
                return;
            }
            now.x += d[i][0];
            now.y += d[i][1];
        }
        // 起步就撞石头了 => 判断在该方向上是否只位移了一段
        if ((now.x == start.x + d[i][0] && now.y == start.y + d[i][1]) || !isLegal(now)) {
            continue;
        }
        // 石头现在已经在“墙上”了,所以这里要将这个墙打碎,并让石头后退一格
        map[now.x][now.y] = 0;
        let temp = {};
        temp.x = now.x - d[i][0];
        temp.y = now.y - d[i][1];
        dfs({ x: temp.x, y: temp.y }, ++answerStep);
        answerStep--;
        map[now.x][now.y] = 1;
    }
}