You are on page 1of 84

Лабораторна робота № 10

МНОЖИННІ ТИПИ
В АЛГОРИТМІЧНІЙ МОВІ
ПРОГРАМУВАННЯ
1. МЕТА РОБОТИ

Мета роботи – ознайомлення із особливостями застосування множинних


типів у мові програмування. Вивчити особливості застосування множин під час
розв’язування завдань. Набути практичних навичок програмування з викорис-
танням множинного типу.

2. ТЕОРЕТИЧНІ ВІДОМОСТІ

2.1. ПОЗНАЧЕННЯ ТА ЗАДАННЯ МНОЖИН


У МОВІ ПАСКАЛЬ

Множина – це неповторюваний, невпорядкований набір однотипних


логічно зв’язаних між собою об’єктів. Множина, що не містить елементів,
називається порожньою. Порядок елементів у множині не має значення. Дві
множини вважаються еквівалентними, якщо вони містять однакові елементи.
Якщо всі елементи однієї множини входять також в іншу множину, то
говорять, що перша множина включена в другу. Приймається, що порожня
множина включена в будь-яку іншу. Значенням множинного типу в мові
Паскаль є множина. Конкретні значення множинного типу (постійні або змінні)
задаються за допомогою конструктора множини, який має вигляд:
• SET OF T
де Т – може бути скалярним, обмеженим (інтервальним) або символьним
типом. Тип Т називається базовим типом множини. Значеннями змінних
множинного типу є множини значень базового типу.
Приклади множин мовою Паскаль:
[] – пуста множина;
[2,3,5,7] – множина, яка містить цілі числа 2, 3, 5, 7;
[-10..-1,2,3,5,7] – множина, яка містить цілі числа від -10 до -1 та
2, 3, 5, 7;
[’а’, ’с’, ’е’, ’d’] – множина, яка містить як елементи літери а,
с, е, d;
[1..100] – множина послідовних цілих чисел від 1 до 100;
[К..2*K] – множина цілих чисел від значення К до значення виразу
2*K, де К – значення змінної чи константи.
Змінні множинного типу називаються також змінними-множинами та
описуються вони, як інші змінні, у розділі опису змінних. Як звичайно, при

2
описі змінних, тип їхніх значень може бути заданим або безпосередньо, або
вказанням імені типу, яке визначене у розділі типів.
Наприклад:
TYPE num = (one, two, three, four);
number = set of num;
• VAR QU: number;
QP: set of num;
Для присвоювання значень змінним-множинам використовується опера-
тор присвоювання виду:
V: = S;
де V – змінна множинного типу;
S – множинний вираз, тобто вираз, значенням якого є множина.
Для наведеного вище прикладу допустимими є конструкції:
QU: = [one, two];
QP: = [three, four];
Задання значень множинам можливе за допомогою константних виразів
у розділі опису констант:
Const
Num = [’0’.. ’9’];
A = [’A’..’Z’,’a’..’z’];
P = A + Num; {константа Р містить цифри та
латинські літери}
Якщо дано опис змінної множинного типу:
VAR
PM : SET OF (LVIV, KYIV, ODESSA);
то усіма можливими значення цієї змінної у ПАСКАЛІ будуть:
[LVIV, KYIV, ODESSA]
[LVIV, KYIV]
[KYIV, ODESSA]
[LVIV, ODESSA]
[LVIV]
[KYIV]
[ODESSA]
[]
Елементи множини є не впорядкованими, тому однаковими є множини:
[1, 7, 4], [4, 7, 1], [1, 4, 7], [ 4, 1, 7], [7, 4, 1], [7, 1, 4] тощо.

3
2.2. ОПЕРАЦІЇ НАД МНОЖИНАМИ. МНОЖИННІ ВИРАЗИ

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


великий набір операцій. В основному цей набір відображає операції, які
передбачені у відповідному розділі математики (теорії множин).
Для одержання нових множинних значень у Паскалі введені операції
об’єднання, перетину та різниці множин. Операндами цих операцій можуть
бути множинні константи, змінні-множини або вирази, які набувають значення
множинного типу.
Об’єднанням двох множин А та В: А  В = {x| x  xB}
називається множина, що складається із елементів, які
входять хоча би в одну із множин А або В.
A
У мові Паскаль для позначення цієї операції вико-
B ристовується знак “+” (плюс).
Наприклад:
Вираз [1,2,3,4,5] + [2,5,6,7,8] дорівнює множині
[1,2,3,4,5,6,7,8].
Перетином двох множин А та В: А  В = {x| x  xB} називається
множина, яка складається із елементів, що одночасно
входять і в множину А, і в множину В. У мові Паскаль для
A позначення операції перетину використовується знак “*”
B (зірочка).
Наприклад:
Вираз [1,2,3,4,5] * [2,5,6,7,8] дорівнює множині [2,5].
Різницею двох множин А та В: А\В= {x| x  xB} називається
множина, що складається із елементів множини А, які не
входять у множину В.
A Операція різниці двох множин у мові Паскаль позна-
B чається символом “-” (мінус).
Наприклад:
Вираз [1,2,3,4,5] - [2,5,6,7,8] дає в результаті множину
[1,3,4].
Симетричною різницею двох множин А та В: А  В= (А  В) \ (А  В) =
= {x| (x  xB)  (x  xB } називається множина, що
складається із елементів множини А, які не входять у множину
A В або елементів множини В, які не входять у множину А.
B Операція симетричної різниці двох множин у мові
Паскаль описується виразом (А+В)-(А*В).

4
Наприклад:
Вираз ([1,2,3,4,5] + [2,5,6,7,8]) – ([1,2,3,4,5] *
[2,5,6,7,8]) дає в результаті множину [1,3,4,6,7,8].

Із використанням множинних операцій можуть будуватись множинні


вирази для отримання множинних значень. Під час обчислення значень мно-
жинних виразів використовується старшинство операцій, аналогічно старшин-
ству операцій під час обчислення арифметичних виразів, а саме: у першу чергу
обчислюються значення виразів, розміщених у дужках, потім виконуються
операції “*”, після чого “+” та “-” у порядку їхньої черговості зліва направо.
Повний синтаксис множинного виразу наведений на рис. 1.

<Множинний вираз> <Множник>

Доданок Конструктор множини

+ Змінна

а)а) -
в)
[ Множинний вираз
*

<Доданок> <Конструктор множини>

Множник [

б)б) *
Індексний вираз

вираз> <Множник>
. . Індексни
г)
Доданок Конструктор множини
,
+ Змінна

в) в)
- [ Множинний вираз ]
*

<Конструктор множини>

Множник [ ]

Індексний вираз
*

. . Індексний вираз
г)

Рис. 1. Синтаксична діаграма множинного виразу

Операції відношення. Операндами цих операцій є множинні вирази.


Основні операції відношення над множинами, що реалізовані у мові Паскаль,
наведені у табл. 1. Особливе місце тут займає операція входження IN. На
відміну від решти операцій, у яких значення двох операндів належать до

5
одного і того самого множинного типу, в операції IN перший операнд повинен
належати базовому типу, а другий – множинному типу значень, побудованому
на основі цього базового типу.
Результатом операцій відношення є логічне значення (TRUE або FALSE).

Таблиця 1
Операції відношення над множинами
Математичний Запис мовою Значення результату
запис Паскаль True False
Якщо множини А та В
збігаються
B
У протилежному
A=B A=B
випадку

A
Якщо множини А та В
не збігаються
У протилежному
AB A<>B B
випадку

Коли усі елементи


множини А належать
множині В
У протилежному
AB A <= B
B випадку

Коли усі елементи


множини В належать
множині А
У протилежному
AB A >= B
випадку
A
B

Якщо елемент х
входить у множину А
x
У протилежному
xA x IN A
випадку

6
2.3. ВВЕДЕННЯ ТА ВИВЕДЕННЯ МНОЖИН

Змінну множинного типу не можна вводити за допомогою процедур


READ або READLN і виводити за допомогою процедур WRITE та WRITELN.
Множину можна тільки сформувати.
Алгоритм формування множини:
1. Прочитати змінну, яка належить базовому типу множини.
2. Перевірити, чи належить ця змінна інтервалу значень, з яких форму-
ється множина, використавши операцію IN.
3. Приєднати змінну до множини, використавши операцію об’єднання
множин. При цьому слід пам’ятати, що елемент, який приєднується
до множини, повинен бути множиною, тобто взяти його в квадратні
дужки.
Приклад: сформувати множину, елементами якої є цифри, які входять у
рядок символів, що закінчується крапкою.
Текст програми мовою Паскаль:
PROGRAM TESTIN (INPUT,OUTPUT);
VAR
LC: SET OF ’0’..’9’;
SYM:CHAR;
BEGIN
LC:=[];
REPEAT
READ(SYM);
IF SYM IN [’0’..’9’] THEN
LC:=LC+[SYM]
UNTIL SYM='.';
END.
Алгоритм виведення множини:
1. Відкрити цикл, параметром якого є змінна базового типу.
2. Перевірити за допомогою операції IN, чи належить ця змінна множи-
ні, що виводиться.
3. Видрукувати змінну.
Приклад: вивести множину LC (де – символ пробілу):
FOR SYM:=’0’ TO ’9’ DO
IF SYM IN LC THEN
WRITE(SYM,’ ’);

7
Таблиця 2
Процедури для роботи з множинами у Turbo Pascal 7
Структура виклику процедури Призначення Еквівалентна дія
Іnclude(<множина>, Включення у зазначену S=S+[I]
<елемент>) множину S нового
елемента I
Exclude(<множина>, Вилучення зазначеного S:=S-[I]
<елемент>) елемента I з множини S

2.4. ПРИКЛАДИ ВИКОРИСТАННЯ МНОЖИННОГО ТИПУ

Приклад: обчислити у текстовому файлі окремо кількість цифр та


окремо кількість латинських літер. Блок-схему алгоритму розв’язання задачі
наведено на рис. 2.
Текст програма мовою Паскаль:
PROGRAM TEST10 (INPUT, OUTPUT);
TYPE CS=SET OF CHAR;
CONST
D: CS=[’0’..’9’];
L: CS=[’a’..’z’,’A’..’Z’];
VAR
KD, KL : INTEGER;
F:TEXT;
CH: CHAR;
BEGIN
ASSIGN(F,’TESTSET.TXT’);
RESET(F);
WHILE NOT EOF(F) DO
BEGIN
READ(F,CH);
IF CH IN D THEN KD:=KD+1;
IF CH IN L THEN KL:=KL+1;
END;
WRITELN(’КІЛЬКІСТЬ ЦИФР=’, KD);
WRITELN(’КІЛЬКІСТЬ ЛІТЕР=’, KL);
CLOSE(F)
END.

8
1 Множина L, яка використовується у
Початок
програмі, за необхідності може бути отри-
2
мана з двох інших множин:
D=['0'..'9']

L=['a'..'z,'A'..'Z'']
SL=[’a’..’z’];
3 BL=[’A’..’Z’];

Відкрити Операцією додавання (об’єднання):


файл

'testset.txt' L:=SL+BL;
Можлива також зворотна дія: з мно-
4
ні
жини L отримати SL та BL за допомогою
Не кінець
файлу а
операції різниці множин.
так SL:=L-[’A’..’Z’];
5
Ввід з BL:=L-[’a’..’z’];
файлу а
ch
Приклад: у заданій послідовності
6
так
7 літер, яка складається із букв латинського
ch  D kd:=kd+1 алфавіту і закінчується крапкою,
ні
визначити загальну кількість входжень у
неї букв “а”, “е”, “о”, “u”.
8 9
так

ch  L kl:=kl+1
Блок-схему алгоритму розв’язання
наведено на рис. 3.
ні
Текст програми мовою Паскаль:
10

Вивід PROGRAM T10(INPUT,OUTPUT);


kd, kl
VAR
11 K:INTEGER;
SYMB: CHAR;
Закрити

файл BEGIN
'testset.txt'
K:=0;
READ(SYMB);
12

Кінець
WHILE SYMB<>’.’ DO
BEGIN
Рис. 2. Блок-схема алгоритму
Рис. 2 Блок-схема алгоритму розв'язку IF SYMB IN
розв’язання [’A’,’E’,’O’,’U’]
THEN K := K+1;
READ(SYMB);
END;
WRITELN (’ЗАГАЛЬНЕ ЧИСЛО ВХОДЖЕНЬ РІВНЕ ’, K)
END.

9
1

Початок

k=0

Ввід

SYMB

4
ні

SYMB  '.'

так

5 6
так
SYMB 
k:=k+1
[A,E,O,U]

ні

Ввід
SYMB

Вивід

Кінець

Рис. 3 Блок-схема алгоритму розв'язку

Рис. 3. Блок-схема алгоритму розв’язання прикладу


прикладу 2

Приклад: сформувати множини на основі операцій присвоювання та


множинних виразів:
Текст програми мовою Паскаль (де – символ пробілу):

PROGRAM LAB10 (INPUT,OUTPUT);


VAR
I,K:INTEGER;
X,Y:SET OF 0..100;
BEGIN
K:=6;
Y:=[2..16];
X:=[1..14]*[K-1,12..K*10] +[4..7] -Y*[K];

10
FOR I:=1 TO 100 DO
IF I IN X THEN WRITE(I,’ ’)
END.
У результаті виконання програми значенням множини Х буде [4, 5, 7,
12, 13, 14].
Приклад: задано множину натуральних чисел від 1 до n (n не може
перевершувати 255). З цієї множини необхідно одержати нову множину, що
містить тільки ті з натуральних чисел від 1 до n, що є квадратами
натуральних чисел.
Текст програми мовою Паскаль:
PROGRAM SETS (INPUT, OUTPUT);
VAR
N, I: INTEGER;
S: SET OF 1..255;
BEGIN
WRITE(’ЗАДАЙТЕ КІЛЬКІСТЬ ЕЛЕМЕНТІВ МНОЖИНИ’);
WRITELN(’N <= 255): ’);
READLN(N);
S := [1..N];
{Задаємо множину, що містить натуральні числа з n
чисел}
FOR I := 1 TO N DO
BEGIN
{Для кожного елемента I початкової
множини S перевіряємо, чи є він квадратом
другого натурального числа. Якщо елемент
I не є квадратом другого числа, він
вилучається з початкової множини}
IF TRUNC(SQRT(I)) <> SQRT(I) THEN
S:=S-[I];
{Якщо елемент I є квадратом другого числа,
він лишається у множині S. Виводимо його
на екран}
IF I IN S THEN WRITE(I:5)
END;
{Після виконання циклу множина s
містить лише ті натуральні числа, які
є квадратами інших чисел}
WRITELN
END.

11
Приклад: задано три множини символьного типу, які задані конструкт-
торами:
Y1=[’A’, ’B’, ’D’, ’R’, ’M’];
Y2=[’R’, ’A’, ’H’, ’D’];
Y3=[’A’, ’R’];
Сформувати нову множину: X=(Y1  Y2)  (Y1\Y2). Вивести на друк
отриману множину. Перевірити, чи включена множина Y3 у множину Х.
Для формування нової множини Х доцільно скористатися оператором
присвоювання. Для виведення на екран елементів нової множини застосо-
вується оператор циклу FOR. Параметром циклу є символьна змінна С, що
набуває значення кожного символу латинського алфавіту від “А” до “R”. В
операторі циклу використовується не весь латинський алфавіт від “А” до “Z”, а
лише його частина. Це пов’язано з тим, що задані множини Y1, Y2, Y3 не
містять символів після літери “R”. Але помилкою не буде, якщо кінцевим
значенням параметра циклу буде символ “Z”.
Текст програми мовою Паскаль:
PROGRAM CREATESET (INPUT,OUTPUT);
VAR
Y1,Y2,Y3,X:SET OF CHAR;
C:CHAR;
BEGIN
Y1:=[’A’, ’B’, ’D’, ’R’, ’M’];
Y2:=[’R’, ’A’, ’H’, ’D’];
Y3:=[’A’, ’R’];
X:=(Y1*Y2)+(Y1-Y2);
WRITELN(’МНОЖИНА X=’);
FOR C:= ’A’ TO ’R’ DO
IF C IN X THEN WRITE(C:2);
WRITELN;
IF Y3<=X THEN WRITE(’Y3 МІСТИТЬСЯ В X’)
ELSE WRITE(’Y3 НЕ МІСТИТЬСЯ В X’);
END.
Приклад: з множини цілих чисел 1...20 виділити окремо: 1) множину чисел,
що діляться на 6 без остачі; 2) множину чисел, що діляться без остачі на 2 і 3.
Нехай К – розмірність множини; N2 – множина чисел, які діляться на 2
без остачі; N3 – множина чисел, які діляться на 3 без остачі; N6 – множина
чисел, які діляться на 6 без остачі; N23 – множина чисел, які діляться на 2 і на 3
без остачі; І – параметр циклу. Блок-схему алгоритму наведено на рис. 4.

12
1

Початок

2 А
k=20 В

N2=Ж 9
15
N3=Ж
i=1, k
i=1, k
3

i=1, k
11
10 ні 16 17
Вивід ні
i  N2 Вивід
4
i i  N6
5 i
так
остача
N2:=N2+[i] так
i / 2 =0 так

ні
18
12
6 так 7 i=1, k
остача i=1, k
N3:=N3+[i]
i / 3 =0

19 20
ні 14 ні
13 ні Вивід
Вивід i  N23
i  N3 i
8 i

N6:=N2 З N3; так


так
N23:=N2 И N3

20

А В Кінець

Рис. 4. Блок-схема алгоритму розв’язання

Текст програми мовою Паскаль:


PROGRAM DSET (INPUT,OUTPUT);
CONST K=20;
VAR
N2,N3,N6,N23:SET OF 1..20;
I:INTEGER;
BEGIN
N2:=[];
N3:=[];
FOR I:=1 TO K DO
BEGIN
IF I MOD 2 =0 THEN N2:=N2+[I];
IF I MOD 3 =0 THEN N3:=N3+[I];
END;
N6:=N2*N3;
N23:=N2+N3;
WRITELN(’МНОЖИНА ЧИСЕЛ, ЩО ДІЛЯТЬСЯ НА 2’);
FOR I:=1 TO K DO
IF I IN N2 THEN WRITE(I:3);
WRITELN;

13
WRITELN(’МНОЖИНА ЧИСЕЛ, ЩО ДІЛЯТЬСЯ НА 3’);
FOR I:=1 TO K DO
IF I IN N3 THEN WRITE(I:3);
WRITELN;
WRITELN(’МНОЖИНА ЧИСЕЛ, ЩО ДІЛЯТЬСЯ НА 6’);
FOR I:=1 TO K DO
IF I IN N6 THEN WRITE(I:3);
WRITELN;
WRITELN(’МНОЖИНА ЧИСЕЛ, ЩО ДІЛЯТЬСЯ НА 2 ТА НА 3’);
FOR I:=1 TO K DO
IF I IN N23 THEN WRITE(I:3);
END.

14
Лабораторна робота № 11
КОМБІНОВАНІ ТИПИ (ЗАПИСИ,
СТРУКТУРИ)
В АЛГОРИТМІЧНІЙ МОВІ
ПРОГРАМУВАННЯ.
1. МЕТА РОБОТИ

Мета роботи – ознайомитись із особливостями застосування комбіно-


ваних типів (записів, структур) у алгоритмічній мові програмування. Вивчити
особливості застосування різних видів записів. Набути практичних навичок
програмування з використанням комбінованих типів.

2. ТЕОРЕТИЧНІ ВІДОМОСТІ

Комбінований тип є гнучким механізмом побудови структур даних.


Комбінований тип задає образ об’єктів, кожна частина якого може мати різні
характеристики.
Комбінований тип характеризує об’єкти, які називаються записами.
Запис – це складна змінна, що містить кілька компонентів. На відміну від
масивів компоненти запису (поля) можуть бути різних типів і доступ до них
здійснюється не за індексом, а за іменем поля.

2.1. НАЙПРОСТІШІ ЗАПИСИ

Запис – це об’єднання фіксованої кількості логічно-з’єднаних компонен-


тів, які називаються полями.
Компоненти запису можуть бути різного типу, кожне поле має своє ім’я.
У загальному, тип запису можна описати:
TYPE
P= RECORD
V1, V2 : T1;
V3, V4 : T2;
...
Vn : Tn
END;
де Р – ім’я типу;
V1,V2,V3,…,Vn – імена полів запису;
T1,T2,…,Tn – типи полів.
Опис комбінованого типу проводиться у розділі опису типів, починається

2
службовим словом RECORD та закінчується словом END.
Типи полів T1,T2,…,Tn можуть набувати:
1) будь-який стандартний тип: REAL, INTEGER, CHAR, BOOLEAN;
2) ім’я типу, описаного раніше;
3) сам тип (перелічуваний, інтервальний тощо)
Синтаксичну діаграму комбінованого типу наведено на рис. 1.

RECORD ім’я поля Тип поля END


:

,
;

Рис. 1. Синтаксична діаграма опису комбінованого типу

Опишемо тип, що містить анкетні дані студента: прізвище, ім’я, число,


місяць і рік його народження:
TYPE
STUD = RECORD
PR, IM : ARRAY [1..10] OF CHAR;
DAT : 1..31;
MIS : 1..12;
RIK : 1970..1990
END;
Якщо описати змінну типу запис:
VAR
STUDENT : STUD;
GRUP : ARRAY [1..30] OF STUD;
то доступ до будь-якого поля буде здійснюватися через складене ім’я, яке
містить ім’я змінної та ім’я поля:
BEGIN
STUDENT.PR := ’ТКАЧУК’;
STUDENT.IM := ’СЕРГІЙ’;
STUDENT.RIK := 1981;
...
Перший оператор занесе у поле PR змінної STUDENT рядок завдовжки
10 символів ’ТКАЧУК ’
Другий оператор занесе у поле IM змінної STUDENT рядок завдовжки 10

3
символів ’СЕРГІЙ ’. Де – символ пропуска (пробіл).
Третій оператор занесе у поле RIK змінної STUDENT ціле число 1981.
Кожне поле запису можна не тільки задавати в операторі присвоювання,
але й вводити з клавіатури або з файла:
FOR I := 1 TO 30 DO
BEGIN
FOR J := 1 TO 10 DO
READ (GRUP [I].PR [J] );
FOR J := 1 TO 10 DO
READ (GRUP [I].IM [J] );
READLN (GRUP[I].DAT, GRUP[I].MIS, GRUP[I].RIK)
END;
За допомогою цього фрагмента програми з клавіатури буде введено
анкетні дані 30-ти студентів у такій формі:
ТКАЧУК СЕРГІЙ 30 01 1981
Тобто в одному рядку буде набрано інформацію про одного студента.
Слід пам’ятати:
1. Ім’я поля завжди вказується явно, а не обчислюється як індекс у маси-
вах.
2. Поля як самостійні програмні об’єкти поза записом не існують, тому
вказувати тільки ім’я поля без імені змінної не можна.
Оскільки поля PR та IM описані як масиви символів, то доцільно вико-
ристовувати цикл FOR під час введення, виведення та роботи з цими полями
(цикл FOR J := 1 TO 10 DO у наведеному прикладі).
Для повних змінних однакового комбінованого типу може викорис-
товуватися єдина операція – операція присвоювання. Наприклад:
GRUP [3] := STUDENT;
За допомогою цього оператора всі поля третього елемента масиву
GRUP заповнюються інформацією, що міститься у змінній STUDENT, тобто:
GRUP [3]. PR буде ‘ТКАЧУК ’
GRUP [3]. IM буде ‘СЕРГІЙ ’
GRUP [3]. RIK буде 1981 тощо.
Над полями можна виконувати ті операції, які допустимі для цього
типу. Так, якщо тип окремого поля INTEGER або REAL, то й операції над
цими полями будуть: +, -, *, /, DIV, MOD та операції порівняння <, >, <=, >=, =,
< >.

4
Приклад: обчислити суму, різницю та добуток двох комплексних
чисел X та Y.
Текст програми мовою Паскаль:
PROGRAM COMP (INPUT, OUTPUT);
TYPE
COMPL = RECORD
RE: REAL;
IM: REAL
END;
VAR X, Y, U, V, W : COMPL;

BEGIN
WRITE (’INPUT X.RE, X.IM’);
READLN(X.RE, X.IM);
WRITE(’INPUT Y.RE, Y.IM’);
READLN(Y.RE, Y.IM);

{U=X+Y}
U.RE := X.RE + Y.RE;
U.IM := X.IM + Y.IM;
WRITELN (’X+Y=’, U.RE, ’+’, U.IM, ’*I’);

{V=X-Y}
V.RE := X.RE - Y.RE;
V.IM := X.IM - Y.IM;
WRITELN (’X-Y=’, V.RE, ’+’, V.IM, ’*I’);

{W=X*Y}
W.RE := X.RE * Y.RE;
W.IM := X.IM * Y.IM;
WRITELN (’X*Y=’, W.RE, ’+’, W.IM, ’*I’)
END.

Приклад: за заданими анкетними даними студентів Вашої групи визна-


чити – хто з них “Лев” за знаком Зодіаку. Блок-схему алгоритму розв’язання
наведено на рис. 2.
Текст програми мовою Паскаль:
PROGRAM ZNAK (INPUT, OUTPUT);
TYPE STUD = RECORD
PR, IM : ARRAY [1..10] OF CHAR;

5
DAT : 1..31;
MIS : 1..12;
1
Початок

2
і = 1, 30

3
Ввід групи

4
L= false

5
і = 1, 30

6 ні 10
Міс =8 ні
Міс =7
так так

7 23≤чис ні
11 ні
1чис22
≤31

так так

12 Вивід
8 Вивід
прізвища
прізвища

9 13
L= true
L= true

14
ні
L =false

так
15
Вивід
Левів нема Рис. 2. Блок-схема алгоритму для задачі про
16 знак Зодіаку
Кінець

6
RIK : 1970..1990;
END;
VAR GRUP : ARRAY [1..30] OF STUD;
L : BOOLEAN;
I,J : INTEGER;
BEGIN
WRITELN (’ВВЕДIТЬ СПИСОК СТУДЕНТIВ’);
FOR I :=1 TO 30 DO
BEGIN
FOR J := 1 TO 10 DO
READ (GRUP[I].PR[J]);
FOR J := 1 TO 10 DO
READ (GRUP[I].IM[J]);
READLN (GRUP [I].DAT, GRUP [I].MIS, GRUP[I].RIK)
END;
L:=FALSE;
{ПЕРЕВIРКА ДАТИ НАРОДЖЕННЯ I ВИВIД}
FOR I := 1 TO 30 DO
BEGIN

IF((GRUP[I].MIS=7)AND(GRUP[I].DAT>=23)AND(GRUP[I].DAT<=31))

OR((GRUP[I].MIS=8)AND(GRUP[I].DAT>=1)AND(GRUP[I].DAT<=22))
THEN
BEGIN
FOR J := 1 TO 10 DO
WRITE (GRUP[I].PR[J]);
WRITELN;
L:=TRUE
END
END;
IF L = FALSE THEN WRITELN (’ЛЕВIВ У ГРУПI НЕМАЄ’)
END.

2.2. ОПЕРАТОР ПРИЄДНАННЯ

З наведеного прикладу видно, що під час роботи із записами утворюються

7
досить довгі програми, оскільки весь час необхідно записувати ім’я змінної GRUP[i].
Щоб спростити такі записи, у мові Паскаль введено оператор приєднання WITH.
Структура оператора WITH:
WITH <ім’я змінної> DO <оператор>;
Синтаксичну діаграму оператора WITH наведено на рис. 3.

WITH Ім’я змінної DO оператор

Рис. 3. Синтаксична діаграма оператора WITH

Усередині оператора WITH змінні позначаються тільки іменем поля.


Програма попереднього прикладу з використанням оператора WITH буде мати
такий вигляд:
PROGRAM ZNAK2 (INPUT,OUTPUT);
TYPE STUD = RECORD
PR, IM : ARRAY [1..10] OF CHAR;
DAT : 1..31;
MIS : 1..12;
RIK : 1970..1990;
END;
VAR GRUP : ARRAY [1..30] OF STUD;
L : BOOLEAN;
I, J : INTEGER;
BEGIN
WRITELN (’ВВЕДІТЬ СПИСОК СТУДЕНТІВ’);
FOR I :=1 TO 30 DO
WITH GRUP [I] DO
BEGIN
FOR J := 1 TO 10 DO
READ (PR [J]);
FOR J := 1 TO 10 DO
READ (IM [J]);
READLN (DAT, MIS, RIK)
END;
L := FALSE;
FOR I :=1 TO 30 DO
WITH GRUP [I] DO
BEGIN
IF ((MIS=7) AND (DAT>=23) AND (DAT<=31)) OR
((MIS=8) AND (DAT>=1) AND (DAT<=22)) THEN

8
BEGIN
FOR J :=1 TO 10 DO
WRITE(PR[J]);
WRITELN;
L := TRUE
END
END;
IF L = FALSE THEN
WRITELN (’ЛЕВІВ У ГРУПІ НЕМАЄ’);
END.
Текст програми суттєво скоротився.
Таким чином оператор приєднання WITH дає змогу використовувати
замість складеного імені безпосередньо ім’я поля. При цьому ім’я запису
виноситься у заголовок оператора приєднання. На етапі трансляції ім’я змінної
підставляється автоматично до імені поля і формується повне ім’я.

2.3. ІЄРАРХІЧНІ ЗАПИСИ

Оскільки ніяких обмежень на тип поля запису не накладається, тому


компонентою запису може бути й запис. Таким чином комбінований тип може
мати виразну ієрархічну структуру.
Опишемо запис, що містить анкетні дані студента, виділивши в окремий
запис дату народження:
TYPE
STUD = RECORD
PR, IM : ARRAY [1..10] OF CHAR;
DATAN : RECORD
DAT : 1..31;
MIS : 1..12;
RIK : 1970..1990
END
END;

При описі змінної:


VAR
STUDENT : STUD;

9
Оператор звертання до поля рік буде мати такий вигляд:
STUDENT.DATAN.RIK :=1981;
Приклад: обчислити кількість студентів Вашої групи, які народилися у
1981 році.
Текст програми мовою Паскаль:
PROGRAM TEST (INPUT, OUTPUT);
TYPE
STUD = RECORD
PR, IM : ARRAY [1..10] OF CHAR;
DATAN : RECORD
DAT : 1..31;
MIS : 1..12;
RIK : 1970..1990;
END
END;
VAR GRUP : ARRAY [1..30] OF STUD;
I, J, K : INTEGER;
BEGIN
FOR I :=1 TO 30 DO
WITH GRUP[I] DO
BEGIN
FOR J :=1 TO 10 DO READ (PR[J]);
FOR J :=1 TO 10 DO READ (IM[J]);
READLN (DATAN.DAT, DATAN.MIS, DATAN.RIK);
END;
FOR I :=1 TO 30 DO
WITH GRUP[I].DATAN DO
IF (RIK = 1981) THEN K :=K+1;
WRITELN (’КІЛЬКІСТЬ СТУДЕНТІВ, ЯКІ НАРОДИЛИСЯ В 1981
РОЦІ = ’, K);
END.

2.4. ЗАПИСИ ЗІ ЗМІННИМИ ПОЛЯМИ

Деколи виникає потреба створити записи, які мають різні структури


залежно від умов. У цьому випадку список полів містить спільну частину
записів і варіантну.
Наприклад: для складання анкети під час прийому на роботу всім спів-

10
робітникам необхідно вказати:
1) прізвище;
2) ім’я;
3) по батькові;
4) рік народження.
Це спільна частина запису.
У варіантній частині слід вказати для жінок:
1) чи заміжня;
2) кількість дітей;
для чоловіків:
1) військовозобов’язаний;
2) військова спеціальність.
Опис такого типу буде мати вигляд:
TYPE
STAT = (MEN, WOM);
ANK = RECORD
PR, IM, PB : ARRAY [1..10] OF CHAR;
RIK : 1990..2000;
MW : STAT;
CASE STAT OF
MEN : (VZ : 0..1; SPEC: ARRAY [1..10] OF CHAR);
WOM : (MER : 0..1; KDIT : INTEGER)
END;

Правила формування записів із варіантом:


1. Варіантна частина запису починається службовим словом CASE і
стоїть після закінчення спільної частини запису.
2. Після закінчення опису варіантної частини оператор END не ставить-
ся. Тобто на відміну від оператора CASE у розділі операторів програ-
ми у розділі опису типу запису END не ставиться.
3. Після закінчення варіантної частини ніякі поля спільні для запису не
ставляться.
4. Усі варіанти описуються всередині оператора CASE. Кожен варіант
характеризується заданим у круглих дужках списком описів, власти-
вих йому компонентів.
5. Перед списком стоїть одна або декілька констант вибору. Тип констант
вибору задано у заголовку оператора CASE.
6. Тип не може бути виразом, повинен бути скалярним і задаватися
іменем. “STAT” – ім’я типу, описаного перед описом запису. У явно-

11
му вигляді вказати тип у заголовку CASE не можна.
7. Не можна використовувати одне й те саме ім’я для визначення
спільної та варіантної частини запису.
8. Звернення до полів записів із варіантом аналогічне зверненню до
звичайних записів:
Якщо описати змінні:
VAR
STUDENT : ANK;
GRUP : ARRAY [1..30] OF ANK;
То оператори присвоювання значення полям запису будуть:
STUDENT.PR := ’ТКАЧУК’;
STUDENT.IM := ’СЕРГІЙ’;
STUDENT.MW := MEN;
STUDENT.VZ := 1;
STUDENT.SPEC := ’ПРОГРАМІСТ’;
Приклад: за анкетними даними визначити і вивести на друк прізвища
студентів, які мають військову спеціальність “Програміст”.
Текст програми мовою Паскаль:
PROGRAM TEST (INPUT, OUTPUT);
TYPE
STAT = (MEN, WOM);
ANK = RECORD
PR, IM, PB : ARRAY [1..10] OF CHAR;
RIK : 1900..2001;
MW : STAT;
CASE STAT OF
MEN:(VZ : 0..1; SPEC : STRING [10]);
WOM:(MER : 0..1; KDIT : INTEGER);
END;
VAR
GRUP : ARRAY [1..30] OF ANK;
L : BOOLEAN;
I, J : INTEGER;
MENWOM : 1..2;
BEGIN
{ВВІД АНКЕТНИХ ДАНИХ}
FOR I :=1 TO 30 DO
WITH GRUP[I] DO
BEGIN

12
WRITELN (’ВВЕДИ ПРІЗВИЩЕ,ІМ’’Я, ПО БАТЬКОВІ, РІК
НАРОДЖЕННЯ ’);
FOR J :=1 TO 10 DO READ(PR[J]);
FOR J :=1 TO 10 DO READ(IM[J]);
FOR J :=1 TO 10 DO READ(PB[J]);
READLN(RIK);
WRITE(’ВВЕДІТЬ СТАТЬ ЧОЛ.- 1, ЖІН. - 2’);
READ(MENWOM);
CASE MENWOM OF
1 : MW := MEN;
2 : MW := WOM
END;
CASE MW OF
MEN : BEGIN
WRITE(’ВІЙСЬКОВОЗОБОВ’’ЯЗАНИЙ ТАК –1, НІ -0’);
READLN(VZ);
WRITE(’ВІЙСК.СПЕЦІАЛЬНІСТЬ’);
READLN(SPEC);
END;
WOM : READLN (MER, KDIT);
END
END;
L := FALSE;
FOR I :=1 TO 30 DO
WITH GRUP[I] DO
BEGIN
IF (SPEC = ’PROGRAMIST’) THEN
BEGIN
FOR J :=1 TO 10 DO WRITE(PR[J]);
WRITELN;
L := TRUE
END;
END;
IF L = FALSE THEN
WRITELN(’ПРОГРАМІСТІВ НЕМАЄ’)
END.

13
Лабораторна робота № 12
ДИНАМІЧНІ ОБ’ЄКТИ
АЛГОРИТМІЧНОЇ МОВИ
ПРОГРАМУВАННЯ.
ВКАЗІВНИКОВІ ТИПИ
1. МЕТА РОБОТИ

Мета роботи – ознайомитись із особливостями застосування динамічних


об’єктів, вказівників; методів формування і обробки динамічних рядків
символів. Набути практичних навичок програмування з використанням
динамічних об’єктів.

2. ТЕОРЕТИЧНІ ВІДОМОСТІ

2.1. ДИНАМІЧНІ ОБ’ЄКТИ

До цього часу ми вивчали лише статичні програмні об’єкти, тобто


об’єкти, які породжуються безпосередньо перед виконанням програми та
існують у пам’яті машини протягом усього часу виконання програми. Розмір
таких об’єктів протягом виконання програми не змінюється.
Прикладом статичної змінної може бути масив. Цей статичний об’єкт
породжується за допомогою відповідного опису змінної:
VAR
A : ARRAY [1..20] OF INTEGER;
Місце для зберігання 20-ти значень цілого типу у пам’яті машини
виділяється на етапі трансляції програми.
У алгоритмічній мові Паскаль посилання на конкретний програмний об’єкт
здійснюється через його ім’я. Запис А [5] вказує на п’ятий елемент масиву А.
У машинній мові посилання на об’єкт здійснюється вказуванням місця у
пам’яті машини, яке займає об’єкт, тобто адреси комірки пам’яті, починаючи з
якої розміщено програмний об’єкт.
Алгоритмічні мови високого рівня дають змогу при написанні програми
не замислюватися, де і як розмістяться програмні об’єкти у пам’яті машини. За
програміста це робить транслятор.
Але використання лише статичних програмних об’єктів деколи викликає
труднощі під час розроблення ефективних програм. Деколи, на етапі написання
програми, ми не лише не знаємо розміру якогось програмного об’єкта, але й
того, чи буде він взагалі існувати.
Тому, крім статичних, існують ще й динамічні програмні об’єкти.

2
Динамічними називаються такі програмні об’єкти, які виникають
під час виконання програми, або розмір яких визначається чи змінюється
під час виконання програми.
Для роботи з динамічними об’єктами у мові Паскаль передбачено спе-
ціальний тип – тип вказівника.
Значення цього типу є вказівник на конкретний програмний об’єкт, за
яким здійснюється доступ до цього об’єкта. Іншими словами вказується адреса
комірки пам’яті, де зберігається відповідний об’єкт.
У мові Паскаль для опису дій над динамічними об’єктами кожному
такому об’єкту зіставляється статична змінна вказівникового типу.
Синтаксис задання вказівникового типу:
<заданий вказівниковий тип> = <ім’я типу>
де  – це ознака вказівникового типу,
<ім’я типу> – ім’я або стандартного, або раніше описаного, створеного
Вами типу.
Значенням змінних типу вказівника є вказівники на динамічні об’єкти
того типу, ім’я якого вказано після стрілки.
Змінні вказівникового типу описуються у розділі опису змінних та їхній
тип визначається або у розділі опису типів, або безпосередньо заданням типу
при описі змінної, але лише іменем.
Наприклад, фрагмент програми:
PROGRAM DINOB (INPUT, OUTPUT);
TYPE
MAS = ARRAY [1..50] OF INTEGER;
DINMAS = MAS;
VAR
P : INTEGER;
Q : CHAR;
AM, BM : DINMAS;
. . .
Значення змінної Р – вказівник на динамічний об’єкт цілого типу; Q –
вказівник на динамічний об’єкт символьного типу; АМ, ВМ – вказівники на
динамічні об’єкти, значення яких є масив цілих чисел.
Зв’язок вказівника з об’єктом схематично можна зобразити так:

P Об'єкт
*

3
* – значення вказівника Р;
→ (стрілка) – відображує вказівник на об’єкт, через який цей об’єкт
доступний у програмі.
Якщо вказівник не зв’язаний з жодним об’єктом, таке значення задається
службовим словом NIL
Схематично оператор присвоєння P := NIL; відображається:

P
*

2.2. ПОРОДЖЕННЯ ДИНАМІЧНИХ ОБ’ЄКТІВ

При описі змінної вказівникового типу вона не вказує на жодний


програмний об’єкт, лише вводить у використання статичну змінну (наприклад:
P, Q, AM, BM) вказівникового типу. Тобто по цьому опису транслятором лише
виділяється місце у пам’яті машини, необхідне для розміщення вказівника.
Для породження самого динамічного об’єкта є стандартна процедура
NEW (“новий”).
У операторі виклику процедури задається один фактичний параметр –
вказівникова змінна породжуваного об’єкта. Наприклад:
NEW(P); NEW(Q);
У результаті виконання цієї процедури породжується новий об’єкт такого
типу, який вказаний при описі вказівникової змінної. Для змінної Р –
динамічний об’єкт цілого типу; для змінної Q – динамічний об’єкт, елементами
якого є символ.
Змінним P та Q присвоюється вказівник на ці породжені об’єкти. Тобто у
пам’яті машини резервується місце для породженого об’єкта, а адреса початку
цього місця присвоюється змінним P та Q.

2.3. ЗМІННА ІЗ ВКАЗІВНИКОМ

При породженні динамічного об’єкта, йому не присвоюється ще ніякого


значення.
Для того, щоб динамічній змінній присвоїти якесь значення або вико -
ристати це значення, вводиться поняття “змінна з вказівником”. У

4
найпростішому випадку – це ім’я змінної вказівникового типу після якого
стоїть стрілка, тобто:
Р – це значення того програмного об’єкта, на який вказує вказівникова
змінна Р.
Після породження динамічних змінних P та Q можна присвоїти їм
початкові значення:

P =  Q := 'A';
P
* 25Q Q * 'A'
схематичне зображення
Змінна з вказівником може використовуватися у будь-яких конструкціях
мови. Але тут треба звертати увагу на тип вказівникової змінної.
Не можна записати:

P := P+Q;

Оскільки змінна Р – цілого типу, а Q – символьного, крім того Р – це


значення об’єкта, а Р – вказівник на нього, тобто адреса.
Але якщо опишемо у нашій програмі змінну S цілого типу, то такі оператори
будуть правильними:
S := S + P; P:= P DIV 5; AM[P+1] := 100;
Змінна вказівникового типу може мати й складнішу форму, наприклад:
TYPE
PS = REAL;
VAR
A : ARRAY [1..20] OF PS;
У цьому випадку маємо послідовність з 20-ти значень вказівникового
типу, до того ж кожна з них вказує на дійсні значення. При такому описі у
програмі будуть фігурувати вказівникові змінні з індексами. Наприклад, А[i],
A[i+2] та відповідні їм змінні з вказівниками:
A[i] та A[i+2]
Значення цих змінних будуть дійсного типу: A[2] := 5.44;

2.4. ОПЕРАЦІЇ НАД ЗМІННИМИ ВКАЗІВНИКОВОГО ТИПУ

5
• Основне правило: над значеннями вказівникового типу не можна
виконувати ніяких операцій, які давали б результат цього самого типу.
Значення вказівникового типу – це адреса тих чи інших програмних об’єктів і
змінювати її на свій розсуд не можна, оскільки розміщенням усіх об’єктів у
пам’яті машини займається транслятор.
• До значень вказівникового типу можуть застосовуватись деякі опе-
рації порівняння, а також вони можуть бути операндами в операторі
присвоювання.
• У загальному оператор присвоювання має вигляд:

• V := E;

де Е – змінна або вираз вказівникового такого самого типу, як і тип змінної V.


У правій частині оператора присвоювання може бути:
1. Пустий вказівник NIL.
2. Вказівникова змінна.
3. Вказівникова функція (тобто функція, результатом якої є вказівник).
Вираз Е повинен мати обов’язково той самий тип, що і змінна V.
Не можна записати:

P := Q;

Оскільки змінна Р може вказувати лише на цілі значення, а змінна Q на


значення типу CHAR.
Для ілюстрації виконання операторів присвоювання опишемо дві вказів-
никові змінні:

VAR P, D : INTEGER;

Після виконання оператора P := D (рис. 1, в) на динамічний об’єкт із


значенням 5 не вказує жодний вказівник – він став недоступним для програми,
хоч місце у пам’яті займає.
З іншого боку, виконання цього оператора не призвело до породження
нового динамічного об’єкта. Просто змінна Р буде вказувати на той самий
об’єкт, що і змінна D. Таким чином на об’єкт із значенням 25 будуть вказувати
два вказівники Р та D.

6
Після виконання оператора D := NIL (рис. 1, г) розривається зв’язок D з
об’єктом 25 і на нього буде вказувати лише змінна Р.
Слід чітко розрізняти вказівник на змінну і змінну з вказівником, тобто
сам об’єкт (рис. 2).
NEW(P); NEW(D)

P D
* Q *
а)

P := 5; D := 25

P 5 D 25
* Q *
б)

P := D;

P 5 D 25
* Q *
в)

D := NIL;

P 5 D 25
* Q *
г)

Рис. 1. Графічне представлення роботи з динамічними змінними

Приклад: маємо дві динамічні змінні P та D (рис. 2).

P 5
P := 5;
*
D := 25 D 25
*

P P 25
*
5
P := D;
*
P := D;
D 25
D
* 25
*

7
Рис. 2. Зміна значень вказівникової змінної P := D
і змінної із вказівником P := D

2.5. ЗНИЩЕННЯ ДИНАМІЧНИХ ОБ’ЄКТІВ

На рис. 1 та 2 видно, що після того, як виконали оператор:


P := D
об’єкт 5 став недопустимим для програми, але в оперативній пам’яті
залишився. Це так зване “сміття”, яке забиває оперативну пам’ять. Тому такі
об’єкти необхідно знищувати. Для знищення динамічних об’єктів існує
стандартна процедура DISPOSE. Параметром цієї процедури є вказівник на
об’єкт, який необхідно знищити:
DISPOSE (P);
Після виконання цієї команди динамічний об’єкт, на який вказує
вказівник Р, перестає існувати, місце, яке він займав у пам’яті, звільняється, а
значення вказівникової змінної Р стає невизначеним. Процедура DISPOSE
знищує тільки сам об’єкт, але не вказівник на нього.
Фрагмент програми та його графічна ілюстрація подано на рис. 3.

PROGRAM DIN2;
VAR
P *
P, D :INTEGER; a)
BEGIN
D *
NEW(P); P * Q
NEW(D);
D * б)

P := 5;
P * 5Q
D := 25; D * 25 в)

DISPOSE(P);
P * 5

D * 25 г)

P
P := D;
*
D * 25 д)

P
D := NIL; *
END. 8 D * 25 е)
Рис. 3. Приклад механізму роботи з динамічними об’єктами операторами програми
Використовувати процедуру DISPOSE треба дуже обережно, щоб не
знищити потрібні об’єкти (рис. 4).
Приклад:

PROGRAM DIN4;
VAR
P
*
P, D : INTEGER; а)
BEGIN
D *
NEW(P);
NEW(D);
P
*
б)
D *
P := 5;
P
* 5
D := 25; D * 25 в)

P 5
P := D; * г)
D * 25

P 5
DISPOSE(P); *
END.
D * 25 д)

Рис. 4. Приклад механізму роботи з динамічними об’єктами


операторами програми

Після виконання оператора P := D; обидва вказівники вказують на один і


той самий об’єкт 25. У результаті виконання оператора DISPOSE(P), дина-
мічний об’єкт, на який вказує змінна Р, буде знищено. Але ж змінна D також
вказує на цей об’єкт, виникає конфліктна ситуація, оскільки об’єкта вже нема
(він знищений).

2.6. ДИНАМІЧНІ СТРУКТУРИ ДАНИХ. РЯДКИ

9
Під рядком (або словом) розуміють упорядковану послідовність символів
конкретного алфавіту.
Рядок може бути поданий у вигляді масиву – тобто статичного об’єкта.
Наприклад, слово “АЛГОРИТМ” можна подати у вигляді вектора
ARRAY [1..10] OF CHAR.
Під час роботи з рядком найчастіше здійснюються такі три операції:
1) пошук входження заданого символу в рядок;
2) вставка заданого символу у вказане місце рядка;
3) видалення заданого символу із вказаного місця рядка.
Оскільки доступ до кожного елемента рядка, описаного як вектор,
здійснюється через індекс цього елемента, то для процедури вставки необхідно
спочатку розсунути рядок і лише після цього вставити потрібний елемент.
Тобто усі елементи масиву, які розміщені після вставленої літери, необхідно
пронумерувати індексом на одиницю більшим від попереднього.
Аналогічна ситуація з видаленням визначеного елемента. Тому недоліка-
ми векторного подання рядка є:
1. На операцію вставки та видалення витрачається занадто багато часу че-
рез необхідність зсувати частину слова і пошуку маркера кінця рядка.
2. Якщо довжина рядка наперед невідома, то необхідно резервувати пам’ять
із запасом.
Але кожен рядок можна зобразити у вигляді ланцюжка символів, у якому
кожен попередній елемент містить вказівник на наступний (рис. 5).

'A' 'Л' 'Г' 'О' 'Р' 'И' 'Т' 'М'

* * * * * * * *
Рис. 5. Динамічний рядок символів слова “АЛГОРИТМ”

Іншими словами опис рядка (ланцюжка) містить два поля: в першому –


записані символи ланцюжка (літери А, Л, Г, ...) а у другому – вказівник на
наступний елемент. Опис такого типу:
TYPE
ZV = POINTER;
POINTER = RECORD
ELEM : CHAR;
NEXT : ZV
END;

10
Кожна ланка запису складається з двох полів: поле ELEM містить
безпосередньо символьні елементи рядка, поле NEXT містить вказівник на
наступний елемент.
При описі типу ZV ми порушили основне правило мови Паскаль: тип
POINTER використовується до того, як описується. Це єдиний випадок, коли
можна використовувати ім’я типу до його опису. Тільки у разі опису вказів-
никових типів допускається використання імені типу до його опису.
Для того, щоб сформувати рядок (рис. 6) або слово, необхідно описати
дві вказівникові змінні:
VSLOV – вказівник на слово,
VLAN – вказівник на ланку (окремий елемент).

' А'
VSLOV
* а)
NIL

' А'
VSLOV

VLAN
* NIL

б)

VLAN.NEXT

' А'
VSLOV
VLAN
* NEXT

в)

VLAN.NEXT

VSLOV ' А' Л

VLAN
* NEXT NIL

г)

' А' ' Л'


VSLOV
* NEXT NIL

VLAN
д)

Рис. 6. Формування динамічного рядка

11
Вказівник VSLOV завжди буде вказувати на початок слова. При по-
родженні:
NEW (VSLOV);
у пам’яті машини виділяється місце для розміщення динамічної
структури з двома полями:
VSLOV.ELEM
VSLOV.NEXT
У поле ELEM – запишемо першу літеру слова, у поле NEXT – запишемо
NIL, оскільки вказівник на слово вказує лише на початок слова та з іншими
ланцюгами не зв’язаний (рис. 6, а).
Наступну, другу літеру слова необхідно розмістити у наступній ланці
ланцюга. Але якщо ми породимо знову VSLOV, то це буде вже нове слово і
зв’язку із літерою “А” не буде.
Використати вказівник VSLOV вже не можна, по-перше, цей вказівник
повинен вказувати завжди на початок слова, по-друге, літера “А” у цьому
випадку стане недоступна.
Якщо породити VLAN командою: NEW (VLAN); то втратиться
зв’язок з першою ланкою, де записана літера “А”. Тому для задання вказівника
на біжучу ланку використовується вказівник VLAN та у перший момент він
буде вказувати на першу літеру слова, тобто (рис. 6, б)
VLAN := VSLOV;
Тепер, у разі породження нового динамічного об’єкта:
NEW (VLAN.NEXT);
у поле NEXT елемента VLAN запишеться адреса наступного елемента,
тобто елемента, де буде записано другу літеру слова (рис. 6, в).
Записуємо у поле породженого елемента VLAN.NEXT значення поля
ELEM літеру “Л”, а у поле NEXT – значення NIL (оскільки невідомо, чи будуть
ще наступні ланки, див. рис. 6, г):
VLAN.NEXT.ELEM :=SYM;
VLAN.NEXT.NEXT :=NIL;
Наступний об’єкт, який повинен породжуватися VLAN.NEXT.NEXT.
Але, щоб не повторювати постійно слово NEXT, доцільно використати оператор:
VLAN := VLAN.NEXT;
Тобто перенести вказівник VLAN на наступну ланку (рис. 6, д).
Породження наступної ланки може виконуватися у циклі, де оператор:

12
VLAN := VLAN.NEXT;
аналогічний оператору I :=I +1 для подання рядка за допомогою вектора.
Фрагмент програми мовою Паскаль для формування слова має такий вигляд
(у разі набору з клавіатури символ вертикальної стрілки  позначається ^ ):
PROGRAM DINSTR (INPUT, OUTPUT);
TYPE
ZV = POINTER;
POINTER = RECORD
ELEM : CHAR;
NEXT : ZV
END;
VAR
VLAN, VSLOV : ZV;
SYM : CHAR;
BEGIN
READ(SYM);
NEW (VSLOV);
VSLOV.ELEM := SYM;
VSLOV.NEXT := NIL;
VLAN := VSLOV;
WHILE SYM <> ’.’ DO
BEGIN
READ(SYM);
NEW(VLAN.NEXT);
VLAN := VLAN.NEXT;
VLAN.ELEM := SYM;
VLAN.NEXT := NIL;
END;
END.
Приклад: Сформувати рядок, що закінчується крапкою, і порахувати
скільки разів у нього входить літера “А”.
Текст програми мовою Паскаль (де – символ пробілу; у разі набору з
клавіатури символ вертикальної стрілки  позначається ^ ):
PROGRAM KBUK (INPUT, OUTPUT);
CONST BUK = ’A’;
TYPE
ZV = POINTER;

13
POINTER = RECORD
ELEM : CHAR;
NEXT : ZV
END;
VAR
VLAN, VSLOV : ZV;
SYM : CHAR;
K : INTEGER;
BEGIN
WRITELN (’ВВЕДИ СИМВОЛИ ДО КРАПКИ’);
READ(SYM);
NEW(VSLOV);
VSLOV.ELEM := SYM;
VSLOV.NEXT := NIL;
VLAN := VSLOV;
REPEAT
READ(SYM);
NEW(VLAN.NEXT);
VLAN := VLAN.NEXT;
VLAN.ELEM := SYM;
VLAN.NEXT := NIL
UNTIL SYM = ’.’;
VLAN := VSLOV; {ПОВЕРНЕННЯ НА ПОЧАТОК РЯДКА}
WHILE VLAN.NEXT <> NIL DO
{ПЕРЕХІД ВІД ЛАНКИ ДО ЛАНКИ ТА ПОШУК ЛІТЕРИ}
BEGIN
IF VLAN.ELEM = BUK THEN K :=K+1;
VLAN := VLAN.NEXT
END;
WRITELN;
{ВВИВІД РЯДКА}
VLAN := VSLOV; {ПОВЕРНЕННЯ НА ПОЧАТОК РЯДКА}
WHILE VLAN.NEXT <> NIL DO
{ПЕРЕХІД ВІД ЛАНКИ ДО ЛАНКИ ТА ВИВІД ЛІТЕРИ ЛАНКИ}
BEGIN
WRITE (VLAN.ELEM);
VLAN := VLAN.NEXT;
END;
WRITELN;

14
WRITELN(’ЛІТЕРА А ВХОДИТЬ У РЯДОК ’,K, ’ РАЗІВ’);
END.

З програми видно, що формування першої ланки ланцюжка виконується


інакше ніж решти. Це є недоліком у подальшій роботі із рядком, а саме під час
використання процедур або функцій пошуку елемента, вставки або видалення.
Тому доцільно застосовувати рядки із ланкою-заголовком. Тобто у поле NEXT
першої ланки записується вказівник на другу ланку, а поле елемента зали-
шається порожнім.
Фрагмент програми формування рядка з ланкою-заголовком має такий
вигляд (у разі набору з клавіатури символ вертикальної стрілки  позначається ^ ):
NEW (VSLOV);
VSLOV.NEXT := NIL;
VLAN := VSLOV;
REPEAT
READ(SYM);
NEW (VLAN.NEXT);
VLAN :=VLAN.NEXT;
VLAN.ELEM := SYM;
VLAN.NEXT := NIL
UNTIL SYM = ’.’;

15
Лабораторна робота № 13
ДИНАМІЧНІ ОБ’ЄКТИ
СКЛАДНОЇ СТРУКТУРИ.
ОДНО- ТА ДВОНАПРАВЛЕНІ
СПИСКИ, СТЕКИ, ЧЕРГИ
1. МЕТА РОБОТИ
Мета роботи – ознайомитись із особливостями застосування динамічних
об’єктів складної структури: списками, стеками та чергами; з операціями, які
виконуються над елементами цих об’єктів. Набути практичних навичок
програмування з використанням динамічних об’єктів складної структури.

2. ТЕОРЕТИЧНІ ВІДОМОСТІ

2.1. ОПЕРАЦІЇ НАД ДИНАМІЧНИМИ РЯДКАМИ

Над динамічним рядком найчастіше виконуються такі операції:


1. Пошук заданого елемента у рядку.
2. Вставка заданого елемента у певне місце рядка.
3. Видалення заданого елемента з вказаного місця рядка.
Найчастіше ці операції подаються у вигляді функції або процедур.
Застосування функцій та процедур простіше в динамічних рядках з ланкою-
заголовком, оскільки, як перша, так і наступні літери рядка формуються і
обробляються за однаковими правилами.

2.1.1. ПОШУК ЗАДАНОГО ЕЛЕМЕНТА В ДИНАМІЧНОМУ РЯДКУ

Для пошуку заданого елемента можна використовувати функцію логіч-


ного типу з побічним ефектом. Через використання побічного ефекту в головну
програму повертається ланка, в якій знайдено задану літеру.
FUNCTION POSH(ST:ZV;BUK:CHAR; VAR REZ:ZV):BOOLEAN;
VAR Q : ZV;
L : BOOLEAN;
BEGIN
L := FALSE;
REZ := NIL;
Q := ST.NEXT;
WHILE (Q <> NIL) AND (REZ = NIL) DO
BEGIN
IF Q.ELEM = BUK THEN
BEGIN
L := TRUE;
REZ := Q
END;

2
Q :=Q.NEXT
END;
POSH := L
END;

Функція POSH може набувати два значення: TRUE – якщо елемент


знайдено, та FALSE – якщо елемент не знайдено. Якщо елемент знайдено,
то через формальний параметр-змінну REZ вказівник на нього передається
у головну програму.

2.1.2. ВСТАВКА ЗАДАНОГО ЕЛЕМЕНТА


У ПЕВНЕ МІСЦЕ ДИНАМІЧНОГО РЯДКА

Схематично вставку елемента “S” після літери “А” наведено на рис. 1:

LANKA LANKA.NEXT LANKA.NEXT.NEXT

'А' 'B' 'C'

NEXT NEXT NEXT

а) a)
LANKA.NEXT
LANKA.NEXT.NEXT
LANKA
'А' 'B' 'C'

NEXT NEXT NEXT

'S'
NEXT
б)
б)

Рис. 1. Алгоритм вставки заданого елемента в динамічний рядок

Алгоритм вставки елемента містить такі етапи:


породити новий динамічний об’єкт Q;
у поле ELEM нового об’єкта записати літеру “S”, яку треба вставити;
у поле NEXT нового об’єкта записати вказівник, взятий з поля NEXT
попереднього елемента “А”;

3
у поле NEXT попереднього елемента “А” внести вказівник на елемент, що
вставляється.
Цей алгоритм відображається процедурою:
PROCEDURE VST(LANKA : ZV; BUK : CHAR);
VAR Q : ZV;
BEGIN
NEW (Q);
Q.ELEM := BUK;
Q.NEXT := LANKA.NEXT;
LANKA.NEXT := Q
END;

2.1.3. ВИДАЛЕННЯ ЗАДАНОГО ЕЛЕМЕНТА

Алгоритм видалення елемента проілюстровано на рис. 2:

LANKA LANKA.NEXT LANKA.NEXT.NEXT

LANKA
'А ' 'B '
LANKA.NEXT
'C '
LANKA.NEXT.NEXT

NEXT NEXT NEXT


А B C
a)
NEXT NEXT а) NEXT
LANKA LANKA.NEXT.NEXT
Qa)

А B C
LANKA LANKA.NEXT.NEXT
Q
NEXT.NEXT NEXT NEXT
А ' ' ' '
B '
C '
NEXT.NEXT NEXT
б) NEXT

б) б)
Рис. 2. Алгоритм видалення елемента з динамічного рядка

Щоб видалити елемент “В” з рядка достатньо у поле попереднього


елемента “А” записати вказівник на елемент “С”, тобто:
LANKA.NEXT := LANKA.NEXT.NEXT
Процедура видалення має такий вигляд:

4
PROCEDURE VUDAL (LANKA : ZV);
VAR Q : ZV;
BEGIN
Q := LANKA.NEXT;
LANKA.NEXT := LANKA.NEXT.NEXT;
DISPOSE(Q)
END;
Щоб не розірвати ланцюжок, слід спочатку запам’ятати об’єкт, який
видаляється під іменем Q, тоді перенести вказівник на наступну ланку і
лише після цього видалити об’єкт Q. Якщо такий порядок буде порушений,
то неминучий розрив ланцюжка.

2.1.4. ПРИКЛАДИ ВИКОРИСТАННЯ ПРОЦЕДУР


ВСТАВКИ ТА ВИДАЛЕННЯ [1]

Приклад 1: Сформувати динамічний рядок, вивести його на друк. Після


кожної літери “R” вставити символ “1”. Видалити одну з літер, що подвою-
ються. Використати процедури вставки та видалення елементів динамічного
рядка/
Текст програми мовою Паскаль (у разі набору з клавіатури символ
вертикальної стрілки  позначається ^ ):
PROGRAM DINSTR(INPUT,OUTPUT);
TYPE
ZV=POINTER;
POINTER=RECORD
ELEM:CHAR;
NEXT:ZV
END;
VAR
VLAN, VSLOV:ZV;
SYM:CHAR;

{ПРОЦЕДУРА ВСТАВКИ ЕЛЕМЕНТА У ДИНАМIЧНИЙ РЯДОК}


PROCEDURE VSTAV (LANKA:ZV; BUK:CHAR);
VAR
Q:ZV;
BEGIN
NEW(Q);

5
Q.ELEM:=BUK;
Q.NEXT:=LANKA.NEXT;
LANKA.NEXT:=Q
END;

{ПРОЦЕДУРА ВИДАЛЕННЯ ЕЛЕМЕНТА З ДИНАМIЧНОГО РЯДКА }


PROCEDURE VUDAL(LANKA:ZV);
VAR
Q:ZV;
BEGIN
Q:=LANKA.NEXT;
LANKA.NEXT:=LANKA.NEXT.NEXT;
DISPOSE(Q)
END;
{ГОЛОВНА ПРОГРАМА}
BEGIN
{ФОРМУВАННЯ ПЕРШОГО ЕЛЕМЕНТА РЯДКА (ЗАГОЛОВКУ)}
NEW(VSLOV);
VSLOV.NEXT:=NIL;
VLAN:=VSLOV;

{ФОРМУВАННЯ ВСЬОГО РЯДКА}


WRITELN(’ВВЕДIТЬ СИМВОЛИ ДО КРАПКИ’);
REPEAT
READ(SYM);
VSTAV(VLAN,SYM);
VLAN:=VLAN.NEXT
UNTIL SYM=’.’;
{ВИВIД СПИСКУ}
WRITELN(’СФОРМОВАНИЙ РЯДОК’);
VLAN:=VSLOV;
VLAN:=VLAN.NEXT;
WHILE VLAN.NEXT<>NIL DO
BEGIN
WRITE(VLAN.ELEM);
VLAN:=VLAN.NEXT;
END;
WRITELN;
VLAN:=VSLOV;

6
VLAN:=VLAN.NEXT;
REPEAT
IF VLAN.ELEM=’R’ THEN
VSTAV(VLAN,’1’);
IF VLAN.ELEM=VLAN.NEXT.ELEM THEN
VYDAL(VLAN);
VLAN:=VLAN.NEXT
UNTIL VLAN.NEXT=NIL;

{ВИВIД РЕЗУЛЬТАТУ}
WRITELN(’РЕЗУЛЬТУЮЧИЙ РЯДОК’);
VLAN:=VSLOV;
VLAN:=VLAN.NEXT;
REPEAT
WRITE(VLAN.ELEM);
VLAN:=VLAN.NEXT
UNTIL VLAN=NIL;
WRITELN;
END.

Приклад 2: Задано слово, що закінчується крапкою.


АААЛЛЛЛЛГГООООООРРРИИТМММ.
Літери у цьому слові повторюються не більше 9 разів. Потрібно:
перед кожною групою літер поставити цифру, що відображає кількість
повторень;
видалити усі повторення літер.
У результаті виконання програми повинні одержати слово:
3А5Л2Г6О3Р2И1Т3М.
Для вставки цифри та видалення повторних входжень використати
процедури.
Блок-схему алгоритму виконання поставленого завдання зображено на рис. 3:
Блок 1 – початок алгоритму.
Блок 2 – формування ланки-заголовка рядка VSLOV.
Блок 3 – встановлення біжучого вказівника VKB на початок слова
(VSLOV).
Блоки 4–7 – цикл з постумовою для формування рядка з біжучим вказів-
ником VKB з використанням виклику процедури вставки елемента VSTAV.
Блок 4 – введення символу з клавіатури у змінну SYM.
Блок 5 – виклик процедури вставки елемента у динамічний рядок.

7
1 2 1 3
Початок
14
2 VKGR.ELEM= ні
Формування VKB.ELEM
заголовку
так
3 15
VKB = VSLOV K=K+1

16
4 Ввід VKB=VKB.NEXT
SYM
17
5 VUDAL
VSTAV

6
VKB =
VKB.NEXT 18
K = ...

ні 7 1 2 9
SYM = '.'
19 20 27
так SYM='1' SYM='2' ... SYM='9'
8
Перехід на
початок 28
VSTAV

29
9 ні VK=VKGR
VKB.ELEM '.'
30
VKGR=VKGR.NEXT
10 так
Вивід
рядка 31
VKB Перехід на
початок
11
Перехід на 32
початок VKB.ELEM '.' ні

так
33
12 ні VK=VK.NEXT
VKGR.ELEM
'.'
34
Вивід
так VK.ELEM
13 35
k =1 Кінець

2 1 3

Рис. 3. Блок-схема алгоритму розв’язання

8
Блок 6 – встановлення біжучого вказівника VKB на утворений наступний
ланцюжок.
Блок 7 – перевірка умови закінчення циклу на наявність введеного символу
крапки.
Блок 8 – встановлення біжучого вказівника VKB на початок рядка.
Блоки 9, 10 – циклічне виведення сформованого рядка VKB.
Блок 9 – перевірка умови виконання циклу, поки нема символу крапки
(ознаки кінця рядка)
Блок 10 – встановлення біжучого вказівника VKB на наступний ланцюжок.
Блок 11 – встановлення біжучого вказівника VK на початок рядка і вказів-
ника на групу однакових літер VKGR на першу літеру рядка VK.NEXT.
Блоки 12–30 – цикл для визначення кількості однакових літер, їх видалення
та запису цифри перед кожною групою літер.
Зокрема:
Блоки 14–17 – цикл для обчислення кількості однакових літер у групі та
видалення однакових літер з групи.
Блок 14 – перевірка умови збігання значення двох полів ELEM вказівників.
Блок 15 – зростання лічильника кількості на одиницю.
Блок 16 – встановлення біжучого вказівника VKB на наступний ланцюжок.
Блок 17 – виклик підпрограми видалення елемента динамічного рядка.
Блоки 18–27 – перетворення порахованої кількості К однакових літер з
цифри у символ цифри (оскільки рядок – це динамічний об’єкт, елементами
якого є символи).
Блок 28 – вставка цифри, що відображає кількість однакових літер перед
групою.
Блок 29 – запис літери у рядок VK.
Блок 30 – перехід на наступну групу.
Блок 31 – перехід на початок динамічного об’єкта VK.
Блоки 32–34 – друк сформованого рядка VK.
Блок 32 – перевірка умови виконання циклу, поки нема символу крапки
(ознаки кінця рядка)
Блок 33 – встановлення біжучого вказівника VKB на наступний ланцюжок.
Блок 34 – вивід на екран значення поля ELEM біжучого вказівника VKB.
Блок 35 – кінець алгоритму.
У програмі будуть використані чотири змінні вказівникового типу:
VSLOV – вказівник на початок рядка;
VKB – біжучий вказівник;
VKGR – вказівник на початок групи однакових літер;
VK – вказівник на ланку, після якої вставляється цифра.

9
Текст програми мовою Паскаль (у разі набору з клавіатури символ
вертикальної стрілки  позначається ^ ):
PROGRAM DINSTR (INPUT, OUTPUT);
TYPE
ZV = POINTER;
POINTER = RECORD
ELEM : CHAR;
NEXT :ZV
END;
VAR
VSLOV,VKB,VKGR,VK :ZV;
K :1..9;
SYM : CHAR;

{ПРОЦЕДУРА ВСТАВКИ ЕЛЕМЕНТА В ДИНАМ. РЯДОК}


PROCEDURE VSTAV(LANKA:ZV; BUK:CHAR);
VAR Q:ZV;
BEGIN
NEW(Q);
Q.ELEM :=BUK;
Q.NEXT := LANKA.NEXT;
LANKA.NEXT := Q
END;
{ПРОЦЕДУРА ВИДАЛЕННЯ ЕЛЕМЕНТА З ДИНАМ. РЯДКА}
PROCEDURE VUDAL (LANKA : ZV);
VAR Q : ZV;
BEGIN
Q := LANKA.NEXT;
LANKA.NEXT := LANKA.NEXT.NEXT;
DISPOSE(Q)
END;
{ГОЛОВНА ПРОГРАМА}
BEGIN
{ФОРМУВАННЯ ЛАНКИ-ЗАГОЛОВКУ}
NEW(VSLOV);
VSLOV.NEXT := NIL;
VKB := VSLOV;
REPEAT {ФОРМУВАННЯ РЯДКА}
READ(SYM);

10
VSTAV(VKB, SYM);{ВИКЛИК ПРОЦЕДУРИ
ВСТАВКИ}
VKB := VKB.NEXT
UNTIL SYM = ’.’;
{ВИВІД СФОРМОВАНОГО РЯДКА}
VKB := VSLOV;
WHILE VKB.ELEM <> ’.’ DO
BEGIN
WRITE(VKB.ELEM);
VKB := VKB.NEXT
END;
{ФОРМУВАННЯ ЛАНЦУЖКА VK ГРУПИ ОДНАКОВИХ ЛІТЕР}
VK := VSLOV;
VKGR := VK.NEXT;
WHILE VKGR.ELEM <> ’.’ DO
BEGIN
K :=1;
VKB := VKGR.NEXT;
WHILE VKGR.ELEM = VKB.ELEM DO
BEGIN
K := K+1;
VKB := VKB.NEXT;
VUDAL(VKGR) {ВИКЛИК ПРОЦЕДУРИ
ВИДАЛЕННЯ}
END;
CASE K OF
1 : SYM := ’1’;
2 : SYM := ’2’;
3 : SYM := ’3’;
4 : SYM := ’4’;
5 : SYM := ’5’;
6 : SYM := ’6’;
7 : SYM := ’7’;
8 : SYM := ’8’;
9 : SYM := ’9’
END;
VSTAV(VK, SYM); {ВИКЛИК ПРОЦЕДУРИ ВСТАВКИ}
VK := VKGR; {ПЕРЕХІД ДО НАСТУПНОЇ ГРУПИ}
VKGR := VKGR.NEXT;

11
END;
{ВИВІД СФОРМОВАНОГО РЯДКА VK}
VK := VSLOV;
WHILE VK.ELEM <> ’.’ DO
BEGIN
VK := VK.NEXT;
WRITE(VK.ELEM)
END;
READLN
END.

2.2. ЛІНІЙНІ ОДНОНАПРАВЛЕНІ СПИСКИ

Розглянутий раніше рядок, зображений у вигляді ланцюжка символів, є


частковим випадком лінійного однонаправленого списку. Для рядка
елементами є символи. Для однонаправленого списка – елементами можуть
бути значення будь-якого типу: дійсні числа, масиви, записи тощо.
Принцип організації та роботи з лінійним однонаправленим списком такий
самий, як із рядком. Кожен елемент, що входить у однонаправлений
список, має вказівник на наступний елемент.
Лінійні однонаправлені списки можуть бути як із ланкою-заголовком, так і
без неї. Слід пам’ятати, що тип елементів однонаправленого списку
повинен бути однаковий. За лінійним однонаправленим списком можна
рухатися лише в одному напрямку від початку до кінця і кожен раз
повертатися на його початок оператором:
VLAN := VSLOV;

2.3. ДВОНАПРАВЛЕНІ СПИСКИ

На практиці часто виникає потреба провести обробку попереднього


елемента у списку. У однонаправленому списку необхідно вертатися на
початок рядка, переглянути всі елементи, до того, який потрібний. Це
незручно і нераціонально.
Тому для подібних задач доцільно створити двонаправлений список, тобто
список, у якому можна рухатися у двох напрямках: як вперед, так і назад. У

12
разі створення такого списку в запис додається ще одне поле – PRIV –
вказівник на попередній елемент. Схематично це зображено на рис. 4:
VSTR

NEXT NEXT NEXT ... NIL


* NIL PRIV PRIV ... PRIV

ELEM ELEM ELEM

Рис. 4. Двонаправлений список

Описується такий динамічний об’єкт:


TYPE
ZV2 = POINT2;
POINT2 = RECORD
ELEM : CHAR;
NEXT : ZV2;
PRIV : ZV2
END;

Наявність у кожній ланці вказівника на попередній та наступний


елементи дає можливість рухатися у двох напрямках.
У списку є ланка-заголовок VSTR (рис. 4).
У поле NEXT цієї ланки записано вказівник на перший елемент списку, а
у полі PRIV – записано NIL. Тобто у ланці-заголовку нема попереднього
елемента. Аналогічно у поле NEXT останнього елемента записано NIL, тобто в
останнього нема наступного.
Поряд з такими списками у програмуванні часто використовуються
двонаправлені списки, де у поле NEXT останнього елемента записано
вказівник на ланку-заголовок, а у поле PRIV ланки-заголовку – вказівник на
останню ланку (рис. 5).

13
VSTR
NEXT NEXT NEXT ... NEXT
* PRIV PRIV
ELEM
PRIV
ELEM
... PRIV
ELEM

Рис. 5. Кільцевий двонаправлений список


Такі списки називаються кільцевими списками. У них рухатися по полю
NEXT до кінця списку і через оператор присвоєння:
VLAN := VSTR;
з останньої ланки переходять на першу.
Часто ланку-заголовок не включають у кільцевий список (рис. 6):

VSTR
NEXT NEXT NEXT ... NEXT
* NIL NIL PRIV
ELEM
PRIV
ELEM
... PRIV
ELEM

Рис. 6. Кільцевий двонаправлений список,


у якого ланка-заголовок не включена у кільце

Над двонаправленими списками виконуються ті самі операції, що і над


однонаправленими ланцюжками.

2.3.1. ПОШУК ЗАДАНОГО ЕЛЕМЕНТА


У ДВОНАПРАВЛЕНОМУ СПИСКУ

Функція пошуку елемента у двонаправленому списку практично не


відрізняється від аналогічної функції для однонаправленого списку. Головне
перебрати всі елементи або спочатку до кінця, або від кінця до початку.
FUNCTION POSH (ST :ZV2; BUK : CHAR: VAR REZ :
ZV2):BOOLEAN;
Формальними параметрами будуть:

14
ST – вказівник на рядок;
BUK – символ або елемент, який шукається;
REZ – параметр-змінна через який передається вказівник на перше
входження заданого символу.

2.3.2. ПРОЦЕДУРА ВСТАВКИ ЗАДАНОГО ЕЛЕМЕНТА


У ДВОНАПРАВЛЕНИЙ СПИСОК, ПОДАНИЙ БЕЗ ЛАНКИ-ЗАГОЛОВКУ

Схематично алгоритм вставки елемента у двонаправлений список зобра-


жений на рис. 7.

LANKA.NEXT
LANKA

NEXT NEXT NEXT

PRIV PRIV PRIV

A B C

NEXT

PRIV

Рис. 7. Алгоритм вставки заданого елемента у двонаправлений список

PROCEDURE VSTAV(LANKA : ZV2; BUK : CHAR);


VAR Q : ZV2;
BEGIN
NEW (Q);
Q.ELEM := BUK;
Q.NEXT := LANKA.NEXT;
Q.PRIV := LANKA.NEXT.PRIV;
LANKA.NEXT.PRIV := Q;
LANKA.NEXT := Q
END;

15
2.3.2. ПРОЦЕДУРА ВИДАЛЕННЯ ЗАДАНОГО ЕЛЕМЕНТА
З ДВОНАПРАВЛЕНОГО СПИСКУ

Схематично алгоритм видалення заданого елемента з двонаправленого


списку зображений на рис. 8.
Щоб видалити елемент В, необхідно:
1. У поле NEXT елемента А записати вказівник на елемент С.
2. У поле PRIV елемента С записати вказівник
LANKA.PRIV на елемент А.
LANKA.NEXT

NEXT NEXT LANKA.NEXT


NEXT
LANKA.PRIV

PRIV PRIV PRIV


NEXT NEXT NEXT
A B C
PRIV PRIV PRIV

'A ' 'B ' ' '


C

а)
а)
а)
LANKA
LANKA.NEXT
LANKA.PRIV
LANKA
NEXT NEXT LANKA.NEXT
NEXT
LANKA.PRIV

PRIV PRIV PRIV


NEXT NEXT NEXT
A' '
PRIV
B' '
PRIV
C
PRIV
' '
A B C

б) б)

Рис. 8. Алгоритм видалення заданого елемента


б)
з двонаправленого списку

PROCEDURE VUDAL(LANKA : ZV2);


VAR Q : ZV2;
BEGIN
Q := LANKA;
LANKA.NEXT.PRIV := LANKA.PRIV;
LANKA.PRIV.NEXT := LANKA.NEXT;
DISPOSE(Q)
END;

2.3.4. ПРИКЛАД ВИКОРИСТАННЯ


ДВОНАПРАВЛЕНОГО КІЛЬЦЕВОГО СПИСКУ [1]

16
Для рядка символів, що складається з літер і цифр, починається з літери
“А” і вводиться до появи крапки, видалити усі цифри, які зустрічаються перед
літерою “D”.
Приклад тестового рядка:
ABS357D45L54IK794DS7K.
Результатом буде рядок:
ABSDS45L54IKDS7K
Текст програми:
PROGRAM DVONAPR(INPUT, OUTPUT);
TYPE ZV2 = POINT2;
POINT2 = RECORD
ELEM : CHAR;
NEXT : ZV2;
PRIV : ZV2
END;
VAR
VSLOV, VLAN, VLAN1 : ZV2;
SYM : CHAR;

{ПРОЦЕДУРА ВСТАВКИ ЕЛЕМЕНТА В ДИНАМ. ДВОНАПРАВЛЕНОГО


СПИСКУ}
PROCEDURE VSTAV(LANKA : ZV2; BUK : CHAR);
VAR Q : ZV2;
BEGIN
NEW (Q);
Q.ELEM := BUK;
Q.NEXT := LANKA.NEXT;
Q.PRIV := LANKA.NEXT.PRIV;
LANKA.NEXT.PRIV := Q;
LANKA.NEXT := Q
END;

{ПРОЦЕДУРА ВИДАЛЕННЯ ЕЛЕМЕНТА З ДИНАМ. ДВОНАПРАВЛЕНОГО


СПИСКУ}
PROCEDURE VUDAL(LANKA : ZV2);
VAR Q : ZV2;
BEGIN

17
Q := LANKA;
LANKA.NEXT.PRIV := LANKA.PRIV;
LANKA.PRIV.NEXT := LANKA.NEXT;
DISPOSE(Q)
END;
{ГОЛОВНА ПРОГРАМА}
BEGIN
{ФОРМУВАННЯ ЛАНКИ-ЗАГОЛОВКУ}
NEW(VSLOV);
VSLOV.ELEM := ’A’;
VSLOV.NEXT := VSLOV;
VSLOV.PRIV := VSLOV;
VLAN := VSLOV;
{ФОРМУВАННЯ ДВОНАПРАВЛЕНОГО СПИСКУ}
REPEAT
READ(SYM);
VSTAV(VLAN.PRIV, SYM) {ВИКЛИК ПРОЦЕДУРИ
ВСТАВКИ}
UNTIL SYM = ’.’;
{ПОВЕРНЕННЯ ПО КІЛЬЦЮ НА ПОЧАТОК СПИСКУ}
VLAN := VSLOS.NEXT;
WHILE VLAN <> VSLOV DO
BEGIN
WHILE (VLAN.ELEM <> ’D’) AND (VLAN <>
VSLOV) DO
VLAN := VLAN.NEXT;
IF VLAN <> VSLOV THEN
BEGIN
WHILE VLAN.PRIV.ELEM IN [’0’..
’9’]DO
VUDAL(VLAN.PRIV);
{ВИКЛИК ПРОЦЕДУРИ}
VLAN := VLAN.NEXT
END;
END;
{ВИВІД РЕЗУЛЬТАТІВ}
VLAN := VSLOV.NEXT;

18
WRITELN;
WHILE VLAN <> VSLOV DO
BEGIN
WRITE(VLAN.ELEM);
VLAN := VLAN.NEXT
END;
WRITELN
END.

2.4. ЧЕРГИ ТА СТЕКИ

У програмуванні існує структура даних, яка називається чергою. Вона


використовується для моделювання реальної черги. За своїм змістом черга є
суто динамічним об’єктом. Довжина черги (набір її елементів) у часі постійно
змінюється.
Над чергою визначені дві операції:
1. Занести елемент у чергу.
2. Видалити елемент з черги.
У черзі доступні дві позиції:
1. Початок черги.
2. Кінець черги.
За дисципліною обслуговування розрізняють два види черг:
1. FIFO (First In First Out) – “перший прийшов – перший пішов”.
2. LIFO (Last In First Out) – “останній прийшов – перший пішов”.
При першому виді черги замовлення, яке першим прийшло – першим і
вибирається на обслуговування. Черги такого типу можна формувати по-
різному:
1) зображувати чергу у вигляді вектора, де запам’ятовуються перший та
останній індекси;
2) зображувати чергу у вигляді однонаправленого списку, в якому за-
пам’ятовуються перша та остання ланка.
Але черги такої дисципліни обслуговування у програмуванні використо-
вуються досить рідко.
При другому виді обслуговування – першим обслуговується елемент,

19
який прийшов останнім.
Черга такого типу називається стеком. Вона найчастіше
використовується у програмуванні.
У стека доступна єдина позиція – вершина стека – тобто позиція, в якій
перебуває останній елемент. Вибрати елемент можна лише з вершини стека,
при цьому вибраний елемент видаляється зі стека.
Стек найчастіше зображується у вигляді динамічної структури, одно-
направленого списку (ланцюжка). Вершиною стека є перша ланка. Ланка-заго-
ловок для стека не потрібна.
Вказівник на стек є вказівником вершини стека. Вказівник на ланку
містить вказівник на наступну ланку стека. “Дно” стека, тобто елемент, занесе-
ний найпершим, містить вказівник NIL.
Схематично стек зображається:

STEK

NEXT NEXT NEXT NIL

* EL EL EL
...
EL

(n) (n-1) (n-2) (1)

Рис. 9. Схематичне зображення стека

Опишемо тип:
TYPE
ZV = POINTER;
POINTER = ROCORD
ELEM : CHAR;
NEXT : ZV
END;
VAR
STEK : ZV;
. . .

Якщо стек порожній, то вказівник на нього є пустий вказівник NIL:


STEK := NIL;
Перед початком роботи зі стеком його необхідно робити пустим.

2.4.1. ПРОЦЕДУРА ЗАНЕСЕННЯ ЕЛЕМЕНТА У СТЕК

20
Процедура має два формальні параметри: вказівник на стек ST і символ,
що заноситься BUK.
PROCEDURE VSTEK(VAR ST : ZV; BUK : CHAR);
VAR Q : ZV;
BEGIN
NEW(Q);
Q.ELEM := BUK;
Q.NEXT := ST;
ST := Q
END;

2.4.2. ПРОЦЕДУРА ВИБОРУ ЕЛЕМЕНТА ЗІ СТЕКА

Алгоритм вибору елемента полягає в тому, що елемент, який перебуває у


вершині стека, присвоюється змінній А та повертається у головну програму.
Ланка, в якій був цей елемент, виключається із стека.
PROCEDURE VDSTEK(VAR ST : ZV; VAR A : CHAR);
BEGIN
A := ST.ELEM;
ST := ST.NEXT
END;

Ця процедура дуже проста, але вона має два недоліки:


1) елемент, який видаляється із стека не знищується, що призводить до
“засмічення” пам’яті машини;
2) якщо стек виявиться пустим, то результат виконання процедури буде
невизначеним.
Тому у процедуру слід додати фрагмент перевірки, чи стек не є пустим, і
процедуру видалення динамічного елемента:
PROCEDURE VDSTEK(VAR ST : ZV; VAR A : CHAR);
VAR Q : ZV;
BEGIN
IF ST = NIL THEN WRITELN(’СТЕК ПУСТИЙ’)
ELSE
BEGIN
A := ST.ELEM;
Q := ST;

21
ST := ST.NEXT;
DISPOSE(Q)
END
END;
У цій процедурі обов’язково потрібно використати додаткову змінну Q,
де запам’ятати вказівник на ланку, яка знищується. Записати DISPOSE(ST) не
можна, оскільки знищиться весь стек.

2.4.3. ПРИКЛАД РОБОТИ ЗІ СТЕКОМ

Під час розроблення трансляторів виникає потреба перевірити баланс


дужок в арифметичних виразах. Для цієї задачі використовується стек. Алго-
ритм полягає в тому, що кожна ліва дужка “(“ записується у стек, коли
виявляється права дужка “)”, то ліва дужка зі стека видаляється. Якщо після
закінчення перегляду вхідного рядка стек порожній, то це означає, що є баланс
дужок. У всіх інших випадках баланс дужок не виконується.
Введемо у процедуру видалення ще один формальний параметр-змінну L
логічного типу, яка набуває значення FALSE – якщо стек пустий.
Текст програми мовою Паскаль:
PROGRAM BALANS(INPUT, OUTPUT);
TYPE
ZV = POINTER;
POINTER = RECORD
ELEM : CHAR;
NEXT : ZV
END;
VAR
K, SYM : CHAR;
S : ZV;
B :BOOLEAN;

{ПРОЦЕДУРА ЗАНЕСЕННЯ ЕЛЕМЕНТА В СТЕК}


PROCEDURE VSTEK(VAR ST : ZV; BUK : CHAR);
VAR Q : ZV;
BEGIN
NEW(Q);
Q.ELEM := BUK;
Q.NEXT := ST;
ST := Q
END;

22
{ПРОЦЕДУРА ВИДАЛЕННЯ ЕЛЕМЕНТА ІЗ СТЕКА}
PROCEDURE VDSTEK(VAR ST:ZV;VAR A:CHAR; VAR L:BOOLEAN);
VAR Q : ZV;
BEGIN
L := TRUE;
IF ST = NIL THEN L := FALSE
ELSE
BEGIN
A := ST.ELEM;
Q := ST;
ST := ST.NEXT;
DISPOSE(Q)
END
END;
{ГОЛОВНА ПРОГРАМА}
BEGIN
S := NIL;
READ(SYM);
B := TRUE;
WHILE (SYM <> ’.’) AND B DO
BEGIN
IF SYM = ’(’ THEN VSTEK(S, SYM)
ELSE
IF SYM = ’)’ THEN
VDSTEK(S, K, B);
READ(SYM);
END;
IF (S = NIL) AND B THEN WRITELN(’БАЛАНС ДУЖОК Є’)
ELSE WRITELN(’БАЛАНСУ ДУЖОК НЕМА’);
END.

23
Лабораторна робота № 14
ДИНАМІЧНІ ОБ’ЄКТИ
СКЛАДНОЇ СТРУКТУРИ.
ДИНАМІЧНІ ТАБЛИЦІ, БІНАРНІ
ДЕРЕВА
1. МЕТА РОБОТИ

Мета роботи – ознайомитись із особливостями побудови та застосування


динамічних об’єктів складної структури: таблиць і бінарних дерев; з операція-
ми, які виконуються над елементами цих об’єктів. Набути практичних навичок
програмування з використанням динамічних об’єктів складної структури
зокрема бінарних дерева.

2. ТЕОРЕТИЧНІ ВІДОМОСТІ

2.1. ТАБЛИЦІ

Використання комп’ютерної техніки набуло широкого розповсюдження в


Автоматизованих інформаційно-пошукових системах (АІПС).
Основне завдання у разі створення таких систем полягає у тому, щоб
організувати зберігання великої кількості різних записів та видавати будь-який
запис при запиті. Черги та стеки для такого виду робіт не повною мірою
задовольняють потреби.
При роботі з Автоматизованою інформаційно-пошуковою системою най-
частіше використовуються операції пошуку та видачі потрібного запису.
Для користувача складно вказувати адресу кожного запису, тому
необхідно, щоб доступ до кожного запису здійснювався за його іменем. Такі
умови задовольняє структура даних, що називається таблицею.
Таблиця – це набір іменованих записів. Ім’я запису в таблиці називається
ключем.
Як ключ найчастіше використовуються або цілі додатні числа, або рядки
символів однакової довжини (оскільки над рядками визначені операції порів-
няння “більше”, “менше”).
Над таблицею виконуються такі операції:
1) пошук запису із заданим ключем;
2) вставка у таблицю запису із заданим ключем. Якщо запис з таким
ключем вже існує, то заміна запису на новий;
3) видалення запису із заданим ключем.
Найпростіший спосіб представлення таблиці – це однонаправлений
список, кожна ланка якого містить (рис. 1):

2
1) ключ;
2) текст запису;
3) вказівник на наступну ланку.
TAB

NEXT NEXT NEXT ... NIL


* ключ ключ ключ ключ

запис запис запис запис

1 2 3 N

Рис. 1. Схематичне представлення таблиці у вигляді однонаправленого списку



• Таке представлення має недоліки:
1) оскільки ключі невпорядковані, то пошук потрібного запису буде
здійснюватися досить довго;
2) якщо потрібно вставити у таблицю якийсь елемент, то необхідно пере-
брати всі ланки запису, щоб переконатись, що такого запису немає.
• Ці недоліки будуть усунені в однонаправленому списку із впорядко-
ваними записами. Тобто в однонаправленому списку підтримується порядок
знаходження записів. Наприклад, за зростанням ключів записів. Але при цьому
ускладнюється процедура запису елемента в таблицю, оскільки, перш ніж
вставити якийсь елемент, необхідно знайти ту ланку, після якої необхідно
вставити новий запис, щоб порядок зростання ключів не порушився.
• Якщо в таблиці є N записів, то для пошуку потрібного ключа необхідно
проглянути N/2 записів.
• Такий принцип покладено в алгоритм дихотомічного пошуку у таблиці.


2.2. БІНАРНІ ДЕРЕВА

Метод представлення таблиці у вигляді однонаправленого списку або


вектора забезпечує швидкий пошук запису, але він малоефективний під час
включення та видалення записів.
Такий метод доцільно використовувати у тих випадках, коли таблиця
змінюється дуже рідко, в основному здійснюється лише пошук елемента.
Представлення таблиці у вигляді бінарного дерева дає можливість
однаково ефективно реалізовувати всі три операції: пошук, включення та
видалення запису.

3
Бінарне дерево можна зобразити так (рис. 2): маємо набір вершин, з
кожної вершини виходить не більше як дві стрілки (гілки), спрямовані
вліво-вниз та вправо-вниз. Існує одна-єдина вершина, в яку не входить
жодна стрілка, ця вершина називається коренем дерева. У всі інші вершини
входить лише одна стрілка.

* *
* *
* *

* * * *
Рис. 2. Бінарне дерево

Щоб представити таблицю у вигляді бінарного дерева, вважаємо, що


текст запису зберігається окремо, на нього вказує вказівник ZAP (рис. 3).

70

LV

PR

ZAP

62 78

LV LV

PR PR

ZAP ZAP

60
72 80
LV
LV LV
PR
PR PR
ZAP
ZAP ZAP

Рис. 3. Схематичне представлення дерева

4
Кожна ланка бінарного дерева буде записом з чотирма полями:
1. Ключ запису – KLYTH;
2. PR – вказівник на вершину вправо-вниз;
3. LV – вказівник на вершину вліво-вниз;
4. ZAP – вказівник на текст запису.
Опишемо тип:
TYPE
VKTEK = CHAR;
VKL = POINTER3;
POINTER3 = RECORD
KLUCH : INTEGER;
PR, LV : VKL;
ZAP : VKTEK
END;
Розглянемо принцип побудови бінарного дерева у разі занесення
записів у таблицю. Нехай в таблицю заносяться записи з ключами 70, 62, 78,
72, 60, 80, 90, 40, 30, 77, 64, 20, 86. Коренем дерева буде перший запис з кодом
70, вказівники на наступні елементи поки що зробимо пустими, тобто NIL.
Якщо ключ наступного запису К2< K1 менше попереднього (К1 = 70; К2 = 62;
К2 < K1), то він записується в ліву вершину; якщо К2 > K1, то він записується у
праву вершину. При К3=78, то К3 > K1, то формується вершина справа (рис. 4).

70

62 78

72 80
60 64

40 77 90

86
30

20

Рис.4. Бінарне дерево із заданими ключами

Алгоритм формування дерева такий:


1. Надходить черговий запис з ключем К.

5
2. Починаючи з кореня дерева, порівнюємо ключ К з ключем чергової
вершини, якщо К > Квер , то переходимо праворуч; якщо К < Квер , то
переходимо ліворуч від цієї вершини.
3. Якщо стрілки від вершини немає, то приєднюється до цієї вершини
нова вершина з ключем К.

• 2.2.1. ПОШУК ЗАПИСУ В ДЕРЕВІ



Пошук конкретного запису в дереві – це пошук вершини із заданим ключем.
Запишемо логічну функцію, яка буде ще й визначати вказівник на
шукану вершину. Формальними параметрами функції будуть К – ключ, D –
дерево, у якому ведеться пошук, REZ – параметр-змінна вказівникового типу,
якій присвоюється вказівник на знайдену ланку.
FUNCTION PDER( K : INTEGER: VAR D, REZ : VKL) : BOOLEAN;
• VAR P : VKL;
• B : BOOLEAN;
BEGIN
B := FALSE;
P := D;
IF D <> NIL THEN
REPEAT
IF P.KLUCH = K THEN B := TRUE
ELSE
IF K < P.KLUCH
THEN
P := P.LV

ELSE
P
:= P.PR
UNTIL B OR (P = NIL);
PDER := B;
REZ := P
END;

Найбільший ефект використання дерева досягається, коли дерево


“збалансоване”, тобто, коли всі вершини, крім останніх, мають як лівого, так і

6
правого послідовника. Для здійснення пошуку таким методом необхідно буде
перебрати log2N вершин.

• 2.2.2. ВСТАВКА ЗАПИСУ В ДЕРЕВО



Вважаємо, що запису із заданим ключем у дереві немає.
При вставці нового елемента спочатку необхідно знайти вершину, до якої
“підвішується” новий запис. Алгоритм пошуку потрібної вершини такий
самий, що й під час пошуку вершини із заданим ключем. Вершину буде
знайдено тоді, коли черговий вказівник PR або LV залежно від напрямку буде
дорівнювати NIL. Але використати функцію PRED ми не можемо, оскільки там
не фіксується вершина, вказівник якої PR або LV є NIL.
Модифікуємо цю функцію, будемо фіксувати вершину, в якій знайдено
вказівник NIL як побічний ефект.
FUNCTION PDER2(K : INTEGER; VAR D, REZ : VKL): BOOLEAN;
VAR P, Q : VKL;
B : BOOLEAN;
BEGIN
B := FALSE;
P := D;
IF D <> NIL THEN
REPEAT
Q := P;
IF P.KLUCH = K THEN
B := TRUE
ELSE
BEGIN
Q := P;
IF K < P.KLUCH THEN
P := P.LV
ELSE
P := P.PR
END
UNTIL B OR (P = NIL);
PDER2 := B;
REZ := Q
END;

7
Процедура вставки запису в таблицю, представлену бінарним деревом,
буде мати вигляд:
PROCEDURE VTABL(K : INTEGER; VAR D : VKL; REC : CHAR);
VAR
R, S : VKL;
T : VKTEK;
BEGIN
IF NOT PDER2(K, D, R) THEN
BEGIN
NEW(T);
T := REC;
NEW(S);
S.KLYСH := K;
S.ZAP := T;
S.LV := NIL;
S.PR := NIL;
IF D = NIL THEN D := S
ELSE
IF K <>
R.KLUCH THEN
R.LV := S
ELSE
R.PR :=S
END
END;

У цій процедурі використовується функція пошуку PDER2 для визна-


чення вершини, до якої “підвішується” наступна гілка.

2.2.3. ВИДАЛЕННЯ ЗАПИСУ З ДЕРЕВА

Видалення запису із заданим ключем здійснюється дуже просто, якщо:


1. Ця вершина є кінцевою (рис. 5, а).
2. Із вершини виходить лише одна гілка (рис. 5, б).
Схематично це можна зобразити:

8
40

35 35
32
32
а)
а) б)б)

Рис. 5. Видалення елемента з дерева



• Складніше, коли з вершини, яку необхідно видалити, виходить два
ребра. Наприклад: необхідно видалити вершину з ключем 40, але з неї виходять
дві гілки (рис. 6, а). На місце вершини, що видаляється, необхідно поставити
іншу, з якою будуть зв’язані вершини з ключем 30 та 50. Найкраще для цього
підійде вершина з ключем 35, яка має лише одну гілку вліво, тобто до неї
можна приєднати праву гілку з вершинами 50 та 60 (рис. 6, б).

72 72

21 100 21 100

13 120 13 35 120
40

150 50 150
30 50 30

27 35 27 60
60 32

а)
32 а) б)
• б)

• Рис. 6. Видалення вузла 40 дерева, який має двох наступників

Оскільки потрібно приєднати праву гілку лівого піддерева, то необхідно


знайти крайній правий елемент цього піддерева, який має значення вказівника
вправо NIL. Якщо видалити елемент з правого піддерева, то необхідно знайти
крайній лівий елемент, що має вказівник вліво NIL.
Наведемо процедуру видалення елемента з дерева запропоновану Віртом
[13]. У ній передбачається три різні умови, які можуть виникати у разі видален-
ня елемента із заданим ключем:

9
1. Ланки із заданим ключем немає.
2. Ланка із заданим ключем має не більше однієї гілки.
3. Ланка із заданим ключем має 2 гілки.
Використаємо допоміжну рекурсивну процедуру UD для третього випадку.
У ній здійснюється “спуск” до крайнього правого елемента лівого піддерева
елемента Q, що видаляється. Значення поля KLUCH та поля ZAP елемента Q
замінюється відповідними значеннями полів KLUCH та ZAP елемента R.
PROCEDURE UDALDER(VAR D : VKL; K : INTEGER);
VAR
Q : VKL;

{ПОЧАТОК ПІДПРОЦЕДУРИ UD}


PROCEDURE UD (VAR R : VKL);
BEGIN
IF R.PR = NIL THEN
BEGIN
Q.KLUCH := R.KLUCH;
Q.ZAP := R.ZAP;
Q := R;
R := R.LV;
END
ELSE UD(R.PR)
END; {КІНЕЦЬ ПРОЦЕДУРИ UD}

BEGIN {ПОЧАТОК ПРОЦЕДУРИ UDALDER}


IF D = NIL THEN
WRITELN(’ЕЛЕМЕНТА З ТАКИМ КЛЮЧЕМ НЕМАЄ’)
ELSE
IF K < D.KLUCH THEN
UDALDER(D.LV, K)
ELSE
IF K > D.KLUCH THEN
UDALDER(D.PR, K)
ELSE
BEGIN
Q := D;
IF Q.PR = NIL THEN
D := Q.LV

10
ELSE
IF Q.LV = NIL THEN
D := Q.PR
ELSE
UD(Q.LV)
END
END;

2.2.4. ПРИКЛАД РОБОТИ З БІНАРНИМ ДЕРЕВОМ

Приклад: у файлі STUD.DAT записана інформація про студентів


факультету. Вона складається з:
1. Прізвища студента – масив з 10 елементів символьного типу.
2. Шифру групи – цілі числа в інтервалі 11…59.
3. Оцінок, одержаних кожним студентом за останню сесію – масив з 5
елементів інтервального типу 2...5
Завдання:
1. Сформувати задану інформацію у вигляді дерева, у вершинах якого
міститься інформація про студентів, прізвища є ключем.
2. Надрукувати прізвища студентів, які отримали за останню сесію
лише оцінки “5”.
3. Надрукувати прізвища студентів, які одержали лише оцінки “2”.
4. Для формування списку “двієчників” використати стек.
Алгоритм буде складатися з таких етапів:
1. Ввід інформації із зовнішнього файла та побудова бінарного дерева.
2. Повний обхід бінарного дерева:
а) друк прізвищ студентів, які одержали лише “5”;
б) запис у стек прізвищ “двієчників”.
3. Друк прізвищ “двієчників” зі стека.
Оскільки друк прізвищ повторюється двічі, тому його доцільно
оформити у вигляді процедури PRINT. Формальним параметром якої є
вказівник на вершину дерева.
Блок-схему алгоритму основної програми наведено на рис. 7.
Блок 1 – початок алгоритму.
Блоки 2, 3 – ввід інформації про студентів: прізвища, що одночасно є
ключем, шифру групи та оцінок, які записані у файлі STUD.DAT.
Блок 4 – присвоювання вказівнику D значення NIL перед формуванням

11
дерева.
Блоки 5, 6 – формування дерева за допомогою процедури VTABL.
Блок 7 – присвоювання вказівникам ST1 та ST2 значення NIL. Вказівник
ST1 використовується під час пошуку в дереві, ST2 – під час формування стека
“двієчників”.
Блок 8 – присвоювання біжучому вказівнику P значення D – кореня
дерева.
Блоки 9–27 – обробка таблиці, представленої у вигляді дерева, поки
біжучий вказівник Р не дорівнюватиме NIL.
Блоки 10–12 – обчислення суми оцінок кожного студента Si.
Блоки 13–15 – друк прізвища відмінника, якщо сума його оцінок Si = 25.
Блоки 16, 17 – занесення у стек ST2 прізвища студента-“двієчника”, у
яких Si10.
Блок 18 – перевірка, чи не досягли кінця дерева, тобто, чи права та ліва
гілки дерева не дорівнюють NIL.

12
1
Поч аток

2
i =1, 500
4
D = NIL
3
Ввід
інформації 5
i =1, 500

6
VTABL

7
ST1 = NIL
ST2 = NIL
8
P=D

9 ні
P  NIL
так
10
Si = 0

11
i = 1, 5

12
Si = Si + OC

13
Si =25
ні
так 14
Вивід
16 ні
Si  10
відмінників

15 17 так
VSTEK
PRINT
ST2

2 1 3

Рис. 7. Блок-схема основної програми

13
NIL
NIL

NIL
NIL
PR

NIL

двієчників

PRINT

Рис. 7. (Продовження) Блок-схема основної програми

14
Блок 19 – якщо не досягли, то заносимо праву гілку у стек ST1.
Блок 20 – перехід на ліву гілку.
Блок 21 – якщо досягнуто кінця дерева, то виконується блок 22.
Блок 22 – перевірка, чи стек ST1 порожній.
Блок 23 – присвоювання біжучому вказівнику Р значення NIL, якщо не
порожній, то виконується блок 24
Блок 24 – видалення із стека ST1.
Блоки 25-27 – рух по дереву вправо або вліво.
Блоки 28-31 – друк прізвища студента-“двієчника” зі стека ST2.
Блок 32 – кінець алгоритму.
У програмі використані процедури:
VSTEK – запис у стек; формальні параметри: ST – ім’я стека (ST1, ST2)
та NEL – інформація, що заноситься у стек.
UDSTEK – видалення зі стека; формальні параметри: ST – ім’я стека
(ST1, ST2); А – інформація, що видаляється зі стека (у цьому випадку – прізви-
ще студента).
VTABL – занесення вузла дерева; формальні параметри: К – ключ, що є
прізвищем студента; D – вказівник на вершину дерева; REC – інформація про
студента, тобто шифр групи та оцінки.
PRINT – друк прізвища студента; формальний параметр: Р – вказівник
на вузол дерева.
У програмі також використано функцію пошуку вузла з відповідним
ключем у дереві: PDER; формальними параметрами є: D – вказівник на корінь
дерева; REZ – вказівник на вузол, у якому знайдено відповідний ключ. Функція
PDER повертає результат логічного типу: TRUE – якщо вузол знайдено, та
FALSE – якщо вузол не знайдено.
Текст програми мовою Паскаль:
PROGRAM DERUSP (INPUT, OUTPUT);
CONST M = 5; N = 10;
TYPE
PRIZV = ARRAY [1..N] OF CHAR;
VKTEK = TEKST;
TEKST = RECORD
GR : 11..59;
OC : ARRAY [1..M] OF 2..5
END;

VKL = POINTER3;
POINTER3 = RECORD

15
KLUCH : PRIZV;
LV, PR : VKL;
ZAP : VKTEK
END;

TELST = VKL;
ZVST = POINTER;
POINTER = RECORD
ELEM : TELST;
NEXT : ZVST
END;
INFORM = RECORD
FM : PRIZV;
INF : TEKST
END;
VAR
STUD : FILE OF INFORM;
STUDENT : ARRAY[1..500] OF INFORM;
ST1, ST2 : ZVST;
Q1 : VKTEK;
P, D : VKL;
FAM : PRIZV;
SI, I, J : INTEGER;
{ПРОЦЕДУРА ЗАПИСУ У СТЕК}
PROCEDURE VSTEK (VAR ST : ZVST; NEL : TELST);
VAR Q : ZVST;
BEGIN
NEW(Q);
Q.ELEM := NEL;
Q.NEXT := ST;
ST := Q
END;
{ПРОЦЕДУРА ВИДАЛЕННЯ ЗІ СТЕКУ}
PROCEDURE UDSTEK(VAR ST : ZVST; VAR A : TELST);
VAR Q : ZVST;
BEGIN
A := ST.ELEM;
Q := ST;
ST := ST.NEXT;
DISPOSE(Q);

16
END;
{ФУНКЦІЯ ПОШУКУ В ДЕРЕВІ}
FUNCTION PDER2(K : PRIZV; VAR D, REZ : VKL): BOOLEAN;
VAR P, Q : VKL; B : BOOLEAN;
BEGIN
B := FALSE;
P := D;
IF D <> NIL THEN
REPEAT
Q := P;
IF P.KLUCH = K THEN
B := TRUE
ELSE
BEGIN
Q := P;
IF K < P.KLUCH THEN
P := P.LV
ELSE
P := P.PR
END
UNTIL B OR (P = NIL);
PDER2 := B;
REZ := Q
END;
{ПРОЦЕДУРА ВСТАВКИ}
PROCEDURE VTABL(k : PRIZV; VAR D : VKL; REC : TEKST);
VAR R, S : VKL; I : INTEGER; T : VKTEK;
BEGIN
IF NOT PDER2(K,D,R) THEN
BEGIN
NEW(T);
T := REC;
NEW(S);
S.KLUCH : =K;
S.LV := NIL; S.PR := NIL;
IF D = NIL THEN D := S
ELSE
IF K < R.KLUCH
THEN R.LV := S

17
ELSE R.PR := S
END
END;
{ПРОЦЕДУРА ВИВОДУ}
PROCEDURE PRINT (R : VKL);
VAR I : INTEGER;
BEGIN
FOR I := 1 TO N DO
WRITE(R.KLYTH[I]);
WRITELN
END;
{ГОЛОВНА ПРОГРАМА}
BEGIN
ASSIGN (STUD, ’STUD.DAT’);
RESET(STUD);
D := NIL;
FOR I := 1 TO 500 DO
READ(STUD, STUDENT[I]);
FOR I := 1 TO 500 DO
VTABL(STUDENT[I].FM, D, STUDENT[I].INF);
ST1 := NIL; ST2 := NIL;
P := D;
WHILE P <> NIL DO
BEGIN
SI := 0;
FOR I := 1 TO M DO
SI := SI + P.ZAP.OC[I];
IF SI = 25 THEN
BEGIN
WRITE(’ВІДМІННИК’);
PRINT(P)
END
ELSE
IF SI <= 10 THEN
VSTEK(ST2,P);
IF (P.PR <> NIL) AND ( P.LV <> NIL)
THEN
BEGIN
VSTEK(ST1, P.PR);
P := P.LV

18
END
ELSE
IF (P.PR = NIL) AND (P.LV = NIL) THEN
BEGIN
IF ST1 = NIL THEN P
:= NIL
ELSE UDSTEK(ST1,P)
END
ELSE
BEGIN
IF P.PR =
NIL THEN P := P.LV

ELSE P := P.PR
END
END;
WRITELN(’СПИСОК ДВІЙОЧНИКІВ’);
WHILE ST2 <> NIL DO
BEGIN
UDSTEK(ST2, P);
PRINT(P)
END
END.

19

You might also like