Вопрос очень сильно зависит от того, как вы представляете граф.
Например, можно возвести матрицу переходов в квадрат. То есть из матрицы переходов город->город за 1 шаг построить матрицу переходов да 1 и 2 шага. Потом возвести её в квадрат ещё раз, получится матрица переходов за 1…4 шаг. Так будет сделано за log(n) действий и мы увидим перечни городов в которые мы можем попасть из каждого города.
Но у вас задача проще – понять, что такое поиск в глубину. Никогда не копируйте в свои программы чужие алгоритмы на графах, которые вам не понятны.
Если на пальцах, то поиск в глубину – это попытки достичь определённой вершины, имея список уже посещенных вершин. В уже посещенные вершины возвращаться нельзя.
Как бы вы действовали в жизни?
Я бы написал списочек, куда пойти можно. Он может быть:
1. пуст – зашли в тупик, но так и не нашли, путь неправильный, надо вернуться (остановка ветви поиска)
2. в списке оказалась искомая вершина. Мы имеем путь к текущей, текущую и искомую – решение найдено.
3. в нем есть какие-то вершины – идём дальше. Надо добавить себя к списку посещенных вершин и с обновленным списком посещенных перебрать все вершины из доступных к переходу, повторяя алгоритм для каждой из них.
Если конкретный путь не важен, можно поступить интереснее:
Иметь вектор признаков (нет_пути | есть_путь | в_процессе), в который можно попасть из вершины 1. Изначально его заполнить исходящими из 1 с признаком в_процессе, остальные – нет_пути.
Если в этом векторе есть вершина X с признаком в_процессе, то нужно добавить в вектор все исходящие из неё вершины. Добавление обновляет признак нет_пути, заменяя его на в_процессе, признаки есть_путь и в_процессе не изменяются. После этого вершина X меняет признак с в_процессе на есть_путь. Повторять алгоритм пока в векторе либо не останется вершин в_процессе (это будет, когда в вершину n нет пути из 1), либо появится вершина n с признаком отличным от нет_пути.
Алгоритм голландского ученого Эдсгера Дейкстры находит все кратчайшие пути из одной изначально заданной вершины графа до всех остальных. С его помощью, при наличии всей необходимой информации, можно, например, узнать какую последовательность дорог лучше использовать, чтобы добраться из одного города до каждого из многих других, или в какие страны выгодней экспортировать нефть и тому подобное.
Минусом данного метода является невозможность обработки графов, в которых имеются ребра с отрицательным весом, т. е. если, например, некоторая система предусматривает убыточные для Вашей фирмы маршруты, то для работы с ней следует воспользоваться отличным от алгоритма Дейкстры методом.
Для программной реализации алгоритма понадобиться два массива: логический visited – для хранения информации о посещенных вершинах и численный distance, в который будут заноситься найденные кратчайшие пути. Итак, имеется граф G=(V, E). Каждая из вершин входящих во множество V, изначально отмечена как не посещенная, т. е. элементам массива visited присвоено значение false.
Поскольку самые выгодные пути только предстоит найти, в каждый элемент вектора distance записывается такое число, которое заведомо больше любого потенциального пути (обычно это число называют бесконечностью, но в программе используют, например максимальное значение конкретного типа данных). В качестве исходного пункта выбирается вершина s и ей приписывается нулевой путь: distance[s]=0, т. к. нет ребра из s в s (метод не предусматривает петель).
Далее, находятся все соседние вершины (в которые есть ребро из s) [пусть таковыми будут t и u] и поочередно исследуются, а именно вычисляется стоимость маршрута из s поочередно в каждую из них:
- distance[t]=distance[s]+весинцидентного s и t ребра;
- distance[u]=distance[s]+ весинцидентного s и u ребра.
Но вполне вероятно, что в ту или иную вершину из s существует несколько путей, поэтому цену пути в такую вершину в массиве distance придется пересматривать, тогда наибольшее (неоптимальное) значение игнорируется, а наименьшее ставиться в соответствие вершине.
После обработки смежных с s вершин она помечается как посещенная: visited[s]=true, и активной становится та вершина, путь из s в которую минимален. Допустим, путь из s в u короче, чем из s в t, следовательно, вершина u становиться активной и выше описанным образом исследуются ее соседи, за исключением вершины s. Далее, u помечается как пройденная: visited[u]=true, активной становится вершина t, и вся процедура повторяется для нее. Алгоритм Дейкстры продолжается до тех пор, пока все доступные из s вершины не будут исследованы.
Теперь на конкретном графе проследим работу алгоритма, найдем все кратчайшие пути между истоковой и всеми остальными вершинами. Размер (количество ребер) изображенного ниже графа равен 7 (|E|=7), а порядок (количество вершин) – 6 (|V|=6). Это взвешенный граф, каждому из его ребер поставлено в соответствие некоторое числовое значение, поэтому ценность маршрута необязательно определяется числом ребер, лежащих между парой вершин.
Из всех вершин входящих во множество V выберем одну, ту, от которой необходимо найти кратчайшие пути до остальных доступных вершин. Пусть таковой будет вершина 1. Длина пути до всех вершин, кроме первой, изначально равна бесконечности, а до нее – 0, т. к. граф не имеет петель.
У вершины 1 ровно 3 соседа (вершины 2, 3, 5), и чтобы вычислить длину пути до них нужно сложить вес дуг, лежащих между вершинами: 1 и 2, 1 и 3, 1 и 5 со значением первой вершины (с нулем):
Как уже отмечалось, получившиеся значения присваиваются вершинам, лишь в том случае если они «лучше» (меньше) тех которые значатся на настоящий момент. А так как каждое из трех чисел меньше бесконечности, они становятся новыми величинами, определяющими длину пути из вершины 1 до вершин 2, 3 и 5.
Далее, активная вершина помечается как посещенная, статус «активной» (красный круг) переходит к одной из ее соседок, а именно к вершине 2, поскольку она ближайшая к ранее активной вершине.
У вершины 2 всего один не рассмотренный сосед (вершина 1 помечена как посещенная), расстояние до которого из нее равно 9, но нам необходимо вычислить длину пути из истоковой вершины, для чего нужно сложить величину приписанную вершине 2 с весом дуги из нее в вершину 4
Условие «краткости» (10<∞) выполняется, следовательно, вершина 4 получает новое значение длины пути.
Вершина 2 перестает быть активной, также как и вершина 1 удаляется из списка не посещённых. Теперь тем же способом исследуются соседи вершины 5, и вычисляется расстояние до них.
Когда дело доходит до осмотра соседей вершины 3, то тут важно не ошибиться, т. к. вершина 4 уже была исследована и расстояние одного из возможных путей из истока до нее вычислено. Если двигаться в нее через вершину 3, то путь составит 4+7=11, а 11>10, поэтому новое значение игнорируется, старое остается.
Аналогичная ситуация с вершиной 6. Значение самого близкого пути до нее из вершины 1 равно 10, а оно получается только в том случае, если идти через вершину 5.
Когда все вершины графа, либо все те, что доступны из истока, будут помечены как посещенные, тогда работа алгоритма Дейкстры завершится, и все найденные пути будут кратчайшими. Так, например, будет выглядеть список самых оптимальных расстояний лежащих между вершиной 1 и всеми остальными вершинами, рассматриваемого графа:
1→1=0
1→2=1
1→3=4
1→4=10
1→5=2
1→6=10
В программе, находящей ближайшие пути между вершинами посредством метода Дейкстры, граф будет представлен в виде не бинарной матрицы смежности. Вместо единиц в ней будут выставлены веса ребер, функция нулей останется прежней: показывать, между какими вершинами нет ребер или же они есть, но отрицательно направленны.
Код программы на C++:
#include "stdafx.h" #include <iostream> using namespace std; const int V=6; //алгоритм Дейкстры void Dijkstra(int GR[V][V], int st) { int distance[V], count, index, i, u, m=st+1; bool visited[V]; for (i=0; i<V; i++) { distance[i]=INT_MAX; visited[i]=false; } distance[st]=0; for (count=0; count<V-1; count++) { int min=INT_MAX; for (i=0; i<V; i++) if (!visited[i] && distance[i]<=min) { min=distance[i]; index=i; } u=index; visited[u]=true; for (i=0; i<V; i++) if (!visited[i] && GR[u][i] && distance[u]!=INT_MAX && distance[u]+GR[u][i]<distance[i]) distance[i]=distance[u]+GR[u][i]; } cout<<"Стоимость пути из начальной вершины до остальных:tn"; for (i=0; i<V; i++) if (distance[i]!=INT_MAX) cout<<m<<" > "<<i+1<<" = "<<distance[i]<<endl; else cout<<m<<" > "<<i+1<<" = "<<"маршрут недоступен"<<endl; } //главная функция void main() { setlocale(LC_ALL, "Rus"); int start, GR[V][V]={ {0, 1, 4, 0, 2, 0}, {0, 0, 0, 9, 0, 0}, {4, 0, 0, 7, 0, 0}, {0, 9, 7, 0, 0, 2}, {0, 0, 0, 0, 0, 8}, {0, 0, 0, 0, 0, 0}}; cout<<"Начальная вершина >> "; cin>>start; Dijkstra(GR, start-1); system("pause>>void"); }
Код программы на Pascal:
program DijkstraAlgorithm; uses crt; const V=6; inf=100000; type vektor=array[1..V] of integer; var start: integer; const GR: array[1..V, 1..V] of integer=( (0, 1, 4, 0, 2, 0), (0, 0, 0, 9, 0, 0), (4, 0, 0, 7, 0, 0), (0, 9, 7, 0, 0, 2), (0, 0, 0, 0, 0, 8), (0, 0, 0, 0, 0, 0)); {алгоритм Дейкстры} procedure Dijkstra(GR: array[1..V, 1..V] of integer; st: integer); var count, index, i, u, m, min: integer; distance: vektor; visited: array[1..V] of boolean; begin m:=st; for i:=1 to V do begin distance[i]:=inf; visited[i]:=false; end; distance[st]:=0; for count:=1 to V-1 do begin min:=inf; for i:=1 to V do if (not visited[i]) and (distance[i]<=min) then begin min:=distance[i]; index:=i; end; u:=index; visited[u]:=true; for i:=1 to V do if (not visited[i]) and (GR[u, i]<>0) and (distance[u]<>inf) and (distance[u]+GR[u, i]<distance[i]) then distance[i]:=distance[u]+GR[u, i]; end; write('Стоимость пути из начальной вершины до остальных:'); writeln; for i:=1 to V do if distance[i]<>inf then writeln(m,' > ', i,' = ', distance[i]) else writeln(m,' > ', i,' = ', 'маршрут недоступен'); end; {основной блок программы} begin clrscr; write('Начальная вершина >> '); read(start); Dijkstra(GR, start); end.
{ V - множество вершин графа (vertex) E - множество рёбер графа w[i,j] - вес (длина) ребра ij a - вершина, расстояния от которой ищутся U - множество посещённых вершин (used) d[u] - по окончании работы алгоритма равно длине кратчайшего пути из a до вершины u (distance) p[u] - по окончании работы алгоритма содержит кратчайший путь из a в u (предшествующий u элемент, previous) } program Dijkstra; const INFINITY = MaxInt; UNDEFINED = -1; const Nmax = 6; type TMatrix = array[0..Nmax - 1, 0..Nmax - 1] of integer; TVertexList = array[0..Nmax - 1] of integer; TDistance = array[0..Nmax - 1] of integer; TUsed = array[0..Nmax - 1] of boolean; function MinOf(const a: TDistance; const m: TUsed): integer; var i: integer; Imin: integer; Min: integer; begin i := low(a); while i <= high(a) do begin if not m[i] then begin Imin := i; Min := a[i]; break; end; inc(i); end; for i := Imin + 1 to high(a) do if (Min > a[i]) and not m[i] then begin Imin := i; Min := a[i]; end; MinOf := Imin; end; procedure DijkstrasAlgorithm(const W: TMatrix; a: integer; var D: TDistance; var P: TVertexList); var v: integer; U: TUsed; i, j: integer; begin {initialization d[a]:=0; p[a]:=0; d[v]:=INFINITY; p[v]:=UNDEFINED; для всех вершин отличных от a } for v := low(D) to high(D) do begin if v = a then begin D[v] := 0; P[v] := UNDEFINED; end else begin D[v] := INFINITY; P[v] := UNDEFINED; end; U[v] := False; end; {основной цикл} for i := low(D) to high(D) do {пока есть нерассмотренные вершины} begin v := MinOf(D, U); {берём непосещённую вершину с минимальным d[v]} U[v] := True; {отмечаем её как посещённую} for j := low(D) to high(D) do if W[v, j] <> INFINITY then if D[j] > D[v] + W[v, j] then begin D[j] := D[v] + W[v, j]; P[j] := v; end; end; end; procedure ShowPath(A, B: integer; const P: TVertexList; const D: TDistance); var Q: TVertexList; Len: integer; v: integer; begin Len := 0; if D[B] = INFINITY then writeln('There is not exist path from ', A, ' to ', B, '.') else begin v := B; repeat Q[Len] := v; Inc(Len); v := P[v]; until v=UNDEFINED; write('A path from ', A, ' to ', B, ' is <'); for v := Len - 1 downto 0 do Write(Q[v]: 4); writeln('>.'); end; end; var W: TMatrix = ( (00, 07, 09, 00, 00, 14), (07, 00, 10, 15, 00, 00), (09, 10, 00, 11, 00, 02), (00, 15, 11, 00, 06, 00), (00, 00, 00, 06, 00, 09), (14, 00, 02, 00, 09, 00) ); var i, j: integer; P: TVertexList; D: TDistance; A, B: integer; begin {для упрощения ввода вместо INFINITY в матрице весов W я использовал нули Поэтому нужно удалить нули из матрицы весов, заменив их на бесконечность} for i := 0 to Nmax - 1 do for j := 0 to Nmax - 1 do if (i <> j) and (W[i, j] = 0) then W[i, j] := INFINITY; {Ввод начальной и конечной точек маршрута} A := 0; B := 4; {Обработка графа по алгоритму Дейкстры} DijkstrasAlgorithm(W, A, D, P); {контрольный вывод значений результирующих массивов} write('Distance: '); for i := low(D) to high(D) do Write(D[i]: 4); writeln; write('Previous: '); for i := low(P) to high(P) do Write(P[i]: 4); writeln; {вывод вершин по кратчайшему пути из A в B} ShowPath(A, B, P, D); end.
Текст работы размещён без изображений и формул.
Полная версия работы доступна во вкладке “Файлы работы” в формате PDF
Задача коммивояжера (ЗК) является одной из знаменитых задач теории комбинаторики. Она была поставлена в 1934 году, и её, как Великую теорему Ферма пытались решить многие математики. В своей области (оптимизации дискретных задач) ЗК служит своеобразным полигоном, на котором испытываются всё новые методы.
Приведем классическую постановку задачи.Коммивояжер (бродячий торговец) должен выйти из первого города, посетить по разу в неизвестном порядке города 2,3, … ,n и вернуться в первый город. Расстояния между городами известны. В каком порядке следует обходить города, чтобы замкнутый путь (тур) коммивояжера был кратчайшим?
Чтобы привести задачу к математическому виду, введём некоторые термины. Итак, города перенумерованы числами jТ=(1,2,3..n). Тур коммивояжера может быть описан циклической перестановкой t=(j1,j2,..,jn,j1), причём все j1..jn – разные номера; повторяющийся в начале и в конце j1,показывает, что перестановка зациклена. Расстояния между парами вершин Сijобразуют матрицу С. Задача состоит в том, чтобы найти такой тур t, чтобы минимизировать функционал
(1)
Относительно математизированной формулировки ЗК необходимо сделать два замечания.
Во-первых, в постановке Сijозначали расстояния, поэтому они должны быть неотрицательными, т.е. для всех jТ:
Сij0; Cjj=∞ (2)
(последнее равенство означает запрет на петли в туре),
симметричными, т.е. для всех i,j:
Сij= Сji , (3)
и удовлетворять неравенству треугольника, т.е. для всех:
Сij+ СjkCik . (4)
В математической постановке говорится о произвольной матрице. Сделано это потому, что имеется много прикладных задач, которые описываются основной моделью, но всем условиям (2)-(4) не удовлетворяют. Особенно часто нарушается условие (3) (например, если Сij – не расстояние, а плата за проезд: часто туда билет стоит одну цену, а обратно – другую). Поэтому различают два варианта ЗК: симметричную задачу, когда условие (3) выполнено, и несимметричную – в противном случае.
Второе замечание касается числа всех возможных туров. В несимметричной ЗК все туры t=(j1,j2,..,jn,j1) и t’=(j1,jn,..,j2,j1) имеют разную длину и должны учитываться оба. Разных туров очевидно (n-1)!.
Зафиксируем на первом и последнем месте в циклической перестановке номер j1, а оставшиеся n-1 номеров переставим всеми (n-1)! возможными способами. В результате получим все несимметричные туры. Симметричных туров имеется в два раз меньше, т.к. каждый засчитан два раза: как t и как t’.
Можно представить, что С состоит только из единиц и нулей. Тогда С можно интерпретировать, как граф, где ребро (i,j) проведено, если Сij=0 и не проведено, если Сij=1. Тогда, если существует тур длины 0, то он пройдёт по циклу, который включает все вершины по одному разу. Такой цикл называется гамильтоновым циклом. Незамкнутый гамильтонов цикл называется гамильтоновой цепью (гамильтоновым путём).
В терминах теории графов симметричную ЗК можно сформулировать так:
Дана полная сеть с n вершинами, длина ребра (i,j)= Сij. Найти гамильтонов цикл минимальной длины.
В несимметричной ЗК вместо “цикл” надо говорить “контур”, а вместо “ребра” ‒ “дуги” или “стрелки”.
Некоторые прикладные задачи формулируются как ЗК, но в них нужно минимизировать длину не гамильтонова цикла, а гамильтоновой цепи. Такие задачи называются незамкнутыми. Некоторые модели сводятся к задаче о нескольких коммивояжерах, но их решать более сложно.
Следующий алгоритм, после алгоритма прямого перебора, ”жадный” алгоритм – алгоритм нахождения наикратчайшего расстояния путём выбора самого короткого, ещё не выбранного ребра, при условии, что оно не образует цикла с уже выбранными рёбрами. “Жадным” этот алгоритм назван потому, что на последних шагах приходится жестоко расплачиваться за жадность.
Рис. 1. ‒ Сеть, представляющую узкий ромб, и найденный “жадным” алгоритмом тур
Посмотрим, как поведет себя при решении ЗК жадный алгоритм. Здесь он превратится в стратегию “иди в ближайший (в который еще не входил) город”. Жадный алгоритм, очевидно, бессилен в этой задаче. Рассмотрим для примера сеть на рис. 1, представляющую узкий ромб. Пусть коммивояжер стартует из города 1. Алгоритм “иди вы ближайший город” выведет его в город 2, затем 3, затем 4; на последнем шаге придется платить за жадность, возвращаясь по длинной диагонали ромба. В результате получится не кратчайший, а длиннейший тур.
В пользу процедуры “иди в ближайший” можно сказать лишь то, что при старте из одного города она не уступит стратегии “иди в дальнейший”.
К идее метода ветвей и границ приходили многие исследователи, но Литтл с соавторами на основе указанного метода разработали удачный алгоритм решения ЗК и тем самым способствовали популяризации подхода. С тех пор метод ветвей и границ был успешно применен ко многим задачам, для решения ЗК было придумано несколько других модификаций метода.
Общая идея метода: нужно разделить огромное число перебираемых вариантов на классы и получить оценки (снизу – в задаче минимизации, сверху – в задаче максимизации) для этих классов, чтобы иметь возможность отбрасывать варианты не по одному, а целыми классами. Трудность состоит в том, чтобы найти такое разделение на классы (ветви) и такие оценки (границы), чтобы процедура была эффективной.
Из рассмотренных методов программно реализуем метод перебора, так как ”жадный” алгоритм, как показано выше, часто приводит к неверному ответу, а метод ветвей и границ достаточно сложен для программирования.
Нахождение кратчайшего пути методом перебора. Классическим примером переборной задачи и служит задача коммивояжера. Пусть дано множество из N городов, расстояния между которыми известны. В каком порядке должен посетить их коммивояжер, заезжая в каждый город лишь один раз, чтобы общий пройденный путь был кратчайшим?
Опишем решение задачи методом перебора или “brute-force enumeration” ‒ “перебор животной силой”, как его называют в англоязычной литературе. Понятно, что полный перебор практически применим только в задачах малого размера. Напомним, что ЗК с N городами требует при полном переборе рассмотрения (N-1)!/2 туров в симметричной задаче и (N-1)! туров в несимметричной, а факториал, как показано в следующей таблице, растет удручающе быстро (табл. 1), однако быстродействие современных ПК позволяет справиться с этой задачей.
Таблица 1
5! |
10! |
15! |
20! |
25! |
30! |
35! |
40! |
45! |
50! |
~102 |
~106 |
~1012 |
~1018 |
~10125 |
~1032 |
~1040 |
~1047 |
~1056 |
~1064 |
Чтобы проводить полный перебор в ЗК, нужно научиться (разумеется, без повторений) генерировать все перестановки заданного числа m элементов.
Предполагаемыми решениями здесь являются перестановки из N городов. Из них нужно выбрать ту, которая даст наименьшую суммарную длину маршрута.
Первое, что нужно сделать, решая задачу коммивояжера, это организовать генерацию перестановок. Занумеруем города числами от 1 до N. Все перестановки можно получить, выбирая из множества [2..N] один элемент всевозможными способами и присоединяя к нему поочередно перестановки и из оставшихся элементов. Это рекурсивный алгоритм, т.к. для построения перестановок из N элементов он нуждается в перестановках из N-1 элемента. Первый элемент в сгенерированной перестановке всегда 1, т.к. мы предполагаем, что автотранспортное предприятие расположено в нем и отсюда начинается маршрут. Последний элемент в перестановке также 1, потому что маршрут считается замкнутым – транспорт возвращается в город с номером 1. Добавим, что единственная перестановка из одного элемента – это сам элемент.
Реализуем алгоритм в среде Паскаль-ABC. Запрограммируем получение перестановок в виде рекурсивной процедуры.
Расчет проведен для следующей симметричной матрицы С6х6 расстояний между городами.
Таблица 2
1 |
2 |
3 |
4 |
5 |
6 |
|
1 |
∞ |
6 |
4 |
8 |
7 |
14 |
2 |
6 |
∞ |
7 |
11 |
7 |
10 |
3 |
4 |
7 |
∞ |
4 |
3 |
10 |
4 |
8 |
11 |
4 |
∞ |
5 |
11 |
5 |
7 |
7 |
3 |
5 |
∞ |
7 |
6 |
14 |
10 |
10 |
11 |
7 |
∞ |
Множество переставляемых элементов сделаем параметром процедуры. Другим параметром будет позиция перестановки, которая заполняется данным вызовом процедуры. Все перестановки будем поочередно строить во внешнем массиве из N целых чисел, за исключением числа 1.
Процедура Comm строит перестановки в массиве А. Перестановка готова, когда S=[]. Процедура дополнена подсчетом длины пути, заданного перестановкой, и выбором самого короткого из них. Для хранения кратчайшего из построенных маршрутов и его длины воспользуемся внешними переменными Аmin и Lmin. Расстояния между городами записаны в двумерном массиве Dist. Листинг программы приведен на рис. 2.
Рис. 2. ‒ Листинг в среде Паскаль-ABC программы нахождения замкнутого маршрута
с минимальной длиной пути
В результате решения по данной программе получен оптимальный замкнутый маршрут (рис. 3) движения коммивояжера: 1→2→6→5→4→3→1, длина пути которого составляет 6+10+7+5+4+4=36 условных единиц.
Рис. 3. ‒ Результаты решения задачи коммивояжера
Оптимальный замкнутый маршрут с минимальной длиной пути можно изобразить в виде следующего графа (рис. 4).
Рис. 4. ‒ Оптимальный замкнутый маршрут с минимальной длиной пути
По подобному алгоритму может быть получен разомкнутый маршрут движения с минимальной длиной пути с заданными начальным и конечным пунктами назначения. Кроме этого под элементами матрицы С понимаются любые затраты (финансовые, временные и т.п.), сумму которых при движении по маршруту необходимо минимизировать.
program min_road; const N=7;{ количество вершин графа} var map:array[1..N,1..N] of integer;{ Карта: map[i,j] не 0, если точки i и j соединены } road:array[1..N] of integer;{ Маршрут - номера точек карты } incl:array[1..N] of boolean;{ incl[i]=TRUE, если точка ) { с номером i включена в road } len:integer;{ Длина последнего найденного маршрута } c_len:integer;{ Длина текущего маршрута } start,finish:integer;{ Начальная и конечная точки } i,j:integer; procedure step(s,f,p:integer); var c:integer;{ Номер точки,в которую делаем очередной шаг } begin if s=f then begin len:=c_len;{ сохраним длину найденного маршрута } write('Путь: '); for i:=1 to p-1 do write(road[i],' '); writeln(' Длина: ',len); end else { выбираем очередную точку } for c:=1 to N do { Проверяем все вершины } if(map[s,c]<>0) AND (NOT incl[c]) AND((len=0) OR (c_len+map[s,c]<len)) { Точка соединена с текущейне включена в маршрут} then begin road[p]:=c;{ Добавим вершину в путь } incl[c]:=TRUE;{ Пометим вершину } { как включенную } c_len:=c_len+map[s,c]; step(c,f,p+1); incl[c]:=FALSE; road[p]:=0; c_len:=c_len-map[s,c]; end; end;{ Конец процедуры step } { Основная программа } begin { Инициализация массивов } for i:=1 to N do road[i]:=0; for i:=1 to N do incl[i]:=FALSE; for i:=1 to N do for j:=1 to N do map[i,j]:=0; { Ввод значений элементов карты } map[1,2]:=1; map[2,1]:=1; map[1,3]:=1; map[3,1]:=1; map[1,4]:=1; map[4,1]:=1; map[3,4]:=1; map[4,3]:=1; map[3,7]:=1; map[7,3]:=1; map[4,6]:=1; map[6,4]:=1; map[5,6]:=1; map[6,5]:=1; map[5,7]:=1; map[7,5]:=1; map[6,7]:=1; map[7,6]:=1; write('Введите через пробел номера начальной и конечной точек -> '); readln(start,finish); road[1]:=start;{ Внесем точку в маршрут } incl[start]:=TRUE;{ Пометим ее как включенную } len:=0; c_len:=0; step(start,finish,2);{Ищем вторую точку } writeln('Для завершения нажмите <Enter>'); readln; end.