Теория программирования: решите лабиринт

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

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

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

второй проходит через каждый последующий элемент, отмеченный 1, проверяет, куда он может идти (вверх, вправо, вниз, влево), выбирает один путь и продолжает свой путь там. Это даже медленнее, чем первый.

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

должны быть лучшие решения для отправки бота через лабиринт.

редактировать
Во-первых: спасибо за хороший ответы!

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

третий вопрос:
Какой из этих алгоритмов Maze solver является / является самым быстрым? (Чисто гипотетически)

14 ответов


вы можете думать о своем лабиринте как о дереве.

     A
    / \
   /   \
  B     C
 / \   / \
D   E F   G
   / \     \
  H   I     J
 / \
L   M
   / \
  **  O

(which could possibly represent)

        START
        +   +---+---+
        | A   C   G |
    +---+   +   +   +
    | D   B | F | J |
+---+---+   +---+---+
| L   H   E   I |
+---+   +---+---+
    | M   O |
    +   +---+
    FINISH

(ignoring left-right ordering on the tree)

где каждый узел является узлом путей. D, I, J, L и O-тупики, и * * - это цель. Конечно, в вашем дереве каждый узел имеет возможность иметь как три дети.

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

глядя на дерево, довольно легко увидеть ваш правильный решение, просто "прослеживая" от ** в самой глубокой части дерева:

A B E H M **

обратите внимание, что этот подход становится только немного сложнее, когда у вас есть" петли " в вашем лабиринте (т. е., когда это возможно, без возврата, вы снова входите в проход, который вы уже прошли). Проверьте комментарии для одного хорошего решения.

теперь, давайте посмотрим на ваше первое решение, которое вы упомянули, применяется к этому дереву.

ваше первое решение это в основном Глубину, который на самом деле не так уж плохо. На самом деле это довольно хороший рекурсивный поиск. В основном, он говорит :" всегда сначала берите самый правый подход. Если ничего нет, пройдите назад до первого места, где вы можете идти прямо или налево,а затем повторите.

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

A B D (backtrack) E H L (backtrack) M ** (backtrack) O (backtrack thrice) I
(backtrack thrice) C F (backtrack) G J

обратите внимание, что вы можете остановиться, как только вы найти **.

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

другой способ поиска дерева -Ширину решение, которое ищет через деревья на глубину. Он будет искать через вышеупомянутое дерево в этом порядок:

A (next level) B C (next level) D E F G (next level)
H I J (next level) L M (next level) ** O

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

на самом деле, есть целый обширный список способов поиска дерево. Я только что упомянул два самых простых и простых способа.

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

он в основном работает следующим образом:

  1. поместите один путь в очередь (путь, по которому вы идете только один шаг прямо в лабиринт). Путь имеет "вес", заданный его текущей длиной + его прямым расстоянием от конца (которое может быть вычислено математически)
  2. Pop путь с наименьшим весом из очереди.
  3. "взорвать" путь в каждый путь, который может быть после одного шага. (т. е., если ваш путь Правый левый левый правый, тогда ваши взорванные пути-это R L L R R и R L L R L, не включая незаконные, которые проходят через стены)
  4. если один из этих путей имеет цель, то победа! Иначе:
  5. вычислите веса взорванных путей и поместите их все обратно в очередь (не включая исходный путь)
  6. сортировка очереди по весу, сначала самая низкая. Затем повторите с шага #2

и A*, которые я представляем специально выделил, потому что это более или менее стандартный алгоритм поиска пути для все применения pathfinding, включая двигать от одного края карты к другим пока избегающ внедорожных путей или гор, etc. Он работает так хорошо, потому что он использует кратчайшее возможное расстояние эвристика, что дает ему его "интеллекта". A * настолько универсален, потому что, учитывая любую проблему, если у вас есть эвристика кратчайшего расстояния (наша просто прямая линия), вы можете применить его.

но очень важно отметить, что A * is не ваш единственный вариант.

в самом деле Википедия категория алгоритмов обхода дерева списки только 97! (лучшее все равно будет на на этой странице связаны раньше)

извините за длину =P (я склонен бродить)


существует множество алгоритмов решения лабиринтов:

http://en.wikipedia.org/wiki/Maze_solving_algorithm

http://www.astrolog.org/labyrnth/algrithm.htm#solve

для робота, Тремо это выглядит многообещающе.


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

Если вы посмотрите на дерево, которое Джастин поставил в своем ответе, вы увидите, что узлы листьев имеют 3 стены. Подрежьте дерево, пока не найдете дорогу.


Как насчет построения графика из вашей матрицы и использования широты первого поиска, глубины первого поиска или алгоритма Dijkstras?


Это один из моих любимых алгоритмов....

1) Move forward
2) Are you at a wall?
2a) If yes, turn left
3) Are you at the finish?
3a) If no, go to 1
3b) If yes, solved

Это очень простое представление для имитации лабиринта в C++:)

#ifndef vAlgorithms_Interview_graph_maze_better_h
#define vAlgorithms_Interview_graph_maze_better_h

static const int kMaxRows = 100;
static const int kMaxColumns = 100;

class MazeSolver
    {
private:
    char m_matrix[kMaxRows][kMaxColumns]; //matrix representation of graph
    int rows, cols; //actual rows and columns

    bool m_exit_found;
    int m_exit_row, m_exit_col;
    int m_entrance_row, m_entrance_col;

    struct square //abstraction for data stored in every verex
        {
        pair<int, int> m_coord; //x and y co-ordinates of the matrix
        square* m_parent; //to trace the path backwards

        square() : m_parent(0) {}
        };

    queue<square*> Q;

public:
    MazeSolver(const char* filename)
        : m_exit_found(false)
        , m_exit_row(0)
        , m_exit_col(0)
        , m_entrance_row(0)
        , m_entrance_col(0)
        {
        ifstream file;
        file.open(filename);

        if(!file)
            {
            cout << "could not open the file" << endl << flush;
            // in real world, put this in second phase constructor
            }
        init_matrix(file);
        }
    ~MazeSolver()
        {
        }
    void solve_maze()
        {
        //we will basically use BFS: keep pushing squares on q, visit all 4 neighbors and see
        //which way can we proceed depending on obstacle(wall)

        square* s = new square();
        s->m_coord = make_pair(m_entrance_row, m_entrance_col);

        Q.push(s);

        while(!m_exit_found && !Q.empty())
            {
            s = Q.front();
            Q.pop();

            int x = s->m_coord.first;
            int y = s->m_coord.second;
            //check if this square is an exit cell
            if(x == m_exit_row && y == m_exit_col)
                {
                m_matrix[x][y] = '>'; // end of the path
                m_exit_found = true;
                //todo: try breaking? no= queue wont empty
                }
            else
                {
                //try walking all 4 neighbors and select best path
                //NOTE: Since we check all 4 neighbors simultaneously,
                //      the path will be the shortest path
                walk_path(x-1, y, s);
                walk_path(x+1, y, s);
                walk_path(x, y-1, s);
                walk_path(x, y+1, s);
                }
            } /* end while */

        clear_maze(); //unset all previously marked visited shit

        //put the traversed path in maze for printing
        while(s->m_parent)
            {
            m_matrix[s->m_coord.first][s->m_coord.second] = '-';
            s = s->m_parent;
            } /* end while */
        }

    void print()
        {
        for(int i=0; i<rows; i++)
            {
            for(int j=0; j<cols; j++)
                cout << m_matrix[i][j];
            cout << endl << flush;
            }
        }

private:
    void init_matrix(ifstream& file)
        {
        //read the contents line-wise
        string line;
        int row=0;
        while(!file.eof())
            {
            std::getline(file, line);
            for(int i=0; i<line.size(); i++)
                {
                m_matrix[row][i] = line[i];
                }
            row++;
            if(line.size() > 0)
                {
                cols = line.size();
                }
            } /* end while */
        rows = row - 1;

        find_exit_and_entry();
        m_exit_found = false;
        }

    //find and mark ramp and exit points
    void find_exit_and_entry()
        {
        for(int i=0; i<rows; i++)
            {
            if(m_matrix[i][cols-1] == ' ')
                {
                m_exit_row = i;
                m_exit_col = cols - 1;
                }
            if(m_matrix[i][0] == ' ')
                {
                m_entrance_row = i;
                m_entrance_col = 0;
                }
            } /* end for */
        //mark entry and exit for testing
        m_matrix[m_entrance_row][m_entrance_col] = 's';
        m_matrix[m_exit_row][m_exit_col] = 'e';
        }

    void clear_maze()
        {
        for(int x=0; x<rows; x++)
            for(int y=0; y<cols; y++)
                if(m_matrix[x][y] == '-')
                    m_matrix[x][y] = ' ';
        }
        // Take a square, see if it's the exit. If not, 
        // push it onto the queue so its (possible) pathways
        // are checked.
    void walk_path(int x, int y, square* parent)
        {
        if(m_exit_found) return;
        if(x==m_exit_row && y==m_exit_col)
            {
            m_matrix[x][y] = '>';
            m_exit_found = true;
            }
        else
            {
            if(can_walk_at(x, y))
                {
                //tag this cell as visited
                m_matrix[x][y] = '-';

                cout << "can walk = " << x << ", " << y << endl << flush;

                //add to queue
                square* s = new square();
                s->m_parent = parent;
                s->m_coord = make_pair(x, y);
                Q.push(s);
                }
            }
        }

    bool can_walk_at(int x, int y)
        {
        bool oob = is_out_of_bounds(x, y);
        bool visited = m_matrix[x][y] == '-';
        bool walled = m_matrix[x][y] == '#';

        return ( !oob && !visited && !walled);
        }
    bool is_out_of_bounds(int x, int y)
        {
        if(x<0 || x > rows || y<0 || y>cols)
            return true;
        return false;
        }
    };


void run_test_graph_maze_better()
        {
        MazeSolver m("/Users/vshakya/Dropbox/private/graph/maze.txt");
        m.print();
        m.solve_maze();
        m.print();
        }


#endif

просто идея. Почему бы не бросить туда несколько ботов в стиле Монте-Карло. Назовем первое поколение ботов gen0. Мы только держим ботов от gen0, которые имеют некоторые непрерывные дороги таким образом:
-от начала до некоторой точки
или - от какой-то точки до конца

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

Так для genn мы пытаемся соединиться с ботами формы gen0, gen1,... Генн-1.

конечно, поколение длится только конечное количество времени feasibil.

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


некоторые хорошие сайты для идей:
http://citeseerx.ist.psu.edu/
http://arxiv.org/


у меня была аналогичная проблема в одном из моих университетских компов. Научный. курсы. Решение, которое мы придумали, было следовать левой стене (правая стена будет работать так же хорошо). Вот какой-то псевдокод

While Not At End
    If Square To Left is open,
        Rotate Left
        Go Forward
    Else
        Rotate Right
    End If
Wend

это в основном все. Сложная часть отслеживает, в каком направлении ваша сторона, и выясняет, какое положение сетки слева от вас на основе этого направления. Это срабатывало для любого теста, который я ставил против него. Достаточно интересным было решение профессоров что-то вроде:

While Not At End
    If Can Go North
        Go North
    ElseIf Can Go East
        Go East
    ElseIf Can Go South
        Go South
    ElseIf Can Go West 
        Go West
    EndIf
Wend

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

SXXXXXXXXXXXXX
   X         X
   X         X
   X         X
 XXX         X
 X X         X
 X XXXXXXXXXXX     XXXE
 X                 X
 XXXXXXXXXXXXXXXXXXX

С S и E, являющимися началом и концом.

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


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

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


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

предположим, у вас есть следующие свойства...

  • вы перемещаете робота, и вы хотите минимизировать его движения, а не ЦП.
  • этот робот может либо проверять только соседние ячейки, либо смотреть вдоль коридоры либо видеть, либо не видеть перекрестные пути.
  • это GPS.
  • он знает координаты места назначения.

затем вы можете проектировать А. И., который...

  • рисует карту – каждый раз, когда он получает новую информацию о лабиринте.
  • вычисляет минимальное известное путь длиной между все ненаблюдаемыми должности (а сам и пункт назначения).
  • определить приоритеты ненаблюдаемыми позиции для проверки на основе окружающие структуры. (если невозможно добраться до места назначения оттуда в любом случае...)
  • может приоритизировать ненаблюдаемые позиции для проверки на основе направлении и на расстоянии до места назначения.
  • может приоритизировать ненаблюдаемые позиции для проверки на основе опыт сбора информации. (как далеко он может видеть в среднем и как далеко он должен пешком?)
  • может приоритизировать ненаблюдаемые позиции до найти возможные пути. (опыт: есть много петли?)

такой же ответ, как и все вопросы по переполнению стека;)

использовать vi!

http://www.texteditors.org/cgi-bin/wiki.pl?Vi-Maze

Это действительно увлекательно видеть, как текстовый редактор решает ascii-лабиринт, я уверен, что у парней emacs есть эквивалент ..


этот алгоритм azkaban также может помочь вам, http://journals.analysisofalgorithms.com/2011/08/efficient-maze-solving-approach-with.html


лучший способ решить лабиринт-использовать алгоритм связности, такой как union-find, который является квазилинейным алгоритмом времени, предполагающим сжатие пути.

Union-Find-это структура данных, которая сообщает вам, связаны ли два элемента в наборе транзитивно.

чтобы использовать структуру данных union-find для решения лабиринта, сначала данные соседнего подключения используются для построения структуры данных union-find. Затем объединение найти сжимается. Определить, лабиринт разрешим значения входа и выхода сравниваются. Если они имеют одинаковое значение, то они связаны и лабиринт разрешима. Наконец, чтобы найти решение, вы начинаете с входа и исследуете корень, связанный с каждым из его соседей. Как только вы находите ранее не посещенного соседа с тем же корнем, что и текущая ячейка, вы посещаете эту ячейку и повторяете процесс.

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


не специально для вашего случая, но я столкнулся с несколькими вопросами конкурса программирования, где я нашел ли довольно удобно для быстрого кодирования. Его не самый эффективный для всех случаев,но легко провернуть. Вот!--3-->один я взломал для конкурса.