Цель лекции: изучить рекурсивный алгоритм перебора с возвратом, научиться разрабатывать рекурсивную триаду и алгоритм перебора с возвратом при решении задач на языке C++.
Во многих практических задачах из различных предметных областей требуется найти общее количество вариантов решения, число элементов в полном наборе решений. Иногда, исходя из постановки задачи, достаточно найти один из вариантов, соответствующих условию задачи. В некоторых задачах изучается вопрос о существовании решения как такового.
Ответы на поставленные вопросы, как правило, требуют проведения исчерпывающего поиска в некотором множестве всех возможных вариантов, среди которых находятся решения конкретной задачи. Существуют два общих метода организации исчерпывающего поиска: перебор с возвратом (
Решение задачи
При использовании метода решета вместо конструктивного построения решений задачи из множества возможных вариантов исключаются все элементы, не являющиеся решениями. Методы решета нашли широкое применение в теоретико-числовых задачах.
Метод перебора с возвратом и метод решета, строго говоря, не являются ни методами, ни алгоритмами решения задач. Их следует воспринимать как на некоторые общие схемы, которые применяются для решения той или иной задачи. Реализация этих схем в виде конкретных алгоритмов часто требует значительных дополнительных усилий в представлении данных и описании зависимостей между ними.
Соединение
При использовании возвратной рекурсии отпадает необходимость непосредственно организовывать возвраты и отслеживать правильность их осуществления. Они, как правило, становятся встроенной частью механизма выполнения рекурсивных вызовов.
Опишем общую постановку класса задач, к которым заведомо применим алгоритм перебора с возвратом.
Пусть M0, M1, ...,Mn-1 - n конечных линейно упорядоченных множеств и G - совокупность ограничений (условий), ставящих в соответствие векторам вида $$v=(v_{0},v_{1},...,v_{k})^{T}(v_{j}\in M_{j}; j=0,1,...,k; k<=n-1)$$, булево значение $$G(v)\in$$ { истина,ложь } . Векторы v=(v0,v1,...,vk)T, для которых G(v) = истина, назовем частичными решениями. Пусть, далее, существует конкретное правило P, в соответствии с которым некоторые из частичных решений могут объявляться полными решениями. Тогда возможна постановка следующих поисковых задач.
Общий метод решения приведенных задач состоит в последовательном покомпонентном наращивании вектора v слева направо, начиная с v0, и последующих проверках его ограничениями G и правилом P.
В общем случае этот метод приводит к алгоритмам с экспоненциальной временной сложностью, а применяется он в основном к классу так называемых -полных задач (
Опишем схему выполнения недетерминированного алгоритма. Пусть алгоритм выполняется до тех пор, пока не доходит до места, с которого должен быть сделан выбор из нескольких альтернатив. Детерминированный алгоритм однозначно осуществит выбор конкретной альтернативы и продолжит работать в соответствии с эти выбором. Недетерминированный алгоритм исследует все возможности одновременно, как бы копируя себя для реализации вычислений по всем альтернативам одновременно. Далее все копии работают независимо друг от друга и по мере необходимости продолжают создавать новые копии. Копия, сделавшая неправильный или безрезультатный выбор прекращает свою работу. Копия, нашедшая решение задачи, объявляет об этом, давая тем самым сигнал другим копиям о прекращении вычислений. Недетерминированные алгоритмы, являясь весьма полезной и продуктивной абстракцией, рекурсивны по сути, ибо при реализации
Возможно, что многие задачи, решаемые
Пример 1. Задача о расстановке ферзей на шахматной доске.
Составьте n ферзей на шахматной доске размером nxn так, чтобы они не били друг друга ( n –
В соответствии с общей схемой алгоритма перебора с возвратом предложенную задачу можно решать по алгоритму "Все расстановки", но завершить выполнение алгоритма при нахождении первой требуемой расстановки или при получении вывода о невозможности получить нужную комбинацию. Согласно алгоритму ферзи расставляются последовательно на вертикалях с номерами от нуля и далее. В процессе выполнения предписания возможны снятия ферзей с доски (возвраты).
Алгоритм "Все расстановки"
Шаг 1. Полагаем $$D = \varnothing , j = 0$$ ( D - множество решений, j - текущий столбец для очередного ферзя).
Шаг 2. Пытаемся в столбце j продвинуть вниз по вертикали или новый (если столбец j пустой), или уже имеющийся там ферзь на ближайшую допустимую строку. Если это сделать не удалось, то переходим к шагу 4.
Шаг 3. Увеличиваем j на 1, то есть переходим к следующему столбцу. Если j<n-1, то переходим к шагу 2. В противном случае j=n-1, то есть все вертикали уже заняты. Найденное частичное решение запоминаем в множестве D и переходим к шагу 2.
Шаг 4. Уменьшаем j на 1, то есть снимаем ферзь со столбца j и переходим к предыдущему столбцу. Если j>0, то выполняем шаг 2. Иначе вычисления прекращаем. Решения задачи находятся в множестве D, которое, может быть и пустым.
Решение задачи для небольших размеров доски можно найти "вручную", но для разработки алгоритма необходимо провести моделирование условия и остановиться на каком-либо представлении данных.
Проведем параметризацию задачи. Введем четыре вспомогательных вектора: ):
hoi=1, если на горизонтали с номером i (i=0,1,...n-1) имеется ферзь, и hoi=0 - в противном случае;dus=1, если на диагонали с номером s (s=0,1,...2n-2), идущей слева направо и снизу вверх, имеется ферзь, и dus=0 - в противном случае;dds=1, если на диагонали с номером s (s=0,1,...2n-1), идущей слева направо и сверху вниз, имеется ферзь, и dds+1=0 - в противном случае;pos j=i, если в позиции (i,j) (i,j=0,1,...n-1) стоит ферзь.
(рис 36.1) Схема использования вспомогательных массивов в задачеИспользование этих соглашений позволяет получить такие утверждения:
(i,j) можно поставить ферзь, если hoi+dui+j+ddn+i-j=0.(i,j) равносильно присваиваниям: hoi=1, dui+j=1, ddn+i-j=1.(i,j) равносильно присваиваниям: hoi=0, dui+j=0, ddn+i-j=0.Данное описание алгоритма является моделью решения общей задачи о нахождении всех вариантов расстановок. Рекурсия здесь осуществляется по не совсем стандартной схеме. В каждом рекурсивном вызове глубины j делается попытка поместить ферзь в некоторую позицию i столбца j (i,j=0,1,...n-1), а сам вызов соответствует переходу от работы с текущим столбцом к работе со следующим столбцом. При этом в начале вычислений и при переходах к любому последующему рекурсивному вызову параметр i меняется от нуля и далее с шагом, равным единице, пытаясь принять значение наименьшего номера поля, допустимого для установки ферзя. При переходах к любому предыдущему рекурсивному вызову параметр i продолжает изменяться от своего текущего на данном уровне значения с шагом, равным единице, также пытаясь принять значение наименьшего номера поля, допустимого для установки ферзя.
Если в текущем столбце ферзь установить уже не удается, то создавшуюся ситуацию назовем тупиком. Попадание в
После установки ферзя в одну из строк i последнего столбца i=n-1 формируется одно из решений задачи – при поиске одного варианта расстановки на этом этапе следует завершить выполнение алгоритма. При поиске всех расстановок вычисления прекращаются, когда мы попадаем в otv, начиная от первого и далее.
Рассмотрим, как реализуется декомпозиция. Для этого вместо исходной задачи удобно решать ее следующее обобщение.
На доске размера nxm (m=n,n-1,...,0) требуется установить m ферзей так, чтобы они не били друг друга. При этом имеются некоторые клетки доски, на которые ферзь заведомо ставить нельзя. Множество этих "запретных" клеток обозначим через $$\omega.$$
Исходная задача есть $$E (n, n, \varnothing )$$. Проведем ее (A) и оставшейся части (B). Соответственно этому разбиению будем решать задачи $$E (n, 1, \varnothing )$$ и $$E (n, n-1, \omega )$$. Каждое из n возможных решений i (ферзь установлен в строке i=0,1,...n-1 ) первой задачи однозначно определяет множество $$\omega =\omega (i)$$ запретных клеток для второй задачи. При этом в $$\omega (i)$$ попадают те клетки B, которые в "объединенной" доске бьет ферзь, установленный в строке i доски A. Пусть i зафиксировано и найдено множество d(i) решений второй задачи $$E (n, n-1, \omega (i))$$ для доски B.
Тогда расположение ферзя в строке i доски A и любое из полученных решений $$x\in d(i)$$ на B в совокупности дают различные решения otv(i) исходной задачи $$E (n, n, \varnothing )$$. Для получения всех решений этой задачи остается лишь взять объединение по i множеств otv(i).
При анализе трудоемкости алгоритма получаем, что глубина рекурсии равна n2 - при каждом рекурсивном вызове по j (j=0,1,...n-1) происходит n рекурсивных вызовов по i (i=0,1,...n-1).
//нахождение одного варианта расстановки
#include "stdafx.h"
#include <iostream>
using namespace std;
void Initialization(int n, int ***x);
void Destruction(int n, int **x);
bool Queen(int n, int **x);
void Placement(int n, int **a, int *p, int *h, int *du,
int *dd, int i=0, int j=0);
int _tmain(int argc, _TCHAR* argv[]){
int n, i, j;
bool otv;
int **mas;
printf("Введите размер шахматной доски n : ");
scanf ("%d",n);
Initialization(n,mas);
otv = Queen(n,mas);
if ( otv ) printf("Правильное размещение найдено\n");
else printf("Правильного размещения не найдено\n");
Destruction(n,mas);
system("pause");
return 0;
}
//инициализация поля шахматной доски
void Initialization(int n, int ***x){
*x = new int*[n];
for (int i = 0 ; i < n; i++ ){
(*x)[i] = new int[n];
for (int j = 0 ; j < n ; j++ )
(*x)[i][j] = 0;
}
}
//вывод найденной расстановки в файл
void Destruction(int n, int **x){
FILE *f;
if( ( f = fopen("out.txt","w") ) == NULL ){
printf("Файл out.txt не может быть открыт для записи");
}
else{
fprintf(f,"%d\n",n);
for (int i = 0 ; i < n; i++ ){
for (int j = 0 ; j < n ; j++ )
fprintf(f,"%2d",x[i][j]);
fprintf(f,"\n");
}
}
for (int i = 0 ; i < n; i++ )
delete [] x[i];
delete [] x;
}
//проверка возможности постановки ферзя
bool Queen(int n, int **x){
bool rez = false;
int *p, *h, *du, *dd;
p = new int[n];
h = new int[n];
du = new int[2 * n - 1];
dd = new int[2 * n - 1];
for ( int i = 0 ; i < n ; i++ )
p[i] = h[i] = 0;
for ( int i = 0 ; i < (2 * n - 1) ; i++ )
du[i] = dd[i] = 0;
Placement(n,x,p,h,du,dd);
if ( h[0] != 0 ) rez = true;
delete [] dd, du, h, p;
return rez;
}
//описание функции расстановки ферзей
void Placement(int n, int **a, int *p, int *h, int *du,
int *dd, int i, int j){
if ( j >= 0 j < n )
if ( i < n )
if (h[i]==0 du[i+j] == 0 dd[n+i-j-1] == 0 ) {
h[i] = 1;
du[i+j] = 1;
dd[n+i-j-1] = 1;
p[j] = i;
a[i][j] = 1;
Placement(n,a,p,h,du,dd,0,j+1);
}
else
Placement(n,a,p,h,du,dd,i+1,j);
else
if ( j > 0 ) {
h[p[j-1]] = 0;
du[p[j-1]+j-1] = 0;
dd[n+p[j-1]-(j-1)-1] = 0;
a[p[j-1]][j-1] = 0;
Placement(n,a,p,h,du,dd,p[j-1]+1,j-1);
}
}
Пример 2. Задача о количестве расстановок ферзей на шахматной доске.
Составить nxn так, чтобы они не били друг друга.
Предложенную задачу можно решать по приведенным в Примере 1 функциям, упростив их следующим образом. Вместо запоминания найденных решений будем подсчитывать в переменной otv их количество.
Пример 3. Задача об основных расстановках.
Для формулировки следующей задачи введем понятие. Среди всех расстановок n ферзей на доске nxn выделим отдельные непересекающиеся классы Hs (s=0,1,...,q) расстановок так, что все элементы данного класса можно получить из любого его представителя какими-либо элементарными преобразованиями типа:
Взяв по одному представителю из каждого класса Hs (s=0,1,...,q), получим некоторое множество, называемое основными расстановками. Составить n ферзей на шахматной доске размера nxn.
Эта задача решается с помощью otv. Очередная полученная расстановка подвергается проверке - включать или не включать ее в матрицу otv. Делается это следующим образом. Из вектора otv, то otv дополняется новым решением и так далее. Следовательно, по завершении вычислений otv будет содержать некоторое множество основных расстановок.
Возвратная рекурсия – это соединение
Детерминированный алгоритм – это алгоритм, который однозначно осуществит выбор конкретной альтернативы и продолжит работать в соответствии с эти выбором.
Исчерпывающий поиск – это процесс нахождения в некотором множестве всех возможных вариантов, среди которых имеется решение конкретной задачи.
Метод решета – это один из методов организации исчерпывающего поиска, при котором из множества возможных вариантов исключаются все элементы, не являющиеся решениями.
Недетерминированный алгоритм – это алгоритм, который исследует все возможности одновременно, как бы копируя себя для реализации вычислений по всем альтернативам одновременно.
Перебор с возвратом (backtracking) – это один из методов организации исчерпывающего поиска, который строится конструктивно последовательным расширением частичного решения.
Полное решение – это набор вариантов, образующий хотя бы одно решение в целом.
Частичное решение – это неполный набор вариантов, который входит в одно или несколько полных решений.
Цель работы: изучить рекурсивный алгоритм перебора с возвратом, научиться разрабатывать рекурсивную триаду и алгоритм перебора с возвратом при решении задач на языке C++.
При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, которая получает на входе числовые данные, выполняет их обработку в соответствии с требованиями задания и выводит результат на экран. Для обработки данных необходимо реализовать
Теоретические сведения.
Ознакомьтесь с материалом лекции 36.
Задания к лабораторной работе.
Выполните приведенные ниже задания.
n ферзей на шахматной доске размером nxn.nxn.nxn.n предметов, пронумерованных числами от 0 до n-1, для каждого из которых известен набор масс m0,...,mn-1 и набор стоимостей s0,...,sn-1. Определите, какой набор предметов необходимо положить в рюкзак, чтобы его масса не превышала Q, а стоимость предметов была бы наибольшей.Указания к выполнению работы.
Каждое задание необходимо решить в соответствии с изученным рекурсивным алгоритмом перебора с возвратом, реализовав программный код на языке С++. Перед реализацией кода каждой задачи необходимо разработать рекурсивную триаду в соответствии с постановкой задачи: выполнить
Следует реализовать каждое задание в соответствии с приведенными этапами:
Требования к отчету.
Отчет по лабораторной работе должен соответствовать следующей структуре.
Контрольные вопросы
(i,j) можно поставить ферзь, если hoi+dui+j+ddn+i-j=0.(i,j) равносильно присваиваниям: hoi=1, dui+j=1, ddn+i-j=1.(i,j) равносильно присваиваниям: hoi=0, dui+j=0, ddn+i-j=0.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.