程序员的算法趣题:Q26 高效的立体停车场(Java版)

题目说明

最近,一些公寓等建筑也都配备了立体停车场。
立体停车场可以充分利用窄小的土地,通过上下左右移动来停车、出库,从而尽可能多地停车。
现在有一个立体停车场,车出库时是把车往没有车的位置移动,从而把某台车移动到出库位置。
假设要把左上角的车移动到右下角,试找出路径最短时的操作步数。
举个例子,在 3×2 的停车场用如图 13 所示的方式移动时,需要移动 13 步。

图13

不过,如果用如 图 14 所示的移动方法,则只需要移动 9 步。

图14

求在 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

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值