推箱子问题

一 问题描述

你站在一个由方格组成的二维度迷宫中,这些格子可能被填满岩石,也可能没被填满岩石。你可以一步一个格子地往北、往南、往东或往西移动。这样的动作叫作“走”。其中一个空单元格包含一个箱子,你可以站在箱子旁边,推动箱子到相邻的自由单元格。这样的动作叫作“推”。箱子除了用推的方式,不能移动,如果你把它推到角落里,就再也不能把它从角落里拿出来了。将其中一个空单元格标记为目标单元格。你的工作是通过一系列走和推把箱子带到目标格子里。由于箱子很重,所以要尽量减少推动的次数。编写程序,计算最后的移动顺序。

二 输入和输出

1 输入

数据包含多个测试用例。每个测试用例的第 1 行都包含两个整数 r 和 c,表示迷宫的行数和列数。接下来是 r 行,每行 c 个字符,每个字符都描述迷宫中的一个格子,对被填满岩石的格子用“#”表示,对空格用“.”表示,对起始位置用“S”表示,对箱子的起始位置用“B”表示,对目标单元格用“T”表示。输入端以两个 0 终止。

2 输出

对于输入中的每个迷宫,都首先输出迷宫的编号。如果无法将箱子带到目标单元格里,则输出“Impossible”,否则输出一个最小推动次数的序列。如果多个这样的序列,则请选择一个最小总移动(走和推)次数的序列。如果仍然有多个这样的序列,则任务一个都可被接受。将序列输出为由 N、S、E、W、n、s、e 和 w 组成的字符串,大写表示推,小写表示走,祖母分别表示北、南、东和西这 4 个方向。在每个测试用例之后都输出一个空行。

三 输入和输出样例

1 输入样例

1 7

SB....T

1 7

SB..#.T

7 11

###########

#T##......#

#.#.#..####

#....B....#

#.######..#

#.....S...#

###########

8 4

....

.##.

.#..

.#..

.#.B

.##S

....

###T

0 0

2 输出样例

Maze #1

EEEEE

Maze #2

Impossible.

Maze #3

eennwwWWWWeeeeeesswwwwwwwnNN

Maze #4

swwwnnnnnneeesssSSS

四 分析

本问题是推箱子问题,要求先保证推箱子的次数最少,在此基础上再让人走的步数最少。推箱子时,人只有站在箱子反方向的前一个位置,才可以将箱子推向下一个位置。因此在移动箱子时,不仅需要判断新位置有没有岩石,还需要判断人是否可以到达反方向的前一个位置,在两者均有效时,才会让人移动。

先求解箱子到目标位置的最短路径(BFS1),在推箱子的过程中,每推一步,都根据推的方向和箱子的位置得到箱子的前一个位置,再求解人到达这个位置的最短路径(BFS2)。在 BFS1 里面嵌套 BFS2 ,属于嵌套广度优先搜索。

五 算法设计

1 定义一个标识数组 vis[][],并将其初始化为 0,标识所有位置都未被访问。

2 创建一个队列 q 维护箱子的状态,将人的初始位置 (sx,sy)、箱子的初始位置(bx,by)和初始路径(“”)入队,标记箱子的位置 vis[bx][by]=1。

3 如果队列不空,则队头 now 出队,否则返回 false。

4 从箱子的当前位置开始,向北、南、东、西这 4 个方向扩展。

得到箱子的新位置:nbx=now.bx+dir[i][0];nby=now.by+dir[i][1]。

得到箱子的前一个位置:tx=now.bx-dir[i][0];nby=now.by-dir[i][1]。

如果这两个位置有效,则执行 BFS2 搜素人到达箱子的前一个位置(tx,ty)的最短路径,并记录路径 path。如果 BFS2 搜素成功,则判断是否到达目标,如果是,则返回答案 ans=now.path+path+dpath[i];否则标记箱子的新位置被访问 vis[nbx][nby]=1,将人的新位置(now.bx,now.by)、箱子的新位置(nbx,nby)和已走过的路径(now.path+path+dpathB[i]) 入队。

5 转向步骤 3

六 代码

package com.platform.modules.alg.alglib.poj1475;

import java.util.LinkedList;
import java.util.Queue;

public class Poj1475 {
    public String output = "";

    private int N;
    private int M;
    char mp[][] = new char[25][25];
    // 人和箱子的初始位置
    private int sx;
    private int sy;
    private int bx;
    private int by;

    private final int dir[][] = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
    private final char dpathB[] = {'N', 'S', 'E', 'W'};
    private final char dpathP[] = {'n', 's', 'e', 'w'};
    private String ans;

    public String cal(String input) {
        String[] line = input.split("\n");
        String[] words = line[0].split(" ");
        N = Integer.parseInt(words[0]);
        M = Integer.parseInt(words[1]);
        for (int i = 0; i < N; i++) {
            String mapLine = line[i + 1];
            for (int j = 0; j < mapLine.length(); j++) {
                mp[i][j] = mapLine.charAt(j);
                if (mp[i][j] == 'S') {
                    sx = i;
                    sy = j;
                }

                if (mp[i][j] == 'B') {
                    bx = i;
                    by = j;
                }
            }
        }
        if (bfs())
            output += ans;
        else
            output += "Impossible";
        return output;
    }

    boolean check(int x, int y) {
        if (x < 0 || x >= N || y < 0 || y >= M) return false;
        if (mp[x][y] == '#') return false;
        return true;
    }

    boolean bfs() {
        int vis[][] = new int[25][25];
        vis[bx][by] = 1;
        Queue<node> q = new LinkedList<>(); // 创建一个普通队列(先进先出)
        q.add(new node(sx, sy, bx, by, ""));
        while (!q.isEmpty()) {
            node now = q.peek();
            q.poll();
            for (int i = 0; i < 4; i++) {
                // 箱子的新位置
                int nbx = now.bx + dir[i][0];
                int nby = now.by + dir[i][1];
                // 箱子的前一个位置,人必须能到达这个位置
                int tx = now.bx - dir[i][0];
                int ty = now.by - dir[i][1];
                String path = "";
                StringBuilder pathBuilder = new StringBuilder(path);
                if (check(nbx, nby) && check(tx, ty) && vis[nbx][nby] == 0) {
                    if (bfs2(now.px, now.py, now.bx, now.by, tx, ty, pathBuilder)) {
                        if (mp[nbx][nby] == 'T') {
                            ans = now.path + path + dpathB[i];
                            return true;
                        }
                        vis[nbx][nby] = 1;
                        q.add(new node(now.bx, now.by, nbx, nby, now.path + pathBuilder.toString() + dpathB[i]));
                    }
                }
            }
        }
        return false;
    }

    boolean bfs2(int ppx, int ppy, int bbx, int bby, int tx, int ty, StringBuilder pathBuilder) {
        // 局部标识数组,不要定义全局
        int vis[][] = new int[25][25];
        vis[ppx][ppy] = 1; // 人的位置
        vis[bbx][bby] = 1; // 箱子的位置
        Queue<person> Q = new LinkedList<>(); // 创建一个普通队列(先进先出)
        Q.add(new person(ppx, ppy, ""));
        while (!Q.isEmpty()) {
            person now = Q.peek();
            Q.poll();
            if (now.x == tx && now.y == ty) { // 目标位置,即箱子的前一个位置
                pathBuilder.append(now.path);
                return true;
            }
            for (int i = 0; i < 4; i++) {
                // 人的新位置
                int npx = now.x + dir[i][0];
                int npy = now.y + dir[i][1];
                if (check(npx, npy) && vis[npx][npy] == 0) {
                    vis[npx][npy] = 1;
                    Q.add(new person(npx, npy, now.path + dpathP[i]));
                }
            }
        }
        return false;
    }
}

class person {
    int x;
    int y;
    String path;

    person(int x_, int y_, String path_) {
        x = x_;
        y = y_;
        path = path_;
    }
}

class node {
    int px, py, bx, by;
    String path;


    node(int px_, int py_, int bx_, int by_, String path_) {
        px = px_;
        py = py_;
        bx = bx_;
        by = by_;
        path = path_;
    }
}

七 测试

´问题描述: 码头仓库是划分为n×m个格子的矩形阵列。有公共边的格子是相邻格子。当前仓库中 有的格子是空闲的;有的格子则已经堆放了沉重的货物。由于堆放的货物很重,单凭仓库管 理员的力量是无法移动的。仓库管理员有一项任务,要将一个小箱子推到指定的格子上去。 管理员可以在仓库中移动,但不能跨过已经堆放了货物的格子。管理员站在与箱子相对的空 闲格子上时,可以做一次推动,把箱子推到另一相邻的空闲格子。推箱时只能向管理员的对 面方向推。由于要推动的箱子很重,仓库管理员想尽量减少推箱子的次数。 ´编程任务: 对于给定的仓库布局,以及仓库管理员在仓库中的位置和箱子的开始位置和目标位置, 设计一个解推箱子问题的分支限界法, 计算出仓库管理员将箱子从开始位置推到目标位置所 需的最少推动次数。 ´数据输入: 由文件input.txt提供输入数据。输入文件第 1 行有 2个正整数 n和 m(1<=n,m<=100) , 表示仓库是n×m个格子的矩形阵列。接下来有 n行,每行有 m个字符,表示格子的状态。 S 表示格子上放了不可移动的沉重货物; w 表示格子空闲; M 表示仓库管理员的初始位置; P 表示箱子的初始位置; K 表示箱子的目标位置。 ´结果输出: 将计算出的最少推动次数输出到文件 output.txt。如果仓库管理员无法将箱子从开始位 置推到目标位置则输出“No solution!” 。 输入文件示例 输出文件示例 input.txt output.txt
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值