Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по программированию..pdf
Скачиваний:
10
Добавлен:
15.11.2022
Размер:
12.2 Mб
Скачать

{Числитель дроби}" С.Р := А.Р * В.Р; Sokr(C)

End;

Procedure Chastnoe; {Деление дробей}

Begin

{Знаменатель дроби} C.Q :*» A .Q + В.Р; {Числитель дроби} С.Р :« А.Р * B.Q; Sokr(C)

End;

Procedure Stepen; {Возведение дроби в степень} Var I Natur;

Begin

C.Q :* 1; С.Р := 1; Sokr(A);

For I :* 1 To N Do

ProizvedenieCA, С, C)

End;

Function Menshe; {отношение и<" меаду дробями} Begin

Menshe := А.Р * B.Q < A.Q * В.Р

End;

Function Bolshe; {отношение ”>" между дробями} Begin

Bolshe :« A.P * B.Q > A.Q * B.P

End;

Function Aavno; {отношение ”в” между дробями} Begin

Ravno

A.P * B.Q * A.Q * B.P

End;

 

Function BolsheRavno; {отношение ">■" между дробями} Begin

BolsheRavno :* Bolshe(A, B) Or Ravno(A, B)

End;

Function MensheRavno; {отношение ”<=" между дробями} Begin

MensheRavno :=* Menshe(A, B) Or Ravno(A, B)

End;

Function NeRavno; {отношение и<>" между дробями}

Begin

Not Ravno(A, B)

NeRavno

End;

 

{Раздел инициализации модуля} Begin

End.

При разработке модуля рекомендуется такая последовательность

действии:

1)спроектировать модуль, т.е. определить основные и вспомога­ тельные подпрограммы и другие ресурсы;

2)описать компоненты модуля;

3)каждую подпрограмму целесообразно отладить отдельно, после чего “вклеить” в текст модуля.

Сохраним текст разработанной программы в файле DR0BY.PAS и откомпилируем наш модуль. Для этого можно воспользоваться вне­ шним компилятором, поставляемым вместе с Тур бо-Паскалем. Ко­ манда будет выглядеть так: ТРС DR0BY.PAS. Если в тексте нет син­ таксических ошибок, получим файл DR0BY.TPU, иначе будет выведено соответствующее сообщение с указанием строки, содержащей ошибку.

Теперь можно подключить модуль к программе, где планируется его использование. Решим первую задачу: суммирование массива дробен.

Program Sum;

Uses Droby;

Var A Array[1..100] Of Frac;

I, N Integer;

S Frac;

Begin

Write(’Введите количество элементов массива: ’);

ReadLn(N);

S.P := 0; S.Q := 1; {Первоначально сумма равна нулю}

For I := 1 To N Do {Вводим и суммируем дроби}

Begin

WriteО Введите числитель ’, I, ’-й дроби: ’);

ReadLn(A[I] .Р);

Write(’Введите знаменатель ’, I, ’-й дроби: ’);

ReadLn(A[I].Q);

Summa(A[I], S, S);

End;

WriteLnC*Ответ: S.P, ’/*, S.Q)

End.

Вторую задачу предлагаем решить читателю самостоятельно. Как видно из примера, для подключения модуля используется слу­

жебное слово USES, после которого указывается имя модуля. Данная строка записывается сразу же после заголовка программы. Если не­ обходимо подключить несколько модулей, они перечисляются через за­ пятую.

При использовании ресурсов модуля программисту совсем не обя­ зательно знать, как работают вызываемые подпрограммы. Доста­ точно знать назначение подпрограмм и их спецификации, т.е. имена

Лекция 17

17.1.Задачи поиска, метод перебора

Внастоящей лекции нашего курса рассмотрим некоторые задачи, связанные с проблемой поиска информации. Это огромный класс за­ дач, достаточно подробно описанный в классической литературе по программированию (см., например, книги Н. Вирта, Д. Кнута и др.).

Общий смысл задач поиска сводится к следующему: из данной ин­ формации, хранящейся в памяти ЭВМ, выбрать нужные сведения, удовлетворяющие определенным условиям (критериям).

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

Полный перебор является “лобовым” способом поиска и, очевидно, не всегда самым лучшим.

Рассмотрим пример. В одномерном массиве X заданы координаты п точек, лежащих на вещественной числовой оси. Точки пронумерованы. Их номера соответствуют последовательности в массиве X . Опреде­ лить номер первой точки, наиболее удаленной от начала координат.

Легко понять, что это знакомая нам задача определения номера наи­ большего по модулю элемента массива X . Она решается путем пол­ ного перебора следующим образом:

Const N = 100;

Var X Array[1..N] Of Real;

I, Number

1..N;

Begin

 

{___ Ввод массива X____ >

Number :*

1;

For I :* 2 To N Do

If Abs(X[I]) > Abs(X[Number])

Then Number :« I;

WriteLn(Number)

End.

Полный перебор элементов одномерного массива производится с по­ мощью одной циклической структуры.

А теперь такая задача: исходные данные те же, что и в пре­ дыдущей. Нужно определить пару точек, расстояние между которыми наибольшее.

Применяя метод перебора, эту задачу можно решать так: пе­ ребрать все пары точек из N данных и определить номера тех, рас­ стояние между которыми наибольшее (наибольший модуль разности координат). Такой полный перебор реализуется через два вложенных цикла:

Number1 :* 1;

Number2 :■ 2;

For I :* 1 To N Do

For J :* 1 To N Do

If Abs(X[I] - X[J]) > Abs(X[Numberl] - X[Number2])

Then

Begin

Numberl := I;

Number2 := J

End;

Но, очевидно, что такое решение задачи нерационально. Здесь каждая пара точек будет.просматриваться дважды, чапример, при i = 1, j = 2 и г = 2, j = 1. Для случая гг = 100 циклы повторят выполнение

100 х 100 = 10000 раз.

Выполнение программы ускорится, если исключить повторения. Исключить также следует и совпадения значений i и j. Тогда число повторений цикла будет равно

п ( п - 1)

2

При п = 100 получается 4950.

Для исключения повторений нужно в предыдущей программе изме­ нить начало внутреннего цикла с 1 на г +1. Программа примет вид:

Numberl := 1;

Number2 :» 2;

For I :a 1 To К Do

For J := I + 1 To N Do

If Abe(X[I] - X[J]) > Abs(X[Humbert] - X[Humber2])

Then

Begin

Numberl := I;

Number2 := J

End;

Рассмотренный вариант алгоритма назовем п ер ебор ом без п овто ­ рений.

Замечание. Конечно, эту задачу можно было решить и другим спо­ собом, но в данном случае нас интересовал именно переборный алго­ ритм. В случае же точек, расположенных не на прямой, а на плоскости или в пространстве, поиск альтернативы переборному алгоритму ста­ новится весьма проблематичным.

Вследующей задаче требуется выбрать все тройки чисел без пов­ торений из массива X , сумма которых равна десяти.

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

For I

:=

1

То

N Do

For

J

:■

I

+ 1 То N Do

For

К :=

J + 1 To N Do

 

If

XCI]

+ X[J] + X[K] = 10

Then WriteLn(X[I], X[J], X[K]);

А теперь представьте, что из массива X требуется выбрать все группы чисел, сумма которых равна десяти. Количество чисел в груп­ пах может быть любым — от 1 до п. В этом случае количество вари­ антов перебора резко возрастает, а сам алгоритм становится нетри­ виальным.

Казалось бы, ну и что? Машина работает быстро! И все же посчи­ таем. Число различных групп из п объектов (включая пустую) состав­ ляет 2П. При п = 100 это будет 2100 » Ю30. Компьютер, работающий со скоростью миллиард операций в секунду, будет осуществлять такой перебор приблизительно 10 лет. Даже исключение перестановочных повторений не сделает такой переборный алгоритм практически осу­ ществимым.

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

м м м м м м м м м м м

м

 

+

+

+

м

м

 

 

м

м

+

м

+

м

м

м

м

 

+

м

+

м

м м

м

м

м

м

+

м

+

м

 

 

м

м

м

+

м

+

+

м м

м

м

м

м +

м

м

м м м м м м

м

м

+

+

+

м

 

 

 

 

м

м

 

 

м

+

м

м

 

м

м

м

м

м

м

+

+

+

+

+

 

м

м

 

 

 

м

м

м

м

+

м

м

Рис. 17.2

Исходные данные — профиль лабиринта (исходная матрица LAB без крестиков); результат — все возможные траектории выхода из цен­ тральной точки лабиринта (для каждого пути выводится матрица LAB с траекторией, отмеченной крестиками).

Алгоритм перебора с возвратом еще называют методом проб. Суть его в следующем:

1) из каждой очередной точки траектории просматриваются воз­ можные направления движения в одной и той же последовательности; договоримся, что просмотр будет происходить каждый раз против ча­ совой стрелки (справа-сверху-слева-снизу); шаг производится в первую же обнаруженную свободную соседнюю клетку; клетка, в которую сде­ лан шаг, отмечается крестиком;

2)если из очередной клетки дальше пути нет (тупик), то следует возврат на один шаг назад и просматриваются еще не испробован­ ные пути движения из этой точки; при возвращении назад покинутая клетка отмечается пробелом;

3)если очередная клетка, в которую сделан шаг, оказалась на краю лабиринта (на выходе), то на печать выводится найденный путь.

Программу будем строить методом последовательной детализации. Первый этап детализации:

Program

L abirint;

 

 

Const

N

11; {размер лабиринта

NxN клеток}

Type

F ield

* ArrayCl - -Н, 1..N]

Of Char;

Var

LAB

F ield;

 

 

Procedure

GO(LAB

F ield; X, Y

In teger);

Begin

 

 

 

 

{Поиск

путей

из центра лабиринта до края

 

-

каждый найденный путь печатается}

End;

Begin

{Ввод лабиринта)

GO (LAB, N Div 2 + 1, N Div 2 + 1 ) {начинаем с середины)

End.

Процедура GO пытается сделать шаг в клетку с координатами х,у. Если эта клетка оказывается на выходе из лабиринта, то выводится на печать пройденный путь. Если нет, то делается шаг в соседнюю клетку, в соответствии с установленной выше последовательностью. Если клетка тупиковая, то делается шаг назад. Из сказанного выше следует, что процедура носит рекурсивный характер.

Запишем сначала общую схему процедуры без детализации:

Procedure

GO(LAB

F ield; X,

Y

Integer);

Begin

 

 

 

 

 

 

I f {клетка

(x ,y )

свободна)

 

 

Then

 

 

 

 

 

 

Begin

 

 

 

 

 

{

шаг на клетку (x,y) )

 

 

I f

{

дошли до края лабиринта )

Then

{

печатается найденный путь )

Else

{

попытка сделать

шаг в соседние клетки

вусловленной поспедовательностп )

{возвращение на один шаг назад )

End

End;

Для вывода найденных траекторий составляется процедура

PRINTLAB.

В окончательном виде программа будет выглядеть так:

Program

L abirint;

 

 

 

 

Const

N

*

11;

{размер

лабиринта NxN

клеток)

Type

F ield ■

A rray[l..N ,

1..N] Of

Char;

Var

LAB

F ield;

X,

Y

Integer;

 

Procedure

PRINTLAB(LAB

F ield );

 

{Печать найденного пути в лабиринте)

Var X,

 

Y

Integer;

 

 

 

 

Begin

 

 

 

 

 

 

 

 

For X :* 1 To N Do

 

 

 

Begin

 

 

 

 

 

 

For

Y :o

1 To

N

Do

 

 

 

Write(LAB [X,

Y])j

 

 

WriteLn

 

 

 

 

 

End;

 

 

WriteLn

 

 

End; {печати}

Field; X, Y

Integer);

Procedure G0(LAB

Begin

{если хлетха

свободна}

If LAB[X, Y]

Then

 

 

Begin

*+*; {делается шаг}

LAB[X, Y] :=

If (X = 1) Or (X = N) Or (Y = 1) Or (Y = N) {край}

Then

PRINTLAB(LAB) {печатается найденный путь}

Else

Begin {поиск следующего шага}

GO(LAB, X + 1, Y);

GO(LAB, X, Y + 1);

GO (LAB, X - 1,Y);~

GO(LAB, X, Y - 1)

 

End;

 

{возвращение

назад}

LAB[X, Y]

 

End

GO}

 

 

End;

{процедуры

 

 

Begin

{основной программы}

 

 

{ввод лабиринта}

 

 

 

For X :=

1 То N Do

 

 

Begin

:= 1 To N Do

 

 

For Y

 

 

Read(LAB[X,

Y] );

 

 

ReadLn

 

 

 

 

End;

N Div

2 + 1, N Div

2 + 1 ) {начинаем с середины}

End.

GO(LAB,

 

 

 

 

Еще один пример красивой программы с использованием рекурсив­ ного определения процедуры (вспомните Ханойскую башню!).

Схема алгоритма данной программы типична для метода перебора с возвратом. По аналогичным алгоритмам решаются, например, попу­ лярные задачи об обходе шахматной доски фигурами или о расстановке фигур на доске так, чтобы они “не били” друг друга; множество за­ дач оптимального выбора (задачи о коммивояжере, об оптимальном строительстве дорог и т.п.)

Замечание. Из-за использования массива LAB в качестве параметразначения в процедуре GO могут возникнуть проблемы с памятью при исполнении программы на ЭВМ. В таком случае можно перейти i глобальной передаче массива.