题目说明
最近,一些公寓等建筑也都配备了立体停车场。
立体停车场可以充分利用窄小的土地,通过上下左右移动来停车、出库,从而尽可能多地停车。
现在有一个立体停车场,车出库时是把车往没有车的位置移动,从而把某台车移动到出库位置。
假设要把左上角的车移动到右下角,试找出路径最短时的操作步数。
举个例子,在 3×2 的停车场用如图 13 所示的方式移动时,需要移动 13 步。不过,如果用如 图 14 所示的移动方法,则只需要移动 9 步。
求在 10×10 的停车场中,把车从左上角移动到右下角时按最短路径移动时需要的最少步数。
思路
求按最短路径移动时,广度优先搜索是一种行之有效的方法。
如果找到了最终的移动方法,就可以立刻停止搜索,这样可以大幅减少搜索时间。
1.以最终出库状态倒推回起始状态(endState ==> startState)
2.Map<State,Integer>记录已经探索过的状态:State表示“车位数组,空位坐标”;Integer表示倒数第几步(倒推了几步)
3.以空位与上下左右相邻车位交换实现一步步倒推
4.倒推至最初状态(startState)时,结束递归,从Map中根据startState取出值(倒推的步数)
代码
private static int W = 10; // 宽度是10个车位 合法下标[0,W-1] (W > 1)
private static int H = 10; // 高度是10个车位 合法下标[0,H-1] (H > 1)
// 记录了停车场倒推后的各种状态以及倒推至该状态所需的步数。用于避免无效的重复移动,从而提升性能
private static Map<State,Integer> log = new HashMap<>();
// 停车场
private static int[][] parking = new int[H][W]; // 2-目标车,1-其它车,0-空位(注意H和W的顺序)
// 车位分布(起始状态)
private static State startState ;
public static void main(String[] args) {
// 先将parking的全部车位初始化为1
for (int[] arr : parking) {
Arrays.fill(arr, 1);
}
// 复制parking的所有元素,并将车位置为2,将空位(出口)置为0
int[][] start = copyArr(parking);
start[0][0] = 2; // 目标车的位置赋值为2(此处可以修改为其它车位)
start[H-1][W-1] = 0; // 空位(出口)的位置赋值为0
// 最初状态(包含所有车位分布的二维数组和空位的坐标(x,y))
startState = new State(start, new int[]{W - 1, H - 1});
// 最终出库状态1
int[][] end1 = copyArr(parking);
end1[H-1][W-1] = 2; // 目标车的位置(到达出口)
end1[H-1][W-2] = 0; // 空位的位置(空位在车的左侧)
State endState1 = new State(end1, new int[]{W-2,H-1});
// 最终出库状态2
int[][] end2 = copyArr(parking);
end2[H-1][W-1] = 2; // 目标车的位置(到达出口)
end2[H-2][W-1] = 0; // 空位的位置(空位在车的上侧)
State endState2 = new State(end2, new int[]{W-1,H-2});
// 记录最终出库时两种可能的状态,以此为基础进行倒推
log.put(endState1, 0); // 倒推了0步
log.put(endState2, 0);
List<State> stateList = new LinkedList<State>(); // 存储当下可能的所有状态,是自定义递归方法back的参数,作为下一步倒推的基础
stateList.add(endState1);
stateList.add(endState2);
back(stateList, 0); // 开始倒推
System.out.println("result = " + log.get(startState)); // 输出最终结果
}
/**
* 根据preState对所有可能性进行倒推,并分别记录倒推的步数
* 每调用一次back方法,就会将当下的所有情形都倒推“一步”
* @param preStateList 当前的所有状态,在此基础上都倒推一步
* @param depth 当前已经倒推的步数
*/
private static void back(List<State> preStateList, int depth) {
if(preStateList == null || depth < 0) return ;
List<State> stateList = new LinkedList<State>(); // 用于保存倒推的所有可能性,为进一步倒推(递归)做准备
for (State preState : preStateList) { // 对每一种状态都进行上下左右各个方向的倒推尝试
up(preState, depth, stateList); // 上:空位与它上面的车位交换
down(preState, depth, stateList); // 下:空位与它下面的车位交换
left(preState, depth, stateList); // 左:空位与它左面的车位交换
right(preState, depth, stateList); // 右:空位与它右面的车位交换
}
if(stateList.contains(startState)){ // 如果这一波倒推到达了“最初状态”
return ; // 结束递归
}
if(stateList.size() > 0) { // 如果还能继续倒推
back(stateList, depth + 1); // 继续递归
}
}
// 上:空位与它上面的车位交换
private static void up(State preState, int depth, List<State> stateList) {
int[][] snapshot = copyArr(preState.getSnapshot()); // 二维数组备份
int[] vacancy = preState.getVacancy(); // 获取空位的坐标
int x = vacancy[0];
int y = vacancy[1];
if(y > 0){ // 判断是否越界
// snapshot[H][W]
int temp = snapshot[y][x]; // 当前元素
snapshot[y][x] = snapshot[y-1][x]; // 上面的元素
snapshot[y-1][x] = temp;
State curState = new State(snapshot,new int[]{x, y-1}); // 当前车位分布和空位坐标
if(! log.containsKey(curState)){
log.put(curState, depth + 1); // 记录倒推到该状态需要多少步
stateList.add(curState); // 以此为基准,为后续倒推做准备
System.out.println(curState);
}
}
}
// 下:空位与它下面的车位交换
private static void down(State preState, int depth, List<State> stateList) {
int[][] snapshot = copyArr(preState.getSnapshot()); // 二维数组备份
int[] vacancy = preState.getVacancy(); // 获取空位的坐标
int x = vacancy[0];
int y = vacancy[1];
if(y+1 < snapshot.length){ // 判断是否越界
// snapshot[H][W]
int temp = snapshot[y][x]; // 当前元素
snapshot[y][x] = snapshot[y+1][x]; // 下面的元素
snapshot[y+1][x] = temp;
State curState = new State(snapshot,new int[]{x, y+1}); // 当前车位分布和空位坐标
if(! log.containsKey(curState)){
log.put(curState, depth + 1); // 记录倒推到该状态需要多少步
stateList.add(curState); // 以此为基准,为后续倒推做准备
System.out.println(curState);
}
}
}
// 左:空位与它左面的车位交换
private static void left(State preState, int depth, List<State> stateList) {
int[][] snapshot = copyArr(preState.getSnapshot()); // 二维数组备份
int[] vacancy = preState.getVacancy(); // 获取空位的坐标
int x = vacancy[0];
int y = vacancy[1];
if(x > 0){ // 判断是否越界
// snapshot[H][W]
int temp = snapshot[y][x]; // 当前元素
snapshot[y][x] = snapshot[y][x-1]; // 左面的元素
snapshot[y][x-1] = temp;
State curState = new State(snapshot,new int[]{x-1, y}); // 当前车位分布和空位坐标
if(! log.containsKey(curState)){
log.put(curState, depth + 1); // 记录倒推到该状态需要多少步
stateList.add(curState); // 以此为基准,为后续倒推做准备
System.out.println(curState);
}
}
}
// 右:空位与它右面的车位交换
private static void right(State preState, int depth, List<State> stateList) {
int[][] snapshot = copyArr(preState.getSnapshot()); // 二维数组备份
int[] vacancy = preState.getVacancy(); // 获取空位的坐标
int x = vacancy[0];
int y = vacancy[1];
if(x+1 < snapshot[y].length){ // 判断是否越界
// snapshot[H][W]
int temp = snapshot[y][x]; // 当前元素
snapshot[y][x] = snapshot[y][x+1]; // 右面的元素
snapshot[y][x+1] = temp;
State curState = new State(snapshot,new int[]{x+1, y}); // 当前车位分布和空位坐标
if(! log.containsKey(curState)){
log.put(curState, depth + 1); // 记录倒推到该状态需要多少步
stateList.add(curState); // 以此为基准,为后续倒推做准备
System.out.println(curState);
}
}
}
// 二维数组拷贝(因为每个State都是独立的二维数组,所以需要拷贝元素。不能直接传入引用。)
private static int[][] copyArr(int[][] parking) {
if(parking == null) return null;
int[][] newArr = new int[parking.length][];
for (int i = 0; i < parking.length; i ++) {
newArr[i] = Arrays.copyOf(parking[i], parking[i].length);
}
return newArr;
}
/**
* 私有的静态内部类(可以直接访问外部类的H和W)
* 表示停车场车位分布的一种状态、情形
* 包含了所有车位的情况和空位的坐标(保存空位坐标是为了上下左右移动时更方便)
*/
private static class State {
private int[][] snapshot = new int[H][W]; // 停车场车位分布快照
private int[] vacancy; // 空位坐标(x,y) x方向:从左往右;y方向:从上往下 (坐标和数组下标的顺序是颠倒的,注意区分哦)
public State() {
}
public State(int[][] snapshot, int[] vacancy) {
this.snapshot = snapshot;
this.vacancy = vacancy;
}
public int[][] getSnapshot() {
return snapshot;
}
public void setSnapshot(int[][] snapshot) {
this.snapshot = snapshot;
}
public int[] getVacancy() {
return vacancy;
}
public void setVacancy(int[] vacancy) {
this.vacancy = vacancy;
}
// 因为State是存入HashMap的,判断是否containsKey时会调用hashCode,所以必须重写hashCode
@Override
public int hashCode() {
int result = Arrays.deepHashCode(snapshot);
result = 31 * result + Arrays.hashCode(vacancy);
return result;
}
// 根据数组元素和空位坐标两部分内容来判断是否相等
@Override
public boolean equals(Object obj) {
if(obj == null) return false;
if(this == obj) return true;
if(obj.getClass() != this.getClass()) return false;
State state = (State) obj;
// 比较快照是否相同
if(snapshot.length != state.snapshot.length) return false;
for(int i = 0; i < snapshot.length; i ++){
int[] a = snapshot[i];
int[] b = state.snapshot[i];
if(! Arrays.equals(a, b)) return false;
}
if(! Arrays.equals(vacancy, state.vacancy)) return false;
return true;
}
// 以友好的方式呈现state对象的信息
@Override
public String toString() {
StringBuilder sb = new StringBuilder("\n");
for (int[] arr : snapshot) {
sb.append(Arrays.toString(arr) + "\n");
}
sb.append("vacancy = (" + vacancy[0] + "," + vacancy[1] + ")\t");
return sb.toString();
}
}
结果
result = 69