
- •Програмування
- •1. Алгоритми 12
- •2. Комп’ютери і програми 47
- •3. Мова програмування паскаль 56
- •4. Прості типи даних. Лінійні програми 61
- •5. Процедурне програмування 73
- •6. Програмування розгалужень 79
- •7. Оператори повторення з параметром. Масиви 99
- •7.13. Задачі і вправи 114
- •8. Ітераційні цикли 116
- •8.6. Задачі і вправи 124
- •9. Рекурсія 126
- •9.4. Задачі і вправи 135
- •10. Швидкі алгоритми сортування і пошуку 137
- •10.8. Задачі і вправи 148
- •11. Складні типи даних: записи і файли 150
- •11.11. Задачі і вправи 169
- •12. Множини 172
- •12.5. Задачі і вправи 175
- •13. Динамічні структури даних 176
- •14. Методологія структурного програмування: підсумки 192
- •1. Алгоритми
- •1.1. Змістовне поняття алгоритму
- •1.2. Виконавець алгоритмів і його система команд
- •1.3. Основні властивості алгоритмів
- •1.4. Величини
- •1.5. Типи величин
- •1.6. Цілі числа
- •1.7. Дійсні числа
- •1.8. Рядкові величини
- •У слові w знайти слово p і замінити його словом q.
- •1.9. Форми запису алгоритмів
- •1:Кінець.
- •X2 присвоїти значення x1
- •1:Кінець.
- •1.10. Команди управління
- •1.11. Блок - схеми
- •1.12. Допоміжні алгоритми
- •1.13. Базові структури управління
- •1.14. Абстракція даних
- •Приклад 1.7. Атд Планіметрія (виконавець Геометр)
- •1.15. Структурне програмування
- •1.16. Парадигма процедурного програмування
- •2. Комп’ютери і програми
- •2.1. Комп’ютер як універсальний Виконавець
- •2.1.1. Зовнішні пристрої комп’ютера
- •2.1.2. Центральні пристрої комп’ютера
- •2.1.3. Поняття про машинну мову
- •2.2. Мови програмування високого рівня
- •2.2.1. Коротка історія розвитку мов програмування
- •2.2.2. Про історію розвитку методів проектування програм
- •2.3. Основні етапи проектування програми
- •2.4. Технологія трансляції програм
- •2.5. Поняття про систему програмування
- •3. Мова програмування паскаль
- •3.1. Алфавіт мови
- •3.2. Концепція даних
- •3.3. Імена та їх застосування
- •3.4. Структура Pascal-програми
- •3.5. Поняття про лексику, прагматику, синтаксис і семантику мови програмування
- •3.6. Синтаксичні діаграми як засіб визначення мови програмування
- •4. Прості типи даних. Лінійні програми
- •4.1. Заголовок програми
- •4.2. Константи і їх використання. Розділ констант
- •4.3. Змінні програми. Розділ змінних
- •4.4. Стандартні прості типи даних
- •4.5. Тип даних Integer
- •4.6. Тип даних Real
- •4.7. Тип даних Сhar
- •4.8. Поняття виразу. Значення виразу. Тип виразу
- •4.9. Розділ операторів. Оператор присвоювання
- •4.10. Оператори введення - виведення
- •4.11. Приклад лінійної програми
- •4.12. Поняття складності виразу. Оптимізація обчислень
- •4.13. Оптимізація лінійних програм
- •4.14. Задачі і вправи
- •5. Процедурне програмування
- •5.1. Опис процедури
- •5.2. Формальні параметри. Локальні і глобальні об’єкти
- •5.3. Оператор процедури. Фактичні параметри
- •5.4. Функції
- •5.5. Приклади
- •6. Програмування розгалужень
- •6.1. Поняття умови. Тип даних Boolean (логічний)
- •6.2. Складений оператор
- •6.3. Оператори вибору: умовний оператор
- •6.4. Приклади
- •6.5. Задачі вибору й упорядкування
- •6.5.1. Задачі вибору
- •6.5.2. Дерево розв’язувань задачі вибору
- •6.5.3. Задачі на зважування
- •6.5.4. Ефективність алгоритму як кількість його кроків
- •6.5.5. Вибір даного елемента
- •6.6. Задачі упорядкування
- •6.6.1. Упорядкування елементів
- •6.6.2. Порівняння, перестановки і пересилання
- •6.7. Оптимізація розгалужень
- •6.8. Розділ типів. Перелічуваний тип
- •6.9. Оператори вибору: оператор варіанта
- •6.10. Вправи
- •7. Оператори повторення з параметром. Масиви
- •7.1. Оператор циклу з параметром
- •7.2. Циклічні програми. Складність циклічної програми. Оптимізація циклічних програм
- •7.3. Обмежені типи
- •7.4. Складні (складені) типи
- •7.5. Регулярний тип. Масиви
- •7.6. Пошук елемента в масиві
- •7.7. Ефективність алгоритму за часом
- •7.8. Мітки. Оператор переходу. Застосування оператора переходу для дострокового виходу з циклу
- •7.9. Постановка задачі сортування
- •7.10. Сортування масивів
- •7.10.1. Прості алгоритми сортування
- •7.11 Сортування обмінами
- •7.12. Сортування вибором
- •7.13. Задачі і вправи
- •8. Ітераційні цикли
- •8.1. Оператори повторення While і Repeat
- •8.2. Алгоритми пошуку і сортування. Лінійний пошук у масиві
- •8.3. Поліпшений алгоритм сортування обмінами
- •8.4. Бінарний пошук в упорядкованому масиві
- •8.5. Алгоритми сортування масивів (продовження). Сортування вставками
- •8.5.1 * Ефективність алгоритму
- •8.6. Задачі і вправи
- •9. Рекурсія
- •9.1. Рекурсивно-визначені процедури і функції
- •9.2. Приклади рекурсивних описів процедур і функцій
- •I стержень j стержень 6-I-j стержень
- •I стержень j стержень 6-I-j стержень
- •I стержень j стержень 6-I-j стержень
- •9.3. Переваги і недоліки рекурсивних алгоритмів
- •9.4. Задачі і вправи
- •10. Швидкі алгоритми сортування і пошуку
- •10.1. Нижня оцінка часу задачі сортування масиву за числом порівнянь
- •10.2. Швидкі алгоритми сортування: Сортування деревом
- •10.2.1. *Аналіз складності алгоритму
- •10.3. Пірамідальне сортування
- •10.3.1.*Аналіз складності алгоритму
- •10.4. Швидке сортування Хоара
- •10.5. Пошук k-того в масиві. Пошук медіани масиву
- •10.6.* Метод “розділяй і володій”
- •10.7.* Метод цифрового сортування
- •10.8. Задачі і вправи
- •11. Складні типи даних: записи і файли
- •11.1. Складні типи даних у мові Pascal
- •11.2. Записи
- •11.3. Записи з варіантами
- •11.4. Оператор приєднання
- •11.5. Рядки і засоби їх обробки
- •Процедури і функції типу String.
- •11.7. Файли. Управління файлами
- •11.8. Основні задачі обробки файлів
- •11.9. Сортування файлів
- •11.9.1. Алгоритм сортування злиттям
- •11.9.2. Аналіз складності алгоритму
- •11.10. Задача корегування файла
- •11.11. Задачі і вправи
- •12. Множини
- •12.1. Множинний тип
- •12.2. Конструктор множини
- •12.3. Операції і відношення над множинами
- •12.4. Застосування множин у програмуванні
- •12.5. Задачі і вправи
- •13. Динамічні структури даних
- •13.1. Стандартні динамічні структури
- •13.2. Посилальний тип даних. Посилання
- •13.3. Програмування динамічних структур даних
- •13.4. Стеки, списки, черги
- •13.5. Задачі
- •13.6. Дерева
- •13.7. Бінарні дерева
- •13.8. Задачі
- •14. Методологія структурного програмування: підсумки
- •14.1. Основні структури управління
- •14.2. Основні структури даних
- •14.3. Методологія програмування “зверху-вниз”
- •14.4. Приклад: Система лінійних рівнянь
- •14.5. Проектування модулів. Модуль rat
- •14.6. Реалізація модуля
- •14.7. Висновки (модульне програмування)
- •14.8. Заключне зауваження: переходимо до об’єктів
9. Рекурсія
9.1. Рекурсивно-визначені процедури і функції
Описання процедури А, в розділі операторів якої використовується оператор цієї процедури, називається рекурсивним. Таким чином, рекурсивне описання має вид
Procedure A(u, v : ParType);
...
Begin
...; A(x, y); ...
End;
Аналогічно, описання функції F, в розділі операторів якої використовується виклик функції F, називається рекурсивним. Рекурсивне описання функції має вид
Function F(u, v : ArgType) : FunType;
...
Begin
...; z := g(F(x, y)); ...
End;
Використання рекурсивного описання процедури (функції) приводить до рекурсивного виконання цієї процедури (обчислення цієї функції). Задачі, що формулюються природним чином як рекурсивні, часто приводять до рекурсивних розв’язків.
Приклад 9.1. Факторіал.
Розглянемо рекурсивне визначення функції n!=1*2*...*n (n-факторіал). Нехай F(n) = n! Тоді
1.F(0) = 1
2.F(n) = n*F(n - 1) при n > 0
Засобами мови це визначення можна сформулювати як обчислення:
If n = 0
then F := 1
else F := F(n - 1) * n
Оформивши це обчислення як функцію і змінивши ім’я, отримаємо:
Function Fact(n: Integer): Integer;
Begin
If n = 0
then Fact := 1
else Fact := Fact(n - 1) * n
End;
Обчислення функції Fact можна представити як ланцюжок викликів нових копій цієї функції з передачею нових значень аргументу і повернень значень функції в попередню копію.
Ланцюжок викликів обривається при передачі нуля в нову копію функції. Рух у прямому напрямку (розгортання рекурсії) супроводжується тільки обчисленням умови і викликом. Значення функції обчислюється при згортанні ланцюжка викликів. Складність обчислення Tfact(n) функції Fact можна оцінити, виписавши рекурентне співвідношення:
Tfact(n) = Tfact(n-1) + Tm + Tl + Tс
Для того, щоб обчислити Tfact(n), треба здійснити одну перевірку, одне множення і один виклик Tfact(n-1). Виклик Tfact потребує затрат часу Tc на “адміністративні” обчислення: передачу параметра, запам’ятовування адреси повернень і т.п. Поклавши С = Tm+Tl+Tс, отримаємо Tfact(n) = Tfact(n-1) + С. Неважко тепер показати, що Tfact(n) = Сn.
Приклад 9.2. Перестановки. Згенерувати всі перестановки елементів скінченої послідовності, що складається з букв.
Спробуємо звести задачу до декількох підзадач, більш простих, ніж вихідна.
Нехай S = [ s1, s2, ..., sn ] - набір символів.
Через Permut(S) позначимо множину всіх перестановок S, a через Permut(S,i) - множину всіх перестановок, в яких на останньому місці стоїть елемент si. Тоді
Permut(S) = Permut(S,n) Permut(S, n-1) ... Permut(S,1)
Елемент множини Permut(S, i) має вид [sj2,..., sjn,si] де j2,..., jn - всі можливі перестановки індексів, не рівних I. Тому Permut(S, i) = (Permut(S \ si), si) і Permut(S)=(Permut(S \ s1),s1)+...+ (Permut(S \ sn), sn).
Отримане співвідношення виражає множину Permut(S) через множини перестановок наборів з (n-1) символу. Доповнивши це співвідношення визначенням Permut(S) на одноелементній множині, отримаємо:
1. Permut({s}) = {s}
2. Permut(S) = (Permut(S\s1), s1) + ... + (Permut(S\sn), sn)
Уточнимо алгоритм, опираючись на представлення набору S в виді масиву S[1..n] of char.
По перше, визначимо параметри процедури Permut:
k - кількість елементів в наборі символів;
S - набір символів, що переставляються.
Алгоритм починає роботу на вхідному наборі і генерує всі його перестановки, що залишають на місці елемент s[k]. Якщо множина перестановок, в яких на останньому місці стоїть s[j], уже породжена, міняємо місцями s[j-1] і s[k], виводимо на друк отриманий набір і застосовуємо алгоритм до цього набору. Параметр k керує рекурсивними обчисленнями: ланцюжок викликів процедури Permut обривається при k = 1.
Procedure Permut(k : Integer; S : Sequence);
Var
j : integer;
Begin
if k <> 1
then Permut(k - 1, S);
For j := k - 1 downto 1 do begin
Buf := S[j];
S[j] := S[k];
S[k] := Buf;
WriteSequence(S);
Permut(k - 1, S)
end
End;
Begin { Розділ операторів програми}
{Генерація вихідного набору S}
WriteSequence(S); {Виведення першого набору на друк}
Permut(n, S)
End.
Оцінимо складність алгоритму за часом у термінах C(n): Кожний виклик процедури Permut(k) містить k викликів процедури Permut(k-1) і 3(k-1) пересилання. Кожний виклик Permut(k-1) супроводжується передачею масиву S як параметра-значення, що за часом еквівалентне n пересиланням. Тому мають місце співвідношення
C(k) = kC(k-1) + nk + 3(k-1), C(1) = 0, звідки С(n) = (n+3)n!
Оцінимо тепер розмір пам’яті, необхідної для алгоритму. Оскільки S - параметр-значення, при кожному виклику Permut резервується n комірок (байтів) для S, а при виході з цієї процедури пам’ять звільняється. Рекурсивне застосування Permut призводить до того, що ланцюжок вкладених викликів має максимальну довжину (n - 1):
Permut(n) --> Permut(n-1) --> ... --> Permut(2) --> Permut(1)
Тому дані цього ланцюжка потребують n2 - n комірок пам’яті, тобто алгоритм потребує пам’ять розміром O(n2). Кількість перестановок – елементів множини Рermut(S) дорівнює n!. Тому наш алгоритм “витрачає” C(n)/n! = O(n) дій для породження кожної перестановки. Зрозуміло, такий алгоритм неможна називати ефективним. (Інтуїція нам підказує, що ефективний алгоритм повинен генерувати кожну перестановку за фіксовану, незалежну від n кількість дій.) Джерело неефективності очевидне: використання S як параметра-значення. Це дозволило нам зберегти незмінним масив S при поверненні з рекурсивного виклику для того, щоб правильно переставити елементи, готуючи наступний виклик.
Розглянемо інший варіант цього алгоритму. Використаємо S як глобальну змінну. Тоді при виклику Рermut S буде змінюватись. Отже, при виході з рекурсії масив S треба поновлювати, використовуючи обернену перестановку!
Program All_permutations;
Const
n = 4;
Type
Sequence = array[1..n] of char;
Var
S : Sequence;
Buf : char;
i : Integer;
Procedure WriteSequence;
begin
For i := 1 to n do Write(S[i]);
Writeln
end;
Procedure Permut(k : Integer);
Var
j : integer;
Procedure Swap(i, j : Integer);
begin
Buf := S[j];
S[j] := S[i];
S[i] := Buf
end;
Begin
if k > 1
then Permut(k - 1);
For j := k - 1 downto 1 do begin
Swap(j, k); {Пряма перестановка}
WriteSequence;
Permut(k - 1);
Swap(k, j) {Обернена перестановка}
end
End;
Begin
{Генерація вихідного набору S}
WriteSequence; Permut(n)
End.
Тепер оцінка C(n) виходить з співвідношень
C(k) = kC(k-1) + 3(k-1), C(1) = 0 , т.е. C(n) = O(n!)
Цікаво, що цей варіант не потребує і пам’яті розміру O(n2) для збереження масивів. Необхідна тільки пам’ять розміру O(n) для збереження значення параметра k.