Нахождение пути от одного населённого пункта к другому

Цель работы:
Разработать программу, осуществляющую нахождение пути от одного населённого
пункта к другому.


                                  Введение

   В настоящее время индустрия  производства  компьютеров  и   программного
обеспечения для них является  одной  из   наиболее   важных  сфер  экономики
развитых стран. Ежегодно в мире  продаются  десятки  миллионов  компьютеров.
Только  в  США  объем  продаж  компьютеров   составляет  десятки   миллионов
долларов и постоянно продолжает расти.
   В чем же причины  такого  стремительного  роста  индустрии  персональных
компьютеров и их сравнительная выгодность для многих деловых применений?
  Простота  использования,  обеспеченная  с  помощью   диалогового   способа
взаимодействия с компьютером.
 Относительно  высокие  возможности  по   переработке   информации,  наличие
программного обеспечения, а так же  мощных  систем   для  разработки  нового
программного обеспечения.
   Использованная в отчёте программа может использоваться для решения
задач, связанных с проложением маршрута дороги любого типа.

                Определение достижимости населённых пунктов.


   1.1 Анализ требований.

   В списке задаются города (населённые пункты), а также дороги между  ними
(есть  или  нет),  необходимо   разработать   программу   с   использованием
модульного  программирования,  осуществляющую  нахождение  кратчайшего  пути
между населёнными пунктами,  задаваемыми  пользователем  в  процессе  работы
программы.
   Решение поставленной задачи осуществляется следующим методом:
   Cтроится граф, вершины которого - населённые пункты, а  ребра  -  дороги
между ними.
   В процессе работы  программы  в  данном  графе  с  помощью  рекуррентной
процедуры находятся пути из одной  вершины  в  другую.  Данная  процедура  в
качестве параметров получает массив пройденных  вершин,  текущую  вершину  и
количество уже пройденных вершин. На каждом этапе процедура  проверяет  все,
не пройденные достигнутые  вершины,  и  либо  находит  заданный  путь,  если
достигнута  конечная  вершина,  либо  вызывает  саму  себя  для   всех,   не
пройденных вершин.
   Для организации данного алгоритма используется две процедуры:  процедура
нахождения всего пути и рекурсивная процедура поиска единичного маршрута.
   Процедура нахождения всего пути  осуществляет  перебор  всех  населённых
пунктов и вызов рекурсивной процедуры, которая осуществляет  поиск  маршрута
между этими населёнными пунктами.
   Средства   решения    задачи:    используются    средства    логического
программирования  языка Turbo Pascal 7.0.

   1.2 Проектирование.

   Для реализации поставленной задачи программа должна выполнять  следующие
функции:
 Ввод данных пользователем  с  клавиатуры  -  вводятся  названия  населённых
пунктов и дороги, соединяющие их;
  Вывод  данных  -  вывод  на  экран  списка  населённых  пунктов  и  дорог,
соединяющий их.
 Запись в файл - запись  в  файл,  имя  которого  указывает  пользователь  в
диалоговом режиме, названия населённых пунктов и  существующих  дорог  между
ними в виде текстовой информации;
 Считывание файла с  диска,  с  именем,  которое  указывает  пользователь  в
диалоговом режиме
 Вывод результата - пользователь  задаёт  начальный  и  конечный  населённый
пункт, между  которыми  необходимо  проложить  путь,  на  экране  появляется
маршрут, либо сообщение: "маршрут не найден".
   Данная  программа  реализована  с  использованием  принципа   модульного
программирования,   главным   преимуществом   которого   является   простота
использования, возможность подключения программой  разных  модулей,  которые
могли  быть  разработаны  ранее,   быстрое   нахождение   основного   текста
программы, а также исправление и отладка процедур при  использовании  другой
программы или специальной программы-отладчика,  которая  подключает  к  себе
данный модуль.
   Все процедуры, используемые данной программой, находятся  в  unit-модуле
ph.tpu  и  предназначены  для  использования  основной  программой,  которая
находится в файле path.pas.
   Основная программа осуществляет вывод меню на экран, опрос клавиатуры  и
вызов процедуры, соответствующей нажатой клавише.
   Для реализации ввода данных используется  процедура  InputData,  которая
осуществляет ввод имён городов с клавиатуры,  если  вместо  названия  города
был  нажат  ввод,  то  процедура  выводит  список   городов   на   экран   и
пользователь, передвигая курсор и, нажимая ввод,  составляет  список  дорог,
соединяющие эти города  между  собой,  при  нажатии  клавиши  Esc  процедура
прекращает свою работу и выходит в главное меню.
   Для реализации вывода данных на экран используется процедура OutputData,
которая осуществляет чтение в цикле массива городов и вывод его на экран,  а
также массива дорог, соединяющие эти города и выводит из на экран.
   Для реализации запроса имени файла и записи данных в  файл  используется
процедура Save, которая сначала выводит запрос на экран,  осуществляет  ввод
имени  файла,  открывает  файл,  имя  которого  вводится   пользователем   с
клавиатуры в текущем каталоге, в цикле  из  массива  городов  записывает  на
диск список городов, затем также список дорог, соединяющих их.
    Для реализации запроса имени файла и чтения данных из  файла  в  массив
используется процедура load, которая сначала выводит запрос имени файла   на
экран, осуществляет ввод имени файла, открывает файл, имя которого  вводится
пользователем, считывает данные в массив городов, затем в массив дорог.
   Для поиска пути между городами используется процедура FindPath,  которая
осуществляет вывод списка городов  на  экран,  опрос  клавиатуры,  при  этом
пользователь может выбрать курсором начальный и конечный населённый пункт  в
своём  пути,  процедура  FindPath   вызывает   с   параметрами   рекурсивную
процедуру, которая производит поиск оптимального маршрута  между  выбранными
городами.
   Для поиска маршрута используется рекурсивная процедура findnext, которой
при её вызове передаются следующие параметры:
      a(vec) - вектор, каждому городу соответствует номер в маршруте или
      ноль, если города нет в маршруте;
      tv(integer) - город, следующий в маршруте;
      nv(integer) - город, в который необходимо добраться;
      lv(integer) - количество пройденных городов.
   На каждом этапе  процедура  проверяет  все,  не  пройденные  достигнутые
вершины, и либо находит заданный путь,  если  достигнута  конечная  вершина,
либо вызывает саму себя для всех, не пройденных вершин.

   1.3 Кодирование

Краткая функциональная спецификация.

   Процедура InputData
Назначение: Осуществляет ввод исходных данных пользователем с клавиатуры.
Входные данные: нет.
Выходные данные: нет.
Не вызывает никаких процедур.
Вызывается из основной программы.

   Процедура OutputData
Назначение: Осуществляет вывод данных на экран.
Входные данные: нет.
Выходные данные: нет.
Не вызывает никаких процедур.
Вызывается из основной программы.

   Процедура Load
Назначение: Осуществляет запрос имени, чтение файла данных с этим именем в
массив городов и в массив дорог.
Входные данные: нет.
Выходные данные: нет.
Не вызывает никаких процедур.
Вызывается из основной программы.

   Процедура Save
Назначение: Осуществляет запрос имени, запись в файл данных с этим именем
массива городов и в массива дорог.
Входные данные: нет.
Выходные данные: нет.
Не вызывает никаких процедур.
Вызывается из основной программы.

      Процедура FindPath
Назначение: Осуществляет поиск пути между городами.
Входные данные: нет.
Выходные данные: нет.
Вызывает findnext.
Вызывается из основной программы.

      Процедура FindNext
Назначение: Осуществляет поиск маршрута.
Входные данные:
          a(vec) - вектор, каждому городу соответствует номер в
      маршруте или ноль, если города нет в маршруте;
      tv(integer) - город, следующий в маршруте;
      nv(integer) - город, в который необходимо добраться;
      lv(integer) - количество пройденных городов.
Выходные данные: нет.
Вызывает findnext.
Вызывается из FindPath.

   Основная программа
Осуществляет оформление экрана, вывод и обработку меню, опрос клавиатуры,
вызов процедуры, соответствующей выбранному пункту меню.

   1.4 Тестирование

    Разработанное программное средство было протестировано методом
функционального тестирования.
    Введённые в программу данные показали, что результаты работы совпадают
с вычисленными вручную.


                            Программы разработки.
Программа path

program path;
uses crt,ph;
var
  t:town;                            {Данные о городах}
  nt:integer;                        {Число городов}
  r:road;                            {Данные о дорогах}
  nr:integer;                        {Число дорог}
  sl:integer;                        {Выбранный пункт меню}
  c:char;                            {Нажатый символ}
  i:integer;                         {Счетчик}
  fv:vec;                            {Вектор пройденных городов}
  nfv:integer;                       {Количество городов}
Const
  KItems = 6;                        {Количество пунктов меню}
  mas: array[1..KItems] of string =
                                     {Инициализация пунктов меню}
    ('¦ Ввод данных       ¦',
     '¦ Вывод данных      ¦',
     '¦ Запись в файл     ¦',
     '¦ Считывание файла  ¦',
     '¦ Вывод результата  ¦',
     'L------ Выход -------');

{Основная программа}
begin
  sl:=1;
{Городов и дорог нет}
  nt:=0;
  nr:=0;
  repeat
    textattr:=7;                      {Основной цвет меню}
    clrscr;
    for i:=1 to KItems do begin
      gotoxy (25,i+3);
      write (mas[i]);                 {Вывод пунктов меню}
    end;
    textattr:= 77;                    {Цвет активного пункта}
    gotoxy (25,sl+3);
    write (mas[sl]);                  {Вывод активного пункта}
    c:=readkey;                       {Ввод символа с клавиатуры}
    textattr:=7;
    case c of                     {Определить код нажатой клавиши}
      #13: case sl of                  {Клавиша Enter}
        1: InputData;
        2: OutputData;
        3: Save;
        4: Load;
        5: FindPath;
      end;
      #0: begin                     {Анализ функциональных клавиш}
        c:=readkey;
        case c of
          #80: if sl<KItems then sl:=sl+1 else sl:=1;
          #72: if sl>1 then sl:=sl-1 else sl:=KItems;
        end
      end
    end;
  until ((c=#13) and (sl=6) or (c=#27));
  textattr:=7;
  clrscr;
end.


Модуль ph

unit ph;
interface
uses crt;
type
  town= array [1..20] of string;       {Данные о городах}
  road= array [1..200] of record       {Данные о дорогах}
    a:integer;
    b:integer;
  end;
  vec=array [1..20] of integer;      {Данные о пройденных городах}
var
  t:town;                             {Данные о городах}
  nt:integer;                         {Число городов}
  r:road;                             {Данные о дорогах}
  nr:integer;                         {Число дорог}
  fv:vec;                             {Вектор пройденных городов}
  nfv:integer;                        {Количество городов}

procedure InputData;
procedure OutputData;
procedure Save;
procedure Load;
procedure findnext(a:vec; tv:integer; nv:integer; lv:integer);
procedure FindPath;

implementation

{Ввод данных}
procedure InputData;
var
  i:integer;                           {Счетчик}
  n:integer;                           {Выбранный начальный город}
  sl:integer;                          {Выбранный город}
  c:char;                              {Нажатый символ}
begin
{Считывание данных о городах}
  clrscr;
  nt:=1;
  writeln('Введите название города (Пустая строка - закончить: ');
  repeat
    write(' >');
    readln(t[nt]);
    nt:=nt+1;
  until (t[nt-1]='');
  nt:=nt-2;
{Проверка, вводились ли города}
  if (nt>0) then begin
  {Да, ввод дорог}
    nr:=0;
    n:=0;
    sl:=1;
    repeat
      textattr:=7;
      clrscr;
      for i:=1 to nt do begin
        gotoxy (25,i+3);
        write (t[i]);                  {Вывод городов}
      end;
      textattr := 77;                  {Цвет активного города}
      gotoxy (25,sl+3);
      write (t[sl]);                   {Вывод активного города}
      if (n<>0) then begin
        textattr:=66;                  {Цвет отмеченного города}
        gotoxy (25,n+3);
        write (t[n]);                  {Вывод отмеченного города}
      end;
      textattr:=7;
      gotoxy(1,20);
      write('Дороги: ');
      for i:=1 to nr do write(' {',r[i].a,',',r[i].b,'}');
      c:=readkey;                      {Ввод символа с клавиатуры}
      case c of
        #13: begin                     {Нажат ENTER}
        {Либо помечается начальный город}
          if n=0 then n:=sl else
            {Либо происходит попытка добавить дорогу}
            if (n=sl) then n:=0 else begin
              nr:=nr+1;
              if (n>sl) then begin
                i:=n;
                n:=sl;
                sl:=i;
              end;
            {Проверяется, нет ли такой?}
              for i:=1 to nr-1 do
                if ((r[i].a=n)and(r[i].b=sl)) then n:=0;
            {Если нет - добавляется}
              if n<>0 then begin
                r[nr].a:=n;
                r[nr].b:=sl;
              end else nr:=nr-1;
              n:=0;
              sl:=1;
          end;
        end;
        #0: begin                   {Анализ функциональных клавиш}
          c:=readkey;
          case c of
            #80: if sl<nt then sl:=sl+1 else sl:=1;
            #72: if sl>1 then sl:=sl-1 else sl:=nt;
          end
        end
      end;
    until (c=#27);
  end;
end;

{Вывод данных}
procedure OutputData;
var
  i:integer;                           {Счетчик}
begin
{Вывод списка городов}
  clrscr;
  writeln(' Города: ');
  for i:=1 to nt do begin
    gotoxy (10,i);
    write (t[i]);                 {Вывод городов}
  end;
{Вывод списка дорог}
  gotoxy(1,20);
  write(' Дороги: ');
  for i:=1 to nr do write(' {',r[i].a,',',r[i].b,'}');
  gotoxy(2,24);
  write(' ESC- Выход');
{Ожидание ESC}
  repeat until readkey=#27;
end;

{ Запись данных в файл}
procedure Save;
var
  f:TEXT;                              {Файл}
  name:string;                         {Имя файла}
  n:integer;                           {Счетчик}
begin
  clrscr;
  writeln(' Запись данных ');
  write(' Введите имя файла: ');
  readln(name);
  assign(f,name);
  rewrite(f);
  writeln(f,nt);
  for n:=1 to nt do writeln(f,t[n]);
  writeln(f,nr);
  for n:=1 to nr do writeln(f,r[n].a,' ',r[n].b);
  close(f);
end;

{Чтение из файла}
procedure Load;
var
  f:TEXT;                              {Файл}
  name:string;                         {Имя файла}
  n:integer;                           {Счетчик}
begin
  clrscr;
  writeln(' Чтение данных ');
  write(' Введите имя файла: ');
  readln(name);
  assign(f,name);
  reset(f);
  readln(f,nt);
  for n:=1 to nt do readln(f,t[n]);
  readln(f,nr);
  for n:=1 to nr do readln(f,r[n].a,r[n].b);
  close(f);
end;

{Рекурсивная процедура поиска маршрута.
  Входные параметры:
  a:vec - Вектор, каждому городу соответствует номер в маршруте
          или ноль, если города нет в маршруте
  tv:integer - Город, следующий в маршруте
  nv:integer - Город, в который необходимо добраться
  lv:integer - Количество пройденных городов}
procedure findnext(a:vec;tv:integer;nv:integer;lv:integer);
var
  i:integer;                           {Счетчик}
begin
  a[tv]:=lv;                        {Устанавливается в векторе
                                    флаг, что город tv пройден}
  if (tv=nv) then begin
  {Если достигнут необходимый город}
    if ((lv+1)<nfv)or(nfv=0) then begin
{Если найден первый либо более короткий маршрут - он становится найденным}
      nfv:=lv+1;
      fv:=a;
    end;
  end else begin
{Иначе - просмотр всех городов, в которые можно добраться из данного}
    for i:=1 to nr do
{Если город еще не учитывался - запуск для него той же самой функции}
      if ((r[i].a=tv)and(a[r[i].b]=0)) then findnext(a,r[i].b,nv,lv+1);
{Просмотр, но для дорог, где текущий город учитывался как второй}
    for i:=1 to nr do
{Если город еще не учитывался - запуск для него той же самой функции}
      if ((r[i].b=tv)and(a[r[i].a]=0)) then findnext(a,r[i].a,nv,lv+1);
  end;
end;

{Нахождение пути}
procedure FindPath;
var
  i:integer;                       {Счетчик}
  j:integer;                       {Счетчик}
  n:integer;                       {Исходный город}
  sl:integer;                      {Выбираемый город}
  c:char;                          {Считанный с клавиатуры символ}
  v:vec;                           {Вектор для начала рекурсии}
begin
{Изначально первый город не выбран}
  n:=0;
  sl:=1;
  nfv:=0;                           {Маршрут не найден}
{Цикл запроса городов и вывода результатов}
  repeat
    textattr:=7;
    clrscr;
  {Вывод поясняющей надписи}
    gotoxy(2,20);
    if (n=0) then write(' Выберите начальный пункт')
      else writeln(' Выберите конечный пункт ');
  {Вывод списка городов}
    for i:=1 to nt do begin
      gotoxy (25,i+3);
      write (t[i]);
    end;
    textattr:= 77;
    gotoxy (25,sl+3);
    write (t[sl]);
    if (n<>0) then begin
      textattr:=66;
      gotoxy (25,n+3);
      write (t[n]);                 {Вывод отмеченного города}
    end;
    textattr:=7;
  {Вывод найденного маршрута либо надписи о его отсутствии}
    gotoxy(60,1);
    if (nfv>0) then begin
      write(' Найденный маршрут: ');
      for j:=1 to nfv do
        for i:=1 to nt do if fv[i]=j then begin
          gotoxy(60,j+2);
          write(t[i]);
      end;
    end else write(' Маршрут не найден ');
    c:=readkey;                        {Ввод символа с клавиатуры}
    case c of
      #13: begin
      {Либо фиксируется начальный город}
        if n=0 then n:=sl else begin
        {Либо убирается ошибочно выбранный город}
          if (n=sl) then n:=0 else begin
          {Либо происходит поиск нового маршрута}
            nfv:=0;                    {Маршрута нет}
            for i:=1 to 20 do v[i]:=0; {Ни одного пройденного
                                        города}
            findnext(v,n,sl,1);{Вызывается первый раз рекурсивная
                                 процедура}
          end;
          n:=0;
          sl:=1;
        end;
      end;
      #0: begin                    {Анализ функциональных клавиш}
        c:=readkey;
        case c of
          #80: if sl<nt then sl:=sl+1 else sl:=1;
          #72: if sl>1 then sl:=sl-1 else sl:=nt;
        end
      end
    end;
  until (c=#27);
end;

end.

   Результаты выполнения программы.

    ¦ Ввод данных       ¦
    ¦ Вывод данных      ¦
    ¦ Запись в файл     ¦
    ¦ Считывание файла  ¦
    ¦ Вывод результата  ¦
    +------ Выход ------+

Ввод данных:


Введите название города (Пустая строка - закончить):
 >Город 1
 >Город 2
 >Город 3
 >Город 4
 >Город 5
 >
 Дороги:  {1,3} {3,4} {2,5} {1,4} {2,4} {2,3}

Вывод результата:
Найденный маршрут:
Город 1               Город 1
Город 3               Город 2
Город 2               Город 3
Город 5               Город 4
                      Город 5


                       Список используемых источников

1. Белецкий Я. Турбо Паскаль  с   графикой   для   персональных  компьютеров
  /перевод с польского Д. И. Юренкова. -  М. :Машиностроение, 1991.
2. Сергиевский  М.  В.,  Шалашов  А.  В.  Турбо  Паскаль  7.0;  язык,  среда
  программирования.  - М:  Машиностроение, 1994.
3. Справочник по процедурам и функциям Borland  Pascal  With Objects 7.0.  -
  Киев: Диалектика, 1993.