Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
pathfinding.doc
Скачиваний:
43
Добавлен:
18.04.2015
Размер:
351.74 Кб
Скачать

Алгоритм a* История создания

В 1964 году Нильс Нильсон изобрел эвристический подход к увеличению скорости алгоритма Дейкстры. Этот алгоритм был назван А1. В 1967 году Бертрам Рафаэль сделал значительные улучшения по этому алгоритму, но ему не удалось достичь оптимальности. Он назвал этот алгоритм A2. Тогда в 1968 году Петр Э. Харт представил аргументы, которые доказывали, что A2 был оптимальным при использовании последовательной эвристики лишь с незначительными изменениями. В его доказательство алгоритма также включен раздел, который показывал, что новый алгоритм A2 был, возможно, лучшим алгоритмом, учитывая условия.

Принцип работы

Практически, алгоритм A* отличается от алгоритма Дейкстры направленностью обхода узлов графа за счёт использования эвристической функции, определяющей ориентировочное расстояние между данным узлом и концом пути. Иными словами, приоритет отдаётся тем узлам, которые согласно эвристической функции находятся ближе к концу пути.

A* пошагово просматривает все пути, ведущие от начальной вершины в конечную, пока не найдёт минимальный. Сначала рассматриваются те маршруты, которые «кажутся» ведущими к цели. В начале работы просматриваются узлы, смежные с начальным; выбирается тот из них, который имеет минимальное значение эвристической функции, после чего этот узел раскрывается.

В случае с графом, алгоритм продолжает свою работу до тех пор, пока значение f(x) целевой вершины не окажется меньшим, чем любое значение в очереди (либо пока всё дерево не будет просмотрено). Из множественных решений выбирается решение с наименьшей стоимостью.

В случае с двумерным массивом, A* действует подобно направленному волновому алгоритму, поэтому при достижении им конечной точки, формирование кратчайшего пути уже становится возможным и совершается незамедлительно.

Эффективность.

Алгоритм A* на данный момент является оптимальным способом поиска пути между двумя точками в тех случаях, когда существует сравнительно простой эвристический метод оценки расстояния между элементами области поиска. Если такого метода не существует,A* идентичен либо алгоритму Дейкстры в вариации для двух точек, либо волновому алгоритму в зависимости от вида области поиска.

Также алгоритм A* не оптимален, если область поиска статична и поиск пути на ней осуществляется множество раз, поскольку в таком случае все пути можно заранее рассчитать при помощи алгоритма Дейкстры для всех точек.

Практика

На данном примере алгоритм A* будет реализован для двумерного массива наPython2.7.

Теперь о тонкостях реализации: то, что отличает A* от алгоритма Дейкстры и Волнового алгоритма – эвристическая функция оценки расстояния от текущего узла до конечного в данном случае легко выводится из координат этих точек в области поиска, по сути – индексов этих точек в двумерном массиве, представляющем эту область. Поскольку функция эвристическая и точных значений от неё не требуется, можно использовать даже не формулу расстояния между точками в системе координат а просто модуль разности этих координат. Результат в подавляющем большинстве случаев будет одинаковый.

Теперь что касается области поиска. Двумерный массив как и при реализации волнового алгоритма состоит из полностью проходимых и полностью непроходимых элементов. В данном случае проходимые элементы будут представлены символом « » (пробел), а непроходимые – «#». Элемент начала будет представлен буквой «А», конца – «B», а положение исполнителя –«*» . Для удобства создания лабиринтов и создадим функцию, позволяющую вводить их поэлементно с текстовым интерфейсом ввода:

def stepbystep():

length=input('Ширина лабиринта(без учёта границ):')

higth=input('Высота лабиринта(без учёта границ):')

lab=[]

numrow=['_','@','@']

abc=['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','R','Q','S','T','U','V','W','X','Y','Z']

for i in range(1,length+1):

numrow.insert(2,abc[length-i])

labhigh=0

border=[]

start=0

orda=length

absa=higth

finorda=0

finabsa=0

fin=0

while len(border)<2+length :

border+='#'

lab+=[border]

while labhigh<higth :

row=['#']

rowleng=0

while rowleng<length :

print numrow

for i in xrange(0,len(lab)):

print i, ': ', lab[i]

print len(lab), ': ', row, '\n 1 - препятствие \n 2 - начало(обязательно одно) \n 3 – конец лабиринта(один) \n другой символ - пустота \n просьба не делать колонн'

elem = input('Следуюий эллемент:')

if elem==1:

row+='#'

rowleng+=1

elif elem ==2 and start==0:

row+='A'

absa=startabs=len(row)-1

orda=startord=len(lab)

rowleng+=1

start=1

elif elem == 3 and fin==0:

row+='B'

fin=1

finabsa=(len(row)-1)

finorda=(len(lab))

rowleng+=1

else:

row+=' '

rowleng+=1

row+='#'

lab+=[row]

labhigh+=1

lab+=[border]

print "Конечный вариант\n",numrow

for j in xrange(0,len(lab)):

print j, ': ',[lab[j][i] for i in xrange (0,len(lab[j])) ]

print '\n \n'

return lab,[orda,absa],[finorda,finabsa]

Данная функция непосредственно к процессу поиска пути отношения не имеет, кроме чего весьма примитивна, посему комментарии к ней на мой взгляд излишни.

Теперь было бы рационально заранее описать функцию, которая будет генерировать список связей заданного в качестве аргумента элемента области поиска.

def getConnections(massiv,y,x):

connections=[]

class cct:

getCost=None

getToNode=None

getFromNode=None

if massiv[y][x-1]!='#' and massiv[y-1][x]!='#'and massiv[y-1][x-1]!='#':

connection=cct()

connection.getToNode=[y-1,x-1]

connection.getFromNode=[y,x]

connection.getCost=14 connections.append(connection)

if massiv[y][x-1]!='#' and massiv[y+1][x]!='#'and massiv[y+1][x-1]!='#':

connection=cct()

connection.getToNode=[y+1,x-1]

connection.getFromNode=[y,x]

connection.getCost=14 connections.append(connection)

if massiv[y][x+1]!='#' and massiv[y-1][x]!='#'and massiv[y-1][x+1]!='#':

connection=cct()

connection.getToNode=[y-1,x+1]

connection.getFromNode=[y,x]

connection.getCost=14 connections.append(connection)

if massiv[y][x+1]!='#' and massiv[y+1][x]!='#'and massiv[y+1][x+1]!='#':

connection=cct()

connection.getToNode=[y+1,x+1]

connection.getFromNode=[y,x]

connection.getCost=14

connections.append(connection)

if massiv[y][x+1]!='#':

connection=cct()

connection.getToNode=[y,x+1]

connection.getFromNode=[y,x]

connection.getCost=10

connections.append(connection)

if massiv[y][x-1]!='#':

connection=cct()

connection.getToNode=[y,x-1]

connection.getFromNode=[y,x]

connection.getCost=10

connections.append(connection)

if massiv[y-1][x]!='#':

connection=cct()

connection.getToNode=[y-1,x]

connection.getFromNode=[y,x]

cjnnection.getCost=10

connections.append(connection)

if massiv[y+1][x]!='#':

connection=cct()

connection.getToNode=[y+1,x]

connection.getFromNode=[y,x]

connection.getCost=10

connections.append(connection)

return connections

Данная функция возвращает список экземпляров класса cct, т.е. информации о связи. Она будет применяться при обработке узла в процессе основного алгоритма. Помимо движений по вертикали или горизонтали, данная функция будет поддерживать движение в диагональных направлениях, при этом не позволяет такое движение, если в его процессе исполнитель будет проходить через непроходимую область.

Наконец, создадим функцию, отвечающую за взаимодействие с пользователем:

def printAstar():

graph,start,end=stepbystep()

rslt= AStar(graph, start, end)

if rslt==-1:

return 'Непроходимо!'

graph[rslt[0][0]][rslt[0][1]]='*'

print '\n\nНачалопрохождения'

for j in xrange(0,len(graph)):

print [graph[j][k] for k in xrange (0,len(graph[j])) ]

for i in range(len(rslt)-1):

graph[rslt[i+1][0]][rslt[i+1][1]]='*'

graph[rslt[i][0]][rslt[i][1]]=' '

print "Следующийшаг#",i+1

for j in xrange(0,len(graph)):

print [graph[j][k] for k in xrange (0,len(graph[j])) ]

print '\n \n'

return 'Прохождение заверщено!'

Данная функция позволяет пользователю создать область поиска и выбрать точки начала и конца пути, после чего запускает поиск пути и выводит его результаты в наглядном пошаговом виде пользователю.

Теперь создадим непосредственно саму функцию поиска пути в заданной среде. Поскольку A* является модификацией алгоритма Дейкстры, описанного немного ранее, его описание будет также получено путём модификации созданного ранее алгоритма Дейкстры.

Полученный алгоритм будет выглядеть следующим образом:

def AStar(graph, start, end):

class NodeRecord:

node=None

connection=None

costSoFar=None

estimatedTotalCost=None

startRecord =NodeRecord()

startRecord.node = start

startRecord.connection = None

startRecord.costSoFar = 0

startRecord.estimatedTotalCost =((sum(end)-sum(start))**2)**0.5

olist= [startRecord]

clist = []

while len(olist) > 0:

current=olist[-1]

for nodenum in range(len(olist)):

if olist[nodenum].estimatedTotalCost<current.estimatedTotalCost:

current = olist[nodenum]

if current.node == end:

break

connections = getConnections(graph,current.node[0],current.node[1])

for connection in connections:

endNode = connection.getToNode

endNodeCost = current.costSoFar + connection.getCost

if endNode in [clist[ite].node for ite in range(len(clist))]:

endNodeRecord = clist[[clist[ite].node for ite in range(len(clist))].index(endNode)]

if endNodeRecord.costSoFar <= endNodeCost:

continue

clist.pop(clist.index(endNodeRecord))

endNodeHeuristic = endNodeRecord.estimatedTotalCost - endNodeRecord.costSoFar

elif endNode in [olist[ite].node for ite in range(len(olist))]:

endNodeRecord = olist[[olist[ite].node for ite in range(len(olist))].index(endNode)]

if endNodeRecord.costSoFar <= endNodeCost:

continue

endNodeHeuristic = endNodeRecord.estimatedTotalCost - endNodeRecord.costSoFar

else:

endNodeRecord = NodeRecord()

endNodeRecord.node = endNode

endNodeHeuristic = ((sum(end)-sum(endNode))**2)**0.5

endNodeRecord.costSoFar = endNodeCost

endNodeRecord.connection = connection.getFromNode

endNodeRecord.estimatedTotalCost = endNodeCost + endNodeHeuristic

if not endNodeRecord in olist :

olist.append(endNodeRecord)

clist.append(current)

olist.pop(olist.index(current))

if current.node != end:

return -1

else:

path = [end]

while current.node!=start:

for i in clist:

if current.connection==i.node:

current=i

path.insert(0,current.node)

break

return path

Рассмотрим его поэтапно, акцентируя внимание на отличиях от алгоритма Дейкстры:

def AStar(graph, start, end):

class NodeRecord:

node=None

connection=None

costSoFar=None

estimatedTotalCost=None

В описание структуры данных, хранящей информацию об обработанном элементе области поиска, мы добавляем переменную estimatedTotalCost, хранящую ориентировочное расстояние от данного элемента до конечной точки пути, вычисленное эвристически. Кроме того, вследствие перехода с графа на двумерный массив, переменнаяnodeбудет хранить не название данного элемента (никаких особых названий они не имеют), а его координаты в области поиска. Координаты эти инвертированы для удобства подстановки в массив, обозначающий область поиска.

startRecord =NodeRecord()

startRecord.node = start

startRecord.connection = None

startRecord.costSoFar = 0

startRecord.estimatedTotalCost =((sum(end)-sum(start))**2)**0.5

Как и в алгоритме Дейкстры, вначале обрабатывается отдельно узел начала пути, но в данном алгоритме мы также вычисляем для него значение ориентировочного расстояния до концапути.

olist= [startRecord]

clist = []

В алгоритме A* также используются два списка узлов:

  1. Открытый список (olist) – множество необработанных узлов, к которым можно перейти из уже обработанных.

  2. Закрытый список (clist) – множество уже обработанных узлов.

Различия состоят лишь в том, что элементы этих списков будут несколько другими структурами данных, о чём было подробно рассказано при описании этих структур.

while len(olist) > 0:

Алгоритм также будет работать до тех пор, пока остались доступные необработанные узлы, но теперь данный цикл также сворачивается при достижении узла конца пути, что допустимо в силу специфики области поиска.

current=olist[-1]

for nodenum in range(len(olist)):

if olist[nodenum].estimatedTotalCost<current.estimatedTotalCost:

current = olist[nodenum]

На данном этапе выбирается узел из открытого списка, который согласно эвристике будет ближе всего к концу пути.

if current.node == end:

break

Как и было ранее заявлено, фаза обработки узлов будет закончена, как только будет достигнут узел конца пути. Дальнейшая обработка области поиска не имеет смысла, поскольку A* рассчитан исключительно на поиск пути между двумя точками.

connections = getConnections(graph,current.node[0],current.node[1])

for connection in connections:

endNode = connection.getToNode

endNodeCost = current.costSoFar + connection.getCost

if endNode in [clist[ite].node for ite in range(len(clist))]:

endNodeRecord = clist[[clist[ite].node for ite in range(len(clist))].index(endNode)]

if endNodeRecord.costSoFar <= endNodeCost:

continue

clist.pop(clist.index(endNodeRecord))

endNodeHeuristic = endNodeRecord.estimatedTotalCost - endNodeRecord.costSoFar

elif endNode in [olist[ite].node for ite in range(len(olist))]:

endNodeRecord = olist[[olist[ite].node for ite in range(len(olist))].index(endNode)]

if endNodeRecord.costSoFar <= endNodeCost:

continue

endNodeHeuristic = endNodeRecord.estimatedTotalCost - endNodeRecord.costSoFar

else:

endNodeRecord = NodeRecord()

endNodeRecord.node = endNode

endNodeHeuristic = ((sum(end)-sum(endNode))**2)**0.5

endNodeRecord.costSoFar = endNodeCost

endNodeRecord.connection = connection.getFromNode

endNodeRecord.estimatedTotalCost = endNodeCost + endNodeHeuristic

if not endNodeRecord in olist :

olist.append(endNodeRecord)

clist.append(current)

olist.pop(olist.index(current))

Далее, А* как и алгоритм Дейкстры проходит по связям выбранного узла и актуализирует информацию об узлах. Единственное отличие – операции с эвристическими расчётами. Стоит заметить, что хотя в данном случае это и малозаметно, но в случае с более сложными эвристическими функциями выгоднее при возможности вместо перевычисления таких функций просто откатывать их значения до предыдущих.

if current.node != end:

return -1

else:

path = [end]

while current.node!=start:

for i in clist:

if current.connection==i.node:

current=i

path.insert(0,current.node)

break

return path

После окончания фазы обработки графа, путь всё так же генерируется по закрытому списку. Различия в коде обусловлены лишь изменением в способе хранения информации о связи. Также в данный алгоритм встроен счётчик суммарной длины пути.

Взаимодействие пользователя с программой при этом выглядит примерно следующим образом:

Введите новый лабиринт

Ширина лабиринта(без учёта границ):3

Высота лабиринта(без учёта границ):3

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:2

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:4

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:4

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:1

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#', '#']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:1

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#', '#', '#']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:4

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#', '#', '#', ' ', '#']

3 : ['#']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:3

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#', '#', '#', ' ', '#']

3 : ['#', 'B']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:4

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#', '#', '#', ' ', '#']

3 : ['#', 'B', ' ']

1 - препятствие

2 - начало(обязательно одно)

3 - (желательно рядом со стенкой не по-диагонали)

другой символ - пустота

просьба не делать колонн

Следуюий эллемент:4

Конечный вариант

['_', '@', 'A', 'B', 'C', '@']

0 : ['#', '#', '#', '#', '#']

1 : ['#', 'A', ' ', ' ', '#']

2 : ['#', '#', '#', ' ', '#']

3 : ['#', 'B', ' ', ' ', '#']

4 : ['#', '#', '#', '#', '#']

Начало прохождения

['#', '#', '#', '#', '#']

['#', '*', ' ', ' ', '#']

['#', '#', '#', ' ', '#']

['#', 'B', ' ', ' ', '#']

['#', '#', '#', '#', '#']

Следующий шаг # 1

['#', '#', '#', '#', '#']

['#', ' ', '*', ' ', '#']

['#', '#', '#', ' ', '#']

['#', 'B', ' ', ' ', '#']

['#', '#', '#', '#', '#']

Следующий шаг # 2

['#', '#', '#', '#', '#']

['#', ' ', ' ', '*', '#']

['#', '#', '#', ' ', '#']

['#', 'B', ' ', ' ', '#']

['#', '#', '#', '#', '#']

Следующий шаг # 3

['#', '#', '#', '#', '#']

['#', ' ', ' ', ' ', '#']

['#', '#', '#', '*', '#']

['#', 'B', ' ', ' ', '#']

['#', '#', '#', '#', '#']

Следующий шаг # 4

['#', '#', '#', '#', '#']

['#', ' ', ' ', ' ', '#']

['#', '#', '#', ' ', '#']

['#', 'B', ' ', '*', '#']

['#', '#', '#', '#', '#']

Следующий шаг # 5

['#', '#', '#', '#', '#']

['#', ' ', ' ', ' ', '#']

['#', '#', '#', ' ', '#']

['#', 'B', '*', ' ', '#']

['#', '#', '#', '#', '#']

Следующий шаг # 6

['#', '#', '#', '#', '#']

['#', ' ', ' ', ' ', '#']

['#', '#', '#', ' ', '#']

['#', '*', ' ', ' ', '#']

['#', '#', '#', '#', '#']

Прохождение заверщено!

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]