鱼C论坛

 找回密码
 立即注册
查看: 10653|回复: 114

[技术交流] #鱼C五周年嘉年华#算法——送(qiang)快(jiang)递(pin)。--已经结束

[复制链接]
发表于 2015-1-23 00:00:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 wei_Y 于 2015-8-4 12:41 编辑

故事背景:
在一个伸手不见五指滴黑夜~,有一只正在给你送
2.jpg
小甲鱼公仔的快递小哥。

到了你家门口,砰砰砰敲着门,幽幽的说着:
203fb80e7bec54e72b204cd7b9389b504ec26a2e.jpg
"what!这么快!?"
"那是,我可是get到新技能了。"
---故事结束,之后发生的事件略凶残!---

OK,这次的任务就是帮助快递小哥找到送货可以的最短时间!(什么,居然不是最短路径。)

基本介绍
S表示起点
E表示终点
W表示水
B进出加速状态。(进入后需退出。)
非加速状态下移动(URLD)耗时为2.
加速状态下移动(URLD)耗时为1.
进出加速状态耗时为1.
"U" -- Up (北)
"D" -- Down (南)
"L" -- Left (西)
"R" -- Right (东)
"B" -- 进出加速模式。
输入:  一个字符串列表地图。
输出:  一个带路径的字符串。

------------------------------------------------


详细说明:
从起点(S)开始,每次只可以移动一步(上(U)下(D)左(L)右(R)),到达终点(E)结束。到达E时若是加速状态则不可以算完成必须退出加速状态。

水(W)不可以通过,任何状态下都不可以。起点(S)与终点(E)总是分布在地图的左上角(0,0)和右下角.地图最小为1x1,最大为6x6.地图的形式如栗子中所示。经过B可以选择加速or不加速。
需要完成的任务是找到从起点(S)开始到终点(E)的最短时间的走法(可能不唯一)。
最终以函数形式提交,若有多个函数请注明调用哪一个函数。
最终返回最短时间路径的走法。



栗子:


怎么走无所谓只要时间符合就OK。

maps(["S...","....","B.WB","..WE"])

360截图20150123113341656.jpg
["S...",
"....",
"B.WB",
"..WE]

#RRRDDD
最低耗时: 12
--------------------------------

maps(["S...","....","B..B","..WE"])
360截图20150123113350680.jpg
["S...",
"....",
"B..B",
"..WE"]

#DDBRRRBD  
最低耗时: 11
>>> maps(["S...","....","B.WB","..WE"])
'DRRRDD'
>>> maps(["S...","....","B..B","..WE"]) 
'DDBRRRBD'
地图最大6*6。

测试:
["S.BB.E"] "RRRRR"
["S.W...","..WB..","..WW..","....B.","....W.","..B.BE"]  "DDDDDRRBRRBR"
["SBW...",".WWB..","..WW..","......","...WW.","..BWBE"]  "RBLDDDRRRRRDDLBR"
["S..BW.",".WWWWW",".W....",".W.W.B",".W.W..","...W.E"]  'RRRBLLLDDDDDRRUUURRDRBDD'

自己测试或者让我测试都可以哦。通过3个即算完成。


-----------------------

要求:
无任何要求,第三方库最好能提供一下下载地址,自写的模块一起把代码贴上(附上一个#模块名    的注释)或随附件上传。


排名:
高效,可读性高(不要吝啬注释)。若1分钟还不出则失败。(那个,呃……我电脑渣你懂得~)


-----------------------
奖品及换奖品的方式请在此帖查看
http://bbs.fishc.com/forum.php?mod=viewthread&tid=57807&page=1#pid2265719

哦对了,可在此查看一下普通迷宫思路。内有大神的答案,无视我写的吧。
-----------------------------本活动最终解释权归 介哥 and weiy所有!---------------------



P.S没懂得鱼油能不能说一下哪个地方呢?就说个没懂我也不知道该怎么改。。







想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2015-1-29 15:28:05 | 显示全部楼层
本帖最后由 戴宇轩 于 2015-1-31 13:56 编辑

成品 12
版本: Python 3.4.0a0
import re #分开路径用
import sys #报错用
class Go_to_B: #向加速器移动的类
    def __init__(self, direction, opposite): #要传入四个方向和相反路径字典
        self.result = [] #结果存放处
        self.direction = direction #方向存放处
        self.opposite = opposite #相反路径字典
    def __call__(self, path, field, history): #被调用时
        (x, y) = history[-1] #找到现在位置
        if field[x][y] == 'B': #如果找到了加速器
            self.result.append(path) #结果增加这条路径
            del history[-1];return 0 #推向前一个点, 向其他方向继续寻找
        for i in self.direction: #搜索四个方向
            (x, y) = (history[-1][0] + i[0], history[-1][1] + i[1]) #假定向这个方向移动1格
            if not ((x, y) in history or field[x][y] == 'W'): #如果超出边界 or 走过这个点(非递归造成) or 前面被水挡住了, 就搜索另一个方向
                history+=[(x, y)] #如果前面是通路, 将这个点加入history, 下次递归就从这个点开始
                self(path + i[2], field, history) #递归
        del history[-1] #如果找到一条死路(再找就回去了), 就回到上一个点, 继续寻找
    def __str__(self): #被str()时
        self.result.sort(key = len) #找到最短的一条路
        temp = self.result and self.result[0] or '' #如果整个迷宫里没有加速器, 开头结尾路径为空
        return temp + (temp and 'B' or '') + ''.join([self.opposite[i] for i in temp[::-1]]) #并返回
def fix(path): #用来消除路径中的[RL|LR|UD|DU|BB]
    while path.replace('LR', '').replace('RL', '').replace('UD', '').replace('DU', '').replace('BB', '') != path:
        path = path.replace('LR', '').replace('RL', '').replace('UD', '').replace('DU', '').replace('BB', '')
    return path
def checkroute(data): #主函数
    direction = ((0, 1, 'R'), (1, 0, 'D'), (-1, 0, 'U'), (0, -1, 'L')) #定义四个方向
    data = ['W' * (len(data[0]) + 2)] + ['W' + i + 'W' for i in data] + ['W' * (len(data[-1]) + 2)] #给迷宫加上墙壁
    opposite = {'R': 'L', 'U': 'D', 'D': 'U', 'L': 'R'} #相反路径字典
    (start_path, end_path) = ([], []) #开始和结束到加速器的最短距离
    (S_axis, E_axis) = ([], [])
    ##### 这个函数段用于找到S和E的坐标
    x = 0
    for i in data:
        y = 0
        for j in i:
            if j == 'S':S_axis.append((x, y))
            if j == 'E':E_axis.append((x, y))
            y += 1
        x += 1
    ##### 结束
    ##### 判断起点, 终点个数是否正确
    if len(S_axis) != 1:sys.exit('起点个数不正确!')
    if len(E_axis) != 1:sys.exit('终点个数不正确!')
    ##### 结束
    def view_all_path(data): #这个函数用来找到所有路径(不兜圈)
        result = [] #结果路径存放处
        ########## 这个脚本段用来记录起点和终点到加速器后返回的最短距离
        start_path_temp = Go_to_B(direction, opposite)
        start_path_temp('', data, S_axis[:]) #因为Move_to_B()()会把S_axis删光, 浅拷贝一次
        start_path.append(str(start_path_temp))
        end_path_temp = Go_to_B(direction, opposite)
        end_path_temp('', data, E_axis)
        end_path.append(str(end_path_temp))
        ########## 结束
        def serach(path, field, history): #搜索函数
            (x, y) = history[-1] #找到现在位置
            if field[x][y] == 'E': #如果找到了终点
                result.append(path) #结果增加这条路径
                del history[-1];return 0 #推向前一个点, 向其他方向继续寻找
            for i in direction: #搜索四个方向
                (x, y) = (history[-1][0] + i[0], history[-1][1] + i[1]) #假定向这个方向移动1格
                if not ((x, y) in history or field[x][y] == 'W'): #如果超出边界 or 走过这个点(非递归造成) or 前面被水挡住了, 就搜索另一个方向
                    history+=[(x, y)] #如果前面是通路, 将这个点加入history, 下次递归就从这个点开始
                    serach(path + i[2] + ('B' if field[x][y] == 'B' else ''), field, history) #递归
            del history[-1] #如果找到一条死路(再找就回去了), 就回到上一个点, 继续寻找
        serach('', data, S_axis) #搜索, 此时路径为空, 传入起点位置
        return result #将找到的所有路径返回
        ###### 寻找路径函数结束
    raw_path = view_all_path(data) #得到只走一次的路径, 传入迷宫
    factored_path = [] #所有(含开头结尾加速器)路径存放处
    start_path = start_path[0] #开头加速器路径
    end_path = end_path[0] #结尾加速器路径
    final_path = [] #最终路径
    least = {} #时间与路径的字典
    for i in raw_path: #一共有四种路径(原路径, 开头加速器 + 原路径, 原路径 + 结尾加速器, 开头加速器 + 原路径 + 结尾加速器)
        factored_path += [i, start_path + i, i + end_path, start_path + i + end_path]
    for i in [fix(i) for i in factored_path]: #逐个查看消除重复路径后的路径
        if i.count('B') in (0, 2): #如果有0个或2个B在路径内
            final_path.append(i) #直接放入正确路径
        elif i.count('B') > 2: #如果有多于两个B, 仅保留最前和最后的B(这样时间少)
            frount = i.index('B') + 1
            back = len(i) - i[::-1].index('B') - 1
            final_path.append(i[:frount] + i[frount:back].replace('B', '') + i[back - 1:])
        final_path.append(i.replace('B', '')) #再加入一个全程不加速的路径(以上判断皆通过此处)
    for i in final_path: #查看所有正确路径
        all_time = 0 #每条路径时间一开始是0
        split_path = re.findall('[URLD]+(?=B)|B[URLD]+B|(?<=B)[URLD]+|[URLD]+', i) #用正则表达式将路径分开, 如下
        #例子: 'RRRRRBUUUBLLL'   ------>   ['RRRRR', 'BUUUB', 'LLL']
        for j in split_path: #逐个查看所有分开路径
            if j[0] == 'B': #如果处于加速状态
                all_time += len(j) #时间和加上加速后的路径长度(单位: 1)
            else: #否则(非加速状态)
                all_time += len(j) * 2 #时间和加上现在路径的总长度(单位: 2)
        least[all_time] = i #least的现在总时间对应现在的路径
    print('最短时间: %s 路径:' % min(least), least[min(least)]) #输出最短的时间和对应的路径, Done!
# -*- -*- -*- -*- -*- # 测试数据
No_1 = ["SBW...",".WWB..","..WW..","......","...WW.","..BWBE"]
No_2 = ["S..BW.",".WWWWW",".W....",".W.W.B",".W.W..","...W.E"]
checkroute(No_1)
checkroute(No_2)

点评

我很赞同!: 5.0
我很赞同!: 5
  发表于 2015-1-29 15:30

评分

参与人数 1鱼币 +5 收起 理由
挥舞乾坤 + 5 有时间再看看,学习学习

查看全部评分

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 1 反对 0

使用道具 举报

发表于 2015-1-31 15:20:19 | 显示全部楼层
本帖最后由 挥舞乾坤 于 2015-1-31 16:51 编辑

完整版,测试了所有版主提供的路径,并可以实现10*10,支持随机起点和终点,再大的就没有测试了,欢迎测试,鉴于之前版本因为寻路全部由递归完成(递归遍历所有路径真是个大工程)耗时太久,所以又出了这个版本!
import copy

#找到指定地图中,两点之间的最短路径
def maze(data, p1, p2):
    'data:迷宫数据,p1:起点,p2终点'
    
    rows = len(data) # 地图行数
    column = len(data[0]) #地图列数
    result = [] # 可达路径列表
    x, y = p1

    # fx可移动的方向
    fx = [[0, 1, 'R'], [1, 0, 'D'], [0, -1, 'L'], [-1, 0, 'U']]

    #递归找到最短路径,添加到result列表中
    def move(path, x, y, maps):
        'path:存放方向字串,x,y:行,列坐标,maps:迷宫地图'
        maps[x][y] = 'W' #记录走过的点,防止重复
        if (x, y) == p2:
            result.append(path) #如果当前坐标等于终点坐标,把路径添加到列表

         #在方向列表中逐个尝试移动   
        for i in fx[:]:
            if not (0 <= x + i[0] < rows) or not (0 <= y + i[1] < column):
                continue
            if maps[x + i[0]][y + i[1]] in '.SBE':
                move('%s%s' % (path,i[2]), x + i[0], y + i[1], maps)

    fx_ = re_move(fx) #fx_是一个存放所有方向排列组合的生成器,一共24种组合

    #根据不同的方向排列组合循环调用move,每调用一次添加一个可达路径
    for each in fx_:
        fx = each
        move('', x, y, copy.deepcopy(data))

     #返回最短的路径   
    result = {len(x):x for x in result}
    return result[min(result)]

#生成方向的排列组合,返回一个生成器
def re_move(fx):
    for pos1 in fx:
        for pos2 in fx:
            if pos2 == pos1:continue
            for pos3 in fx:
                if pos3 in(pos1,pos2):continue
                for pos4 in fx:
                    if pos4 in(pos1,pos2,pos3):continue
                    yield([pos1,pos2,pos3,pos4])

#找到指定字符在地图中的位置,返回一个坐标列表                            
def find_items(data,item):
    'data:地图数据,item:要找的字符'
    result = []
    for x,each_row in enumerate(data):
        for y,each_str in enumerate(each_row):
            if each_str == item:
                result.append((x,y))
    return result

#找到包含加速的路径,返回一个路径列表
def spend_path(data, s, e, blist):
    'data:地图数据,s:起点,e:终点,blist:所有加速点的列表'
    result = []

    #循环组合出所有加速路径
    for each in blist:
        for other in blist:
            if each == other:continue
            path = ''
            part1 = maze(data, s, each)
            part2 = maze(data, each, other)
            part3 = maze(data, other, e)
            path = '%s%s%s%s%s' % (part1, 'B', part2, 'B', part3)
            result.append(path)
    return result


#计算最短路径,返回一个最短的耗时,和最短路径的字典
def calc_time(path_list):
    'path_list:路径列表'
    result = {}
    spend = False
    for each_path in path_list:
        count = 0
        for each_char in each_path:
            if each_char == 'B':
                spend = not spend
                count += 1
                continue
            sep = 1 if spend else 2
            count += sep
        result.update({count:each_path})
    return min(result),result
            
        
    
#主函数了     
def maps(data):
    data = [list(e.upper()) for e in data]
    rows = len(data)
    column = len(data[0])
    for each in data:
        print(each)

    path_list = []
    S = find_items(data,'S')[0] #找起点
    E = find_items(data,'E')[0] #找终点
    B_list = find_items(data,'B') #找所有加速点

    #加速点大余1个,path_list 获得所有加速路径
    if len(B_list) > 1: 
        path_list = spend_path(data, S, E, B_list)

    #加入起点到终点,不走加速的路径
    path_list.append(maze(data, S, E))

    #计算所有路径耗时,min_time最小耗时,min_path最短路径
    min_time,min_path = calc_time(path_list)
    min_path = min_path[min_time]

    print('地图大小%d * %d' % (rows,column))
    print('最佳路径:#%s' % min_path)
    print('最低耗时:%d' % min_time)
    print('=' * 60)

data0 = ["S.....","......","......","......","......",".....E"]
data1 = ["S...","....","B.WB","..WE"]
data2 = ["S...","....","B..B","..WE"]
data3 = ["S.BB.E"]
data4 = ["S.W...","..WB..","..WW..","....B.","....W.","..B.BE"]
data5 = ["SBW...",".WWB..","..WW..","......","...WW.","..BWBE"]
data6 = ["S..BW.",".WWWWW",".W....",".W.W.B",".W.W..","...W.E"]
data7 = ["EBW.S.",".WWB..","..WW..","......","...WW.","..BWB."]
data8 = ["S..BW.....",".WWWWWw...",".Wbw......",".W.W.Bw...",".W.W..www.","...W..w...","......w...",".wwww.w..b","..........",".....b..be"]
data9 = ['s.........','..........','..........','..........','..........','..........','..........','..........','..........','.........e']

maps(data0)
maps(data1)
maps(data2)
maps(data3)
maps(data4)
maps(data5)
maps(data6)
maps(data7)
maps(data8)
maps(data9)

测试数据:
data0 = ["S.....","......","......","......","......",".....E"]
data1 = ["S...","....","B.WB","..WE"]
data2 = ["S...","....","B..B","..WE"]
data3 = ["S.BB.E"]
data4 = ["S.W...","..WB..","..WW..","....B.","....W.","..B.BE"]
data5 = ["SBW...",".WWB..","..WW..","......","...WW.","..BWBE"]
data6 = ["S..BW.",".WWWWW",".W....",".W.W.B",".W.W..","...W.E"]
data7 = ["EBW.S.",".WWB..","..WW..","......","...WW.","..BWB."]
data8 = ["S..BW.....",".WWWWWw...",".Wbw......",".W.W.Bw...",".W.W..www.","...W..w...","......w...",".wwww.w..b","..........",".....b..be"]
data9 = ['s.........','..........','..........','..........','..........','..........','..........','..........','..........','.........e']

点评

我很赞同!: 5.0
我很赞同!: 5
不用写那么全啦。效率高!  发表于 2015-1-31 16:42
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 00:14:38 | 显示全部楼层
我怎么有点晕,题目没怎么看懂啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 00:28:30 From FishC Mobile | 显示全部楼层
雪是梅之香 发表于 2015-1-23 00:14
我怎么有点晕,题目没怎么看懂啊

你还学python?   
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 09:05:50 | 显示全部楼层

恩,虽然只是刚开始
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 09:54:44 | 显示全部楼层
题目不太容易理解..晕晕的
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 10:07:05 | 显示全部楼层
又有新题目了啦,支持,学习
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 10:11:37 | 显示全部楼层
@wei_Y 版主大大,为什么第一个例子 结果不是#RRRDDBD
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 10:13:55 | 显示全部楼层
呃,这个参加有难度
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-1-23 11:39:57 | 显示全部楼层
挥舞乾坤 发表于 2015-1-23 10:11
@wei_Y 版主大大,为什么第一个例子 结果不是#RRRDDBD

已更新说明。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 11:57:44 | 显示全部楼层

回帖奖励 +1 鱼币


我怎么有点晕,题目没怎么看懂啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-1-23 12:05:50 | 显示全部楼层
尧子哥 发表于 2015-1-23 11:57
我怎么有点晕,题目没怎么看懂啊

已更新说明,哪里晕能否说一下,我好改改。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 14:02:00 | 显示全部楼层

回帖奖励 +1 鱼币

看这好难的样子
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 16:15:22 | 显示全部楼层

回帖奖励 +1 鱼币

困惑,加速状态是持续的,还是有限的?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 16:20:47 | 显示全部楼层

回帖奖励 +1 鱼币

哇哦~顶起~越来越难啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 16:31:55 | 显示全部楼层

回帖奖励 +1 鱼币

瞬秒爆加速 发表于 2015-1-23 16:15
困惑,加速状态是持续的,还是有限的?

应该是持续的,遇到下一个B结束
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-1-23 16:36:09 | 显示全部楼层
瞬秒爆加速 发表于 2015-1-23 16:15
困惑,加速状态是持续的,还是有限的?

不持续没有意义吧,走一步就消失时间1+1,直接走是2。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 16:49:45 | 显示全部楼层

回帖奖励 +1 鱼币

先看看再说
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 16:55:08 | 显示全部楼层
wei_Y 发表于 2015-1-23 16:36
不持续没有意义吧,走一步就消失时间1+1,直接走是2。。

那栗子一的走法,是怎么回事,都进过B了,为什么没有加速状态?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-1-23 17:24:26 | 显示全部楼层
瞬秒爆加速 发表于 2015-1-23 16:55
那栗子一的走法,是怎么回事,都进过B了,为什么没有加速状态?

一看你打游戏就不多,经过和进入是两回事。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2015-1-23 18:10:02 | 显示全部楼层

回帖奖励 +1 鱼币

好难好难好难好难啊!
木有看懂。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-6-14 16:17

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表