#algorithm #search #priority-queue #depth-first-search
#алгоритм #Поиск #приоритет-очередь #поиск в глубину
Вопрос:
Ниже приведен псевдокод реализации поиска в глубину (DFS) с одним стеком и большой таблицей для обозначения посещенных узлов:
DFS(N0):
StackInit(S)
S.push((N0, null))
if isGoal(N0) then do
return true
markVisited(N0)
S.push((null, N0))
while !isEmpty(S) then do
(N, parent) := S.pop()
R := next((N, parent))
if isNull(R) then do
continue // So no new node add to this layer.
S.push((R, parent))
if marked(R) then do
continue
if isGoal(R) then do // If it's goal don't have to explore it.
return true
markVisited(R)
if depthMax((R, parent)) then do
continue
S.push((null, R))
return false
Проблема, которую я хочу решить, — это его модификация: он заменяет стек S
на PriorityQueue PQ
. Этот алгоритм используется для моделирования алгоритма IDA * (это изложено в учебнике, который, к сожалению, не написан на английском языке, поэтому я не буду приводить название ссылки / книги):
DFS2(N0, f, LIMIT):
PriorityQueueInit(PQ)
// A node (N, parent) stored in PQ represents a path from `N0` to `N`
passing the node `parent`; A node with smaller value on f() is
prioritized than those with larger value.
PQ.push((N0, null))
if isGoal(N0) then do
return true
markVisited(N0)
PQ.push((null, N0))
while !isEmpty(PQ) then do // (1)
(N, parent) := PQ.poll()
R := next((N, parent)) // (2)
if isNull(R) then do
continue
PQ.offer((R, parent))
if marked(R) then do
continue
if isGoal(R) then do
return true
markVisited(R)
if f((R, parent)) > LIMIT then do
continue
PQ.offer((null, R))
return false
- (1): В алгоритме A * приоритетная очередь используется для хранения узлов, которые еще не были исследованы, т.Е. Открытого списка. В то время как в первом псевдокоде DFS, который я предоставил, стек
S
является закрытым списком, поэтому я предполагаю, что во втором псевдокодеPQ
тоже есть закрытый список. Итак, как второй псевдокод может имитировать алгоритм IDA * с закрытым списком? - (2): Он извлекает текущий наименьший узел из
PQ
, который , вероятно, не является родственным узлом узлаN
, т. Е. Он переходит к другому поддереву из текущего поддерева , содержащегосяN
в нем. Какова цель этой строки?
Может кто-нибудь показать мне правильность второго алгоритма, т.Е. Почему его можно использовать в алгоритме IDA *?
обновлено для получения дополнительной информации: я потратил много времени и усилий на этот вопрос, но, похоже, это очень сложно из-за следующих моментов:
- Все графики, появляющиеся в учебнике, рисуются древовидно, т. Е. Только Один родительский элемент для каждого узла, чтобы показать концепции. Это меня смущает: работает ли второй алгоритм только для деревьев?
- Учитывая строку
if f((R, parent)) > LIMIT then do ...
Если второй также работает для графика, а не только для дерева, тогда может быть много родителей
R
, должен ли я рассматривать все случаи или только текущийparent
?
Комментарии:
1. (Раньше я не сталкивался с итеративным углублением A * . Можете ли вы указать, почему имитация более уместна, чем выполнение / реализация ?)
2. @greybeard: Да, я имел
DFS2(N0, f, LIMIT)
в виду, что может использоваться для реализации итеративного углубления A * в соответствии с книгой.3. поскольку в этом сообщении есть несколько вопросов, я не уверен, какой ответ вы ожидаете. Какие пункты вам нужны в ответе, чтобы ответить на него правильно?
4. @TomerShetah: Мое замешательство в том, что я не верю, что второй алгоритм будет реализовывать алгоритм IDA *. Итак, вы можете ответить только на эту строку: «Может ли кто-нибудь показать мне правильность второго алгоритма, т. Е. Почему его можно использовать в алгоритме IDA *?»
5. @TomerShetah: Всегда пожалуйста.
Ответ №1:
Здесь многое происходит.
Насколько я могу судить, этот код всегда возвращает первый целевой узел, который достигает вершины очереди. Согласно приведенным ниже предположениям о f и next, этот целевой узел является f-оптимальным. Но я бы не назвал это IDA *.
Во-первых, обычно и A *, и IDA * одновременно открывают всех соседей текущего узла. Этот код … не работает. Он использует итераторы, но только один цикл. Первый толчок предназначен для следующего родственного узла текущего узла, а второй толчок предназначен для дочернего узла. Я думаю, это нормально, за исключением того, что братья и сестры должны быть перечислены в возрастающем порядке f, чтобы многообещающий брат не скрывался после бесперспективного.
Во-вторых, в отличие от A *, IDA * не имеет закрытого списка. В некотором смысле IDA * всегда имеет дерево поиска, поскольку, если оно достигает двух эквивалентных узлов, оно все равно обрабатывает их так, как если бы они были отдельными узлами. У A * есть закрытый список, но он сложнее, чем представленный здесь. Способ, которым A * обрабатывает циклы, заключается в том, что, если он обнаруживает более дешевый путь к уже закрытому узлу, он снова открывает узел. Этот код этого не делает, поэтому он корректен только в том случае, если нет необходимости повторно открывать узел. В результате этому коду необходимо, чтобы f было так называемой последовательной эвристикой (на каждом пути f никогда не уменьшается по мере следования по пути), в то время как A * требуется только, чтобы f было допустимым (f никогда не недооценивает стоимость достижения целевого состояния).
Комментарии:
1. «Во-первых, обычно как A *, так и IDA * одновременно открывают всех соседей текущего узла». Да! Я пришел к тому же выводу. Я прочитал доказательство алгоритма кратчайшего пути Дейкстры с одним источником, и в доказательстве ключевым моментом является это.
2. Строка
R := next((N, parent)) // (2)
,next()
действительно, соответствует f-порядку, который вы имели в виду.3. Из вашего ответа я уверен, что код, приведенный в книге, неверен, я свяжусь с учителем (также автором книги), чтобы убедиться в этом. Вот награда ~