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

目录

1. 问题描述

2. 解题分析

2.1 BFS基本流程

2.2 状态表示方法

2.3 遍历下一个状态

3. 代码及测试

运行结果:

4. 后记


1. 问题描述

        最近,一些公寓等建筑也都配备了立体停车场。立体停车场可以充分利用窄小的土地,通过上下左右移动来停车、出库,从而尽可能多地停车。

        现在有一个立体停车场,车出库时是把车往没有车的位置移动,从而把某台车移动到出库位置。假设要把左上角的车移动到右下角,试找出路径最短时的操作步数。

        举个例子,在 3×2 的停车场用如图 13 所示的方式移动时,需要移动 13 步。

1 车从左上角移动到右下角的示例113步)

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

2 车从左上角移动到右下角的示例19步)

        问题:

        求在 10×10 的停车场中,把车从左上角移动到右下角时按最短路径移动时需要的最少步数。

2. 解题分析

        因为是求最短路径,所以可以用广度优先搜索策略来解决。

        广度优先搜索有与深度优先搜索相同的三个基本要素:

  1. 基本算法流程
  2. 状态表示方法
  3. 遍历下一个状态

        其中(2)和(3)相关联的,合理的状态表示和遍历方法的选择是解决问题的效率的关键。

2.1 BFS基本流程

 

2.2 状态表示方法

        本题考虑以车和空位的位置坐标表示当前状态(或节点),再加上对应的层数,即[[x1,y1, x2,y2], layer].其中[x1,y1]表示车的位置,[x2,y2]表示空位的位置。

        车的其实位置为[0,0], 空位的其实坐标为[N-1,M-1],N和M分别为车库的行数和列数。

        当车的位置移动到[N-1,M-1],即算到达目标。

2.3 遍历下一个状态

        本问题搜索下一个状态(或者说移动方式)是一件麻烦事。

        焦点是空位的移动。因为空位是一定移动的,车则不一定,车只有在与空位相邻的时候才有机会移动。要点如下:

  1. 空位在中心位置的话,有4个方向都可以移动
  2. 空位在边缘的话就会受到限制
  3. 如果车恰好在空位某个方向的相邻位置,则空位往这个方向的移动是交换车和空位的位置

        参见findNext()函数。本题解中findNext()函数写得比较冗长,暂时没有想到更简洁的写法。不过这个不是关键点,暂时将就一下。

3. 代码及测试

# -*- coding: utf-8 -*-
"""
Created on Mon Sep 13 07:10:26 2021

@author: chenxy
"""

import sys
import time
import datetime
import math
# import random
from   typing import List
# from   queue import Queue
from   collections import deque
import itertools as it

class Solution:
    def parkingMove(self,N,M):
        
        '''    
        The content stored in Queue:
            [[x1,y1,x2,y2], layer]. (x1,y1): car position; (x2,y2): empty position
        'visited' is implemented as dict() for faster item search, the value is irrelant, hence set to ''.
        (x1,y1,x2,y2) is used as the key of 'visited', instead of [x1,y1,x2,y2], because list is unhashable type.
                
        Returns
        -------
            The number of moves needed to move car to the right-down lot, from top-left lot.
        '''

        def findNext(curState):                        
            nxtStateLst = []
            # empty up
            carPos,emptyPos   = curState[0:2], curState[2:]
            if emptyPos[0] > 0:
                # print('up')
                if carPos[0] == emptyPos[0] - 1 and carPos[1] == emptyPos[1]:                    
                    carPos[0]   = carPos[0] + 1
                emptyPos[0] = emptyPos[0] - 1                    
                nxtStateLst.append(carPos+emptyPos)
            # empty down
            carPos,emptyPos   = curState[0:2], curState[2:]
            if emptyPos[0] < N-1:
                # print('down')
                if carPos[0] == emptyPos[0] + 1 and carPos[1] == emptyPos[1]:                    
                    carPos[0]   = carPos[0] - 1
                emptyPos[0] = emptyPos[0] + 1      
                nxtStateLst.append(carPos+emptyPos)                          
            # empty left
            carPos,emptyPos   = curState[0:2], curState[2:]
            if emptyPos[1] > 0:
                # print('left')
                if carPos[1] == emptyPos[1] - 1 and carPos[0] == emptyPos[0]:                    
                    carPos[1]   = carPos[1] + 1
                emptyPos[1] = emptyPos[1] - 1                                
                nxtStateLst.append(carPos+emptyPos)
            # empty right
            carPos,emptyPos   = curState[0:2], curState[2:]
            if emptyPos[1] < M-1:
                # print('right')
                if carPos[1] == emptyPos[1] + 1 and carPos[0] == emptyPos[0]:                    
                    carPos[1]   = carPos[1] - 1
                emptyPos[1] = emptyPos[1] + 1      
                nxtStateLst.append(carPos+emptyPos)                                      
            # print('findNext: ',curState,' -> ',nxtStateLst)                
            return nxtStateLst
        
        # Initialization
        q = deque()
        visited = dict()
        car_start, empty_start = [0,0], [N-1,M-1]
        car_target = [N-1,M-1]
        
        q.append([car_start + empty_start, 0])
        visited[tuple(car_start + empty_start)] = ''
    
        while len(q) != 0:
            node = q.popleft()            
            # print(node)
            car_pos = node[0][0:2]
            layer   = node[1]
            if car_pos == car_target:
                return layer
            
            nxtStateLst = findNext(node[0])
            for nxtState in nxtStateLst:
                if tuple(nxtState) not in visited:
                    visited[tuple(nxtState)] = ''
                    q.append([nxtState, layer+1])
                            
    
if __name__ == '__main__':      

    sln     = Solution()   
    tStart  = time.perf_counter()
    N,M     = 10,10 # N*M parking-lots
    numOfMoves = sln.parkingMove(N,M)
    tCost   = time.perf_counter() - tStart
    print('N,M={0},{1}, num-of-moves = {2}, tCost = {3:6.3f}(sec)'.format(N,M,numOfMoves,tCost))

运行结果:

        N,M=10,10, num-of-moves = 69, tCost =  0.145(sec)

4. 后记

        以上题解中用deque来实现队列,需要注意的一点是,deque是双向队列,其入队离队方法为append(), pop(), appendleft(), popleft(),其中append()与popleft()构成一对,appendleft()与pop()构成一对。如果配错对了就不是队列而变成栈了,那就得不到最短距离解了。

        上一篇:​​​​​​​​​​​​​​Q25: 时髦的鞋带系法

        下一篇:​​​​​​​Q27: 禁止右转

        本系列总目录参见:程序员的算法趣题:详细分析和Python全解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨牛慢耕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值