[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi
.pdf
101
26:tmp:=M[ind]; {меняем местами минимальный элемент и m[i]}
27:M[ind]:=M[i];
28:M[i]:=tmp;
29:end;
30:
31:writeln('Отсортированный массив');
32:for i:=1 to n do
33:write(M[i],' ');
34:readln;
35:end.
6.8. Сортировка вставками
Сортировка вставками напоминает расстановку карт по старшинству: берем подряд все карты, для каждой из них ищем подходящее место и вставляем ее на это место.
В сортировке вставками массив разбивается на 2 части: отсортированную (она будет находиться в начале массива) и неотсортированную (остальная часть массива). Сначала отсортированная часть массива – это первый элемент. Далее по очереди выбираются элементы из неотсортированной части, после чего для них ищется место в отсортированной части. Когда для элемента место найдено, его надо освободить. Для этого все элементы, начиная с этого индекса, надо сдвинуть вправо на 1 единицу. После этого элемент можно вставить на его законное место и перейти к следующему числу.
Давайте теперь отсортируем наш любимый массив вставками.
21 4 3 -2
Неотсортированная часть массива – это элементы M[2]…M[5].
1 этап: сохраняем число M[2] (нам оно понадобится) и ищем место для числа M[2] = 1. Он должен стоять первым. Теперь сдвигаем число M[1] на единицу вправо. Получим массив
2 |
2 |
4 |
3 |
-2 |
Осталось вставить число 1 (которое мы сохранили в дополнительной переменной) в первый элемент массива:
1 |
2 |
4 |
3 |
-2 |
2 этап: надо вставить в отсортированную часть элемент M[3] = 4. Конечно, мы с вами видим, что можно сразу перейти к следующему элементу, но компьютер то этого не знает, поэтому он добросовестно сохраняет M[3] в дополнительной переменной, потом ищет место в памяти, куда этот M[3] надо вставить, и т.д.
3 этап: надо найти подходящее место для элемента M[4]=3. Для этого надо сместить вправо M[3]:
1 |
2 |
4 |
4 |
-2 |
и вставить число 3 куда положено:
1 |
2 |
3 |
4 |
-2 |
Последнюю итерацию описывать не будем.
Пример 11: Сортировка методом вставок.
1 : uses Crt;
2 : const
3 : n=100;
102
4 : var
5 : M:array [1..n] of integer;
6 : i,j,k:word;
7 : tmp:integer; {Вспомогательная переменная}
8 : BEGIN
9 : Clrscr;
10:randomize;
11:for i:=1 to n do {Заполняем массив случайными числами}
12:M[i]:= random(1000);
13:writeln('Исходный массив:');
14:for i:=1 to n do
15:write(M[i],' ');
16:writeln;
17:{Алгоритм сортировки методом вставок}
18:for i:=2 to n do
19:begin
20:tmp:=M[i]; {запоминаем M[i]}
21:j:=1;
22:while tmp>M[j] do {ищем место, куда надо вставить M[i]}
23:j:=J+1;
24:for k:=i-1 downto j do {Сдвигаем элементы массива}
25: |
M[k+1]:=M[k]; |
{чтобы освободить место для M[i]} |
|
26: |
M[j]:=tmp; |
{Вставляем элемент на нужную позицию} |
|
27: |
end; |
|
|
28: |
|
|
|
29:writeln('Отсортированный массив');
30:for i:=1 to n do
31:write(M[i],' ');
32:readln;
33:end.
6.9. Сравнение простейших алгоритмов сортировки
Интересно, какой из алгоритмов сортировки, рассмотренных нами, работает быстрее всех. Для сравнения скорости их работы можно воспользоваться экспериментальным методом: проверять скорость работы алгоритмов для массивов различной длины. Так мы и поступим, когда в 8-й главе будем изучать быструю сортировку, т.к. ее анализ довольно сложен.
Второй метод анализа алгоритмов – теоретический: подсчитать, как зависит количество действий, которые требуется выполнить для того, чтобы отсортировать массив. Анализ сложных алгоритмов сортировки требует бОльших знаний математики, чем те, которые у нас есть, но с теми простыми алгоритмами, которые мы изучили, мы сможем справиться.
Один этап сортировок пузырьком и обменом заключается в просмотре неотсортированной части массива. На каждой итерации проводится по крайней мере операция сравнения. Следовательно, на i -м шаге будет проведено n − i операций.
Общее количество операций будет равно 1+ 2 + 3 + ... + n −1 = n(n −1) .
2
Аналогичные оценки справедливы и для сортировки вставками: на каждом этапе надо просмотреть часть массива для того, чтобы найти позицию, куда надо вставить
103
элемент, и сдвинуть оставшуюся часть массива. Значит, количество действий на i -м
этапе будет равно i . Общее количество операций будет тоже n(n −1) .
2
В выкладках мы складывали операции сравнения с операциями пересылки данных (если бы использовались и арифметические операции, мы бы и их свалили в одну кучу). Это вполне правомерно, т.к. любая операция может быть представлена в виде конечного числа «универсальных операций» процессора. Поэтому в будущем если не будет указываться, какие именно операции имеются в виду, то это – универсальные операции. Конечно, при более детальном анализе полезно знать отдельно количество сложений, умножений и т.д.
Итак, время работы всех сортировок равно c |
n(n −1) |
. При больших n |
n(n −1) |
≈ |
n2 |
, |
|||
|
|
|
|||||||
|
|
2 |
|
|
2 |
2 |
|
||
поэтому приближенное время работы будет |
c n2 |
(константа |
c зависит от алгоритма |
||||||
|
1 |
|
|
|
1 |
|
|
|
|
сортировки).
Мы доказали, что время сортировки массива для трех рассмотренных алгоритмов пропорционально квадрату его размера. Но мы еще не знаем, какой из них работает быстрее. Сейчас мы с вами докажем, что сортировка вставками работает быстрее, чем сортировка пузырем.
•Назовем транспозицией перестановку соседних элементов.
Вкачестве меры неупорядоченности массива выберем минимальное количество транспозиций, которое необходимо для того, чтобы упорядочить массив.
Например: чтобы упорядочить массив
1 |
4 |
3 |
2 |
8 |
|
|
|
|
достаточно сделать 3 транспозиции. |
||||||||
|
Теперь можно перейти к анализу алгоритмов. Пусть дан массив размера n |
|||||||
степени неупорядоченности x . |
||||||||
|
Чтобы его отсортировать методом пузырька, надо сделать |
n(n −1) |
сравнений и x |
|||||
|
|
|||||||
|
|
|
|
2 |
|
|||
перестановок (3x присваиваний). |
||||||||
|
Для сортировки вставками надо |
n(n −1) |
− x сравнений и x + 2(n −1) присваиваний. |
|||||
|
|
|||||||
|
|
|
|
2 |
|
|
|
|
|
Уже сейчас можно сказать, что сортировка вставками значительно лучше, чем |
|||||||
сортировка пузырьком, |
т.к. при больших x количество присваиваний и сравнений |
|||||||
значительно меньше. |
|
|
|
|
|
|||
|
Анализировать сортировку выбором значительно труднее, так как два массива |
|||||||
одинакового размера с одинаковой степенью неупорядоченности могут быть отсортированы за разное количество действий. Например, массивы
5 2 3 4 1 и 5 2 4 1 3
можно отсортировать за 7 транспозиций, но первый массив сортировка выбором упорядочит быстрее. Эти и другие сложности затрудняют анализ, поэтому мы не будем на нем останавливаться (есть гораздо лучшие алгоритмы сортировки).
104
Задачи
1.Заполнить массив А числами 1, 2, 3, …, n, где n – длина массива.
2.Дан массив А[n]. Найти разность между максимальным и минимальным элементами массива.
3.Найти среднее арифметическое и среднее геометрическое элементов массива.
4.Дан массив А[n], упорядоченный по неубыванию. Найти количество различных чисел среди элементов этого массива.
5.Найти сумму чисел, порядковый номер которых – число Фибоначчи.
6.Если вы запустите программу вычисления многочленов Чебышева для достаточно больших n (например, 10…15), то вы увидите, что при четных n в многочлен входят только четные степени x , а при нечетных n - нечетные степени x . Докажите этот результат для любых чисел n (т.е. докажите, что Tn (−x) = (−1)n Tn (x) ).
7.(!) Докажите следующее предложение:
l
• если f (−x) = − f (x) , то ∫ f (x)dx = 0
−l
•Используя предыдущее утверждение и результат предыдущей задачи, докажите,
1
что если g (−x) = g (x) , и m + n нечетно, то ∫ g (x)Tm (x)Tn (x)dx = 0 .
−1
8.(Модернизация последовательного поиска). На каждом проходе цикла неявно проводится сравнение, достиг ли счетчик конца массива, или нет. На эти сравнения тратится время. Этого можно избежать: если в последний элемент массива записать число х, то элемент в массиве точно будет найден, и бесконечного цикла не будет. Используя эту идею, модернизируйте алгоритм последовательного поиска.
9.Используя результат задачи №6 предложите способ вычисления коэффициентов многочлена Чебышева без использования дополнительных массивов, и реализуйте его на ТР.
10.Дан целочисленный массив (не отсортированный). Сосчитать и напечатать, сколько различных чисел в этом массиве. Число действий должно быть порядка m2 .
11.Дан массив целых чисел из m+n элементов, рассматриваемый как соединение двух его отрезков: начала A[1]..A[m] из m элементов и конца A[m+1]..A[m+n] из n элементов. Не использую дополнительных массивов, переставить начало и конец. Число действий должно быть порядка m+n.
12.Даны два массива длины m и n соответственно, отсортированные по неубыванию. Найти количество общих элементов в массивах.
13.В примерах на работу с многочленами полиномы печатались в виде массива коэффициентов. Напишите программу, которая бы по массиву коэффициентов
печатала бы сам многочлен. Например: по массиву 1 0 -3 4 7 -3 0 надо напечатать
x^6-3*x^4+4*x^3+7*x^2-3*x. |
|
|
||
14.Некоторое |
число |
содержится в каждом из |
трех |
неубывающих массивов |
x[1] ≤ x[2] ≤ ... |
≤ x[n] , |
y[1] ≤ y[2] ≤ ... ≤ x[m] , z[1] ≤ z[2] ≤ ... |
≤ z[ p]. |
Найти это число. Число |
действий должно быть порядка n + m + p .
15.Задан массив натуральных чисел P . Найти минимальное натуральное число, не представимое суммой никаких элементов массива P . В сумму элементы массива могут входить только по одному разу.
105
16.Задан числовой массив. Найти отрезок максимальной длины, в котором первое число равно последнему, второе – предпоследнему и т. д. Вывести длину отрезка.
17.Даны два упорядоченных по неубыванию массива длины m и n соответственно. Образовать новый массив из m + n элементов, также упорядоченный по неубыванию. Алгоритм должен быть оптимальным.
18.Отсортировать массив с помощью метода пузырька с подсчетом перестановок на каждом витке цикла. Если на некотором шаге перестановок нет, то это означает, что массив уже отсортирован.
19.Представьте себе, что исходный массив имеет такой вид: 9 0 7 1 3 5 6 6 10 5. Хотелось бы не менять 9 со всеми элементами по очереди, пока не доберемся до 10, а сделать как в сортировке вставками: найти первый элемент, больший 9, и вставить его перед ним, просто сдвинув на 1 все числа, стоящие между ними. Скорость сортировки при этом возрастет. Если число промежуточных чисел равно х, то по стандартному алгоритму пузырька надо сделать х операций сравнения и 3*х операций присваивания, а по нашему хитрому улучшению только х сравнений и х операций присваивания (еще х увеличений на 1, т.к. надо искать индекс – но это не страшно).
20.Модернизируйте сортировку вставками: ищите позицию в отсортированной части массива не последовательным поиском (как в примере 11), а бинарным.
21.Отсортировать массив так, чтобы в начале массива стояли положительные элементы в порядке убывания, а в конце стояли неположительные элементы в порядке возрастания. Разработать оптимальный алгоритм.
22.Написать программу, которая по заданному натуральному числу будет возвращать число, у которого отсортированы все цифры (14423 –> 12344). Массивы в программе не использовать.
23.(Сортировка слиянием) Если разбить массив на 2 половины и отсортировать их отдельно, а затем применить алгоритм из задачи 17, то скорость сортировки возрастет. Используя это наблюдение, разработайте алгоритм, который будет сортировать массив за время не cn2 , как рассмотренные нами алгоритмы, а за
c2n log n .
106
Глава 7: Подпрограммы
Вы знаете, что подпрограмма – это фрагмент кода, который можно вызывать из основной программы (а также других подпрограмм).
Подпрограммы позволяют программисту мыслить не в терминах отдельных операторов, а в терминах блоков операторов, выполняющих определенные действия. Кроме того, использование подпрограмм позволяет создавать библиотеки подпрограмм, которые могут использоваться в дальнейшем для других проектов. Причем желательно, чтобы подпрограммы были как можно более общими (сортировать не массивы из 10 элементов, а массивы из любого числа элементов любого типа, причем в произвольном порядке).
|
7.1. Описание подпрограмм |
|
Описание процедур |
procedure Имя (Список формальных параметров); |
|
label |
{Описание меток} |
const |
{Описание констант} |
type |
{Описание типов} |
var |
{Описание переменных} |
procedure |
{Блок описания внутренних процедур } |
function |
{и функций} |
begin {Исполняемая часть} {Операторы}
end;
Описание функций
function Имя (Список формальных параметров): тип результата;
label |
{Описание меток} |
const |
{Описание констант} |
type |
{Описание типов} |
var |
{Описание переменных} |
procedure |
{Блок описания внутренних процедур } |
function |
{и функций} |
begin {Исполняемая часть}
{Операторы, один из которых присваивает имени функции значение результата}
end;
Вы видите, что по синтаксису описания процедуры и функции напоминают синтаксис описания основной программы: в них тоже можно описывать переменные, константы, типы и даже другие подпрограммы.
•Переменные, которые описываются внутри подпрограмм, называются локальными переменными.
•Переменные, объявляемые в основной программе, называются глобальными переменными.
Глобальные переменные существуют на протяжении всего времени работы
программы, а локальные создаются после вызова подпрограммы, а непосредственно
107
перед окончанием ее работы они стираются из памяти. Более подробно различие между локальными и глобальными переменными будет рассмотрено позднее.
Пример 1: Несколько простых процедур и функций.
1 |
: uses Crt; |
|
2 |
: |
|
3 |
: procedure Hallo; {Hallo = Привет} |
|
4 |
: begin |
|
5 |
: |
writeln('Привет!'); |
6 |
: end; |
|
7 |
: |
|
8 |
: function Inhalt(R:real):real;{Inhalt - площадь, объем} |
|
9 |
: begin |
|
10:Inhalt:=Pi*R*R;
11:end;
12:
13:function Faktor(n:byte):longint; {вычисление факториала}
14:var
15:i:byte;
16:f:longint;
17:begin
18:f:=1;
19:for i:=2 to n do
20:f:=f*i;
21:Faktor:=f;
22:end;
23:
24:{Вычисляет периметр прямоугольного треугольника по 2-м катетам}
25:function Perimeter(a,b:real):real;
26:begin
27:if (a<0) or (b<0) then
28:begin
29:writeln('Вы ввели неверные данные');
30:halt;
31:end;
32:perimeter:=a+b+sqrt(a*a+b*b);
33:end;
34:
35:Begin
36:Hallo;
37:writeln('Площадь круга радиусом 5 ед. = ',Inhalt(5));
38:writeln(Faktor(4));
39:writeln(Perimeter(3,4));
40:END.
Эта программа состоит из одной процедуры, трех функций, и основного блока
(между begin и end.)
Начинается выполнение программы, как всегда, с основного блока (строка 35). В нем мы можем вызывать написанные нами подпрограммы (сами по себе они не вызываются).
108
В строке 36 вызывается подпрограмма Hаllo. Вызов подпрограммы производится так: управление передается в подпрограмму, выполняются все ее операторы, а затем управление передается обратно в основную программу. В нашем случае после вызова Hallo будет напечатано сообщение “Привет!“, а затем выполнение программы продолжится с оператора на строке 37.
Давайте теперь рассмотрим описание функции Inhalt.
После названия функции в скобках следует список формальных параметров.
Вбольшинстве случаев в подпрограммы надо передавать дополнительные параметры, которые необходимы для их работы, например, в функцию sqrt надо передать значение числа, корень которого эта функция должна извлечь.
Параметров может быть несколько: если функция должна вычислять площадь прямоугольника по заданным длинам сторон, то параметров должно быть 2.
Передаются параметры в скобках после названия подпрограммы.
Вфункции Inhalt один вещественный параметр – радиус круга (R:real). Передавая его в функцию, можно получать площади кругов разных радиусов.
После списка формальных параметров должен стоять тип возвращаемого значения. В данном случае – тоже real.
Замечание: процедуры не возвращают значений, поэтому типа возвращаемого значения у них нет.
Функция в результате своей работы возвращает значение, которое находится в переменной с названием, совпадающим с названием функции.
Встроке 10 в переменную Inhalt записывается π R2 . Именно это значение и будет результатом функции Inhalt.
Замечание: Не забывайте записывать результат работы функции перед выходом из нее
всоответствующую переменную.
Передача параметров в функцию осуществляется следующим образом: внутри функции создается переменная R типа real (это – формальный параметр), затем в нее копируется то значение параметра, которое вы в эту функцию передали (это –
фактический параметр). Например:
Inhalt(5) В переменную R будет записано значение 5
Inhalt(x) В переменную R будет записано значение переменной x.
Замечание: часто считают, что если формальный параметр - это переменная R – то и в функцию в качестве параметра могут передаваться лишь значение переменной R. Это неверно: безразлично от того, какую переменную или константу мы передадим в подпрограмму, все равно внутри нее будет создана переменная R, в которую будет скопировано значение фактического параметра, и затем работа внутри функции будет проводиться лишь с этой только-что-созданной переменной R, т.е. значения фактических параметров изменяться не будут.
В 37-й строке вызывается функция Inhalt, и результатом ее будет значение площади круга с радиусом 5.
В 38-й строке вызывается функция вычисления факториала. В результате этого управление переносится в функцию Faktor. При этом создается, как вы уже знаете, переменная n типа byte, и в нее записывается 4. Но, кроме нее, будут созданы переменные i и f, объявленные в функции (до вызова функции этих переменных не существует).
109
После того, как все необходимые приготовления выполнены, отрабатывает сама функция, и в строке 21 результат записывается в переменную Faktor. После окончания работы функции Faktor в место вызова функции (строку 38) возвращается значение переменной Faktor, а затем все переменные, созданные внутри функции Faktor, стираются из памяти.
В39-й строке вызывается функция, вычисляющая периметр прямоугольного треугольника по двум 2 катетам (тип real). Однотипные параметры в ТР можно перечислять через запятую (см. строку 25).
Вэтой функции проводится проверка правильности входных параметров (для наглядности она не была приведена предыдущих функциях). Если хотя бы один из катетов меньше 0, то выдается сообщение об ошибке, и затем вызывается процедура halt, осуществляющая выход из программы. Раньше мы с вами пользовались процедурой exit. Но дело в том, что exit выходит только из подпрограммы, а не из программы полностью, поэтому раньше, когда вся наша программа состояла из одного блока, функции halt и exit работали бы одинаково.
Если значения параметров допустимы, то в переменную perimeter записывается значение периметра прямоугольного треугольника, которое и возвращается функцией.
Давайте теперь подытожим все вышесказанное:
При вызове подпрограммы:
1.Выделяется память под формальные параметры и локальные данные.
2.Значения фактических параметров копируются в память, выделенную для формальных.
Во время работы программы:
1.Значения переменных фактических параметров не меняются, значения переменных формальных параметров – изменяются.
При окончании работы подпрограммы:
1.Память, выделенная под формальные параметры и локальные данные очищается.
2.Новые значения формальных параметров, полученные в процессе работы подпрограммы, теряются вместе с очисткой памяти.
Кроме статической памяти, в которой размещается программа и глобальные переменные, программе выделяются дополнительные области памяти – стек и куча (или динамическая память). Все локальные переменные размещаются в стеке, причем всю работу по их размещению и удалению выполняет компилятор (т.е. он генерирует соответствующий набор команд на языках низкого уровня). Как работать с кучей мы рассмотрим в главе 13.
7.2.Передача массивов в подпрограммы
Впредыдущем разделе этой главы вы научились передавать в подпрограммы параметры. Но мы с вами в качестве параметров использовали только числа. В принципе, передача массивов способом, описанным в предыдущей главе, не запрещена. Но давайте теперь посмотрим, что произойдет при этом. Предположим, что описана следующая процедура (то, что она делает, для нас не важно):
Procedure Test(M:Mas);
Здесь М – некоторый массив. При вызове процедуры Test(A); будут выполнены следующие действия:
110
1.Будет выделено место в стеке под формальный параметр М
2.В массив М будут скопированы соответствующие значения массива А.
3.Будет выполнено тело процедуры
4.Массив М будет стерт из стека.
Проблемы, возникающие при таком подходе
1.Создание дополнительного массива сильно расходует память (особенно, если массив достаточно большой).
2.Увеличивается время работы программы (тратится время на копирование данных из одного массива в другой).
Кроме того, есть еще одна принципиальная проблема. Так как внутри процедуры
мы работаем с формальными параметрами, а не фактическими, то выходит, что с помощью описанного выше подхода нельзя написать, например, процедуру заполнения массива.
Пример 2: Проблема заполнения массива числами (füllen - заполнять).
1 : uses Crt;
2 : const
3 : n=5;
4 : type Mas=array[1..n] of integer;
5 :
6 : var
7 : K:Mas;
8 : i:byte;
9 :
10:procedure FulleMas(A:Mas);
11:var
12:i:integer;
13:begin
14:for i:=1 to n do
15:A[i]:=i;
16:writeln('Массив А (формальный параметр) после заполнения');
17:for i:=1 to n do
18:write(A[i],' ');
19:writeln;
20:end;
21:
22:BEGIN
23:Clrscr;
24:writeln('Массив К до вызова процедуры FulleMas');
25:for i:=1 to n do
26:write(K[i],' ');
27:writeln;
28:
29:FulleMas(K);
30:writeln('Массив К после вызова процедуры FulleMas');
31:for i:=1 to n do
32:write(K[i],' ');
33:End.
