Pull to refresh

Задача о браках и режим возвратов

Reading time6 min
Views4.3K
Бектрекинг – механизм решения переборных задач. Позволяет выбрать либо 1 приемлемую альтернативу, либо наилучшую.

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

Особенность Бектрекинга состоит в том, происходит откат по управлению, а также по данным. При откате программа «забывает» все, что она сделала в результате выбора неудачной альтернативы — данные восстанавливают свои предыдущие значения, отменяются все последствия выбора альтернативы. После отката берем следующую альтернативу и заново выполняем все действия. Проверяем, какие альтернативы для нас приемлемы. Если в данной развилке все альтернативы оказываются неудачными, распространение неуспеха происходит на вышележащие развилки.

Бектрекинг обеспечивает:
— откат по управления;
— откат по данным;
— перебор списка альтернатив.

Существуют действия неподверженные откату. Пример: печать на экран, принтер и т. д.

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

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

— список альтернатив: фиксированный, вычисляемый динамически, может не храниться вообще;

— автоматическое изменение порядка набора альтернатив.
Предсказать какая альтернатива принесет успех. (предсказание требует ресурсов.). но перебирая альтернативы в итоге придем к результату.

— возможно отсутствие конструкции “неудача”; (существует конструкция для установления точки развилки или не существует)

— откат возможен: к ближайшей развилке, к указанной статически / динамически, автоматическое определение нужной развилки;
Если перебрали все альтернативы, то неудачу распространяем наверх.
Чтобы не откатываться по шагам, а к той, которую хочешь необходимо:
1. отметить все развилки и откатываться туда
2. откат к развилке определяется динамически.

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

— можно связать с сигналом о неуспехе сообщения, и возможность программного анализа.

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

Схема реализация бектрекинга на процедурно-ориентированном языке.
В данном подходе развилки эмулируются при помощи рекурсии, а перебор альтернатив при помощи цикла.
procedure TryAlt(N:trazvilka; var uspeh:boolean);
begin
{Инициализировать перебор переменных}
uspeh:=false;
repeat {выбрать очередную альтернативу}
if {альтернатива приемлима} then begin {записать альтернативу}
if {решение полное(других развилок нет)} then
uspeh:=true
else begin
try(N+1;uspeh);{перейти к слудующей развилке}
if uspeh then
{ничего решение уже построено}
{стереть альтернативу}
end;
end;
until uspeh or альтернатив больше нет
end.


Задача о стабильных браках


Предположим, что даны два непересекающихся множества А и В равного размера n. Требуется найти набор из n пар <a,b>- таких, что а из А и b из B удовлетворяют некоторым ограничениям. Критериев распределения может быть много. Мы рассмотрим один из них, который называется правилом стабильных браков.
Пусть A – множество мужчин, а B – множество женщин. Каждый мужчина и каждая женщина указали предпочтительных для себя партнеров. Если пары сформированы так, что существуют мужчина и женщина, которые являются мужем и женой, но которые предпочли бы друг друга своим фактиче6ским супругам, то такое распределение будет нестабильным. Если таких пар нет, то распределение стабильно. Следует заметить, что список распределений остается неизменным и после того, как сделано распределение по парам.
Возможное направление поиска решения – пытаться распределять по парам членов двух множеств одного за другим, пока не будут исчерпаны оба множества. Для решения данной задачи используем схему бектрекинга, слегка модифицировав ее в сторону упрощения.

Procedure tryMan (m : man);
Var
R : rang;
Begin
If m < n then
For r := 1 to n do begin
Взять r-ю кандидатуру из списка мужчины m
If допустима then begin
Записать супругов;
Try (следующий за m);
Отменить брак;
End;
end;
else
записать стабильное решение
end;

Исходные данные представим двумя матрицами, указывающими предпочтения мужчин и женщин.
Var
womanForMan : array [1..n,1..n] of woman
manForWoman : array [1..n,1..n] of man

Соответственно womanForMan[m,r] – женщина, находящаяся в списке мужчины m на r-м месте. Аналогично manForWoman[w,r] – мужчина, находящийся в списке женщины w на r-м месте.
Результат представим массивом woman так, что woman[m] обозначает супругу мужчины m. Для симметрии введем man так, что man[w] обозначает супругу мужчины w. На самом деле массив man избыточен, так как в нем содержится информация уже содержащиеся в woman. Информация в массивах man и woman нужна для определения стабильности предполагаемого множества браков. Поскольку это множество строится шаг за шагом посредством соединения индивидов в пары и проверки стабильности после каждого предполагаемого брака, массивы man и woman нужны еще до того, как будут определены все компоненты. Чтобы отслеживать, какие компоненты уже определены, мы введем булевские массивы:
singleMan, SingleWoman: array [1..n] of Boolean
истинность компонента означает то, что соответствующий мужчина (женщина) свободны.
с их помощью можно уточнить предикат допустима как конъюнкцию singleWoman[w] & брак стабилен, где предикат брак стабилен еще предстоит доопределить.

Ключевая задача теперь – уточнить алгоритм определения стабильности. Первая особенность, о которой нужно помнить, состоит в том, что по определению, стабильность следует из сравнения рангов (то есть позиций в списке предпочтений). Однако нигде в нашей коллекции данных, определенных до сих пор, нет непосредственно доступных рангов мужчин и женщин. Разумеется их можно вычислить по значениям womanForMan и manForWoman, но так как вычисление стабильности очень частая операция, целесообразно обеспечить более прямой доступ к этой информации. Для этого введем две матрицы,
Rmw: array [man,woman] of rang;
Rwm: array [woman,man] of rang;
При этом rmw[m,w] обозначает ранг женщины w в списке мужчины m, и rwm[w,m] – ранг мужчины m в списке женщины w. Значения этих вспомогательных матриц не меняются и могут быть определены в самом начале по значениям womanForMan и manForWoman.
Теперь предикат Брак стабилен может быть вычислен точно следуя его определению. Напомню, что нам нужно проверить возможность соединить браком m и w, где w=womanForMan[m,r]. Для начала предположим, что такой брак допустим, а потом попытаемся обнаружить помехи для стабильности этого брака. Таких помех может быть две:
1) Может найтись женщина pw с рангом более высоким, чем у w, по мнению m, и которая и сама предпочитает m своему мужу;
2) Может найтись мужчина pm с рангом, более высоким, чем у m, по мнению w, и который сам предпочитает w своей жене
Чтобы обнаружить помеху первого рода, сравним ранги rwm[pw,m] и rwm[pw,man[pw]] для всех женщин, которых m предпочитает w, то есть для всех pw=womanForMan[m,i] таких, что i<r. Нас самом деле все женщины pw уже замужем, так как, будь любая из них еще не замужем, m выбрал бы ее еще раньше. Чтобы обнаружить помеху второго рода, нужно проверить всех кандидатов pm, которых w предпочитает своей текущей паре m, то есть всех мужчин pm=manForWoman[w,i], i<rwm[w,m], по аналогии с первым случаем, однако нужно не забыть пропустить сравнения с теме woman[pm], где pm еще не женат. Это обеспечивается проверкой pm< m, так как мы знаем, что все мужчины до m женаты.

Полный текст процедуры проверки стабильности выглядит так
Function stable(m, w, r : integer) : Boolean;
Var
Pw, pm, I, lim : integer;
S : Boolean;

Begin
I := 0; s := true;
Repeat
Inc(i);
If i<r then begin
Pw := womanForMan[m,i];
If not(singleWoman[pw]) then
S := rwm[pw,m] >rwmn[pw,man[pw]];
End;
Until (i=r) or (not(s));

I := ;0 lim := rwm[w,m];
Repeat
Inc(i);
If I < lim then begin
Pm := manForWoman[w,i];
If pm<m then
S := rmw[pm,w]>rmw[pm,woman[pm]
End;
Until (i=lim) or (nor(s));
End;

Заключение


Данный алгоритм порождает все возможные варианты решений и его можно доработать таким образом, чтобы находилось либо решение оптимальное для мужчин, либо для женщин, либо среднее. Все зависит от того, для каких целей применяется данный алгоритм.
Tags:
Hubs:
Total votes 13: ↑7 and ↓6+1
Comments6

Articles