[大创]一步一步写路径规划并绘图可视化 IV (solutions.py)

介绍

作者:dragonbean
从名字solutions就能看出,这个py文件是一个算法集,内涵了多种不同的solution算法,下面就来对其中的算法进行讲解。算法所处理的对象是 visual.py 传来的可视点的字典。
{障碍物顶点:[可视点,可视点,可视点,...],...} 数据结构是:{(x0,y0):[(x1,y1),(x3,y3),...],(x1,y1):[(x0,y0),...],...}

算法一

全部递归 全局最优
对某个点的所有可视点进行遍历,遍历时又对遍历到的点的所有可视点进行遍历(简单说就是递归),直到被遍历到的点是终止点,就返回到上层递归里去。

'''
      author:dragonbean
      href:https://www.cnblogs.com/dragonbean/p/13498421.html
      blog:https://www.cnblogs.com/dragonbean/
'''
def solution_one(start, end, graph):
    def __searchPath(start, end, graph):
        results = []
        __generatePath(graph, [start,0], end, results)
        results.sort(key=lambda x:x[-1])
        return results

    def __generatePath(graph, path, end, results):
        current = path[-2]
        if current == end:
            results.append(path)
            #print(path)
        else:
            for n in graph[current]:
                if n not in path and path[-1]<(minDistance*1.5):                                                    #递归深度为 3/2起始点终止点间距离
                    distance = path[-1] + math.sqrt((n[0]-path[-2][0])**2 + (n[1]-path[-2][1])**2)                  #增量计算不同路径的距离
                    __generatePath(graph, path[:-1]+[n,distance], end, results)

    def showPath(results):
        print('The path from ', results[0][0], 'to' , results[0][-1], 'is :')
        for path in results:
            print(path)
    
    minDistance = math.sqrt((end[1]-start[1])**2 + (end[0]-start[0])**2)
    results = __searchPath(start, end, graph)
    if __name__ == "__main__":
        showPath(results)
    return results[0]

这里需要注意的是,我对递归的深度做了优化处理,导致递归量大幅减小:在递归的过程中记录路程长度,并与起始点、终止点间距离的1.5倍做比较,小于这个数值的将继续递归下去,大于的将直接返回递归。这么处理可以有效避免多余的内存开销,只是这个1.5倍不对所有情况适用,属于这里的bug。

算法二

直接寻路 局部最优
从起始点开始,遍历该点的所有可视点,计算到该可视点的直线距离与该可视点到终止点的直线距离之和,比较后得出最优的可视点,作为下一次递归的“起始点”,一步一步直到找到终止点end。

'''
      author:dragonbean
      href:https://www.cnblogs.com/dragonbean/p/13498421.html
      blog:https://www.cnblogs.com/dragonbean/
'''
def solution_two(start, end, graph):
    print(graph)
    #还阔以再优化,再计算多一个点,就更趋于最优了
    def __findNext(start,end,graph, path):
        minDistance = [None,None]
        for each in graph[start]:
            if each == end:
                path.append(end)
                return
            else:
                distance = math.sqrt((each[0]-start[0])**2+(each[1]-start[1])**2)+math.sqrt((end[0]-each[0])**2+(end[1]-each[1])**2)
                if minDistance[1]==None or minDistance[1]>distance:
                    minDistance = [each,distance]
        print(minDistance[0])
        path.append(minDistance[0])
        __findNext(minDistance[0],end, graph, path)

    path = [start]
    __findNext(start, end, graph, path)
    print(path)
    return path

还可以对这个算法进行优化,正如注释里所说的,多计算一层的点(就是可视点的下一层所有可视点的“距离”也计算在内进行比较),以选出最优的第一个可视点,接着对它进行递归。

'''
      author:dragonbean
      href:https://www.cnblogs.com/dragonbean/p/13498421.html
      blog:https://www.cnblogs.com/dragonbean/
'''
def solution_three(start, end, graph):
    def __findNext(start,end,graph, path):
        minDistance = [None,None]
        for each in graph[start]:
            if each in path:
                continue
            elif each == end:
                path.append(end)
                return
            next_min_distance = None
            tmp_list = graph[each][:]
            try:
                tmp_list.remove(start)
                print(start)
                print(tmp_list)
            except:
                pass
            for i in tmp_list:
                if i == end:
                    path.extend([each,i])
                    return
                next_distance = math.sqrt((end[0]-i[0])**2+(end[1]-i[1])**2) + math.sqrt((i[0]-each[0])**2+(i[1]-each[1])**2)
                if next_min_distance==None or next_min_distance>next_distance:
                    next_min_distance = next_distance
            distance = next_min_distance + math.sqrt((each[0]-start[0])**2+(each[1]-start[1])**2)
            if minDistance[1]==None or minDistance[1]>distance:
                minDistance = [each,distance]
        path.append(minDistance[0])
        __findNext(minDistance[0],end, graph, path)

    path = [start]
    __findNext(start, end, graph, path)
    print(path)
    return path

还有一种优化,是将算法一和算法二结合起来,先确定一条可行的路径,将这个可行路径的值作为算法一递归深度的阈值,对算法一的全局最优做优化。

'''
      author:dragonbean
      href:https://www.cnblogs.com/dragonbean/p/13498421.html
      blog:https://www.cnblogs.com/dragonbean/
'''
def solution_four(start, end, graph):
    def __searchPath(start, end, graph):
        results = []
        __generatePath(graph, [start,0], end, results)
        results.sort(key=lambda x:x[-1])
        return results

    def __generatePath(graph, path, end, results):
        current = path[-2]
        if current == end:
            results.append(path)
            #print(path)
        else:
            for n in graph[current]:
                if n not in path and path[-1]<(minDistance*1.5):
                    distance = path[-1] + math.sqrt((n[0]-path[-2][0])**2 + (n[1]-path[-2][1])**2)                  #增量计算不同路径的距离
                    __generatePath(graph, path[:-1]+[n,distance], end, results)

    def showPath(results):
        print('The path from ', results[0][0], 'to' , results[0][-1], 'is :')
        for path in results:
            print(path)
    
    #先得到一条可行的路径,将它的长度作为阈值。
    prePath = solution_three(start,end,graph)
    minDistance = 0.0
    for i in range(len(prePath)-1):
        minDistance += math.sqrt((prePath[i+1][0]-prePath[i][0])**2+(prePath[i+1][1]-prePath[i][1])**2)
    results = __searchPath(start, end, graph)
    return results[0]

SPFA算法

队列优化算法
基于spfa(Shortest Path Faster Algorithm)算法思想编写,确实是路程最短、耗时最少的算法(就我之前写的这些算法而言)。可真是太棒了这个算法。至于这个算法本身我就不过多的赘述,可自行百度哦。
解释不下去了,我还是讲一下spfa

'''
      author:dragonbean
      href:https://www.cnblogs.com/dragonbean/p/13498421.html
      blog:https://www.cnblogs.com/dragonbean/
'''
def solution_spfa(start, end, graph):
    #核心代码
    def spfa(point):
        visualpoint = graph[point]
        #如果这个点可以直接可视终止点end,那么这个点到end点的距离一定是最短的,两点之间直线最短。
        if end in visualpoint:
            distance = math.sqrt((point[0]-end[0])**2 + (point[1]-end[1])**2)
            distance += roadDict[point][1]
            if roadDict.get(end):
                oldDistance = roadDict[end][1]
                if distance < oldDistance:
                    roadDict[end] = [point,distance]
            else:
                roadList.append(end)
                roadDict[end] = [point,distance]
        else:
            #一般情况处理【核心】
            for each in visualpoint:
                distance = math.sqrt((point[0]-each[0])**2 + (point[1]-each[1])**2)
                distance += roadDict[point][1]
                if roadDict.get(each):
                    oldDistance = roadDict[each][1]
                    if distance < oldDistance:
                        roadDict[each] = [point,distance]
                else:
                    roadList.append(each)
                    roadDict[each] = [point,distance]
    
    #反推出路径
    def getroad():
        road = [end]
        node = end
        while True:
            nextnode = roadDict.get(node)[0]
            road.append(nextnode)
            node = nextnode
            if nextnode == start:
                break
        road.reverse()
        return road
    
    roadDict = {start:[start,0]}
    roadList = [start]
    #这个遍历是实时更新的,不安全。
    for node in roadList:
        if node != end:
            spfa(node)
    
    result = getroad()
    return result

基于spfa思想,结合程序本身。我自己编写了一个简化版的spfa代码,不同于网上的spfa代码(主要是我不太看得懂,且没有耐心研读....)。

总结

solutions.py 看似是一个算法集合,但是自我将spfa算法放进去之后,经过测试,除了spfa,其他的算法我都看不上了(简直瞧不起自己),算法一的优化存在极大的问题,在某些(很多)情况下,1.5倍的两直线距离卡递归深度是简陋的,是极不稳定的,是跑不出来结果的。而算法二的直接寻路,局部最优的思想得到的并不一定是全局最优的结果,也就是说,他虽然在任何情况下都能跑出来结果,但是规划得到的路径不一定是最短路径,最优路径。而当前两个算法结合之后,以算法二寻得的次优路径值作为递归深度的阈值,计算速度还是相当可观的,计算结果也是全局最优的。
但是,but,but,but,spfa算法,不论是计算速度和计算结果,都是最优的,相比于算法一和算法二的结合,spfa的速断还是更快。毕竟虽然卡了递归深度,但是仍然要进行算法一的众多递归,而且先执行算法二寻找出一条可行的次优路径就已经浪费掉了时间,而之后又要再寻找一次最优路径,就相当于是执行了两次寻路算法,逻辑上都比spfa慢2倍,更别说递归的时间复杂度不是线性的。
因此以后的实验,都会选择用spfa来实现。
转载请注明出处:https://www.cnblogs.com/dragonbean/p/13498421.html

原文地址:https://www.cnblogs.com/dragonbean/p/13498421.html