Логика алгоритма лабиринта на основе стека

Я новичок в программировании на C++ и работаю над алгоритмом решения лабиринта. Мне нужно использовать явный стек, чтобы отслеживать ходы выполнения, без рекурсии.

По сути, я отвечаю за алгоритм «Решатель», я могу проверить, доступен ли ход или заблокирован, и сделать это, или я могу отменить ход. Ходы влево, вправо и вперед. Уже есть код, который занимается отрисовкой и обновлением лабиринта.

Мне бы не помешала помощь в понимании базового алгоритма обхода лабиринта. Я просмотрел много разных программ для этого, но я просто не могу понять это. Лабиринт, который я решаю, генерируется случайным образом, без циклов.

Вот что я не могу уловить: скажем, у меня есть прямой участок стены, но есть еще и ответвление, выходящее из стены. Скажем, я решил пойти по этой другой ветке, но в конце концов она ведет в тупик. Я запихнул все ходы, которые сделал, в стек. По сути, как мне узнать, сколько ходов мне нужно вытолкнуть из стека, чтобы узнать, что я вернулся к исходному перекрестку, чтобы я мог выбрать другую ветвь вместо заблокированной?

Спасибо за любую помощь!


person user2302335    schedule 20.04.2013    source источник
comment
«Мне нужен стек… так что никакой рекурсии» — это не имеет смысла. Хочешь объяснить?   -  person Konrad Rudolph    schedule 20.04.2013
comment
возможный дубликат проблемы лабиринта и алгоритма рекурсивного возврата   -  person Moo-Juice    schedule 20.04.2013
comment
@ Moo-Juice Это плохой дубликат. Это вопрос решения лабиринта, но конкретный вопрос совсем другой.   -  person John Kugelman    schedule 20.04.2013
comment
@KonradRudolph Ты прав. Я имел в виду, что мы еще не работали с рекурсией, поэтому наш алгоритм решения не должен быть рекурсивным, но это упражнение на основе стека, как мы только что изучили.   -  person user2302335    schedule 20.04.2013
comment
@user2302335 user2302335 Интересно и странно, с дидактической точки зрения, обучение рекурсии обязательно должно предшествовать обучению структуре данных стека. Но ничего, ответ от этого не меняется.   -  person Konrad Rudolph    schedule 20.04.2013
comment
рекурсия вообще плохая идея, не знаю зачем ее всунули в молодые умы...   -  person nhed    schedule 20.04.2013
comment
@nhed На самом деле это не так, если только вы не делаете это неправильно.   -  person Cubic    schedule 20.04.2013
comment
@JohnKugelman, справедливое замечание - мой плохой.   -  person Moo-Juice    schedule 20.04.2013
comment
@Cubic, по моему опыту, никогда не бывает лучше ... поэтому, пожалуйста, просветите меня   -  person nhed    schedule 21.04.2013
comment
@nhed Рекурсивные решения иногда могут быть проще для чтения, и некоторые виды рекурсии в любом случае могут быть оптимизированы для циклов, поэтому фактических потерь нет.   -  person Cubic    schedule 21.04.2013
comment
@Cubic, если вы преобразуете его в цикл, он больше не будет рекурсивным. С другой стороны, вы потребляете пространство стека, которое может быть гораздо более ограниченным (по крайней мере, в некоторых средах), поэтому это хорошо только по академическим причинам, что восходит к моей исходной точке.   -  person nhed    schedule 03.05.2013
comment
@nhed Хм ... Я не говорю о том, что превращаю это в циклы. Я говорю об оптимизации хвостовых вызовов, которая позволяет вам вызывать функции в хвостовой позиции других функций в рекурсивном случае без необходимости вообще выделять какое-либо пространство стека. За исключением java, все языки, с которыми я работал до сих пор, поддерживают это (или, по крайней мере, хвостовой рекурсивный случай).   -  person Cubic    schedule 03.05.2013
comment
@Cubic код C/C++ должен быть очень конкретным, чтобы гарантировать, что оптимизация хвостового вызова действительно может продолжаться, последней вещью в функции должна быть рекурсия (правильно?), поэтому теперь у нас есть код, который будет хорошо себя вести при первом написано (возможно), а затем сойдет с ума в стеке, если кто-то еще изменит его для функциональности или отладки ... кошмар обслуживания ИМХО, я думаю, что мы должны просто согласиться с несогласием   -  person nhed    schedule 04.05.2013


Ответы (4)


Вот что я не могу понять: скажем, я решил пойти по этой другой ветке, но в конце концов она ведет в тупик. Я запихнул все ходы, которые сделал, в стек. По сути, как мне узнать, сколько ходов мне нужно вытолкнуть из стека, чтобы узнать, что я вернулся к исходному перекрестку, чтобы я мог выбрать другую ветвь вместо заблокированной?

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

Ходы влево, вправо и вперед.

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

Каждый шаг, который вы отступаете, проверяйте эти ходы снова. Если вы отменяете выбор право, вы будете знать, что пробовали влево и вправо, но еще не пробовали < em>вперед.

person Drew Dormann    schedule 20.04.2013
comment
Это хорошее решение с минимальным объемом памяти. Но у меня возникло ощущение, что использование стека было частью вопроса. - person Agentlien; 20.04.2013
comment
@Agentlien: здесь есть стек. Чтобы отменить выбор, вы должны его запомнить. И вы сделали много выборов, чтобы прийти к тому, что вы есть. - person Matthieu M.; 20.04.2013
comment
@MathieuM. Позвольте мне уточнить: хотя это и объясняет логику того, что нужно сделать, и это должно быть реализовано с использованием стека, я чувствую, что это пропускает то, как и почему стек появляется на картинке. Я чувствовал, что это была неотъемлемая часть того, что ОП, похоже, хотел разъяснить. - person Agentlien; 20.04.2013
comment
@Agentlien, надеюсь, ОП разъяснит, неясно ли использование стека. Мое впечатление от вопроса состоит в том, что толкающие и выталкивающие движения понятны. - person Drew Dormann; 20.04.2013
comment
Большое спасибо всем за очень полезные отзывы! Я думал слишком далеко вперед, теперь я понял алгоритм. Очень признателен. - person user2302335; 21.04.2013

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

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

Теперь посмотрите на позицию, которую вы вытащили из стека. Это ваше текущее положение.

Добавьте в стек все ходы из позиции, которую вы открыли, которые ведут к неисследованным позициям. Если какая-либо из них является целевой позицией, все готово.

Остальное позаботится само о себе. Подумай, попробуй несколько случаев на бумаге, увидишь. :)

person Agentlien    schedule 20.04.2013

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

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

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

Вероятно, лучше сохранить решение стека и пометить узлы в вашем стеке, чтобы указать ветвь, и какое направление (т. е. какое поддерево) ветви было < em>исследовано (или какие пути остались). Если вы всегда выполняете исследование в одном и том же порядке, этот тег может быть просто числом:

  • 0 слева
  • 1 для переда
  • 2 справа
  • 3 для возврата

или еще лучше перечисление.

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

enum Branch {
   LEFT,
   FORWARD,
   RIGHT,
   BACKTRACK
};

struct BacktrackException{
};

template <typename MazeMove>
struct StackNode {
    MazeMove move;
    Branch branch;
    StackNode(MazeMove m): move(m), branch(LEFT) {}
    MazeMove nextBranch(){
        switch(branch){
            case LEFT:
                if (move.can_left()){
                    branch = FORWARD;
                    return move.left();
                }
            case FORWARD:
                if (move.can_forward()){
                    branch = RIGHT;
                    return move.forward();
                }
            case RIGHT:
                if (move.can_right()){
                    branch = BACKTRACK;
                    return move.right();
                }
            default:
                throw BacktrackException();
        }
    }
};

Приведенный выше код предоставляет оболочку для возможного класса MazeMove, используемого со стеком, который отслеживает предпринятое направление. Метод nextBranch возвращает следующий возможный ход или выдает исключение.

Преимущество в том, что ваш стек не будет уничтожен непроверенными ходами. Вы нажимаете StackNode каждый раз, когда достигаете новой позиции, и раскручиваете ее, когда все ее варианты проверены. Когда вы доберетесь до выхода из лабиринта, ваш стек будет содержать только необходимые ходы.

person didierc    schedule 20.04.2013

По сути, как мне узнать, сколько ходов мне нужно вытолкнуть из стека, чтобы узнать, что я вернулся к исходному перекрестку, чтобы я мог выбрать другую ветвь вместо заблокированной?

Нет — вы всегда возвращаетесь ровно на один шаг назад. Затем проверьте все (оставшиеся) альтернативы, затем вернитесь на один шаг назад и т. д. Это называется возвратом. .

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

person Konrad Rudolph    schedule 20.04.2013