КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Обход шахматной доски ходом коня
Реализация рекурсии Программный стек Многие компиляторы языков программирования (в частности компиляторы Си и Си++) используют так называемый программный стек для размещения аргументов подпрограмм (функций) и локальных переменных. Программный стек – некоторая область памяти, используемая программой для размещения своих объектов. Рассмотрим в качестве примера следующую программу, состоящую из трех функций – main, f1, f2.
void main(void){ char x,y; int z; // 1 x=f1(x,z); // 3 ……………………………………………… z=f2(z); // 7 } //--------------------- char f1(char t, int u){ float q; // 2,5 ………………………………………………… } //--------------------- int f2(int k){ char p=7; // 4 p=f1(1,2); // 6 ……………………………………………………… } В процессе работы, программа проходит ряд точек, отмеченных в программе комментариями: · 1 – после входа в функцию main · 2 – после входа из main в f1 · 3 –после выхода из f1 в main · 4 –после входа в f2 в main · 5 – после входа в f1 из f2 · 6 – после выхода из f1 в функции f2 · 7 – после выхода из f2 в функции main При входе в функцию программа помещает в программный стек значения аргументов в байты памяти, отводимой для параметров и локальных переменных, после чего вершина стека поднимается (Push). При выходе из функции, вершина стека опускается до уровня, предшествующего входу (Pop). В таблице 1 изображено состояние стека в различных точках программы. По горизонтали – адреса байтов программного стека, по вертикали – номера точек.
Таблица 1. Состояние стека в процессе выполнения программы
Примечания: - здесь не учитывается выравнивание адресов на границу слова, двойного слова, - длина типа int – 4 байта, - не учитывается что адрес точки возврата при обращении к функции тоже помещается в стек. Использование механизма программного стека для распределения памяти для параметров и локальных переменных (класс памяти automatic) приводит к тому, что никаких дополнительных усилий для реализации рекурсии уже не требуется. Каждое новое обращение к рекурсивной функции помещает в стек новое поколение параметров и локальных переменных. Некоторые языки программирования не допускают использования рекурсивных функций, то есть таких функций, которые обращаются сами к себе прямо или косвенно через другие функции. Используя стек, который функция должна обслуживать сама, программист, тем не менее, может реализовать рекурсивные алгоритмы. Типичными задачами, решаемыми с помощью рекурсии, являются задачи перебора с возвратами (Back Track), а также алгоритмы, в которых часть работы выполняется как вся работа. В качестве примеров рассмотрим две задачи: обход шахматной доски ходом коня и вычисление определенного интеграла. Конь должен побывать в каждой клетке доски размером ровно один раз. Примем нумерацию возможных ходов коня из клетки как это изображено на рисунке. В стеке будем хранить координаты покидаемой клетки и номер выполняемого хода. Когда обнаружится, что никакой ход из клетки невозможен, берем координаты предыдущей клетки из стека и пытаемся покинуть ее с помощью хода, имеющего номер на единицу больше, чем тот ход, с помощью которого ее покидали ранее (операция " возврат "). Если уже посетили все клеток, то задача решена, и трасса обхода лежит в стеке. Если при попытке взять клетку из стека, обнаруживается, что стек пуст, то это означает, что задача не имеет решения. Ниже приведен текст программы. struct STACK{ int i,j; // координаты клетки int move; // номер хода, которым покидаем клетку }; // элемент стека bool Horse(int n, STACK *stack); //----------------------------------- static bool Move(int N, int i1,int j1, int n, int *i2, int *j2){ // Ход коня. Конь ходит из клетки i1,j1 ходом номер n // и попадает в клетку *i2,*j2 // N - размер доски // Возвращает true при успехе switch(n){ case 0: *i2=i1+1; *j2=j1+2; break; case 1: *i2=i1+2; *j2=j1+1; break; case 2: *i2=i1+2; *j2=j1-1; break; case 3: *i2=i1+1; *j2=j1-2; break; case 4: *i2=i1-1; *j2=j1-2; break; case 5: *i2=i1-2; *j2=j1-1; break; case 6: *i2=i1-2; *j2=j1+1; break; case 7: *i2=i1-1; *j2=j1+2; break; default: // сюда можно попасть только при ошибке в программе *i2=INT_MAX; *j2=INT_MAX; } return (*i2>=0 && *i2<N && *j2>=0 && *j2<N); } //----------------------------------- bool Horse(int n, STACK *stack){ // n - размер доски. // результат будет получен в стеке stack. // Каждый раз, когда покидаем клетку, ее координаты // и номер хода помещаем в стек. // возврат true - успех bool **Board=NULL; // в массиве доска n*n помечаем факт посещения клетки int i,j; // текущие координаты клетки int v; // указатель стека int BegMove,k,i2,j2; bool out=true,Good; // выделим память для доски Board=new bool * [n]; for(i=0;i<n;i++){ Board[i]=new bool[n]; } // Board[i][j]==true означает, что клетка посещалась // Ни одна клетка еще не посещалась for(i=0;i<n;i++){ for(j=0;j<n;j++){ Board[i][j]=false; } } v=-1; // стек пуст i=0;j=0; // начальная клетка BegMove=0; // Номер хода, с которого начинается перебор M: // ищем допустимый ход из текущей клетки Good=false; // предикат "Ход найден" for(k=BegMove;k<8;k++){ if(Move(n,i,j,k,&i2,&j2) &&!Board[i2][j2]){ Good=true; break; } } if(Good){ // текущую клетку и номер хода помещаем в стек v++; stack[v].i=i; stack[v].j=j; stack[v].move=k; // пометить клетку, как посещенную Board[i][j]=true; BegMove=0; i=i2; j=j2; // может обошли все? if(v==n*n-2){ // последнюю клетку - в стек v++; stack[v].i=i2; stack[v].j=j2; goto finish; } goto M; // все повторить для очередной клетки } else { // ход не найден // взять из стека, если он не пуст if(v<0){ // стек пуст - задача не имеет решения out=false; goto finish; } // взять из стека i=stack[v].i; j=stack[v].j; BegMove=stack[v].move+1; v--; Board[i][j]=false; goto M; } finish: // освободим память for(i=0;i<n;i++){ delete [] Board[i]; } delete [] Board; return out; } Общая схема решения задачи перебора с возвратами В общем случае схема решения задачи перебора с возвратами имеет вид: bool BackTrack(параметры){ // опустошить стек // задать начальные значения переменным bool Good=false; // Good – решение найдено M: bool Find=Найти следующий допустимый узел в дереве решений(); if(Find){ // данные узла поместить в стек if(пройдены все пути в дереве){ Good=true; // решение находится в стеке goto finish; } goto M; // повторить процесс исходя // из нового узла дерева решений } else { // новый узел не найден // взять из стека предыдущий узел, если стек не пуст if(v<0){ // стек пуст - задача не имеет решения Good=false; goto finish; } // взять из стека данные предшествующего узла // и сделать его текущим goto M; } finish: // приберём за собой (освободим память, закроем файлы…) return Good; }
Дата добавления: 2014-12-08; Просмотров: 1027; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |