Как стать автором
Обновить
89.59
Skillfactory
Онлайн-школа IT-профессий

Как написать решатель «Пятнашек» на C#

Время на прочтение15 мин
Количество просмотров15K
Автор оригинала: Arnaldo Perez Castano

Цель этой статьи — пробудить интерес читателей к удивительному миру и показать различные способы решения таких же интересных головоломок, как «Пятнашки». Создайте свою базу данных с шаблонами и начните решать головоломки менее чем за 50 миллисекунд. Материалом делимся к старту курса по разработке на C#.


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

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

Человеческий интеллект остаётся самым способным, сложным, совершенным в известной нам части Вселенной. А если это так и в ближайшем будущем вряд ли что-либо сравнится с ним по мощи, которой он сегодня обладает, то зачем создавать ИИ? 

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

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

Можно привести другой пример ИИ с использованием рационального агента — программы на C# для решения головоломки «Пятнашки». Рациональность агента здесь обеспечивается алгоритмом поиска A*, который связан с эвристикой, направляющей поиск к максимальному результату.

Головоломка

Считается, что головоломку «Пятнашки» в 1870-х гг. создал шахматист и составитель шахматных задач Сэм Лойд (1841–1911). Она состоит из игрового поля размером N x M (рис. 1), в каждой клетке которого может быть всё что угодно: цифра, буква, изображение и т. д.:

1. Игровое поле размером 3 х 3
1. Игровое поле размером 3 х 3

Цель головоломки — переставить клетки из начального состояния так, чтобы все они в итоге располагались в соответствии с конфигурацией целевого (рис. 2).

Перемещение клеток из начального состояния в целевое

Целевое состояние достигается за несколько ходов. Каждый ход — это замена пустой клетки на соседнюю. Вот решение предыдущего примера:

2. Чтобы достичь целевого состояния, нужно переместить две клетки
2. Чтобы достичь целевого состояния, нужно переместить две клетки

Чтобы достичь цели необходима последовательность движений; один ход — это замена пустой плитки на другую плитку. Предыдущий пример решается, как показано на рисунке 3.

3. Решение головоломки
3. Решение головоломки

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

4. Сможете решить эту задачу размером 3 x 3?
4. Сможете решить эту задачу размером 3 x 3?

Кое-кто утверждал, что нашёл решение, но на самом деле решения нет. Как же узнать, есть ли решение у исходной конфигурации? Доказано, что конфигурация имеет решение при выполнении следующих условий:

  • Если ширина поля — нечётное число, для нахождения решения нужно сделать чётное число перестановок.

  • Если ширина поля — чётное число, для нахождения решения нужно сделать: а) чётное число перестановок, если пустая клетка находится на нечётной строке, считая снизу; б) нечётное число перестановок, если пустая клетка находится на чётной строке, считая снизу.

Перестановка происходит, когда за одной клеткой следует другая с меньшим значением. В целевом состоянии перестановок не будет. Например, в головоломке 4 х 4, где все числа идут по порядку, кроме числа 12 (оно в левом верхнем углу), будет 11 перестановок. В общем случае две клетки a и b меняются местами, если b идёт после a, но b меньше a.

Алгоритм A*

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

5. Информированный поиск A*
5. Информированный поиск A*

Ключевой элемент A* находится в значении, которое присваивается каждому состоянию и даёт возможность резко сократить время на поиск целевого состояния из заданного начального состояния (рис. 6). Функция, которой выводится значение, привязанное к каждому состоянию s, раскладывается так:

f(s) = g(s) + h(s)

где g — это стоимость достижения s из начального состояния (число ходов от начального состояния до s), h — рациональный компонент агента (эвристика), позволяющий определять стоимость всех возможных путей, начинающихся в s:

6. Нахождение целевого состояния из заданного начального
6. Нахождение целевого состояния из заданного начального

Эвристической функцией к агенту привязываются эмпирические правила. Поэтому, чем лучше добавляемые правила, тем скорее достигается целевое состояние. Функция MisplacedTiles(s), которой выводится число неправильно расположенных клеток для состояния s, может считаться потенциально эвристической: по ней всегда понятно, насколько далеко мы от целевого состояния. При создании или включении эвристики важна её допустимость, необходимая, чтобы гарантировать полноту и оптимальность алгоритма A*, то есть что алгоритм находит решение и это решение оптимальное.

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

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

Основа алгоритма A* — это поиск в ширину. При поиске в ширину значение всех узлов равно 1, поэтому не важно, какой узел расширять: в A* всегда расширяется узел, которым максимизируется нужный результат. В случае с головоломкой это узел с минимальной стоимостью, то есть кратчайшим путём до целевого состояния.

На рисунке 7 показано выполнение алгоритма A* (в качестве эвристической функции используются неправильно расположенные клетки):

7. Запуск алгоритма A* с неправильно расположенными клетками
7. Запуск алгоритма A* с неправильно расположенными клетками

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

8. Что происходит, когда учитывается пустая клетка
8. Что происходит, когда учитывается пустая клетка

В предыдущем узле мы всего в шаге от целевого состояния, но согласно эвристике до него остаётся не менее двух шагов (неправильно расположены 8-я и пустая клетка). Это явное завышение реальной стоимости, а потому недопустимо.

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

Листинг класса StateNode

        class StateNode<T>: IComparable<StateNode<T>> where T: IComparable
        {
            public double Value { get; set; }
            public T[,] State { get; private set; }
            public int EmptyCol { get; private set; }
            public int EmptyRow { get; private set; }
            public int Depth { get; set; }
            public string StrRepresentation { get; set; }

            public StateNode() { }

            public StateNode(T[,] state, int emptyRow, int emptyCol, int depth)
            {
                if(state.GetLength(0) != state.GetLength(1))
                    throw new Exception("Number of columns and rows must be the same");
                
                State = state.Clone() as T[,];
                EmptyRow = emptyRow;
                EmptyCol = emptyCol;
                Depth = depth;

                for (var i = 0; i < State.GetLength(0); i++)
                {
                    for (var j = 0; j < State.GetLength(1); j++)
                        StrRepresentation += State[i, j] + ",";
                }
            }

            public int Size
            {
                get { return State.GetLength(0); }
            }

            public void Print()
            {
                for (var i = 0; i < State.GetLength(0); i++)
                {
                    for (var j = 0; j < State.GetLength(1); j++)
                        Console.Write(State[i,j] + ",");
                    Console.WriteLine();
                }
                Console.WriteLine();
            }

            public int CompareTo(StateNode<T> other)
            {
                if (Value > other.Value)
                    return 1;
                if (Value < other.Value)
                    return -1;

                return 0;
            }
        }

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

Помните, что нужно будет рассчитать число перестановок, сравнивая элементы. Также обратите внимание на допущение, что ширина и высота игрового поля одинаковы (n = m), а пустая клетка равна 0.

Вот описание каждого поля и свойства:

  • Value — это значение f = g + h;

  • T[,] State — конфигурация или состояние, представленные данным узлом;

  • EmptyCol — номер столбца, в котором находится пустая клетка;

  • EmptyRow — номер строки, в которой находится пустая клетка;

  • Depth — глубина этого состояния от начального;

  • StrRepresentation — строковое представление конфигурации (то есть 1, 2, 3, 4, 5, 6, 7, 8, 0). Строится при создании объекта в конструкторе.

Метод Print() в формате CSV выводит значения клеток на игровом поле, а свойство Size возвращает его размер. Учитывая, что мы наследуем от IComparable<StateNode>, нужно реализовать метод CompareTo, его логика проста. Класс AStar представлен во втором листинге.

Листинг 2: класс AStar

        class AStar<T> where T:IComparable
        {
            public int StatesVisited { get; set; }
            public Dictionary<string, int> PatternDatabase { get; set; }

            private readonly StateNode<T> _goal;
            private T Empty { get; set; }
            private readonly PriorityQueue<StateNode<T>> _queue;
            private readonly HashSet<string> _hash;
            
            public AStar(StateNode<T> initial, StateNode<T> goal,  T empty) 
            {
                _queue = new PriorityQueue<StateNode<T>>(new[] { initial });
                _goal = goal;
                Empty = empty;
                _hash = new HashSet<string>();
            }

            public StateNode<T> Execute() ...

            private void ExpandNodes(StateNode<T> node) ...

            private double Heuristic(StateNode<T> node) ...

            private int MisplacedTiles(StateNode<T> node) ...

            private  int ManhattanDistance(StateNode<T> node) ...

            private int LinearConflicts(StateNode<T> node) ...

            private int DatabasePattern(StateNode<T> node) ...

            private int FindConflicts(T[,] state, int i, int dimension) ...

            private int InConflict(int index, T a, T b, int indexA, int indexB, int dimension)...
            }

        }

Снова описание каждого поля и свойства:

  • StatesVisited указывает число посещённых во время поиска состояний, равное числу удалённых из очереди узлов;

  • Словарь PatternDatabase используется в эвристике базы данных с шаблонами (подробнее о ней — позже), целью является цель StateNode;

  • Empty это пустая клетка;

  • queue — очередь с минимальным приоритетом, чтобы легко и эффективно получать из набора узлов узел состояния с минимальным значением f;

  • hashSet — хеш-множество для хранения строкового представления каждого найденного состояния.

Головоломка циклическая: начиная с исходного состояния, после ряда ходов мы можем оказаться в нём же. Чтобы избежать бесконечного цикла, каждое посещённое состояние сохраняем в хеше.

Теперь рассмотрим методы Execute() и Expand (узел StateNode) в листингах 3 и 4. Остальное относится к эвристике и понадобится в конце при запуске алгоритма и сравнениях.

Листинг 3: метод Execute

            public StateNode<T> Execute()
            {
                _hash.Add(_queue.Min().StrRepresentation);

                while(_queue.Count > 0)
                {
                    var current = _queue.Pop();
                    StatesVisited++;

                    if (current.StrRepresentation.Equals(_goal.StrRepresentation))
                        return current;
                   
                    ExpandNodes(current);
                }

                return null;
            }

Это очень простой метод, напоминает тело алгоритма поиска в ширину. Мы добавляем строковое представление начального состояния, а затем попадаем в цикл, который заканчивается, когда в _queue нет узлов. В текущей переменной всегда есть узел с минимальным значением, который и будет расширен. Расширение состоит из всевозможных перемещений от текущего состояния.

Листинг 4: метод ExpandNode

            private void ExpandNodes(StateNode<T> node) 
            {
                T temp;
                T[,] newState;
                var col = node.EmptyCol;
                var row = node.EmptyRow;
                StateNode<T> newNode;

                // Up
                if (row > 0)
                {
                    newState = node.State.Clone() as T[,];
                    temp = newState[row - 1, col];
                    newState[row - 1, col] = Empty;
                    newState[row, col] = temp;
                    newNode = new StateNode<T>(newState, row - 1, col,  node.Depth + 1);
                    
                    if (!_hash.Contains(newNode.StrRepresentation))
                    {
                        newNode.Value = node.Depth + Heuristic(newNode);
                        _queue.Push(newNode);
                        _hash.Add(newNode.StrRepresentation);
                    }
                }

                // Down
                if (row < node.Size - 1)
                {
                    newState = node.State.Clone() as T[,];
                    temp = newState[row + 1, col];
                    newState[row + 1, col] = Empty;
                    newState[row, col] = temp;
                    newNode = new StateNode<T>(newState, row + 1, col,  node.Depth + 1);
                    
                    if (!_hash.Contains(newNode.StrRepresentation))
                    {
                        newNode.Value = node.Depth + Heuristic(newNode);
                        _queue.Push(newNode);
                        _hash.Add(newNode.StrRepresentation);
                    }
                }

                // Left
                if (col > 0)
                {
                    newState = node.State.Clone() as T[,];
                    temp = newState[row, col - 1];
                    newState[row, col - 1] = Empty;
                    newState[row, col] = temp;
                    newNode = new StateNode<T>(newState, row, col - 1, node.Depth + 1);
                    
                    if (!_hash.Contains(newNode.StrRepresentation))
                    {
                        newNode.Value = node.Depth + Heuristic(newNode);
                        _queue.Push(newNode);
                        _hash.Add(newNode.StrRepresentation);
                    }
                }

                // Right
                if (col < node.Size - 1)
                {
                    newState = node.State.Clone() as T[,];
                    temp = newState[row, col + 1];
                    newState[row, col + 1] = Empty;
                    newState[row, col] = temp;
                    newNode = new StateNode<T>(newState, row, col + 1, node.Depth + 1);
                    
                    if (!_hash.Contains(newNode.StrRepresentation)) 
                    {
                         newNode.Value = node.Depth + Heuristic(newNode);
                        _queue.Push(newNode);
                        _hash.Add(newNode.StrRepresentation);
                    }
                }
            }

В каждом операторе if проверяется, возможно ли то или иное перемещение. Если да, клонируется текущее состояние (помните: массивы являются ссылочными типами), а затем пустая клетка меняется местами с клеткой, куда она перемещается. Помещаем строковое представление newNode в очередь и добавляем в хеш, если его там нет. Описав «скелет» алгоритма A*, переходим к главному компоненту поиска — эвристике.

Листинг 5: эвристический метод

            private double Heuristic(StateNode<T> node)
            {
                return DatabasePattern(node);
            }

            private int MisplacedTiles(StateNode<T> node) 
            {
                var result = 0;
		 
	            for (var i = 0; i < node.State.GetLength(0); i++)
			    {
			        for (var j = 0; j < node.State.GetLength(1); j++)
                        if (!node.State[i, j].Equals(_goal.State[i, j]) && !node.State[i, j].Equals(Empty))
                            result++;    
			    }
	               
                return result;
            }

Эвристика 1: неправильно расположенные клетки

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

Листинг 6: лаборатория эвристики (неправильно расположенные клетки)

        static void Main()
        {

            var initWorstConfig3x3 = new[,] {   {8,6,7},
                                                {2,5,4},
                                                {3,0,1}
                                    };

            var initConfig4x4 = new[,] {     {5,10,14,7},
                                             {8,3,6,1},
                                             {15,0,12,9},
                                             {2,11,4,13}
                                    };

            var finalConfig3x3 = new[,] {    {1,2,3},
                                             {4,5,6},
                                             {7,8,0}
                                    };

            var finalConfig4x4 = new[,] {    {1,2,3,4},
                                             {5,6,7,8},
                                             {9,10,11,12},
                                             {13,14,15,0}
                                    };

            var initialState = new StateNode<int>(initWorstConfig3x3, 2, 1, 0);
            var finalState = new StateNode<int>(finalConfig3x3, 2, 2, 0);    

            var watch = new Stopwatch();
            var aStar = new AStar<int>(initialState, finalState, 0)
            {
                PatternDatabase = FillPatternDatabase()
            };
                            

            watch.Start();
            var node = aStar.Execute();
            watch.Stop();
            
            Console.WriteLine("Node at depth {0}", node.Depth);
            Console.WriteLine("States visited {0}", aStar.StatesVisited);
            Console.WriteLine("Elapsed {0} miliseconds", watch.ElapsedMilliseconds);
            Console.Read();
        }

Для тестирования эвристики возьмём одну из худших конфигураций для игрового поля 3 x 3, показанную на рисунке 9. Для неё требуется 31 ход:

9. Игровое поле 3 х 3 (31 ход для решения)
9. Игровое поле 3 х 3 (31 ход для решения)

Вот полученные результаты:

10. Результаты выполнения алгоритма A* на игровом поле рисунка 9
10. Результаты выполнения алгоритма A* на игровом поле рисунка 9

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

Эвристика 2: манхэттенское расстояние

Манхэттенское расстояние, или расстояние городских кварталов между точками A = (x1, y1) и B = (x2, y2), определяется как сумма абсолютной разности их соответствующих координат:

MD = |x1-x2| + |y1-y2|

В листинге 7 манхэттенское расстояние допустимо как эвристика: по каждой клетке возвращается минимальное число шагов для перемещения в её целевое положение.

Листинг 7: эвристика 2 (манхэттенское расстояние)

            private  int ManhattanDistance(StateNode<T> node)
            {
                var result = 0;

                for (var i = 0; i < node.State.GetLength(0); i++)
                {
                    for (var j = 0; j < node.State.GetLength(1); j++)
                    {
                        var elem = node.State[i, j];
                        if (elem.Equals(Empty)) continue;
                        // Variable to break the outer loop and 
                        // avoid unnecessary processing
                        var found = false;
                        // Loop to find element in goal state and MD
                        for (var h = 0; h < _goal.State.GetLength(0); h++)
                        {
                            for (var k = 0; k < _goal.State.GetLength(1); k++)
                            {
                                if (_goal.State[h, k].Equals(elem))
                                {
                                    result += Math.Abs(h - i) + Math.Abs(j - k);
                                    found = true;
                                    break;
                                }
                            }
                            if (found) break;
                        }
                    }
                }

                return result;
            }

Вот результаты применения A* с манхэттенским расстоянием:

11. Результаты A* с манхэттенским расстоянием
11. Результаты A* с манхэттенским расстоянием

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

Эвристика 3: линейный конфликт

В эвристике линейного конфликта даётся информация о необходимых ходах, которые не учитываются при расчёте манхэттенского расстояния. Клетки tj и tk находятся в линейном конфликте, если они и их целевые положения располагаются на одной линии, при этом tj — справа от tk, а целевое положение tj — слева от целевого положения tk.

На рисунке 12 клетки 3 и 1 хотя и в одной строке, но не на своих местах:

12. Клетки 3 и 1 в правильной строке, но в неправильном положении
12. Клетки 3 и 1 в правильной строке, но в неправильном положении

Чтобы привести их в целевые положения, нужно переместить одну из них вниз, а затем снова вверх (эти перемещения не учитываются в манхэттенском расстоянии). Клетка не может быть более чем в одном конфликте, ведь разрешение определённого конфликта может подразумевать разрешение других конфликтов в той же строке или столбце. Поэтому, если клетка 1 связана конфликтом с клеткой 3, она не может быть связана конфликтом с клеткой 2 — это может привести к завышению кратчайшего пути до целевого состояния и сделать эвристику недопустимой. Методы, реализующие эту эвристику, представлены в листинге 8.

Листинг 8: метод LinearConflict

            private int LinearConflicts(StateNode<T> node)
            {
                var result = 0;
                var state = node.State;

                // Row Conflicts
                for (var i = 0; i < state.GetLength(0); i++)
                    result += FindConflicts(state, i, 1);

                // Column Conflicts
                for (var i = 0; i < state.GetLength(1); i++)
                    result += FindConflicts(state, i, 0);

                return result;
            }

            private int FindConflicts(T[,] state, int i, int dimension)
            {
                var result = 0;
                var tilesRelated = new List<int>();

                // Loop foreach pair of elements in the row/column
                for (var h = 0; h < state.GetLength(dimension) - 1 && !tilesRelated.Contains(h); h++)
                {
                    for (var k = h + 1; k < state.GetLength(dimension) && !tilesRelated.Contains(h); k++)
                   {
                        // Avoid the empty tile
                        if (dimension == 1 && state[i, h].Equals(Empty)) continue;
                        if (dimension == 0 && state[h, i].Equals(Empty)) continue;
                        if (dimension == 1 && state[i, k].Equals(Empty)) continue;
                        if (dimension == 0 && state[k, i].Equals(Empty)) continue;

                        var moves = dimension == 1 
                            ? InConflict(i, state[i, h], state[i, k], h, k, dimension) 
                            : InConflict(i, state[h, i], state[k, i], h, k, dimension);
                        
                        if (moves == 0) continue;
                        result += 2;
                        tilesRelated.AddRange(new List<int> { h, k });
                        break;
                    }
                }

                return result;
            }

            private int InConflict(int index, T a, T b, int indexA, int indexB, int dimension)
            {
                var indexGoalA = -1;
                var indexGoalB = -1;

                for (var c = 0; c < _goal.State.GetLength(dimension); c++)
                {
                    if (dimension == 1 && _goal.State[index, c].Equals(a))
                        indexGoalA = c;
                    else if (dimension == 1 && _goal.State[index, c].Equals(b))
                        indexGoalB = c;
                    else if (dimension == 0 && _goal.State[c, index].Equals(a))
                        indexGoalA = c;
                    else if (dimension == 0 && _goal.State[c, index].Equals(b))
                        indexGoalB = c;
                }

                return (indexGoalA >= 0 && indexGoalB >= 0) && ((indexA < indexB && indexGoalA > indexGoalB) ||
                                                                (indexA > indexB && indexGoalA < indexGoalB))
                           ? 2
                           : 0;
            }

Чтобы протестировать эвристику линейного конфликта, понадобится игровое поле 4 x 4 (рис. 13), где до целевого состояния нужно сделать 55 ходов. Значение узла s теперь будет определяться как f(s) = depth(s) + md(s) + lc(s). Объединим обе эвристики, ведь их ходы не пересекаются и завышения не будет:

13. Игровое поле 4 x 4 для тестирования эвристики линейных конфликтов
13. Игровое поле 4 x 4 для тестирования эвристики линейных конфликтов

Вот результаты:

14. Результаты выполнения с линейным конфликтом
14. Результаты выполнения с линейным конфликтом

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

Эвристика 4: база данных с шаблонами

Эвристика базы данных с шаблонами (рис. 15) определяется базой данных разных состояний игры. Каждое состояние связано с минимальным числом ходов, необходимым для приведения шаблона (подмножества клеток) в его целевое положение. Для этого случая мы создали небольшую базу данных с шаблонами, проведя поиск в ширину в обратном направлении — начиная с целевого состояния (3 x 3). Результаты сохранили в файле .txt всего с 60 000 записей. Для базы данных выбрали шаблон fringe с клетками из верхней строки и самого левого столбца:

15. Игровое поле 3 х 3 для запуска эвристики базы данных с шаблонами
15. Игровое поле 3 х 3 для запуска эвристики базы данных с шаблонами

Эвристическая функция базы данных с шаблонами в листинге 9 вычисляется с помощью функции табличного поиска. В данном случае это поиск по словарю с 60 000 сохранённых шаблонов. Он принципиально схож с техниками динамического программирования и подходом «разделяй и властвуй».

Листинг 9: эвристическая функция базы данных с шаблонами

            private int DatabasePattern(StateNode<T> node)
            {
                var pattern = node.StrRepresentation
                    .Replace('5', '?')
                    .Replace('6', '?')
                    .Replace('7', '?')
                    .Replace('8', '?');

                if (PatternDatabase.ContainsKey(pattern))
                    return PatternDatabase[pattern];
                return ManhattanDistance(node);
            }

Вот результаты:

16. Результаты эвристики базы данных с шаблонами
16. Результаты эвристики базы данных с шаблонами

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

Эвристика базы данных с шаблонами — это определяющая альтернатива при решении головоломки с игровым полем не менее чем 4 x 4.

Об авторе

Арнальдо Перес Кастаньо — кубинский специалист компьютерных наук, автор серии книг по программированию (JavaScript Fácil, HTML, CSS Fácil и Python Fácil (Marcombo S.A.). Он имеет опыт работы с Visual Basic, C#, .NET Framework, ИИ и предлагает услуги фрилансера на nubelo.com. Его хобби — кино и музыка. Связаться с ним можно по адресу arnaldo.skywalker@gmail.com.

А мы поможем вам прокачать скиллы или освоить новую профессию, которая останется актуальной в любое время:

Выбрать другую востребованную профессию.

Краткий каталог курсов и профессий
Теги:
Хабы:
+4
Комментарии5

Публикации

Информация

Сайт
www.skillfactory.ru
Дата регистрации
Дата основания
Численность
501–1 000 человек
Местоположение
Россия
Представитель
Skillfactory School