Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Паскаль / okulov / okulov / chapter2.DOC
Скачиваний:
95
Добавлен:
10.12.2013
Размер:
7.1 Mб
Скачать

2.2.9. «Канадские авиалинии»

Вы победили в соревновании, организованном Канадскими авиалиниями. Приз - бесплатное путешествие по Канаде. Путешествие начинается с самого западного города, в который летают самолеты, проходит с запада на восток, пока не достигнет самого восточного города, в который летают самолеты. Затем путешествие продолжается обратно с востока на запад, пока не достигнет начального города. Ни один из городов нельзя посещать более одного раза за исключением начального города, который надо посетить ровно дважды ( в начале и в конце путешествия). Вам также нельзя пользоваться авиалиниями других компаний или другими способами передвижения. Задача состоит в следующем: дан список городов и список прямых рейсов между парами городов; найти маршрут, включающий максимальное количество городов и удовлетворяющий вышеназванным условиям.

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

Пусть мы каким-то образом решили задачу для всех городов, которые находятся западнее города с номером i, т.е. для городов с номерами 1..(i-1). Что значит решили? У нас есть маршруты для каждого из этих городов, и нам известно, через сколько городов они проходят. Обозначим через множество городов западнее i и связанных с городом i авиалиниями. Для этих городов задача решена, т.е. известны значения d[j] (j) - количество городов в наилучшем маршруте, заканчивающемся в городе с номером j.

Итак:

d[i]:=0;

for jQido

if d[j]+1>d[i] then d[i]:=d[j]+1;

Остается открытым вопрос, а где же маршрут? Ибо значение d дает только количество городов, через которые он проходит. А для этого необходимо запомнить не только значение d[i], но и номер города j, дающего это значение. Возможно использование следующей структуры данных:

A:array[1..max] of record

d, l:byte;

end;

И реализация сказанного имеет вид:

FillChar(A,SizeOf(A),0);

A[1].d:=1;

for i:=2 to n do begin

for j:=1 to i-1 do if west[j,i] then if A[j].d+1>A[i].d then begin

A[i].d:=A[j].d+1;

A[i].l:=j;

end;

end;

Видим, что это не что иное, как метод динамического программирования в действии. Осталось выполнить обратный просмотр массива A по значениям поля l, начиная с элемента A[n].l и вывести из массива name:array[1..max] of string[15] названия городов.

Этот вывод можно осуществить с помощью следующей простой рекурсивной процедуры.

procedure rec(i:byte);

begin

if i<>1 then begin rec(A[i].l); writeln(name[i]);end;

end;

Как обобщить этот набросок решения для первоначальной формулировки задачи? Для удобства перенумеруем города в обратном порядке - с востока на запад. Изменим массив A:

A:array[1..max,1..max] of record

d,l:byte;

end;

Под элементом A[i,j].d- путь из городаi в городj - понимается максимальное число городов в маршруте, состоящем из двух частей: отiдо 1 (на восток) и от 1 доj(на запад). По условию задачи нам необходимо найти наилучший по числу городов путь изn-го города вn-й. Считаем, чтоA[1,1].d=1иA[i,j]=A[j,i]. ЧерезQi обозначим множество городов, из которых можно попасть в городi.Верны следующие соотношения:

  • A[i,j].d=max(A[k,j].d+1), приkÎQi,k<>j, еслиj<>1;

  • A[i,i].d=max(A[k,i].d), приi>1 иkÎQi.

    Рассмотрим логику формирования элементов массиваAна следующем примере.

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

i

j

Qi

k

Действия

2

1

[1]

1

A[2,1].d:=A[1,1].d+1=2 A[2,1].l:=1

2

2

-

-

-

3

1

[1]

1

A[3,1].d:=A[1,1].d+1=2 A[3,1].l:=1

3

2

[1]

1

A[3,2].d:=A[1,2].d+1=3 A[3,2].l:=1

3

3

-

-

-

4

1

[3]

3

A[4,1].d:=A[3,1].d+1=3 A[4,1].l:=3

4

2

[3]

3

A[4,2].d:=A[3,2].d+1=4 A[4,2].l:=3

4

3

[3]

3

-

4

4

-

-

-

5

1

[2,3,4]

2

A[5,1].d:=A[2,1].d+1=3 A[5,1].l:=1

5

1

[2,3,4]

3

A[5,1].d~A[3,1].d+1

5

1

[2,3,4]

4

A[5,1].d:=A[4,1].d+1=4 A[5,1].l:=4

5

2

[2,3,4]

2

-

5

2

[2,3,4]

3

A[5,2].d:=A[3,2].d+1=4 A[5,2].l:=3

5

2

[2,3,4]

4

A[5,2].d:=A[4,2].d+1=5 A[5,2].l:=4

5

3

[2,3,4]

2

A[5,3].d:=A[2,3].d+1=4 A[5,3].l:=2

5

3

[2,3,4]

3

-

5

3

[2,3,4]

4

A[5,3].d~A[4,3].d+1

5

4

[2,3,4]

2

A[5,4].d:=A[2,4].d+1=5 A[5,4].l:=2

5

4

[2,3,4]

3

A[5,4].d~A[3,4].d+1

5

4

[2,3,4]

4

-

5

5

[2,3,4]

2

A[5,5].d:=A[2,5].d+1=6 A[5,5].l:=2

5

5

[2,3,4]

3

A[5,5].d~A[3,5].d+1

5

5

[2,3,4]

4

A[5,5].d~A[4,5].d+1

После этой прокрутки массив Aпо полюd имеет вид:

A[i,j].d

1

2

3

4

5

1

1

2

2

3

4

2

2

1

3

4

5

3

2

3

1

0

4

4

3

4

0

1

5

5

4

5

4

5

6

а по полю l («*» обозначено неопределенное значение):

A[i,j].l

1

2

3

4

5

1

*

1

1

3

4

2

1

1

1

3

4

3

1

1

1

*

2

4

3

3

*

3

2

5

4

4

2

2

2

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

procedure Way(i,j:integer);

begin

if (i=1) and (j=1) then writeln(name[1])

else if i>=j then begin

writeln(name[i]);

Way(A[i,j].l,j);

end

else begin

Way(i,A[i,j].l);

writeln(name[j]);

end;

end;

Получаем цепочку:Way[5,5] ®Way[2,5] ®Way[2,4] ®Way[2,3] ®Way[2,1] ®Way[1,1]. Жирным шрифтом выделены процедуры, в которых имя города выводится на входе в рекурсию, курсивом - на выходе из рекурсии. Цепочка имеет вид: 5 2 1 3 4 5 (индексы городов, напомним, что нумерация изменена для удобства на противоположную).

Осталось привести процедуру нахождения A (ее фрагмент).

...

FillChar(A,SizeOf(A),0);

A[1,1].d:=1;

for i:=2 to n do

for j:=1 to i do

if (i<>j) or (i=n) then

for kÎQi do

if ((k<>j) or (j=1)) and (A[k,j].d<>0) and (A[k,j].d+1>A[i,j].d) then

begin

A[i,j].d+A[k,j].d+1;A[j,i].d:=A[i,j].d;

A[i,j].l:=k;A[j,i].l:=A[i,j].l;

end;

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

New:array[1..max] of boolean;

way_r:array[1..2*max] of byte;

Первый массив необходим для хранения признака -посещался или нет город (New[i]=true - города с номером i нет в маршруте). Во втором и третьем массивах запоминаются по принципу стека текущий и наилучший маршруты соответственно. Для работы со стеками необходимы указатели - переменные yk и yk_max. Логика перебора поясняется на нижеприведенном рисунке. Ищем путь из города с номером i.

Фрагмент основной программы.

begin

...

FillChar(New,SizeOf(New),true);

yk:=1;Way[yk]:=1;pp:=true;yk_max:=0;

rec(1);

...

(* Вывод результата *)

...

end.

И процедура поиска решения.

procedure rec(i:byte);

var j,pn,pv:byte;

begin

if i=n then pp:=false; (* меняем направление *)

if (i=1) and not pp then begin

(* вернулись в первый город *)

if yk>yk_max then begin (* запоминаем решение *) yk_max:=yk;

way_r:=way;

end;

pp:=true; (* продолжаем перебор *)

end;

(* по направлению определяем номера просматриваемых городов*)

if pp then begin pn:=i+1; pv:=n end

else begin pn:=1; pv:=i-1 end;

for j:=pn to pv do begin if West[i,j] and New[j] then begin

(* в город с номером j летают самолеты из города i и город j еще не посещался *)

(* включаем город j в маршрут *)

Inc(yk);Way[yk]:=j;New[j]:=false;

rec(j);

(* исключаем город j из маршрута *)

New[j]:=true;Way[yk]:=0;Dec(yk);

(* если исключается самый восточный город (n), то меняем направление *)

if j=n then pp:=true;

end;

end;

Следует отметить, что при этой реализации упрощается вывод результата.

writeln(n);

writeln(yk_max-1);

for i:=1 to yk_max do writeln(name(Way_r[i]]);

Целесообразно сравнение решений при больших значениях n по времени выполнения.

Соседние файлы в папке okulov