You are on page 1of 144

Міністерство освіти і науки України

Львівський національний університет імені Івана Франка

О. Костів

СТРУКТУРИ ДАНИХ

Львів
Видавничий центр ЛНУ імені Івана Франка
. 2005
Міністерство освіти і науки України
Львівський національний університет імені Івана Франка

О. Костів

СТРУКТУРИ ДАНИХ

Львів
Видавничий центр ЛНУ імені Івана Франка
2005
ББК 3973.2-018
К-72
УДК 004.4'232
Рецензенти:
канд. екон. наук, доц. О. Т. Іващук,
(Тернопільська академія народного господарства);
д-р фіз.-мат. наук, проф. Г. Г. Цегелик,
(Львівський національний університет);
канд. фіз-.мат. наук, доц. С. Б. Костенко,
(Львівська комерційна академія)

Відповідальний за випуск В.Черняхівський

Рекомендовано до друку Вченою радою


Львівського національного університету імені Івана Франка.
Протокол №30/3 від ЗО березня 2005 р

Костів О.
К-72 Структури даних: Навч. посібн. - Львів: Видавничий центр ЛНУ
імені Івана Франка, 2005. - 146 с.

У посібнику розглянуто основні структури даних на основі багаторівне-


вого відображення. Наведено класифікацію абстрактних структур даних та
структур зберігання даних. Описано абстрактні структури: масив,, таблиця,
рядок та динамічні структури (стек, черга, дек), списки, дерева та графи.
Наведено відображення цих структур у структури зберігання даних і базові
операції над ними.
Для студентів факультету прикладної математики та інформатики і всіх,
хто цікавиться програмуванням.

ББК 3973.2-018

© Костів О., 2005


ВСТУП

Опрацювання на комп'ютері інформації про реальний світ


вимагає, щоб структура інформації була точно визначена і відповідно
зображена в обчислювальній машині. Її носіями є дані. Структури
даних відображають структурні відношення, які існують між
елементами даних. Дані відтворюють зображення реального сві гу. Ллє
їх застосування для вирішення певних проблем корисне лише тоді,
коли вони точно відображають реальний світ.
Існує багато способів для відображення реального світу. Зокрема
це фотографія або ескіз. Можна зображати окремі області реального
світу за допомогою математичних моделей, які дають змогу
передбачати результат при зміні певних значень відповідно до
можливих подій в реальному світі.
Той, хто проекту»: обчислювальні системи, пробує зобразити
реальний світ шляхом струкіуризації даних. Тому дані є описами
об'єктів реального світу. Причому під час розв'язування конкретних
проблем зазвичай розглядають лише деякі об'єкти, сукупність яких
становить предметну область, а самі об'єкти називаються об'єктами
предметної області. Об'єктами можуть бути, наприклад, люди,
перелічені в деякій відомості, або предмети, які виготовляє
підприємство. Також об'єктами можуть бути уявні побудови,
наприклад, рахунки в банку.
Розв'язуючи задачі з використанням комп'ютера, ми вирішуємо
принаймні чотири взаємопов'язані підзадачі.
1. Добре зрозуміти взаємовідношення між елементами даних,
суттєвими для розв'язування задачі.
2. Вибрати потрібні операції над логічно пов'язаними
елементами даних.
3. Розробити методи зображення елементів даних у пам'яті
машини, які дають змогу: а) якнайповніше зберегти логічні
відношення, які є між елементами даних; б) легко та ефективно
виконувати операції над елементами даних.
4. Вибрати, які саме засоби (зокрема мова програмування)
будуть найпридатнішими для цієї задачі і допоможуть користувачеві
4
виражати у найзручнішому вигляді ті операції, які він захоче викотим
над даними.
Для розв'язання задачі треба створити програму, пам'ятаючи,
що програма - це не мета програмування, а лише засіб для одержанні!
результату. Програми - конкретні втілення абстрактних алгоритмі»,
побудовані на реальному представленні даних. Не можна приймати
рішення про подання даних, не знаючи, які алгоритми будуть до них
застосовуватись. І навпаки, часто вибір алгоритму залежить від
структури даних, до яких він застосовується. Структура програми і
структура даних тісно пов'язані між собою.
В основу пропонованого підходу покладено багаторівневе
відображення даних [4]. Основна ідея такого підходу полягає в тому,
що дані - це абстракції реальних об'єктів і їх доцільно розглядати як
деякі абстрактні утворення зі структурами. В процесі проектування
програм представлення даних поступово уточнюють і узгоджують з
обмеженнями, які накладають системи програмування чи можливості
реалізації. Абстрактне поняття інформації перетворюється в конкретне
понятгя зображення даних.

Кожну обчислювальну задачу можна вивчати на двох рівнях:


абстрактному і конкретному. На кожному рівні можна розглядати
задачу в двох аспектах: статичному і динамічному.
Всюди першочергове значення має структура. Всі види
структур називають ОБЧИСЛЮВАЛЬНИМИ СТРУКТУРАМИ.
Абстрактну статичну компоненту, пов'язану з даними -задачі,
називають структурою інформації, абстрактну динамічну компо-
ненту - структурою алгоритму, конкретну статичну - струк-
турою пам'яті, конкретну динамічну - структурою управління.
Виділення таких типів структур дає змогу наголосити у програ-
мах на двох аспектах, завдяки яким Вірт [ 1 ] записав
Алгоритми + Структури Даних = Програми.
Обробка на ЕОМ даних про реальний світ передбачає
абстракцію дійсності, оскільки деякі властивості і характеристики
реальних об'єктів ігноруються, бо не є суттєвими для розв'язуваних
задач. Інформація, яку заносять у комп'ютер, складається з певної
множини даних.
Довільний набір знаків, який розглядають безвідносно до
його змісту, називають даними. Дані можна розглядати як
сукупність деяких інформаційних одиниць, між якими є пенні
відношення. Ці відношений визначають структуру даних
Найменшу, логічно неподільну структуру, називають елементом
даних.
Структури даних визначають семантику даних, а також способи
організації та управління даними. У програмних застосуваннях дані
довільного типу повинні обробляти за допомогою відповідних
операцій. Насамперед необхідно знайти адекватне представлення
даних у пам'яті обчислювальної машини. Складність цієї задачі
визначають головно мовою програмування, яку використовують для
реалізації алгоритму.
Головними поняттями, які використовують для задания
структури даних, є тип, атрибут, значення атрибуту і т.д. Чим
більше типів відношень дає змогу відобразити структура даних, тим
більшою загальністю вона володіє. Вибір відношень залежить від мети
зображення і може бути різним. Кажугь, що відношення - цс
різновид структур даних.
1. БАГАТОРІВНЕВЕ ВІДОБРАЖЕННЯ ДАНИХ

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


рівні зображення даних. Одна зі схем, яка відповідає принципу
розділення логічного і фізичного представлення даних, зображена на
рис.1.
Кожний з пропонованих рівнів розгляду даних відповідає
певному етапу розв'язування задачі. Змістовний рівень відповідає
постановці задачі, бо дає змогу описувати її неформалізовано у
термінах предметної області. Побудова моделі передбачає вибір
абстрактних структур даних, а реалізація програми використовує
базовий рівень збереження даних.
У багаторівневому відображенні інформації про об'єкти
реального світу на машинні носії виділено п'ять головних рівнів:
змістовний, абстрактний, декларативний, агрегований, базовий . За
своїм цільовим призначенням вони стосуються проблемного
(змістовний та абстрактний), процедурного (декларативний) або
машинного (агрегований і базовий) середовища існування даних. Далі
детальніше розглянемо кожен із головних рівнів.

Змістовний рівень опису даних


Дані цьоГо рівня - це неформалізовані описи об'єктів,
інформація про які повинна бути опрацьована в системі. Об'єкти
стосуються певної предметної області, і їхній змістовний опис
виконується з урахуванням термінології, яку використовують у цій
галузі. Описи охоплюють назви об'єктів, назви і значення їхніх
атрибутів, відомості про структурну організацію чи фрагментацію
об'єктів, відношення між ними. Кожний об'єкт зображається
певним записом або певною організованою сукупністю записів.
Запис складається з полів, які відповідають окремим атрибутам
об'єкта. Впорядкованість полів у записі може визначатися наперед
8 1. Б А Г А Т О Р І В Н Е В Е В І Д О Б Р А Ж Е Н Н Я ДАНИХ
вибраним форматом. Такі записи називають форматними і
використовують для зображення об'єктів з відомим набором а т р и б у т і
та відношень. У тих випадках, коли інформація про стан предметної
області не підлягає попередній класифікації, використовують
неформатні записи. Довільна предметна область може бути описанії
сукупністю форматних і неформатних записів.
Дані змістовного рівня виникають на етапі інформаційного
аналізу предметної області та формулювання вимог до процесу
обробки даних у програмній системі.
Наступний крок — формалізація змістовних структур даних на основі
певного математичного апарату.

Абстрактний рівень опису даних


Через математичне моделювання описують абстрактні
характеристики досліджуваного процесу. Також фіксують відношення,
які є між цими характеристиками. Це дає змогу описати дослі-
джуваний процес у вигляді формальних конструкцій і застосувати
відповідний математичний апарат для одержання різних властивостей
процесу. Сьогодні для задач, розв'язуваних з використанням комп'ю-
тера, склався певний набір дискретних математичних об'єктів, які
використовують як стандартні формальні моделі змістовних структур
даних.
Дискретні математичні структури, які є формальними
моделями даних змістовного рівня, називають абстрактними
структурами даних (АСД).
Одну з можливих класифікацій АСД можна виконати на основі
доступу до складових елементів структури [3]. Ця класифікація
зображена на рис. 2.
Усі математичні об'єкти, перелічені у схемі як стандартні АСД,
відображають деякий спосіб комбінування елементів у структури,
тобто є комбінаторними об'єктами.
На абстрактному рівні розгляду алгоритмів програміст уводить власні
позначення і поняття, які відповідають його розумінню проблеми.
Крім типових операцій над комбінаторними об'єктами,
Абстрактний рівень опису даних 9

Абстрактні структури даних

Рис. 2
використовують операції, специфічні для конструйованого алгоритму і
класу об'єктів, вибраних як АСД.
Виконаний на абстрактному рівні опис алгоритму може містити
багато операцій, які дуже корисні під час розробки алгоритму і
дослідженні його властивостей, проте неефективні під час реалізації
у машині. Вилучення таких операцій під час переходу від
абстрактного опису алгоритму до програми конкретною мовою
програмування становить етап стратегічної оптимізації програми.
Можливість заміни абстрактних структур даних у разі
побудови реалізації початкового алгоритму зумовлена тим, іцо
відношення на множині елементарних об'єктів, задане АСД, у
20 1. Б А Г А Т О Р І В Н Е В Е В І Д О Б Р А Ж Е Н Н Я Д А Н И X
більшості випадків може бути зображене також іншими формальними
символьними конструкціями, а алгоритмічні дії над початковою АСД
можуть виражатися у термінах операцій над цими конструкціями
Наприклад, множини можна задати лінійними масивами, логічними
шкалами, таблицями, деревами. Розріджені масиви зображаюті.
таблицями, ключі яких формують з індексів позицій ненульових
елементів масиву. Таблиці та масиви можна зображати деревами,
дерева - рядками. Дерева довільної структури можна привести до
двійково-пошукового виду. Графи можна подавати у матричній і
множинній формах, з використанням некореневих дерев, а також за
допомогою рядків. У результаті вибору найефективнішого зображення
з можливих форм логічного задания АСД і розробки процедурної
реалізації алгоритму стосовно вибраного представлення, абстрактний
опис алгоритму конкретизується і перетворюється у конструктивно
задану процедуру.

Декларований рівень даних


Конструктивні процедури алгоритмів описують певною мовою,
яка відповідає вибраним абстрактним структурам даних. У мові
використовують здебільшого великі одиниці дій над абстрактними
структурами, пов'язані з виконанням досить складних перетворень на
машинному рівні. Реалізуючи процедури в машині, а точніше —
реалізуючи їх у конкретному обчислювальному середовищі,
програміст повинен розробити зображення АСД на базі структур
даних, які є в тій чи іншій мові програмування, і описати процес
фбробки цих структур у термінах операторів мови. З цією метою
використовують мови програмування різного рівня з різними типами
даних. Нерідко надають перевагу спеціалізованим мовам, орієн-
тованим на даний клас АСД, які мають змогу задавати складні
перетворення над об'єктами цього класу за допомогою одного
оператора. У тих випадках, коли спеціалізовані мови реалізовані за
допомогою препроцесорів, їхнє застосування не завжди забезпечує
потрібну ефективність, оскільки двоступінчастий транслятор доволі
складна система і не надто ефективна порівняно зі звичайними
компіляторами. Тоді перевагу можна віддати універсальним мовам
високого рівня зі стандартними або конструйованими типами даних.
Останнім часом для обробки АСД складної структури все частіше
Базовий рівень даних 11
використовують мови, які дають змогу вводити нові типи даних,
зокрема мови з абстрактними типами даних. Особливі класи
становлять мови маніпулювання даними в банках даних і мови
представлення знань. Часто мови програмування не дають змоги
безпосередньо і легко зображати абстрактні структури зі складними
взаємозв'язками елементів. Виникає потреба зобразити АСД логічною
структурою, допустимою в мові. Незважаючи на різноманітність
можливих форм логічного задання графів і наявності спеціалізованих
графових мов з ефективними теоретико-множинними зображеннями
графів, широке застосування отримали лише матричні форми,
оскільки вони легко відображаються у структури даних типу масив,
поширених мов числової обробки (Паскаль, С, Фортран та ін.).
Довільною мовою при належному кодуванні можна запрограмувати
довільну задачу, а масивами вдається відобразити довільні специфічні
абстрактні структури.
Необхідність заміни АСД допустимою у мові логічною
структурою нерідко вимагає винахідливості, може суттєво затруднити
програмування алгоритму і різко знизити ефективність робочих
програм базового машинного рівня. Етап розміщення алгоритму в
конкретному обчислювальному середовищі зазвичай супроводжується
пошуком системи програмування, вхідна мова якої володіє
структурами даних, найпридатнішими для зображення АСД і
операторами їхньої обробки, а реалізація цієї системи на машині
гарантує досить ефективну робочу програму.
Отже, використовувані системи програмування мають свої
вимоги до задання АСД і приводять до нового рівня зображення
даних - вхідного рівня обчислювального середовища. Структури
даних цього рівня декларує програміст за допомогою синтаксичних
засобів конкретної мови програмування. їх називають декларованими
структурами даних (ДСД), а опис ДСД у мовах програмування -
синтаксичним зображенням ДСД. Тій самій абстрактній структурі
даних (масиву, таблиці, дереву і т.д.) у різних мовах відповідають
різні синтаксичні зображення.

Базовий рівень даних


Незважаючи на те, що архітектура сучасних ЕОМ змінюється,
базова структура пам'яті, використовувана для збереження ДСД, є
_12 1. Б А Г А Т О Р І В Н Е В Е В І Д О Б Р А Ж Е Н Н Я ДЛІІИХ
єдиною для всіх традиційних моделей машин (машин фон
Нейманівського типу) - це майже нескінченна лінійна послідовніш,
двійкових розрядів, розділена з фіксованим кроком на адресовані
одиниці. У адресованих одиницях основної пам'яті та швидких
регістрах зберігаються послідовності бітів певної довжини - байти,
слова, півслова, подвійні слова. До них можливий доступ за
допомогою зазначення адреси, і тому їх називають елементами
доступу або елементами збереження. У машинах з адресацією
окремих двійкових розрядів до числа елементів доступу належать і
біти. Отже, на базовому рівні декларовані структури даних
зображають сукупністю елементів доступу - адресованих наборів
бітів, розміщених в одиницях пам'яті.

Агрегований рівень даних


Залежно від застосовуваної системи програмування і організації
даних у програмі, базова структура даних може бути використана по-
різному. Дані можуть зберігатись лише в сусідніх одиницях пам'яті
або ж одиницях, розкиданих по пам'яті і з'єднаних за допомогою
спеціальних адрес зв'язку (вказівників). Інший вид доступу до
розкиданих одиниць - обчислення адрес за деякою формулою.
Відповідно до цих трьох типів відношень на сукупності одиниць
пам'яті, які відводяться для збереження ДСД, розрізняють неперервне
(послідовне), зв'язане (зчеплене) та розсіяне розміщення даних у
пам'яті.
Який би не був спосіб розміщення даних, відповідна сукупність
одиниць пам'яті організована за допомогою певних програмних
засобів і моделює структуру пам'яті вищого рівня, ніж базова. Такі
програмно-організовані "надбудови" над базовою структурою
нам'яті називатимемо агрегатами пам'яті, а структури даних, які
зберігаються у цих віртуальних надбудовах — структурами
зберігання даних ( СЗД).
Залежно від того, який тип відношення між одиницями в
агрегаті пам'яті, що містить СЗД, розрізняють неперервні, зв'язані
та розсіяні СЗД.
Отже, довільні різновиди СЗД - це програмно-організовані
сукупності елементів доступу, що утворюють програмно-агрегований
рівень збереження даних, який виникає на етапі розміщення ДСД у
машинному середовищі віртуальних агрегатів пам'яті.
2. СТРУКТУРИ ЗБЕРІГАННЯ ДАНИХ
Розгляд структур зберігання даних почнемо з таких загальних
понять як концепція типу та квантування пам'яті.

Концепція типу
Найпростіше поняття, яке часто застосовують у мовах
гіроірамування, - це понятгя типу. Основні принципи концепції типу
такі [1]:
1) довільний тип даних визначає множину значень, до яких
може відноситись константа і яке може приймати змінна (чи вираз),
або виробляти операція (чи функція);
2) тип довільної величини може бути визначений за її виглядом
або описом;
3) кожна операція або функція вимагає аргументів певного типу
і дає результат також фіксованого типу.
Поняття тину даних відіграє важливу роль у мовах
програмування високого рівня. Тип можна визначити шляхом
задання множини значень даних, які належать до цього типу, та
операцій, визначених над елементами цієї множини.
Типом даних називають іноді метод інтерпретації бітової
комбінації, використовуваної для зображення даних. Та сама
послідовність 0 і 1 може розглядатись як число, символ або масив
логічних значень. Наприклад, послідовність 01000001 може
інтерпретуватись як ціле число (41) 16 , символ A (ASCII- код) або
логічний масив зі значеннями: хибне, істинне, хибне, хибне, хибне,
хибне, хибне, істинне.
Система команд конкретної ЕОМ уміє інтерпретувати
послідовності бітів. Таку інтерпретацію називають стандартними
простими типами. Можна вважати, що ці тини вбудовані в ЕОМ.
Найпоширеніші вбудовані типи:

логічний (boolean);
символьний (character);
]4 2. СТРУКТУРИ ЗБЕРІГАННЯ ДАІIIIX
цілий (integer);
дійсний (real).
Набір таких тииів даних, підтримуваних комп'ютерам,
визначають функції, закладені в його апаратну частину.
Традиційно використовувані в універсальних монах
програмування типи даних повинні задовольняти дві основні вимоги:
1) опис об'єктів реального світу з використанням засобів мони
програмування повинен бути, по можливості, простим для розуміння і
при цьому невеликим за обсягом;
2) реалізація мови програмування повинна забезпечувати
високу ефективність роботи існуючих комп'ютерів.
Основні типи даних можна класифікувати так:
1) прості типи;
2) структуровані типи;
3) тип посилання.
Прості типи даних не володіють внутрішньою структурою.
Набір цих типів скінченний. їх називають примітивними, елемен-
тарними чи базовими.
До простих типів, крім вбудованих, відносять також переліче-
ний тип. Вбудовані типи можна поділити на два класи: арифметичні
та рядкові. Елементи арифметичних тинів мають своїм значенням
числа і можуть бути використані в арифметичних операціях. Рядкові
елементи мають за значення послідовності символів деякого алфавіту.
Структуровані типи даних призначені для задання складних
структур даних. їх конструюють зі складових елементів, названих
компонентами, які можуть володіти структурою. Основою конструю-
вання є визначені правила (операції) конструювання (конструктори
типів).
Тип посилання призначено для забезпечення можливості
зазначити інші дані. Цей тип є засобом організації обробки складних
зв'язаних структур даних. Його називають вказівником.

Квантування пам'яті
Для опису різновидів СЗД зручно користуватися поняттям квант
пам'яті. Квант пам'яті - це сукупність сусідніх одиниць пам'яті, вміст
яких інтерпретується як єдине ціле, наприклад, як двійковий код для
зображення символу, число з фіксованою чи плаваючою крапкою,
адреса, елемент даних разом з його дескриптором, який описує,
15
наприклад, розмір елемента. Тоді внутрішню структуру агрегатів
пам'яті можна буде описувати не в термінах одиниць пам'яті базового
рівня, а враховуючи об'єкти вищого рівня — кванти пам'яті, які
містять елементи даних або вказівники зв'язку.
Адресація одиниць пам'яті визначає її структуру і доступ до неї.
Використання адреси кванта пам'яті (адреси його першої одиниці) дає
змогу реалізувати прямий доступ до пам'яті. Якщо задано адресу, то
можна додавати до неї деяке фіксоване число, яке залежить від
розміру кванта, тобто користуватися послідовним доступом до
пам'яті. Довільний доступ не вводить жодних структурних зв'язків
між квантами, а послідовний доступ накладає на пам'ять лінійну
структуру. Якщо сусідні кванти об'єднано у сукупність, то адресу
сукупності називають базовою, а адресу кванта щодо початку
сукупності - зміщенням,і тоді
адреса кванта = база + зміщення.

Послідовні структури зберігання

Об'єднання сусідніх квантів у єдиному блоці пам'яті без


пропусків приводить до струкгури пам'яті послідовного тину,
яку називають вектором, якщо всі кванти мають однаковий розмір, і
неоднорідним вектором у протилежному випадку. Для неоднорідного
вектора використовують термін "сегмент пам'яті".
Вектори застосовують для зберігання однорідних структур
даних, які мають один дескриптор для всіх елементів. Дескриптор -
це вектор, який містить додаткову інформацію, потрібну для декоду-
вання ланцюжка бітів. Такою може бути інформація про тип елементів
вектора, довжину окремого елемента і т.д. Сегменти використовують
для неоднорідних структур з різними дескрипторами для різних
елементів. Структури зберігання даних, розташовані у векторах та
сегментах, називають векторними структурами даних і записами
відповідно.
Вектор визначає:
1) база - адреса першого елемента;
2) розмір елемента - кількість одиниць пам'яті (квантів), яку займає
один елемент даних;
3) довжина - кількість елементів даних.
Схематично векторну структуру зберігання зображатимемо так:
]6 2 . С Т Р У К Т У Р И ЗБЕРІГАННЯ ДАІIIIX

Адресу і-го елемента А і визначають за формулою:


At (аі) = А + (і-1 )*d , де А — база, d - довжина елемента.
Для випадку сегмента кожен елемент повинен, крім іншої,
містити також інформацію про довжину. Зображення сегмента може
бути, наприклад, таким:

Адреса і-го сегмента визначатиметься згідно з наступною


формулою:

Послідовний спосіб збереження структур даних компактніший


стосовно розташування у пам'яті, проте явно не відображає
складних відношень у структурі.

Спискові структури
Об'єднання квантів, розташованих по базовій пам'яті довільно,
за допомогою посилань від одного кванта до іншого, приводить до
агрегатів пам'яті зв'язного типу - спискових структур.
Спискова структура - це сукупність взаємозв'язаних ланок,
кожна з яких складається з двох полів: поля даних та поля зв'язку.
У полі зв'язку містяться адреси ланок, які визначають операцію
прямування на структурі. ГІоле даних призначено для збереження
елементів інформації або вказівника на інформацію.
Зв'язані структури класифікують за кількістю елементів у полі
зв'язку та за напрямом зв'язку. Іноді ще використовують фіксатори-
вказівники на певні ланки структури, які дають змогу реалізувати
прямий доступ до цих ланок. За кількістю елементів у полі зв'язку
спискові структури можна поділити на одноелементні та
багатоелем ентні.
Спискові структури 17
Одноелементні списки інакше називають лінійними або
ланцюговими. їх характеризує наявність єдиного зв'язку — з
наступною ланкою. Схематично ланцюгові списки зображають так:

Кожна ланка містить поле даних і поле зв'язку. Можливі ланки


з двох вказівників.
Загальний обсяг пам'яті, потрібної для п-елементного списку
можна обчислити за формулою
р = n*d + n*z, де d — довжина поля даних, z - довжина поля
зв'язку.

Багатоелементні з довільною кількістю елементів


Коли поле зв'язку кожен раз містить різну кількість елементів,
то треба або зазначати їхню кількість, або записувати спеціальну
ознаку кінця поля зв'язку. Розглянемо різні варіанти таких списків.
1. Список із заданиям кількості елементів (див. рис.З).
2. Список з ознакою кінця ланки (див. рис. 4).
Обсяг пам'яті, яку займають ці структури, майже однаковий
(можливо рівний, якщо ціле число займає стільки ж пам'яті, скільки
вказівник). Але недоліком такого зображення є практична складність
реалізації обробки таких списків.
Обсяг пам'яті можна визначити за формулою:

де d - довжина поля даних, я, - кількість зв'язків, v, - довжина поля


кількості елементів або ознаки кінця.
Тому можливе використання списків з фіксованою кількістю
елементів у полі зв'язку (див. рис. 5).
Недоліком такого зображення є непродуктивне використання
пам'яті. Обсяг пам'яті для такого варіанта списку обчислюють за
формулою р = n*(d + k*z).
18 2. С Т Р У К Т У Р И З Б Е Р І Г А Н Н Я ДАНИХ

Беручи до уваги згадані недоліки, багатозв'язні списки


використовують рідко.
Кожен список має один фіксатор — вказівник початку, який дає
змогу проходити по пам'яті в одному напрямі, йдучи за вказівниками.
Повертатись назад за ними не можна. Якщо ж це все-таки потрібно
зробити, то треба або вводити додаткові вказівники, щоб орієнтація спис-
ку стала двосторонньою, або використовувати додаткову структуру - стек.
Розсіяні структури 19

Рис. 5
За напрямом зв'язків спискові структури поділяють на дві
групи: однонаправлсні та двонаправлені.
Лінійний список є прикладом однонаправленого списку. У
кожній ланці є явна вказівка на наступника. Якщо додати до кожної
ланки посилання на попередника, то одержимо найпростіший випадок
двонаправленого списку (див. рис. 6).

Прикладом двонаправленого списку є також кільцевий список, у


якому наступником останнього елемента є перший, а попередником
першого - останній (див. рис. 7).

Початок
списку
20 2. СТРУКТУРИ ЗБЕРІГАННЯ ДАНИХ
Основною перевагою двонаправлених списків є можливість
переміщатись по них в обох напрямах.
Можливі варіанти організації багатозв'язних двонаправлених
списків. Структури даних, які розміщені в таких списках, іноді
називають плексами.
Важливим елементом спискової структури є вказівник початку
списку. У ролі такого вказівника може буги використана окрема ланка,
яку називають початковою. Крім посилання на перший елемент,
початкова ланка може містити адресу початку вільної пам'яті,
потрібної під час роботи зі списками.
У зв'язаних аїрегатах вказівники використовують не лише для
забезпечення доступу до пам'яті, але й для з'єднання пов'язаних за
змістом елементів даних. Вони допомагають організувати зв'язки
різного характеру між одними і тими ж елементами даних.
Вказівники надають пам'яті гнучкості, необхідної для
зображення та складної логічної обробки комбінаторних об'єктів.

Розсіяні структури
Третій тип структур зберігання даних - розсіяні структури -
виникає у результаті використання спеціальних механізмів розташу-
вання квантів у базовій пам'яті. Зазвичай це пов'язано з моделю-
ванням асоціативної пам'яті, яка на відміну від традиційної, адресу-
ється не за фізичним розташуванням даних, а за вмістом ключового
поля даних. Ключ, як послідовність бітів, інтерпретують не лише як
частину елемента даних, а й як число, над яким виконують
арифметичні дії, зазначені у функції розстановки (її називають також
функцією хешування, розсіювання, адресною функцією). У результаті
обчислень визначають відносну позицію елемента з заданим ключем у
блоці пам'яті, відведеному під агрегат, забезпечуючи швидкий пошук
даних із заданим вмістом.
Функція розстановки може бути задана таблицею, яка ставить у
відповідність ключам даних фізичні адреси. Організацію асоціативної
пам'яті за допомогою функції розстановки широко використовують у
трансляторах, системах інформаційного пошуку, банках даних.
До розсіяного збереження призводить також організація пам'яті
з використанням адресної арифметики. У такому випадку довільні
відношення, які можна зобразити у цілочисловій арифметиці,
Розсіяні структури 21
розуміють як відношення між позиціями квантів, які розставлені
відповідно до цих відношеннь. Такий спосіб особливо зручний для
збереження комбінаторних об'єктів регулярної структури.
Принципи послідовного, зв'язаного і розсіяного зберігання
даних можуть використовуватися разом, утворюючи структури
зберігання даних гібридного типу.
3. АБСТРАКТНІ СТРУКТУРИ ДАНИХ ТА ЇХНЄ
ЗОБРАЖЕННЯ СТРУКТУРАМИ ЗБЕРІГАННЯ ДАНИХ
Абстрактні структури даних - це організовані певним чином
сукупності даних. Кожну структуру визначають правила і обмеження,
за якими визначено зв'язки між різними частинами даних.
Важливим понятгям для визначення прямого доступу до
окремих елементів даних є поняття індексації.
Множину X називають індексованою, якщо задане її
відображення у натуральний ряд чисел, тобто

Індексація дає змогу посилатися на окремі елементи, задаючи


їхні номери. Її можна використовувати двома способами:
- перебирати всі елементи один за одним;
- вибирати елементи за індексами у довільному порядку.
Структури даних будують з окремих елементів з використанням
певних методів структуризації. Очевидно, що мова програмування,
визначаючи базові типи, повинна володіти також декількома методами
структуризації. Вони можуть бути еквівалентними у математичному
сенсі і їх розрізняють лише операціями побудови їхніх значень та
вибору компонент цих значень.
Крім елементів даних, кожна мова визначає деякі прості, стан-
дартні (атомарні) операції та методи структуризації, які допомагають
описувати довільні дії в термінах простих операцій.
-Найважливішими операціями є:
- порівняння (перевірка рівності) ;
- присвоєння (визначення рівності).
Крім цих, є ще один клас основних, неявно визначених операцій
- операцій перетворення типів. Ці операції змінюють одні типи даних
на інші. Особливо важливими є вони для складних типів даних або
структур.
Розглядаючи кожну структуру, доцільно:
- дати визначення абстрактної структури даних (АСД) та
зазначити її особливості;
Розсіяні структури 23
- визначити базові операції над АСД;
- розглянути можливі варіанти відображення АСД у структури
зберігання даних (СЗД);
- розглянути реалізацію базових операцій для різних форм СЗД;
- розглянути можливість реалізації СЗД і базових операцій над
АСД у конкретній мові програмування.
4. МАСИВИ
Масив - це, напевно, найвідоміша структура даних, оскільки у
багатьох мовах це структура, яка існує в явному вигляді. Масив це
регулярна структура; всі його компоненти належать до одного гину,
який називають базовим.
Масив - це структура з прямим доступом; усі його компоненти
можна довільно вибрати і вони однаково доступні.
Масив - це набір елементів, з кожним з яких пов'язана
послідовність цілих чисел, які називаються індексами. Індекси
однозначно визначають місце елемента у масиві і забезпечую'!'і.
прямий доступ до нього.
Кількість індексів визначає розмірність масиву. Кожний
індекс має певно визначений діапазон зміни. Кількість елементів у
масиві визначає його розмір. Розмір та розмірність масиву фіксовані
для конкретного масиву.
Локалізувати елемент у масиві - означає зазначити місце його
розташування, визначити його індекси.
Масив називають прямокутним , якщо кожен з його індексів
змінюється у своєму діапазоні з постійним кроком від нижнього
значення до верхнього, тобто належить лінійній ортохональній
послідовності. Наприклад, можливий опис М: array [1..3,0..7,5..9] of
Т визначає прямокутний тривимірний масив з розмірами 3, 8 і 5
елементів по кожному виміру.
Найпростіший масив - одновимірний, також його називають
вектором. Наприклад, А={а і ,...,а n } - одновимірний масив з n -
елементів. Доступ до і-го елемента відбувається за іменем А[і].
Двовимірний масив - матриця, де кожний елемент B[i,j]
належить одночасно двом лінійним ортогональним послідовностям:
і-му рядку та j-му стовпцю.
Прямокутні масиви, напевно, найчастіше використовують для
реалізації обробки однотипних об'єктів.
Найпоширенішими операціями над масивами є:
1) пошук елемента за індексом (чи сукупністю індексів);
Розсіяні структури - 25
2) локалізація елемента у масиві;
3) зміна значення елемента у масиві (запис елемента);
4) сортування масиву;
5) копіювання масиву.
Відповідно до потреб під час використання масивів можуть бути
введені й інші операції. Проте треба відзначити, що новий елемент не
можна ні включити в масив, ні видалити з нього.
Універсальні мови програмування дають змогу працювати лише
з масивами ортогональної структури. Але масиви можуть бути
різними.
З усіх виділених операцій найцікавішою є операція локалізації
елемента - визначення розміщення по відношенню до першого
елемента масиву. Крім того, ця операція лежить в основі
відображення масиву у послідовну структуру зберігання даних.
Для одновимірного масиву локалізація не представляє жодних
труднощів, оскільки розташування елемента однозначно
визначається його номером у послідовності

де d - довжина окремого елемента масиву, А - адреса початку


масиву.
26' 4. МАСИНИ

зберіганні n-мірного масиву "по стовпцях".


Серед матриць часто бувають спеціальні. Наприклад, є матриці
трикутні, симетричні, діагональні, квазідіагональні. Матриця
називається трикутною, якщо всі її елементи, розташовані вище
головної діагоналі, мають однакове значення (Вij = v, якщо j>i).
Матриця симетрична, якщо В ij = В ji . Матриця називається
діагональною (чи квазідіагональною ширини d), якщо її елементи для
яких і != j (чи abs(i-j)>d), мають однакове значення. Трикутні матриці
називають тетраедральними. Крім того, є ще так звані розріджені
матриці, у яких більшість елементів є нульовими (чи такими, що
дорівнюють деякому одному значенню).

Відображення масивів у структури зберігання


Найпростіше відображають масиви у вектори (послідовні
структури зберігання) з врахуванням локалізації. У багатьох мовах
програмування є відповідні структури даних.
Цікавішими є відображення спеціальних масивів. Наприклад,
для представлення трикутної матриці

можна використа ти вектор, де елементи розташовано так

і адреса елемента a(j обчислюється за формулою:

Якщо елементи багатовимірних масивів розміщені у п о с л і д о в н і й


пам'яті, то щоб обчислити їхні адреси треба виконувати бах аго
обчислень, зокрема для п-вимірнохо масиву потрібно зробити (n-1)
множення. Проте с спеціальний метод зберігання, який не потребує
виконання операцій множення, а використовує поєднання послідов-
ного та спискового відображень у пам'ять. Це - метод Айліфа [11].
0 0 6 0 9
Можна ці три вектори подати як один, зазначивши значення
ненульового елемента і його індексів. Для нашого прикладу це вектор
з такими значеннями:
6 1 3 9 1 5 2 2 1 7 2 4 8 2 5 12 4 3 3 5 4.
Доволі часто розріджені матриці зберігають у вигляді
зв'язаних структур. Є спеціальні алгоритми введення та виконання
основних арифметичних операцій з такими матрицями.
Можливі різні варіанти спискових представлень. Зокрема можна
зображати розріджену матрицю у вигляді ланцюгового списку, де
кожна ланка містить інформацію про ненульовий елемент (його
значення та індекси) та вказівник на наступний такий самий елемент
матриці.
Проте можна використати двозв'язний список для зображення
розрідженої матриці. Д. Кнут [4] розглядає такий спосіб-. Для
зображення кожного ненульового значення створюють елемент
списку, який охоплює індекси та зв'язки з наступними ненульовими
елементами відповідно зліва у рядку чи зверху у стовпці. Ці
елементи об'єднують у циклічні списки. Для кожного рядка 1 стовпця
є спеціальні головні вузли списків.
Структура окремої ланки списку така
Відображення масивів у структури зберігання 29
5. ТАБЛИЦІ
Абстрактними структурами даних, які допускають прямий
доступ до елементів, але не з допомогою індекса, є таблиці. Таблиці
відносяться до нелінійних структур, оскільки між їхніми елементами t
відношення складніші, ніж лінійне відношення фізичного сусідства.
Широко використовують у сучасному світі довідково
інформаційне обслуговування, яке містить накопичення даних,
прийом і видачу даних на вимогу, причому видача потрібної
інформації повинна відбуватися незалежно від того, які дані
видавалися раніше. В таких задачах використовують структуру даних,
яку називають таблицею.
Варто зазначити, що таблиці є найвикористовуванішою
структурою даних у трансляторах. До 50% часу роботи транслятора
йде на формування таблиць і пошук у них.
Таблиця - це набір елементів, з кожним з яких пов'язано
ознаку, названу ключем, яка одночасно визначає позицію
елемента і забезпечує прямий доступ до нього.
У ролі ключа може бути використано ім'я (ідентифікатор)
об'єкта, введеного у таблицю. Ключ може складатись з кількох
атрибутів. Він повинен мати такі властивості:
1) однозначна ідентифікація елемента;
2) відсутність надмірності - ніякий атрибут не можна вилучити з
ключа, щоб при цьому не порушилась однозначність ідентифікації.
Для одного елемента може буги декілька наборів атрибутів, які
задовільняють ці дві властивості. Ключ, який використовуватиметься
для ідентифікації записів, називають первинним. Атрибути для нього
треба вибирати так, щоб для них був відомий діапазон значень, а
кількість їх була якнайменшою.
Узагальненням поняття таблиці є поняття файла.
Таблиці всіх тинів мають таку структуру (див. рис. 10).
Відображення масивів у структури зберігання 31

Якщо кожний елемент займає k квантів і потрібно зберігати N


елементів, то необхідно k*N квантів пам'яті. Розташувати інформацію
можна двома способами:
1. Кожний елемент
помістити в k послідовних
квантів і мати таблицю з k*N
квантів.
2. Мати k таблиць
Т1,Т2,...Тk кожна з N квантів.
Весь і-тий елемент при цьому
буде у квантах ТІi ,..., Ткі.
Якщо таблиця є
таблицею символів, то
замість ідентифікатора в
таблицю можна помістити
вказівник на нього. Це
зберігає фіксований розмір
аргумента (див. рис. 11).

Значення
(властивості ідентифікатора)

Рис. 11
Основна операція в таблиці - пошук. Вона полягає в тому, щоб
за заданим ключем визначити адресу зберігання запису, якщо він є.
Важливо вибрати таку організацію, яка допускала б ефективний
пошук. Для оцінки цієї операції в термінах очікуваного числа
порівнянь важливим є коефіцієнт заповнення таблиці lf=n/N, де n -
Відображення масивів у структури зберігання 33

Тобто у таблиці, яка містить п елементів, пыд час пошуку буде


приблизно п/2 порівнянь. Якщо п більше за 20, то такий спосіб
організації таблиці неефективний.
Невпорядковані таблиці неефективні для пошуку, але щоб
додати новий елемент до них, потрібний мінімальний час. Тому іноді
їх використовують як тимчасові. Початково таблиця формується як
невпорядкована, а пізніше її перетворюють у таблицю потрібної
будови.
Невпорядковані таблиці можна відображати у послідовну
(векторну) чи зв'язану (спискову) пам'ять.
Відображаючи такі таблиці у векторну пам'ять, перші позиції
використовують для зазначення максимально допустимого номера
елемента таблиці і номепа пер-
шого вільного елемента векто-
ра.
Як приклад розглянемо
таблицю ідентифікаторів (див.
рис. 12).
Тіло має інформацію про
тип, довжину значення і адресу
в пам'яті кожного ідентифіка-
тора.
Векторне зображення
таблиці може бути, наприклад,
таким (довжина одного елемен-
та таблиці дорівнює 10 кван-
там):
Відображення масивів у структури зберігання 35

Рис. 15
Впорядкована таблиця може бути одержана з невпорядкованої
за допомогою будь-якого алгоритму сортування після її остаточного
заповнення. Впорядкування таблиці вимагає додаткових затрат часу,
тому впорядковані використовуються як постійні таблиці, наприклад,
у трансляторах. Можливий варіант побудови впорядкованої таблиці,
коли записи додають у табли-
цю в певному порядку, але це
вимагає додаткових затрат.
Ще один спосіб пред-
ставлення таблиці в пам'яті - у
вигляді дерева. Це різновид
спискового представлення.
Кожний елемент таблиці, орга-
нізованої у вигляді двійкового
дерева, супроводжується двома
Рис. 16
вказівниками на записи відпо-
35
5. ТАБЛИЦІ

Цю ж таблицю можна відобразити у спискову пам'ять (див. рис. 13).


Ланцюговий список повинен починатись із заголовної ланки. Заголовна
ланка повинна містити, крім вказівника на початок таблиці, вказівник на
початок списку вільної пам'яті.

Рис. 14
Пошук може бути ефективнішим, якщо таблиця впорядкована.
Якщо елементами таблиці є рядки літер, то найприроднішим є
лексикографічний порядок. Ефективним методом пошуку у
впорядкованих таблицях є двійковий пошук. Суть такого пошуку -
символ S, який треба знайти, порівнюють з елементом, що має
номер (n+1)/2 і розташований у середині таблиці. Якщо цей елемент
не є шуканим, то треба переглянути лише блок від 1 до (n+1)/2-1
або від (n+1)/2+1 до n, залежно від того чи елемент S менший чи
більший від елемента, з яким його порівнювали. Потім цей процес
продовжують, але кожен раз над блоком меншого розміру. Оскільки
число елементів для кожного перегляду скорочується наполовину, то
максимальне число порівнянь 1 + log 2 n.
1
Таблиці з прямим доступом 37
Оцінюючи складність доступу до окремого запису у таблиці, як
необхідну для цього кількість порівнянь, для таблиць з
обчислюваними входами ми отримаємо метод з мінімальною
складністю, яка дорівнює 1. Звичайно, треба затратити деякий час на
обчислення адресної функції, але для простих функцій доступ до
таблиці з обчислюваними входами має значну перевагу за часом.
Мірою використання пам'яті у таких таблицях є коефіцієнт
заповнення If, який визначають як відношення кількості записів п до
числа місць у таблиці N.

Таблиці з прямим доступом


Таблиця, функція розстановки для якої задає взаємно-
однозначну відповідність між ключами й адресами, називається
таблицею з прямим або безпосереднім доступом. Середня довжина
пошуку для таких таблиць мінімальна і дорівнює 1.
За заданим ключем обчислюється адреса з діапазону від 0 до N-
1, де N - кількість елементів у таблиці.
Розглянемо приклад таблиці з прямим доступом. Нехай у
таблицю потрібно помістити записи, ключі яких складаються з двох
латинських літер. Як коди літер використовують номери їхніх місць в
алфавіті, а саме А - 0, В - 1, ..., Z - 25. Функція розстановки може
бути, наприклад, такою:
f(sls2) = ksl*26 + ks2,
де ksl та ks2 - коди першої та другої літер.
За такою функцією адресації буде всього 26*26=676 можливих
адрес. Тобто, таблиця складатиметься з 676 елементів. У таку таблицю
можуть бути занесені всі елементи з ключами з двох літер. Не треба
зберігати ключі, оскільки вони однозначно відповідні з адресами і
можуть бути відновлені за ними.
Наприклад, для деяких ключів адреси будуть такими:

Перемішані таблиці
Природним розвитком таблиць з прямим доступом є таблиці,
обчислення адреси для яких відбувається не в повному проміжку від 0
1. Викликаючи програму, прийняти, що цілочислова змінна R
дорівнює 1.
2. Обчислювати кожне pj так:
а) визначити R =R*5;
б) виділити молодші q+2 розряди R і помістити результат у R;
в) взяти величину з R, зсунути її праворуч на 2 розряди і
результат назвати р j.
Важлива властивість цього методу, який попереджає
згромадження елементів в одній частині таблиці, полягає в тому, що
всі числа від pj до pj+q різні. (1/lf) * ln (1/(1-lf)(1/lf) )
Очікувану кількість порівнянь дає формула: Е = (l/lf)*ln(l-lf).
Рехешування іноді називають відкритою адресацією.
Розглянемо приклад побудови таблиці, в яку заноситимемо
прізвища студентів групи з 25 осіб. За хеш-функцію візьмемо
порядковий номер в алфавіті першої букви прізвища. Це дасть змогу
побудувати таблицю з 33 елементів (за кількістю букв в алфавіті). Для
розв'язання колізії скористаємось випадковим рехешуванням,
оскільки величина таблиці є степенем числа 2 (N = 33; q = 5).
Таблиця складатиметься з двох частин: таблиці означень (в яку
заноситимемо посилання на відповідні імена) та таблиці імен. У
таблиці імен будуть знаходитись ключі (імена) і значення, які
міститимуть деяку додаткову інформацію, пов'язану з відповідним
іменем. Для посилання на таблицю імен будуть використані значення
індексів з цієї таблиці.
Нехай треба помістити в таблицю такі прізвища:
Будна
Тройська
Годій
Гойцак
Гупаловська
38 5. ТАБЛИЦІ
до N-1, а в деякому обмеженому діапазоні. Цей метод відомий як
метод перемішаних адрес, або перемішаної пам'яті.
У простій таблиці з прямим доступом є N можливих ключів і
для кожного з них можна згенерувати унікальну адресу з діапазону від
0 до N-1. Якщо насправді є лише К ключів і K<<N, то не
використовується великий обсяг пам'яті. У такому випадку треба
обчислювати адресу в меншому діапазоні, наприклад, від 0 до р-1,
де К< р<<N. Це означає, що різні ключі можуть привести до однієї і
тієї ж адреси. Такий випадок називають колізією. Для розв'язання
колізії є два способи: рехешування та метод ланцюжків (або
використання області переповнення).
Суть рехешування. Нехай обчислено значення хеш-функції
для ключа S і це значення - h. При спробі занести елемент з цим
ключем у таблицю виявляється, що інший елемент зайняв місце за
адресою h. Тоді порівнюємо S з елементом поля (h + р1) mod N (де N
- довжина таблиці) для деякого значення р{. Якщо знову виникає
колізія, то порівнюємо S з елементом (h + р 2 ) mod N. Цей процес
продовжують доти, доки не буде знайдено деякий елемент
(h + pj) mod N, який або порожній, або містить S, або знову є
елементом h. В останньому випадку робота алгоритму припиняється,
оскільки таблиця повна.
Отже, якщо виникло і колізій, буде виконано j+1 порівняння з
елементами h j = ( h + p j ) m o d N . Величину p j треба вибирати так,
щоб очікуване число порівнянь Е було невеликим і щоб можна було
розглянути якнайбільше елементів. В ідеальному випадку pj повинні
охоплювати цілі числа 0,1,...,N-1.
Рехешування зазвичай пов'язують з терміном розсіяної
пам'яті, оскільки заповнені позиції виявляються розсіяними по всій
таблиці.
Тип рехешування визначається тим, як вибирають значення
Рi . Найпоширенішими є:
40 5. ТАБЛИЦІ
Дзіковська
Ці прізвища разом з інформацією, яку ми не описуватимемо,
будуть у таблиці імен, а в таблицю означень буде занесено вказівки па
них.
Для кожного імені, яке заносимо в таблицю, обчислюємо хеш-
функцію та (у разі потреби усунення колізії) значення р j .
Обчислимо хеш-функцію для першого прізвища:
h(Будна) = 2. Оскільки таблиця порожня, то з занесенням у неї
цього елемента немає жодних проблем.
Так само без проблем буде занесено у таблицю і друге прізвище,
бо h(Гронська) = 4.

Таблиця означень

0 12 24
1 13 25
2 100 14 26
3 300 15 27
4 150 16 28
5 200 17 29
6 350 18 30
7 19 31
8 20
9 21
10 250 22
11 23

Таблиця імен

<адреса> <ключ> <додаткова


інформація>
100 Будна
150 Гронська
Тройська
200 Годій
250 Гойцак
300 Гупаловська
350 Дзіковська
Перемішані таблиці 41

Спроба занести у таблицю прізвище Годій спричинить колізію,


бо h(Годій) = 4. Обчислимо р 1 , використавши алгоритм випадкового
рехешування: R = 1; R = 5 ; 0000101; р1= 1. (h + р 1 )
mod 33 = 5 і новий елемент буде занесено за адресою 5.
Так само далі.
Ь(Гойцак) = 4 - колізія. Обчислюємо = 1; ( h + р1) mod 33 = 5
знову колізія і тому обчислюємо р2: R = 25 ; 0011001 ; р2 = 6; ( h +
+ р 2 ) mod 33 = 10 тому прізвище Гойцак буде занесено за адресою 10.
h(Гупалонська) = 4 - колізія. Обчислюємо р ] - 1; ( h + р1)
mod 33 = 5 знову колізія і тому обчислюємо р2: R = 25 ; 0011001 ; р2-
6; (h + р 2 ) mod 33 = 10 знову колізія і тому обчислюємор 3 : R=125;
01111101 ; р 3 = 31; ( h + р 3 ) mod 33 =3 тому прізвище Гупаловська
буде занесено за адресою 3.
h(Дзіковська) = 5 - колізія. Обчислюємо р1 = 1; ( h + р1) mod
33 = 6 і тому прізвище Дзіковська буде занесено за адресою 6.

Інший спосіб розв'язання колізії передбачає наявність, крім


основної, додаткової таблиці, куди поміщають записи, які вступили у
колізію. Зберігання записів у додатковій таблиці організовують по-
різному: наприклад, в ній розташовують усі записи послідовно, а це
означає, що для пошуку у додатковій таблиці застосовують
послідовний перегляд. Пошук у таблиці може бути пришвидшений,
якщо у кожній позиції основної і додаткової таблиць створити
додаткове поле для зберігання посилання на записи, які вступили у
колізію. Такі таблиці називають таблицями з ланцюжками для
розв'язання колізії.
Метод ланцюжків використовує хеш-таблицю, елементами якої
є вказівники з порожнім початковим значенням, та таблицю елементів.
Таблиця спочатку порожня і вказівник р, який показує на поточне
положення останнього елемента в таблиці, встановлено на елемент
перед таблицею. Елементи таблиці мають додаткове поле, яке може
містити порожній вказівник або адресу іншого елемента таблиці.
Хеш-функція, застосована до ключа, дає місце в хеш-таблиці, де
розташовано вказівник, який або порожній, або вказує на перший
42 5. ТАБЛИЦІ
елемент таблиці елементів з даним значенням хеш-функції. Поле і
адреси кожного елемента використовують для того, щоб зв'язати у
ланцюжок елементи, для яких хешування ключа дас одне і те ж
значення.
Після того, як обчислено значення хеш-функції, виконують
таке:
1) змінюють значення вказівника р у таблиці елементів;
2) значення елемента заносять у позицію, визначену цим вказів-
ником;
3) значення р заносять у хеш-таблицю на місце, визначене хеш-
фукцією.
За цим алгоритмом заносять ті елементи, які мають різні значення
хеш-функції. Якщо ж виникає колізія, то змінюють поле адреси у
таблиці елементів.
Розглянемо як будуть виглядати обидві таблиці після
занесення в них імен В1, А, А2, С, В2 (див. рис. 16). За хеш-
функцію візьмемо номер в алфавіті першої літери імені.
Хеш-таблиці Таблиця елементів

Рис. 16

Середній час пошуку в таблиці, функція розстановки якої


рівномірно розподіляє записи у позиціях таблиці, з розв'язанням
колізії за допомогою ланцюжків оцінюється величиною l+lf/2, тобто
залежить від густоти заповнення таблиці.
Функція розстановки (функція хешування) 43
Функція розстановки ( функція хешування )
Вибираючи хеш-функцію, ми зазвичай не знасмо, які саме
будуть ключі. Але варто створювати хеш-функцію у певному сенсі
„випадковою", яка добре „розсіває" значення по елементах таблиці.
Зрозуміло, що „випадкова" функція повинна бути все ж
детермінованою у тому сенсі, що під час повторних викликів з одним і
тим самим аргументом вона повинна повертати одне і те ж хеш-
значення.
Функцію розстановки вважають "доброю", якщо вона забезпечує
рівномірний розподіл записів у таблиці: для чергового ключа всі ш
хеш-значень повинні бути рівноймовірні. Щоб це припущення мало
сенс, зафіксуємо розподіл ймовірностей Р на множині можливих
ключів U. Припустимо, що ключі з и = {0,1,...,N - 1} вибирають
незалежно один від одного і кожний розподілений з ймовірністю Р.
Тоді рівномірне хешування означає, що

На жаль, розі годі л Р переважно невідомий і перевірити цю


формулу неможливо. І ключі не завжди варто вважати незалежними.
На практиці при виборі хеш-функції користуються різними
евристиками, основаними на специфіці задачі. Треба врахувати, що
час обчислення функції розстановки визначає середній час пошуку в
таблиці, тому її треба задавати доволі простою обчислювальною
процедурою.
Відомо декілька методів побудови функції розстановки. Вони
зазвичай ґрунтуються на тому, що ключ задається в комп'ютері
деяким цифровим кодом, який допускає прості арифметичні та
логічні перетворення.
Передбачають, що область визначення хеш-функції - множина
цілих невід'ємних чисел. Якщо ключі не є такими, їм можна надати
такого вигляду. Наприклад, послідовності символів можна інтерпре-
тувати як числа, записані у системі числення з основою, яка підходить
для зображення числа. Наприклад, ідентифікатор pt - це пара чисел
(112, 116), які є ASCII кодами. Цю пару можна розглянути як
зображення числа в системі з основою 128, і тоді цьому
ідентифікаторові буде відповідати числовий код 112*128+116=14452.
44 5. ТАБЛИЦІ
Якщо символ S, який є аргументом хешування, займає більше
оДйого машинного слова, то на першому кроці хешування з S можна
сформувати одне машинне слово S'. Здебільшого S' обчислюють
сумуванням усіх слів за допомогою звичайного додавання або за
допомогою порозрядного додавання за модулем 2.
На другому кроці з S' обчислюють результуючий індекс,
причому це можна зробити кількома способами, наприклад,
помножити S' на себе і використати q середніх бітів за значення
функції хешування (якщо таблиця має 2q елементів). Оскільки q
середніх бітів залежать від кожного біта S', цей метод дає добрі
результати. Детальніше з цими питаннями можна ознайомитися у
працях Д. Гріса [2], Т. Кормена [7] та Ж. Трамбле [17].

Напевно найпоширенішою є функція хешування, що


ґрунтується на методі ділення. За цією функцією кожному ключу k
ставлять у відповідність залишок від ділення k на N, де N - кількість
можливих хеш-значень (розмір таблиці): h(x) = х mod N.
Наприклад, при N=12, k=100, хеш-функція буде h(k)=4.
ІІри відображенні ключів у адреси методом ділення зберігається
рівномірність розподілу, яка є на множині ключів. Ключі з близькими
значеннями відображаються в унікальні адреси. Якщо два чи більше
згромадження ключів відображаються в ті самі адреси, то збереження
рівномірності буде недоліком. Наприклад, ири дільнику 101 ключі
2000, 2001, ..., 2017 відображаються в адреси 79, 80, 82, ..., 99. А
ключі 3310, 3311, ..., 3324 - у адреси 79, 80, 82, ..., 92, 93. Тому
відбудеться багато колізій. Причина у тому, що ключі цих двох груп
однакові за модулем 101.
Звідси видно, що деяких значень N треба уникати. Наприклад,
якщо N = 2Р, то h(k) - це просто р молодших розрядів ключа k. Якщо
немає впевненості, що всі комбінації молодших бітів ключа будуть
перетинатись з однаковою частотою, то степінь двійки за N не
вибирають. Так само недобре вибирати за розмір таблиці степінь 10,
якщо ключі є десятковими значеннями.
Якіцо ключі є числа в системі з основою 2Р, то погано
визначати N як значення 2Р — 1, оскільки при цьому однакове хеш-
значення мають ключі, які відрізняються лише перестановкою цифр з
системи " 2 Р "
Функція розстановки (функція хешування) 45
Якщо за модулем d збігається багато ключів, а N і d не є взаємно
простими числами, то використання N як дільника може призвести до
низької ефективності хешування діленням. Якщо ж N і d є взаємно
простими числами, то звичайно ключі не збігаються за модулем N, і
тому за дільник треба вибирати просте число. Особливо треба
уникати парних дільників. Як показують дослідження, добрі
результати отримують при простому дільнику, далекому від степеня
двійки.
46 5. ТАБЛИЦІ

При хешуванні методом середини квадрата ключ


множиться сам на себе, а адресу одержують відтинанням бітів або
цифр від обох кінців добутку, яке виконують доти, доки число бітів чи
цифр, які залишились, не стане рівним потрібній довжині адреси. У
всіх отримуваних добутках повинні використовуватись ті самі позиції.
Наприклад, нехай ключ тризначний 738, його квадрат
544644. Якщо потрібна двоцифрова адреса, то за її значення можна
взяти 3 - 4 розряди результату, тобто 46.

У методі згортання ключ розбивають на частини, кожна з


яких має довжину, яка дорівнює довжині потрібної адреси (крім
останньої). Щоб сформувати адресу, частини додають, при цьому
ігнорують перенесення у старшому розряді. Якщо ключі зображені у
двійковому коді, то замість додавання можна використати операцію
виключного АБО (XOR).
Є різні варіації цього методу. Згортання є функцією
хешування, зручною для стискання багатослівних ключів і наступного
переходу до інших функцій хешування.
Наприклад, нехай ключ є 187249653, а довжина адреси З
цифри. Найпростіше згортання передбачає таке підсумовування
187+249+653 і дає адресу 89.

У методі граничного згортання інверсують цифри у крайніх


частинах ключа, і отже, у нашому прикладі будуть додані числа 781 +
+ 249 + 356 і отримана адреса 386.

Перетворення системи числення є методом хешування, в


якому робиться спроба одержати випадковий розподіл ключів за
адресами в таблиці. Ключ, поданий у системі числення q (q переважно
2 або 10), розглядають як число в системі числення р (q<p), причому р
і q - взаємно прості. Це число з системи числення р перетворюють у
систему q і адресу формують шляхом вибору правих цифр (чи бітів)
нового числа або застосовують метод ділення.
Наприклад, ключ (5403) 10 розглядають як (5403) 11 і перетворю-
ють у десяткову систему (5403)11 = 5 * 1 1 3 + 4*11 2 + 0*11 + 3 = 7142.
Якщо нас цікавить двоцифрова адреса, то це дві праві цифри, тобто 42.
6. РЯДКИ
Лінійна структура, доступ до елементів якої не змінює її, - це
рядок. Для визначення рядків та операцій над ними треба визначити
поняття абстрактного алфавіту та операції конкатенації.
Алфавітом V називають скінчену непорожню множину
символів. Наприклад, Vl={a,b,...,z} - алфавіт латинських букв; V2 =
{0,1} - двійковий алфавіт.
Конкатенацією називають операцію приєднання символів.
Наприклад, у результаті конкатенації символів 'а' та V буде отримано
послідовність 'ab'. Операція конкатенації не комутативна.
Рядком (ланцюжком) над алфавітом V називається або
символ з V, або послідовність символів, яку одержали у результаті
конкатенації елементів з алфавіту V.
Якщо V={ 1,2,3}, то ланцюжками над цим алфавітом будуть,
наприклад, рядки 'Г,'2','3','12','23'.
Важливою характеристикою рядка є його довжина, яку
визначають за кількістю елементів у рядку. Наприклад, довжина
рядка '123' дорівнює 3. Рядок нульової довжини називається
порожнім. Для його позначення використовують спеціальний символ
(наприклад, @ ).
Визначимо операцію ітерації над алфавітом V так:
48 - 6. РЯДКИ
Порожній рядок має властивості одиниці, тобто х@ = @х = х для
будь-якого рядка х.
Під час роботи з рядками дуже важливим є поняття підрядка.
Рядок b є підрядком рядка d, якщо є такі рядки а та с (можливо
порожні), що d = abc.
Якщо хоча б один рядок а чи с непорожній, то b називають
власним підрядком d.
Нехай d = abc, dl = agc. Кажуть, що рядок dl утворено з рядка
d операцією підстановки підрядка g замість Ь. Якщо при цьому а не
містить входження Ь, то підстановку називають канонічною
(відбувається заміна першого зліва входження підрядка у рядок).
Підстановку, при якій у рядку d підрядок b розміщений після
а, заміняється на g позначатимемо f ( d, a, b, g). Канонічну підстановку
позначатимемо fc d, a, b, g).
Наприклад,
f(ararat, аг, ага, @)=art
fc(ararat, ага, @)=rat
fc(tack, @, s)=stack.
Дві найвідоміші системи обробки рядків - це нормальні
алгоритми Маркова та граматики.
Система нормальних алгоритмів базується на операції
підстановки. Згідно з теорією алгоритмів будь-яке перетворення рядка
можна зобразити як послідовність канонічних підстановок. Загалом
будь-який алгоритм можна задавати такими послідовностями. В теорії
алгоритмів послідовність підстановок називають нормальним алго-
ритмом (алгоритмом Маркова).
У системі граматик основу становить породження або розпізна-
вання деякої підмножини з множини рядків.

Нормальні алгоритми Маркова


Систему нормальних алгоритмів А. А. Марков запропонував у
1954 р. Алгоритми Маркова - це формальна математична система.
Вони були основою для першої мови обробки рядів СОМІТ. Крім того,
є подібність між моделлю Маркова і мовою СНОБОЛ, яка з'явилась
після СОМІТ.
Загальна стратегія роботи алгоритму Маркова полягає у тому,
щоб, застосувавши декілька операцій до вхідного рядка х, перетво-
рити його у вихідний рядок у. Цей процес перетворення є звичайним у
Нормальні алгоритми Маркова 49
таких областях застосування ЕОМ, як редагування тексту або
компіляція програми.
Простою продукцією (формулою підстановки) називають
запис вигляду и -> w, де u,w - рядки в V *, причому V не містить
символів '->' та '.' . Величина и називається антицедентом, a w -
консеквентом. Формула и -> w може бути застосована до рядка Z є V*
, якщо є хоча б одне входження и в Z. Інакше вона не застосовна до
рядка Z. Якщо формула може бути застосована, то канонічне (перше
зліва) входження и в Z заміняється на w. Наприклад, якщо формула
'ва' ->'с' може бути застосована до вхідного рядка 'ававав', то в
результаті буде отримано рядок 'асава'. Водночас формула 'ваа'->'с' до
рядка 'ававав' не може бути застосована.
Марківський алгоритм містить впорядковану множину
продукцій Р1,Р2,...,Рn. Послідовність виконання алгоритму залежите
від того, чи може бути застосована до рядка чергова формула
підстановки. Виконання починається з перевірки першої продукції.
Якщо вона може бути застосована до рядка, то рядок перетворюється.
Якщо ж формула не може бути застосована (тобто в рядку не знайдено
підрядка, який можна було б замінити), то відбувається перехід до
перевірки наступної формули.
Марківський алгоритм завершується в одному з двох випадків.
1. До рядка не може бути застосована жодна з наявних
формул підстановки.
2. До рядка застосовується заключна (термінальна) формула.
Заключну підстановку позначають так:
х ->. у або х->у. , де х,у є V*
Розглянемо приклад. Нехай над словами з алфавіту \ - { а , Ь , с }
задано алгоритм з формулами підстановки
PI:'ab'-> 'b'
Р2: 'ас' ->'c'
РЗ: 'аа' -> 'а'
Цей алгоритм вилучає всі входження символа 'a' у рядку за
винятком випадку, коли 'а' знаходиться у кінці рядка.
Простежимо роботу алгоритму, якщо вхідний рядок має
вигляд 'bacaabaa'. Далі символ => буде використовуватись для того,
щоб вказати на результат перетворення, а підрядок, який підлягає
заміні, будемо підкреслювати.
50 6. РЯДКИ

Оскільки далі жодна з формул не може бути застосована, то на


цьому робота алгоритму завершується.

Формальні граматики
Доволі часто буває ситуація, коли інтсрсс викликають не всі
рядки, одержані із заданого алфавіту, а лише певна їх підмножина.
Таку иідмножину називають мовою : L с V* . Рядки, які належать
мові, називають реченнями. Речення будують на основі правил
граматики. Ці ж правила слугують для розпізнавання належності
певного рядка деякій мові.
Граматикою G називають четвірку (Vn, Vt, S, Р), де Vn -
алфавіт нетермінальних символів; Vt - алфавіт термінальних символів
(таких, які трапляються лише у правій частині правил); S - почат-
ковий символ (аксіома граматики); Р - система правил вигляду U :: =
х, де U - негермінальний символ, х - рядок з (Vn U Vt)* . Ці правила
іноді називають правилами підстановки.
Наприклад, розглянемо граматику для побудови цілих чисел
без знака.

Це приклад породжуючої граматики.


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

Операції над рядками


Треба звернуги увагу на особливості структури даних, яку
називають рядок:
- кожний елемент рядка, за винятком першого і останнього, має
свого попередника і наступника;
- кожний елемент не містить жодної інформації про інші
елементи рядка;
- доступ до кожного конкретного символа може бути
виконаний лише шляхом послідовного перегляду символів один за
одним (сканування).
Сучасні універсальні мови програмування володіють засобами
обробки рядків, які реалізують основні операції над рядками. На
основі аналізу базових засобів обробки рядків можна визначити
набір базових операцій над рядками, зокрема:
1) створити рядок;
2) виконати конкатенацію двох рядків для одержання нового;
3) знайти і замінити (якщо необхідно) заданий підрядок у рядку;
4) перевірити рядки на тотожність;
5) визначити довжину рядка.
Створення рядка передбачає не лише можливість формування
представлення рядка, але і здатність зберігати значення рядка в деякій
змінній величині чи ділянці пам'яті. Здатністю створювати рядки
повинна володіти довільна система обробки рядків.
В операції конкатенації операндами можуть бути рядкові
константи та змінні.
Під час пошуку підрядка у заданому рядку погрібно визначити
позицію підрядка, якщо цей підрядок знайдено. За результат служить
номер позиції найлівішого символа шуканого підрядка (або 0, якщо
підрядок не входить у рядок).
Операція заміни (підстановки) розбивається на кілька
простіших, таких як:
- послідовний перебір символів рядка;
- включення заданого символа у зазначене місце рядка;
- вилучення символа із зазначеного місця;
52 6. РЯДКИ
-пошук входження заданого підрядка і визначення місця
входження.
Перевірка тотожності рядків передбачає існування деякого
предиката, який отримує значення в результаті порівняння рядка з
іншим на рівність чи нерівність.
З цих примітивних будуються інші, складніші операції, такі,
наприклад, як пошук за зразком.

Відображення рядків у структури зберігання


Відображаючи рядки у структури зберігання, розглядають
випадки різних видів рядків: фіксованої, змінної та необмеженої
довжини. За структури зберігання вибирають, зазвичай, вектори і
ланцюгові списки.
Якщо рядок фіксованої довжини, то можна використати вектор
з дескриптором довжини.
Наприклад:

Інший спосіб відображення рядка у вектор може використо-


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

Ці два способи використовують для зображення рядків змінної


довжини.
Для зберігання рядків фіксованої довжини вибирають вектор
такої ж довжини, або деякої максимальної (для даної реалізації).
Гнучкішим є відображення рядків у ланцюгові списки. Приклад
ланцюгового списку:
Відображення рядків у структури зберігання 53
Розглянемо реалізацію деяких базових операцій над рядками для
різних структур зберігання.
Операція конкатенації повинна давати змогу використовувати
рядкові змінні і константи. Для векторного зображення можливі
певні варіанти реалізації цієї операції.
Якщо використовувати вектор з дескриптором довжини, то
конкатенацію можна зобразити так:
Нехай

Тоді результатом конкатенації є рядок с = а Ь.

Для вектора з обмежувачем конкатенація реалізується майже так


само.

Для спискового зображення конкатенацію можна зобразити так:


Рядок а:
54 - 6. РЯДКИ

Операція конкатенації на спиековій структурі є ефективнішою за


кількістю операцій (швидкістю), оскільки передбачає лише одну
зміну вказівника.
У випадку векторного зображення доводиться копіювати один
(якщо перша структура придатна для розміщення копії) або обидва
рядки у нову векторну структуру.
Пошук підрядка передбачає пошук за зразком. Рядок, який
відповідає шуканому підрядку, накладається на основний рядок,
починаючи з першого символа. Якщо знайдено входження підрядка,
то фіксується позиція в рядку, де починається підрядок.
Наприклад, нехай у рядку

Для списку ця операція реалізується подібно - відбувається


поелеменгне порівняння.
З погляду реалізації обидва представлення майже однакові, але
спискове проірає за затратами пам'яті.
Операція заміни підрядків передбачає необхідність виконан-
ня операцій вставки та видалення. Для реалізації цих операцій кращою
структурою зберігання є список.
54
Відображення масивів у структури зберігання 54
Справді, нехай у рядку

потрібно видалити символ Ь. Для цього треба скопіювати


частину початкового рядка, яка стоїть після символа b.

При реалізації вставки - дії аналогічні. Це за умови, що


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

Визначаючи довжииу рядка, у разі потреби підраховують


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

L
7. ДИНАМІЧНІ СТРУКТУРИ
Є три динамічні структури - стеки, черги та деки. Особливістю
всіх динамічних структур є те, що елементи перебувають у них до
першого звертання. Додавання і видалення елементів відбувається не в
довільному місці структури, а в місці, яке визначається власне
структурою даних. Будь-яке звертання до такої структури змінює її
вміст.

Стеки
Однією з найважливіших динамічних структур є стек (stack).
Стек - це впорядкована, лінійна, динамічно змінювана
послідовність елементів, для якої виконуються такі умови:
1) новий елемент приєднується завжди до одного і того ж
краю послідовності;
2) доступ до елементів відбувається завжди з того краю
послідовності, до якого елементи приєднуються;
3) елемент зберігається в послідовності до моменту його
виклику.
Стек називають також послідовністю типу LIFO (last in first out).
Схематично стек можна зобоазити так:

Елемент, доступний у стеку, називається вершиною стека.


Перший елемент у стеку називають дном або низом стека. Зазвичай на
дно поміщають певний елемент, який враховують алгоритми обробки
стека. Нехай це буде символ #.
Розрізняють стеки з проштовхуванням (push down) та без
проштовхування. У стеках першого типу закріплена вершина (див.
рис. 17). У стеках без проштовхування закріплене дно, а вершина
рухома (див. рис. 18).
Над стеком визначено дві операції:
- включення (занесення) елемента в стек;
Стеки 57
- вилучення елемента зі стека.
Крім того, для реалізації алгоритмів над стеками необхідне
існування предикату перевірки стека на пустоту.

Рис. 17 Рис. 18

Операцію включення елемента в стек іноді називають


"проштовхуванням", а операцію виключення - "виштовхуванням".
Основною перевагою стека над іншими способами організації
даних є тс, що у ньому не потрібна адресація елементів. Тому стек
може бути базовою структурою у безадресних машинах.
Стеки використовують на різних етапах трансляції, зокрема для
синтаксичного аналізу, побудови внутрішнього представлення про-
грам, розподілу пам'яті, реалізації рекурсії.
До рекурсивної процедури (функції) можна звертатись як з неї
самої, так і ззовні, і тому для забезпечення правильного функціо-
нування вона повинна зберігати адреси повернення в такому порядку,
щоб повернення з неї проводилось у потрібне місце, наступне за
оператором виклику. Процедура повинна також зберігати формальні
параметри, локальні змінні під час входу і відновлювати всі ці
параметри та змінні під час виходу з процедури.
Як приклад використання стека розглянемо один з алгоритмів
побудови внутрішнього представлення арифметичних виразів, а також
алгоритм обчислення значення таких виразів.
Звичайна форма запису виразів, у тім числі й арифметичних,
називається інфіксною, оскільки операції розташовуються між (in)
операндами. Обчислення значення виразу у такій формі передбачає
багатократний його перегляд. Наприклад, щоб обчислити значення
58 7. ДИНАМІЧНІ СТРУКТУРИ
виразу 3*(4+5) - (6-2)*4 потрібно переглянути його принаймні тричі:
за першим переглядом обчислити значення у дужках, за другим -
виконати множення і, нарешті, за третім переглядом виконати дію
віднімання.
Багатократного перегляду можна уникнути, якщо перетворити
інфіксний запис у еквівалентний бездужковий суфіксний або
префіксний запис. Суфіксний запис виразу (польський інверсний
запис) для бінарної операції мас таку структуру:
<операнд>< операнд ><операція>,
а префіксний запис (польський запис) - таку:
<операція><операнд><операндх>
Тобто, попередній вираз у суфіксній формі буде таким:
345 + *62-4*-,
а у префіксній таким:
- *3 + 45 * - 6 2 4 .
Транслятори перетворюють вирази у суфіксну форму. Для цього
використовують стек, куди заносять операції з урахуванням їхніх
пріоритетів. Для опрацювання дужок теж використовується стек.

Розподіл пріоритетів такий:


символ пріоритет
#,(,) 0
+,- 1
*,/ 2

Вхідний рядок переглядають поелементно. Залежно від


сканованого елемента можливі такі варіанти обробки:
1) якщо елемент є операндом, то занести його у результуючий рядок;
2) якщо елемент є відкриваючою дужкою "(", то занести його у стек;
3) якщо елемент є закриваючою дужкою ")", то вилучити зі стека і
занести у результуючий рядок елементи, які є над відкриваючою
дужкою. Ця дужка теж вилучається зі стека, але у результуючий
рядок не заноситься;
4) якщо елемент є операцією, то видаляти зі стека і заносити у
результуючий рядок елементи доти, доки пріоритет операції
менший чи рівний (<=), тобто не перевищує пріоритету елемента у
вершині стека. У протилежному випадку занести операцію в стек;
Стеки 59
5) якщо розглянуто весь вираз, то переписати вмістимо стека у
результуючий рядок.
Обробка одного елемента вхідного рядка передбачає виконання
одного з пунктів 1-4, після чого відбувається перехід до розгляду
наступного елемента вхідного рядка. Цей алгоритм може аналізувати
правильність побудови вхідного рядка.
Розглянемо приклад роботи алгоритму для перетворення у
бездужковий суфіксний запис виразу a*(b+c)+d*e. (тут крапка "."
завершує вираз).

Результуючий рядок буде таким: a b c + * d e * + .

Обчислення значення виразу, зображеного у суфіксній формі,


можна виконати за один перегляд виразу за таким алгоритмом (у
припущенні, що операції бінарні, інакше треба враховувати арність
операцій).
1. Знайти у виразі крайню ліву операцію. При цьому всі
операнди, які їй передують, помістити у стек.
2. Вибрати два операнди, які стоять безпосередньо ліворуч
від знайденої операції.
3. Виконати операцію.
4. Замінити операнди та операцію одержаним результатом.
Наприклад, обчислення значення виразу 3*(4+5) - (6-2)*4 ,
який у суфіксній формі виглядатиме 3 4 5 + * 6 2 - 4 * - буде
60 7. ДИНАМІЧНІ СТРУКТУРИ
проходити так (тут підкреслено операнди і операція, яка виконується
над ними на кожному кроці):

Відображення стеків у структури зберігання


Стеки можна зображати векторами і списковими структурами
зберігання. Коли вектор використовують для реалізації стека, то цей
вектор повинен бути досить великим для розміщення максимально
заповненого стека.
Якщо стек без проштовхування,
який використовують частіше, то
потрібний вказівник на вершину.
Реалізуточи стек з проштовхуванням при
фіксованій вершині, потрібний вказівник
на перший елемент стека. У ролі такого
Вершина вказівника можна використовувати
Рис. 19
індекс відповідного елемента вектора.
Схематично реалізацію стека у
вигляді вектора можна зобразити так (див. рис. 19):
Позначимо вектор для розміщення стека S, а вказівник вершини
- V. Вказівником вершини для вектора буде індекс.
Операція включення елемента у стек передбачає такі кроки.
" 1. Якщо V > n, де n - довжина вектора, відведеного під стек, то він
пере-повнений і включення
неможливе, інакше до п. 2.
Збільшити вказівник вершини (V :=
V + 1).
3. Занести новий елемент у вершину:
Вершина S[V] := X.
Рис. 20 Після виконання операції стек
буде таким (див. рис. 20).
Відображення стеків у структури зберігання 61
При реалізації вилучення необхідна перевірка стека на
наявність у ньому елементів. Таку перевірку можна реалізувати по-
різному.
Якщо на дні стека немає
спеціального символа, то стек буде
порожнім у тому випадку, коли
індекс вершини має значення менше
від індекса дна. Це схематично
можна зобразити так (див. рис. 21).

Іноді, формуючи стек,


доцільно на його дно поміщати
спеціальний символ, і тоді читання цього символа означає, що стек
порожній (під час читання його зі стеку не вилучають). Такий стек
можна зобразити так (див. рис. 22).
Операція вилучення елемента зі стека, зображеного вектором,
може бути реалізована так.
1. Перевірити, чи стек порожній.
Якщо так, то вилучення не-
можливе і тоді кінець, інакше
перейти до п. 2.
2. Вилучити елемент (наприклад,
Z := S[V]).
3. Змінити і значення вказівника
вершини (V := V - 1).
Векторне представлення компактне, але пам'ять під стек
повинна бути виділена на постійно, на весь час роботи зі стеком, і
фактично не відображає динамічного характеру роботи зі стеком.
Гнучкішою є спискова структура. Системи, які володіють засобами
обробки спискових структур зберігання даних, дають змогу
реалізувати динамічну роботу зі списками.
Стек, представлений списком, передбачає наявність вказівника
на вершину. Елемент стека містить поле даних і поле посилання.
Ланка, яка зображає дно стека, має порожнє поле посилання. Якщо
вказівник вершини порожній, то стек - порожній.
Включення елемента у стек передбачає створення нового
елемента, під'єднання його до існуючого спискового зображення і
62 7. Д И Н А М І Ч Н І С Т Р У К Т У Р И
зміну вершини стека. Операція включення може бути зображена так
(див. рис. 23):
Стек до включення

Вилучення елемента зі стека передбачає зміну вершини і


приєднання вилученого елемента до списку вільної пам'яті.
Схематично цю операцію можна зобразити так (див. рис. 24):

Черги
Черга (queue) - це лінійна динамічно змінювана послідовність
елементів, для якої виконуються такі умови:
1) новий елемент приєднується завжди до одного і того ж
краю послідовності;
2) доступ до елементів, тобто їх вилучення, відбувається
завжди з іншого краю послідовності.
Місце вилучення називають головою черги, а місце включення
— хвостом.
Отже, у структурі черги потрібні два вказівники: один -
посилання на голову послідовності для вилучення елементів, а другий
— посилання на хвіст для включення елементів у чергу. Завжди при
читанні з черги вилучається найстаріший елемент.
Чергу також називають послідовністю типу FIFO (first in first
out).
Крім класичних черг, які ще називають лінійними, черги
бувають з пріоритетом та циклічні [8].
Прикладом лінійної черги є черга завдань в операційній системі,
коли кожне чергове завдання опрацьовується лише після того, як
опрацьовано поточне.
Чергу, для якої є можливість включати або вилучати елементи з
певної позиції залежно від деяких їхніх характеристик (таких,
наприклад, як пріоритет задачі), називають пріоритетною чергою.
Прикладом пріоритетної черги може бути порядок розв'язування задач
з потоку у деяких операційних системах.
Така черга зводиться до послідовності лінійних черг. Кожна така
черга складається з елементів з однаковим пріоритетом. Елементи з
черги з нижчим пріоритетом опрацьовують лише тоді, коли черги з
вищим пріоритетом порожні. Наприклад, нехай є потік завдань з
різними пріоритетами;
Ідентифікатор завдання

Пріоритет

Черга першого пріоритету

Черга другого пріоритету


64 7. Д И Н А М І Ч Н І С Т Р У К Т У Р И

Нерга третього пріоритету

Черга другого пріоритету буде оброблятись тоді, коли черга


першого пріоритету порожня, а черга третього пріоритету — тоді, коли
порожні черги першого та другого пріоритетів. При включенні
елементи приєднують до хвоста однієї з черг згідно з пріоритетом.
Циклічна черга передбачає розташування за останнім
елементом першого (див. рис. 25).
Прикладом циклічної черги може бути черга завдань без
пріоритетів операційної системи, які вона обробляє у режимі
розділення часу. Для кожного завдання система виділяє певний
інтервал часу — квант. За
квант реалізується частина
певного завдання, і якщо
виконання завдання не
закінчене, а квант закінчився,
завдання знову поміщається
у чергу на обслуговування.
Над чергою, як і над
стеком, визначено операції
включення та вилучення
елемента та перевірка черги
на пустоту.
Рис. 25

Відображення черг у структури зберігання


Черги відображають у вектори і списки. Реалізація операцій
залежить від структури зберігання, і тому, як і у випадку стеку,
розглядатимемо операції
стосовно цих структур.
Черга, відображена у век-
тор, може бути зображена
так (див. рис. 26). Тут Р -
вказівник початку черги,
К - вказівник кінця.
Відображення черг у структури зберігання 65
Вказівники можуть бути значеннями індексів 1 < Р, К < n (де n -
кількість елементів у векторі).
Включення елемента у чергу передбачає такі кроки.
1. Перевірити можливість запису елемента у чергу (К. < n ?). Якщо
запис можливий, то перейти до п. 2, інакше повідомити про
неможливість операції.
2. Змінити вказівник хвоста черги (К := К + 1).
3. Занести елемент у чергу (СН [К] := X).
Якщо включення у чергу було першим, то необхідно, крім
зазначених дій, визначити вказівник голови черги (Р := 1).
Вилучення елемента з черги вимагає таких дій.
1. Перевірити чи черга порожня. Якщо так, то операція неможлива,
інакше перейти до п. 2.
2. Прочитати елемент з голови черги (X := СН [Р]) і, можливо,
знищити його у черзі.
3. Змінити значення вказівника початку черги (Р := Р + 1).
Як приклад роботи з чергою на рис. 27, наведено вигляд черги з
рис. 26 після виконання одного включення та двох вилучень.
З рисунка видно, що
черга переміщається по пам'я-
ті, роблячи недоступною ту
частину вектора. звідки
відбувалось читання, і ймовір-
но, що вектор мййже порожній,
а запис у чергу неможливий.
Щоб уникнути такої проблеми,
роботу з чергою можна реалі-
зувати кількома способами.
Очевидно, що всі способи
мають свої нюанси реалізації операцій включення і вилучення
елементів та перевірки черги на пустоту.
Перший спосіб передбачає, що вилучення елемента з черги
вимагає зсуву залишку черги на позицію, яка вивільнилась. Для такої
черги фіксується вказівник початку черги, і для роботи з чергою
достатньо одного вказівника - вказівника на кінець черги. Проте
реалізація такого підходу вимагає додаткових затрат для підтримки
багатократного копіювання вмісту черги.
66 7. ДИНАМІЧНІ СТРУКТУРИ
Другий спосіб передбачає, що зсув елементів черги
відбувається лише у випадку, коли неможливе занесення елементів у
чергу, тобто вказівник кінця черги встановлено на останній елемент
вектора.
Третій спосіб передбачає перетворення вектора для черги у
циклічну структуру. Це означає, що після заповнення останнього
елемента вектора чергове занесення елемента відбувається на початок
вектора, який вивільнився у результаті вилучення елементів. Приклад
такої черги зображено на рис. 28: (а) черга до виконання операції
включення; (б) черга після виконання операції включення.

Так само як у випадку


стека, ефективніше використо-
вується пам'ять і динамічно
реалізуються операції тоді,
коли черга відображена у спис-
кову структуру. Така черга
вимагає двох вказівників - на
початок і на кінець черги.
Включення елемента у
чергу передбачає створення но-
вого елемента, під'єднання
його до існуючого спискового
зображення і зміну вказівника
кінця черги. Операція вклю-
чення може бути зображена так
(див. рис. 29).
Деки 67

Вилучення елемента з
черги передбачає зміну верши-
ни і приєднання вилученого
елемента до списку вільної
нам'яті. Схематично цю опера-
цію можна зобразити так
(див. рис. ЗО).

Деки
Дек - це впорядкована, лінійна, динамічно змінювана послідов-
ість елементів, для якої виконуються такі умови:
1) новий елемент може приєднуватись до будь-якого краю
послідовності;
2) вибір елемента теж може відбуватись з будь-якого краю
послідовності.
Схематично дек можна зобразити так:
68 8. СПИСКИ
Деки - це структури, які володіють більшою загальністю, ніж
стеки чи черги. їх часом називають чергами з двома кінцями, у яких
включення та вилучення відбувається з двох країв. У чистому вигляді
деки використовують у теоретичних дослідженнях. На практиці
використовують деки з обмеженнями.
Дек з обмеженим виходом. Це дек, у якому доступ (вилучення)
відбувається лише з одного кінця, а занесення елементів - з двох.
Наприклад, дек з правим виходом можна зобразити так:

відбувається лише з одного кінця, а вилучення елементів - з двох.


Наприклад, дек з правим входом можна зобразити так:

Дек з обмеженим входом може працювати або як стек, або як


черга. Дек з обмеженим виходом може працювати як стек, але не може
працювати як черга.
Деки, так само як стеки і черги, можуть буги відображені у
послідовні та зв'язані структури зберігання даних.
8. СПИСКИ
До нетривіальних структур відносять списки. Треба відрізняти
спискову організацію пам'яті від абстрактної структури список.
Список (linked list) як абстрактну струкгуру визначають як
впорядковану послідовність елементів, кількість яких може
змінюватись.
Список дуже гнучка структура, оскільки її можна збільшувати
чи зменшувати за рахунок додавання чи вилучення нових елементів у
довільні позиції списку. Списки можна об'єднувати або розбивати на
менші. Розмір списку з часом може змінюватись унаслідок включення
та вилучення елементів.
Порядок елементів визначається не індексами (номерами), як у
масиві, а вказівниками. Якщо елемент не має попередника, то це
голова списку, а інші елементи списку — це його хвіст.
Застосування списків дуже різноманітне. їх можна використо-
вувати в програмах інформаційного пошуку, трансляторах чи при
моделюванні різних процесів. Методи керування пам'ятно
використовують списки.
У математиці список - цс послідовність елементів певного типу:
а і , а2,...аn, n — 0. Кількість елементів п називають довжиною списку.
Якщо n=0, то список порожній.
Список можна визначати рекурсивно як скінчену послідовність
атомів або списків, кількість яких може бути рівною нулю. У нашому
випадку "атом" - базове поняття, яке стосується елементів, взятих з
довільної множини предметів, лише б виконувалась умова, що можна
відрізнити атом від списку. За допомогою простої системи умовних
позначень, які містять коми та дужки, можна відрізняти атоми від
списків та вказувати їх впорядкування всередині списків. Приклад
списку з п'яти елементів
L=( а , (b, а, b), ( ) , с, ( ( ( с ) ) ) ) .
Такий список графічно можна зобразити так:
70 8. С П И С К И

Тут символ „* " позначає список, який відрізняється від атома.


Список, у якому відображено відношення сусідства між
елементами, називається лінійним. Будь-який інший список називають
нелінійним.
Було створено низку мов, спеціально призначених для обробки
зв'язних списків. Найвідоміша з них — ЛІСП. В інших мовах
включено процедури або функції, які виконують потрібні операції над
списками.
Операції, які виконують над списком як абстрактною
структурою, подібні до операцій над множиною. Операції можуть
стосуватися списку як структури в цілому, так і окремих елементів
списку.
Операціями, які стосуються структури списку, є:
- створення списку - створити порожній список;
- об'єднання двох або більше списків для формування нового;
- розділення списку на два: голова (перший елемент) та хвіст
(усі інші елементи);
- копіювання списку — створення нової структури списку;
- визначення кількості елементів у списку;
- виведення (друк) усіх елементів списку.
Операції, які стосуються окремих елементів у списку, є такими:
- включення елемента - включити заданий елемент у визначену
позицію списку;
Відображення списків у структури зберігання 71
- вилучення елемента — вилучити елемент, який знаходиться в
заданій позиції списку;
- локалізація елемента - визначити позицію заданого елемента в
списку (можливо першого входження);
- пошук значення елемента на заданій позиції;
- визначення позиції наступної або попередньої до заданої.
Мови програмування, орієнтовані на обробку списків, зазвичай
мають вбудовані операції цього типу. Крім цього, очевидно, для
роботи потрібні операції перетворення інформації із зовнішнього
представлення у внутрішнє зображення списків, і навпаки.

Відображення списків у структури зберігання


Списки можна відображати у послідовні або спискові структури
зберігання.
При відображенні списків у послідовні структури зберігання
елементи розташовують у суміжних комірках. Це дає змогу легко
переглядати вміст списку і додавати елементи в кінець списку. Проте
включення і вилучення елемента з середини списку буде вимагати
переміщення елементів.
Векторне представлення може використовуватись, наприклад,
для дужкового зображення списку. Наприклад, список (а,(а,b)) можна
відобразити на послідовну пам'ять так, як показано на рис. 32.

Якщо список є лінійним, то його можна відображати у


послідовну пам'ять так само, як лінійний масив.

Очевидно, що найзручніше відображати списки у зв'язані


структури зберігання, коли для об'єднання елементів у спискову
структуру використовують вказівники. При цьому використовують
заголовну ланку, яка дає доступ до першого елементу списку.
72 8. С П И С К И
Ще одним способом зберігання є моделювання зв'язаної пам'яті
на послідовній, коли роль вказівників відіграють цілі числа - курсори
[1], які показують на позицію елементів у векторі. Наприклад, список з
цілочислових значень (15, 20, 25) за допомогою курсорів можна
зобразити так (див. рис. 33). Ознакою кінця є значення 0 у полі, яке
відповідає вказівнику.
При відображенні списків у структури зберігання обов'язково
має бути визначено вказівник та початок списку. А при відображенні у
послідовну пам'ять має бути визначено вільне місце пам'яті, куди
будуть заноситись значення при додаванні нових елементів до списку.
Вибір форми відображення списків у структури зберігання
залежить від того, які операції повинні виконувати над списками і як
часто їх будуть виконувати.

Розглянемо детальніше зв'язане зображення спискових струк-


тур.
Однонапрямлений ланцюговий список формується як ланцюг,
кожен елемент якого складається зі значення, вказівника на наступний
та значення свого розміру, оскільки елементи можуть бути різного
розміру. У наведеному на рис. 34 ланцюговому списку перше поле
елемента використовується для визначення розміру елемента, друге -
вказівник для утворення снискової структури, а третє - власне
значення елемента.
Очевидно, що на такому представленні операції включення-
вилучення є константними, оскільки зводяться лише до зміни
вказівників. Такими ж є операції об'єднання чи поділу списку. Проте
операції локалізації, пошуку чи підрахунку кількості елементів
передбачають перегляд цілого списку.
Відображення списків у структури зберігання 73

Константною є також операція переходу до наступного


елемента. Проте визначення попереднього вимагає перегляду списку
від початку. Щоб позбутись цієї проблеми використовують двозв'язні
списки (див. рис. 35).

Такий список може бути легко модифікований у кільцевий,


який відрізняється від двозв'язного тим, що наступним для останнього
елемента списку є заголовна ланка, а попередньою для заголовної -
74 8. С П И С К И
останній елемент. Така організація списку спрощує операцію пошуку
або переходу до наступного чи попереднього елемента.
Коли інформаційні записи об'єднують у список, утворюється
група записів з певним внутрішним порядком. Цю- групу можна
розглядати як єдиний інформаційний об'єкт і утворювати списки з
таких об'єктів. Наприклад, можна організувати список вищих
навчальних закладів, де кожному ВНЗ відповідає список факультетів.
Така ієрархія може бути багатоступінчатою.
Якщо елементами списку верхнього рівня є підпорядковані
списки, то отримуємо ієрархічний список. Зручно вважати, що з
погляду списку верхнього рівня, запис, який в нього входить — це
посилання на підпорядкований список (заголовна ланка підпоряд-
кованого списку). Список верхнього рівня поряд із заголовками
підпорядкованих списків може містити і самостійні елементи
(звичайні записи).
Кількість рівнів ієрархії може бути будь-якою. На кожному
рівні можна використовувати списки різної структури (див. рис. 36).
На верхньому рівні у наведеному прикладі використано
двонапрямлений список з чотирьох елементів, причому, першим і
третім елементами є звичайні записи. Другий елемент цього списку -
однонапрямлений список, який відповідно містить ще один
підпорядкований список.

Часто одні і ті ж об'єкти представляють інтерес з різних точок


зору, і виникає природня потреба включати їх у різні списки, які
формуються за різними ознаками. Якщо в різних списках потрібна
Відображення м а с и в і в у структури зберігання 75
одна і та сама інформація про кожний об'єкт, то бажано не дублювати
записи. Позбавитись такого дублювання дають змогу асоціативні
списки. їх організовують на одному спільному наборі записів,
причому кожний з асоціативних списків об'єднує у певному порядку
ті записи з набору, які володіють деякою характерною ознакою. Назву
"асоціативний" вибрано тому, що записи об'єднуються в список,
асоціюючи їх з деякою ознакою. Прикладом асоціативних списків
можуть бути списки студентів, які проживають у гуртожитку, та
одружених студентів, побудовані на записах, що містять інформацію
про студентів певного факультету.
9. ДЕРЕВОВИДНІ СТРУКТУРИ

Найважливішими нелінійними структурами, які трапляються в


обчислювальних процесах, є дерева. Вони найкраще пристосовані для
розв'язування задач штучного інтелекту (побудова таблиць рішень),
синтаксичного аналізу (побудова дерева виводу, розпізнавання
ієрархічних структур), аналітичних перетворень (зображення виразів),
організації баз даних та інших задач.

Основні визначення
Поняття дерева (tree) виводять з теорії графів, але воно отримало
широке застосування в інформатиці. Тому в літературі [2, 6]
трапляється два визначення дерева:
1) на основі теорії графів: дерево - це зв'язний ациклічний граф;
2) рекурсивне: дерево - це скінчена множина Т, яка складається
з одного чи більше вузлів таких, що
• є один спеціальний вузол, який називають коренем
даного дерева;
• решта вузлів входять до складу m>0 множин T 1 ,...,T m ,
які попарно не перетинаються і кожна з яких сама є деревом
(T 1 ,--T m , називають піддеревами).
Рекурсивність є природною характеристикою деревовидних
структур і суттєво використовується у багатьох алгоритмах обробки
дерев.
Визначення дерева як ациклічного графа є загальнішим і в [6]
таке дерево названо вільним. Якщо ациклічний граф орієнтований і в
ньому виділено один спеціальний вузол, названий коренем, то дерево
називають кореневим. Визначення кореневого дерева еквівалентне
рекурсивному визначенню дерева.
Основні визначення 77
Кореневе орієнтоване дерево позначимо так: G=<A, Г> , де
А={а0,а],...,ап} - множина вузлів, Г- відношення підпорядко-
ваності на А, яке задає топологію дерева.
Дерево повинно мати принаймні одну вершину. Ізольована
вершина також дерево.
Є декілька способів зображення дерев [2, 6], але найчастіше
використовують графічне. Приклад такого зображення подано на
рис. 37.
Визначимо основні поняття, які використовують під час обробки
деревовидних структур.
З теорії графів використано поняття вузла та ребра. Вузол, у
який не входить жодне ребро, називають коренем (на рис. 37 - це
вузол а). Вузли, з яких не виходять ребра, називають термінальними
вузлами, або листками (на рис. 37 термінальними є вузли d. e.f, і, И).
Вузли, які не є термінальними, називають нетермінальними, або
внутрішніми.
Вузол у, який знахо-
диться безпосередньо під
вузлом х, називається без-
посереднім нащадком х; х
називається безпосереднім
предком у. Безпосередніх
нащадків вузла х називають
його синами, а сам вузол
називають батьківським. На
рис. 37 с, d, е — сини вузла Ь,
a f - батько вузла g. Ця
термінологія взята з генеало-
гічних дерев.
Довжина шляху між вузлами визначається кількістю ребер між
цими вузлами. Довжина шляху від кореня до деякого вузла
називається рівнем цього вузла. Рівень кореня дорівнює 0 (іноді
корінь вважається вузлом рівня 1, а рівень кожного вузла визначається
як довжина шляху, збільшена на 1). Максимальний рівень дерева
називається його висотою (висота дерева на рис/ 37 дорівнює 3).
Часом розглядається параметр дерева, який називається довжиною
шляху дерева, і визначається як сума довжин шляхів до всіх його
вузлів від кореня. Його часом називають довжиною внутрішнього
9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
шляху. Наприклад, довжина внутрішнього шляху дерева на рис. 37
дорівнює 16.
Важливою характеристикою кожного вузла є кількість його
безпосередніх нащадків, яка називається степенем виходу вузла. Всі
термінальні вузли мають степінь виходу, рівний 0. Степінь виходу
вузла b на рис. 37 дорівнює 3. Максимальний степінь всіх вузлів є
степенем дерева.
Кожний вузол дерева задає піддерево, утворене всіма вузлами,
які можуть бути досягнуті із нього. Отже, кожне дерево - це система
вкладених одне в одне піддерев. Така властивість дерев відображає
їхній рекурсивний характер.
Якщо з дерева видалити корінь і ребра, які з'єднують його з
вузлами першого рівня, то отримаємо деяку множину незв'язаних
дерев, яка називається лісом. Оскільки кожен вузол визначає
піддерево, то можна вважати, що піддерева, розташовані під вузлом,
утворюють ліс.
Дерево називається впорядкованим, якщо має значення
відносний порядок його піддерев. Наприклад, на рис. 38 подано два
різні впорядковані дерева.
Якщо R - відношення
порядку на множині вузлів, то
трійку D=<A,Г,R> називають
впорядкованим деревом. Одному
дереву G відповідає деякий клас
еквівалентних йому впорядкова-
них лепен
Окремий важливий клас складають двійково-пошукові
(двійкові) дерева. Ці дерева визначають так: з кожного вузла виходить
не більше двох гілок, і кожна гілка ідентифікується як ліва або як
права. Приклад двійково-пошукового дерева наведено на рис. 39.

Двійково-пошукові дерева відрізняються


від бінарних, у яких кожен вузол має або
два піддерева, або не має жодного. Бінарні
дерева є представниками класу k — арних
(регулярних) дерев, у яких степені виходу
всіх внутрішніх вузлів рівні між собою. На
рис. 40 наведено приклади бінарних дерев.
Основні визначення 79

Основна різниця між деревами і двійково-пошуковими деревами


полягає в тому, що:
1. Дерево ніколи не буває порожнім;
2. Двійково-пошукове дерево може бути порожнім і кожен з
його вузлів може мати 0, 1 або 2 підпорядковані вузли, які
визначаються як лівий чи правий.
У [6] подано теорему про зв'язок звичайних та двійкових дерев.
Теорема. Всякий ліс можна представити у вигляді двійкового
дерева.
Доведення цього твердження конструктивне, тобто відбувається
за шляхом визначення алгоритму, який дає змогу це зробити.
Розглянемо для прикладу наступний ліс із двох дерев:

Відповідне двійкове дерево отримуємо, якщо з'єднати разом усі


вузли, підпорядковані деякому одному вузлу, і видалити всі
вертикальні зв'язки, залишивши лише зв'язок до першого зліва вузла.
80 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И

Якщо повернути діаграму на 4 5 ° , то отримаємо двійкове дерево:

Має місце і обернене твердження: всякому двійковому дереву


відповідає ліс дерев.
Наведені твердження дають зв'язок між звичайними та
двійковими деревами. Тому багато операцій, застосованих до
двійкових дерев, можуть бути також застосовані і до звичайних дерев.
' Однією з важливих операцій є обхід двійкового дерева. Ця
операція часто застосовується в алгоритмах обробки дерев, а також
для лінеаризації ієрархічної структури дерева. Кожен обхід передбачає
опрацювання кореня (К), лівого (JI) та правого (П) піддерева. Залежно
від порядку, в якому відбувається опрацювання частин дерева,
визначають обхід. Кожен з обходів визначають рекурсивно, як і саму
структуру дерева [2, 6]. Найпоширенішими є три обходи:
• прямий (нисхідний);
• зворотний (змішаний);
• симетричний (висхідний).
Основні визначення 81
К о ж е н з обходів проілюструємо на дереві, зображеному на рис.
43.

Прямий обхід.
Опрацювання кореневого вузла.
Прямий обхід лівого піддерева.
Прямий обхід правого піддерева.
Для дерева на рис. 43 прямий обхід дасть таку послідовність вузлів:
abcdefgh.
Зворотний обхід.
Зворотний обхід лівого піддерева.
Опрацювання кореневого вузла.
Зворотний обхід правого піддерева.
Для дерева на рис. 43 прямий обхід дасть таку послідовність вузлів:
с b d е a g f h.
Симетричний обхід.
Симетричний обхід лівого піддерева.
Симетричний обхід правого піддерева.
Опрацювання кореня.
Для дерева на рис. 43 прямий обхід дасть таку послідовність вузлів: с е
d b g h f a.
Крім цих обходів можна визначити ще порівневі та
правосторонні обходи.
Пропоновані обходи можна узагальнити на дерева довільної
структури [1].
Нехай дерево Гскладається з кореня R та піддерев T1,T2,...,Tn,
як показано на рис. 44.
82 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И

Тоді всі способи обходу можуть бути визначені так. Якщо


дерево вироджене (складається з одного вузла), то результуюча
послідовність буде містити лише цей вузол. Якщо дерево Т
складається з кореня і піддерев Т1,Т2 ,...,Тп , то кожне з піддерев,
спочатку піддерево Т1, потім піддерево Т2 і наприкінці піддерево Тn,
обходиться у відповідному порядку.
Деревами зручно зображати складні ієрархічні структури даних.
Це обумовлено тим, що кожен вузол дерева, крім внутрішньої
інформації, містить посилання на вузли нижчого рівня. Жоден вузол
не може вказувати на вузол, розташований вище.
Для вузлів найнижчого рівня характерне тс, що вони можуть
містити інформацію, зовнішню по відношенню до дерева. Тобто,
кожен листок може бути деякою структурою даних.
У практичних задачах обробки інформації вузли дерева
навантажені елементами деякої множини Ф. Четвірку D=<A,Г,R,φ>,
де φ функція φ: А->Ф називають навантаженим кореневим
впорядкованім деревом.
Навантаження може нести деяку додаткову інформацію про тип
вузла дерева (наприклад, у випадку зображення деревом арифме-
тичного чи логічного виразу). Якщо ж навантаження вузла не містить
додаткової інформації, то його називають міткою вузла. Іноді в
Базові оператори над деревами 83
задачах трапляються навантажені мічені вузли, при цьому
навантаження і мітка можуть оброблятися по-різному.

Базові оператори над деревами


Під час опрацювання деревовидних структур використовують
низку допоміжних понять, за допомогою яких описують базові
оператори. Розглянемо деякі з них.
Піддеревом, визначеним вузлом а (на рис. 45 позначено Т(а)),
називається піддерево, утворене коренем а і всіма вузлами, які можна
досягнути з а, і таких, що знаходяться нижче від цього вузла.
Кущем, визначеним вузлом а (на рис. 46 позначено К(а)),
називається піддерево, утворене вузлом а і всіма його синами.
Під час роботи з деревами використовують вказівники -
вказівник дерева та вказівник вузла.

Вказівник дерева встановлено на початок дерева, його


значенням є дерево і переміщення такого вказівника всередині дерева
неможливе. Зняття вказівника передбачає
знищення доступу до дерева.
Крім вказівника дерева використовують
вказівники вузлів, які рухаються по дереву
згідно з алгоритмом. На дереві може бути
встановлено масив таких вказівників.
Значенням вказівника є вузол.
84 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
Позначимо через Р вказівник вузла (див. рис. 47). Далі вузлом Р
будемо називати вузол, визначений вказів-ником Р.
В операторах обробки дерев використовують об'єкти типів:
Дерево, Кущ, Вузол, Вказівник, Навантаження, Мітка, Цілий,
Логічний, Літерний. Набір об'єкгів одного типу складає Масив.
Об'єкти типів Дерево, Кущ, Вузол утворюють клас Деревовидних
структур. Вказівник може бути встановленим і невстановленим.
Невстановлений вказівник значення не має. З вузлом пов'язані об'єкти
типів Навантаження і Мітка, які можуть бути елементами Цілого чи
Літерного типу .
Аналіз різних класів алгоритмів обробки дерев дав змогу
виділити набір базових операторів над кореневими впорядкованими
навантаженими деревами.
Набір В T , базових операторів є доволі великим, тому доцільно
провести його розбиття на гри підмножини, які визначають відповідно
три рівні операторів:

Мінімальний базис повинен включати в себе оператори, які


дають змогу будувати довільне дерево Т' з класу Кг кореневих

впорядкованих навантажених дерев, виходячи з підмножини Т с К, ,


яка містить дерева зі всіма вузлами, необхідними для побудови дерева
" 'І' 1 . Цей базис складають оператори встановлення і зняття вказівника
вузла, зсуву його до наступного в заданому обході вузла, суміщення
двох вказівників, приєднання вузла до дерева, присвоєння вузлу
нового значення навантаження і мітки, копіювання елемента (у тім
числі деревовидної структури) та перевірки двох елементів на
еквівалентність.
Ці оператори складають функціонально-незалежну і структурно-
повну систему.
Оператори локальних перетворень призначені для заміни
значення об'єкта (оператори розмітки вузла, зсувів вказівника вверх
до батьківського вузла, вниз по дереву, переміщення вказівника до
Відображення дерев у структури зберігання. 85
вузла праворуч чи ліворуч, приформування чи видалення піддерева), а
також перетворення типів (обчислення кількості синів вузла і числа
термінальних серед них, встановлення термінальності, навантаженості
і відміченості вузла, виродженості дерева).
Оператори базису В TT реалізують найчастіше вживані
перетворення дерев. Вони є основними складовими алгоритмів
обробки дерев. У базис В TT входять оператори визначення числа
складових елементів у складних об'єктах, глибини вузла, зміни дерева
шляхом перестановки піддерев, наклейки піддерев, виділення в дереві
шляхів між двома вузлами (зокрема, до вершини, до листків),
виділення заданого рівня, визначення висоти та ширини дерева.
Детальний опис операторів опрацювання дерев можна знайти в [8].
Реалізація кожного з операторів суттєво залежить від структури
зберігання дерева.

Відображення дерев у структури зберігання.


Послідовні структури
Деревовидні структури можна відображати у послідовні,
спискові чи розсіяні структури зберігання.
Під час відображення дерев у послідовні структури зберігання
враховують той факт, чи є дерево навантаженим, чи ні. Якщо дерево
не навантажене, то його називають топологічним. Воно визначає деяку
ієрархічну структуру. Для зображення такого дерева існують коди, які
грунтуються на обходах дерева.
Розглянемо деякі коди. У двійковому коді використовують
лише двійкові цифри. Його будують за таким правилом. Починаючи з
кореня, дерево обходять у прямому порядку, позначаючи спуск вниз 1,
а підйом вверх 0.
Цей код для дерева з рис. 48 матиме
вигляд 1011010010.
Можна побудувати код дерева,
використовуючи степені виходу кожного
вузла. Дерево обходять у прямому
порядку, виписуючи для кожного вузла
його степінь виходу. Такий код для дерева
з рис.48 матиме вигляд 302000.
86 9. ДЕРЕВОВИДНІ СТРУКТУРИ
Частіше вузли дерева є навантажені (помічені) деякими
значеннями. Причому значення у вузлах можуть бути такими, що
несуть додаткову інформацію. Наприклад, коли дерево зображає
арифметичний вираз, то значення його вузлів несуть інформацію про
термінальність чи нетермінальність вузла. Термінальними є вузли з
операндами, а нетермінальними - вузли з операціями. Векторне
зображення двійкового дерева, яке отримується з використанням
прямого обходу, називається бездужковим і для дерева з рис. 49
матиме вигляд: * + a b - / c d f .

Для дерева такого виду мас


місце таке співвідношення між
загальною кількістю вузлів п та
кількістю термінальних t, та не-
термінальних N серед них:

Для бездужкового запису


дерева можна виділити піддерево
(підвираз), скориставшись таким
алгоритмом, починаючи з першого
елемента шуканого піддерева:
імвол;
1) переглянути черговий символ;
2) якщо розглянений символ - операція, то занести його в стек і
перейти до п. 1;
3) якщо розглянений операнд, то читати елемент зі стеку. Якщо
стек не порожній, то перейти до п.1, інакше кінець.
Кінець: піддерево пройдено, і елемент, доступний у рядку, вже
не належить до піддерева.
За таким алгоритмом можна пройти і ціле дерево. У цьому
випадку перегляд рядка треба починати з першого символа
бездужкового запису.
Якщо дерево не двійкове, то для його векторного зображення
недостатньо інформації про навантаження вузлів дерева. Для явного
виділення піддерева використовують додаткові символи, наприклад,
дужки. Дерево можна зображати в однодужковому чи дводужковому
зображенні.
Відображення дерев у структури зберігання. 87
Однодужкове зображення можна використати лише для випадку,
коли навантаження вузла несе додаткову інформацію про тип вузла.
Початком піддерева є нетермінальний вузол, а для вказання кінця
піддерева у векторне представлення вводять закриваючу дужку.
Причому, якщо дерево складається лише з одного термінального
вузла, то його виділяти окремою дужкою не варто. Тобто, кількість
дужок у виразі дорівнює кількості нетермінальних вузлів у дереві.
Наприклад, для дерева з
рис. 50 однодужкове век-
торне зображення матиме
вигляд * a + b c d ) - e f ) ) .
Якщо навантаження (
мітки ) у вузлах дерева не
несуть додаткової інфор-
мації, то для відображення
таких дерев у послідовні
структури можна викори-
стати дводужкове або дво-
векторне зображення.
У дводужковому зображенні для виділення піддерева
використовують парні дужки. Очевидно, що таке зображення може
бути використано для зображення довільного дерева, зокрема для
дерева з рис. 50 матиме вигляд ( *, а , ( +, b, с, d ), ( - , е, f)).
У попередніх векторних зображеннях структура і навантаження
дерева зберігались разом. Двовекторне зображення передбачає
розділене зберігання топології дерева та його навантаження. Для
зображення топології зручним є використання степенів виходу.
Наприклад, для дерева з рис. 50 двовекторне зображення є таким:
3 0 3 0 0 0 2 00
*a + b c d - e f
Крім наведених векторних кодів зображення дерев, іноді
використовують послідовні структури разом з різними курсорами, які
дають змогу легко переміщатись по дереву [1]. Особливість таких
зображень полягає в тому, що вони, по суті, моделюють спискове
представлення дерева, відображене на лінійну пам'ять комп'ютера.
Зокрема, якщо врахувати, що у двійкових деревах кожен вузол
може мати два піддерева, то однією з форм відображення таких дерев
у масив є використання курсорів на вузли-сини. Кожен елемент
88 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
масиву складається з трьох полів: поля-навантаження вузла та двох
полів, значеннями яких є індекси у масиві безпосередньо
підпорядкованих вузлів. Для термінальних вузлів ці поля мають
значення 0. Елементи у масив записують у прямому, порядку. Для
дерева з рис. 51 масив курсорів на синів матиме вигляд (перший
стовпчик — індекси у масиві):

Спискові структури зберігання

Щоб відобразити існуючі ієрархічні зв'язки в дереві, можна


використати спискові структури зберігання. Дерева можна зображати
різними списками (детально спискові структури описані в розділі 2).
Особливість цих структур полягає у використанні вказівників.
А. Ланцюговий список. Наприклад, для дерева з рис. 51 ця
структура виглядатиме так (див. рис. 52).
Така форма зображення хоч є значно гнучкішою за векторну,
проте є досить громіздкою і непродуктивно використовується значний
обсяг пам'яті.
Відображення дерев у структури зберігання. 89

Б. Двозв'язний однонаправлений список явно відображає


зв'язки підпорядкування, які існують між вузлами дерева. Такий
список може бути використаний лише для двійкових дерев. Для дерева
з рис. 51 цей список матиме вигляд (див. рис. 53):
90 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
В. для довільного дерева можна скористатись багатозв'язним
однонаправленим списком, який для дерева з рис. 50 має вигляд:

Така форма, хоч і зручна для зображення дерев, у реальних


задачах важка до реалізації, оскільки для опису вузла використовують
нерегулярні структури.
Головним недоліком усіх розгляну-
тих спискових структур зберігання є те, що
вони дають змогу проходити дерево лише в
одному напрямку від кореня. Якщо
алгоритм передбачає переходи в дереві на
вищі рівні, то можливе включення у кожну
ланку, використовувану для зображення
вузла, вказівника на батьківський вузол.
Г. Двонапрямлений багатозв'язний
список для дерева з рис. 55 матиме ізигляд,
поданий на рис. 56.
Застосування деревовидних структур 91
Застосування деревовидних структур
Деревовидні структури належать до найвикористовуваніших
структур даних. Розглянемо деякі з можливих застосувань дерев.

Зображення виразів
Будь-які вирази у дужковому записі - це лінеаризовані
ієрархічні структури, причому лінеаризація відбувається унаслідок
використання дужок, які дають змогу вказати пріоритет, тобто
визначити ієрархію операцій. Якщо ж не проводити лінеаризацію, а
зображати вираз безпосередньо як ієрархічну структуру, то для цього
використовують дерева.

Обходи дерева дають зображення дерева, використовувані у


трансляторах. Так, прямий обхід дає префїксний запис виразу, а
симетричний - суфіксний або ПОЛІЗ (польський інверсний запис),
розбір яких продемонстровано у розділі 7.
92 9. ДЕРЕВОВИДНІ СТРУКТУРИ
Синтаксичні дерева (дерева виводу)
Під час трансляції мов високого рівня доводиться перевіряти
синтаксичну правильність конструкцій цих мов. Під час
синтаксичного аналізу відбувається побудова дерева виводу. Якщо від
заданої мовної конструкції, використовуючи дерево виводу, можна
перейти до базового поняття (аксіоми) відповідної граматики, то
конструкція належить мові, заданій граматикою. Дерево будується від
термінальних вузлів, які зображають аналізовану конструкцію, до
кореня - аксіоми граматики.
Наприклад, перевірити чи вираз і + і * і належить мові, побудованій
за граматикою, заданою такими правилами:
< е > ::=<t> + < e > | < t >
<t> ::=<f> * <t> I < f >
< f > ::= ( < e > ) | і
У цій граматиці аксіомою є символ < е >, а символи + , * та і є
термінальними. Дерево виводу буде таким ( див. рис. 59):
Застосування деревовидних структур 93
Таблиця символів у вигляді двійкового дерева
(впорядкування масиву).
Для ефективного пошуку символів таблицю організують у
вигляді двійкового дерева (про побудову такої таблиці йшлося у
розділі 5). Далі буде детальніше розглянуто побудову такого дерева,
додавання та вилучення елементів і пошук по ньому.
Наприклад, нехай треба розмістити в таблиці ідентифікатори D1,
В, С2, Е4, F, А, К . Тоді дерево матиме вигляд, зображений на рис. 60.
Симетричний обхід цього дерева дає відсортовану у
лексикографічному порядку послідовність ідентифікаторів.

Дерево складових
У лінгвістиці для опису будови синтаксичної структури
речення використовують дерева. При цьому в реченні виділяють
складові частини — групи слів, які функціонують як цілісні
синтаксичні одиниці. Наприклад, у реченні „Реве та стогне Дніпр
широкий" (Т. Г. Шевченко) складовими будуть: все речення Р, кожне
його окреме слово та групи слів А = Реве та стогне; Б = та стогне; П =
Дніпр широкий. Дерево для цього речення подано на рис. 61.
94 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И

Таблиці рішень
Ці таблиці показують як умови зв'язані з діями. Детально
використання дерев для зображення таблиць рішень подано у [19].

Дерева сортувань
Як попередньо було показано, дерева можуть бути використані
для впорядкування масиву елементів за деякою ознакою, якщо поряд з
поступанням нових елементів використовують уже впорядковану
частину масиву, і подальшого пошуку деякого елемента у цьому
масиві.
Традиційно використовувані методи сортування приводять у
такому випадку до малоефективних алгоритмів, оскільки вимагають
для свого використання або взаємного переміщення елементів у
пам'яті, або неоднократного проходження впорядкованої частини
масиву, що займає значний час. Дерева сортувань дають змогу
визначити місцеположення нових елементів майже так само швидко,
як у впорядкованому послідовному масиві. Це позбавляє від
необхідності переміщати елементи, щоб звільнити місце для
розташування нових. Пошук при вже відсортованих елементах
виконується швидше, ніж у невпорядкованому масиві.
Нехай задано масив елементів, для яких визначено операцію
порівняння. Побудова дерева сортування може бути подана таким
алгоритмом (враховуючи те, що для зображення дерева буде
використано двозв'язний список):
Застосування д е р е в о в и д н и х структур 95
1. Перший елемент масиву занести в корінь дерева
(сформувати ланку з першим значенням і двома порожніми
вказівниками та встановити на неї вказівник дерева).
2. Кожний наступний елемент порівнювати з елементом у
вузлі. Якщо новий елемент має менше значення, то іти по
лівому піддереву, інакше по правому. Рух по дереву
здійснювати доти, поки не стане можливим приєднати новий
вузол (посилання відповідного напрямку - порожнє).
3. Для всіх елементів масиву повторити п. 2.
Нехай потрібно сформувати дерево сортування для масиву з
таких значень: 12, 8, 15, 14, 6, 7, 3, 17. Воно матиме вигляд (див. рис.
62):

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


сортування є пошук, додавання і видалення запису.
Опишемо ці операції, використовуючи стандартні конструкції
керування.
Пошук елемента грунтується на ідеї, покладеній в основу
побудови дерева сортувань, і його можна визначити таким
алгоритмом.
Алгоритм пошуку
початок
потрапити в корінь дерева;
поки (елемент не є шуканим або не є термінальним)
виконувати
якщо шуканий елемент < елемента у вузлі
го застосувати алгоритм до лівого піддерева
інакше застосувати алгоритм до лівого піддерева
кінець.
96 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
Якщо елемент не є шуканим, а є термінальним, то шуканого в
дереві немає.
Оцінка цього алгоритму здійснюється у кількості порівнянь.
Мінімальною оцінкою є константа - треба здійснити одне порівняння
у випадку, коли шуканий елемент знаходиться у корені дерева.
Найгіршим є варіант, коли масив впорядковано, і тоді дерево -
витянуге. У цьому випадку, щоб встановити факт не існування
шуканого елемента, потрібно виконати п порівнянь. Середній час
пошуку - log 2 п, де п - кількість вузлів дерева.
Додавання елемента. Під час додавання елемента виконують
пошук відповідного місця доти, доки не буде вільного місця у
відповідному напрямку, тобто у вузла, до якого можна під'єднати
додаваний елемент, не буде всіх піддерев, або доки не буде досягнуто
термінального вузла. Після додавання новий вузол стає термінальним.
Наприклад, нехай потрібно додати елемент зі значенням 10 до
масиву, зображеного деревом на рис. 62. У цьому випадку спочатку
значення 10 порівнюється зі значенням у корені, тобто зі значенням
12. Оскільки між цими елементами істинним є відношення 10<12, то
переходимо по лівій галузці і порівнюємо 10 та 8. Між цими
елементами істинне відношення 10>8 і у вузла зі значенням 8 праве
піддерево відсутнє, то новий вузол буде додано як праве піддерево.
Тоді нове дерево матиме вигляд, поданий на рис. 63 (доданий вузол та
шлях до нього виділено).

Якщо треба додати елемент, але у вузла, до якого треба


під'єднати новий, є два піддерева, то відбувається пошук
термінального вузла, до якого і буде під'єднано новий вузол.
Застосування д е р е в о в и д н и х структур 97
Наприклад, до дерева на рис. 63 додати елемент зі значенням 5. У
цьому випадку значення 5 буде під'єднано як праве піддерево для
термінального вузла зі значенням 3 і нове дерево подано на рис. 64
(доданий вузол та шлях до нього виділено).
Видалення залежить від типу вузла, до якого застосовують цю
операцію. Видалення термінального вузла не є складним, бо
відтинається листок і у його батьківського вузла лише змінюється
вказівник.
Дещо складнішим є видалення вузла, який має підпорядковані
елементи, тобто є коренем деякого піддерева. Якщо видаляють такий
вузол, то його місце займає вузол, значення якого є найбільшим у
лівому піддереві вузла, який видаляють (найгіравіший з лівої гілки)
або найменшим у правому піддереві (найлівіший з правої гілки).
Розглянемо приклади видалення елемента з масиву, зображе-
ного деревом. Перший приклад стосується видалення термінального, а
другий - нетермінального вузла.

Нехай з дерева, зображеного на рис. 63 треба видалити вузол


зі значенням 17. Цей вузол термінальний, і тому його видалення
передбачає лише знищення вказівника на нього у вузла зі значенням
15. Результат роботи показано на рис.65.
98 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И

Ще один приклад видалення з дерева з рис. 63 вузла, який є


коренем деякого піддерева. Нехай треба видалити вузол зі значенням
8. Його місце займе вузол зі значенням 7 (найбільшим значенням з
лівого піддерева дерева зі значенням 8 у корені). Результат роботи
зображено на рис. 66. Стрілкою показано переміщення вузла на нове
місце.
Застосування деревовидних структур 99
Формування дерева сортування залежить від послідовності, в
якій поступають елементи при його побудові. Оцінка пошуку у
кількості порівнянь залежить від уже відсортованої частини.
Так, одна і та сама
множина елементів {4, 2, 6, 1,
З, 5, 7}, задана у вказаному
порядку, приведе до побудови
дерева (див. рис. 67) з
середньою довжиною пошуку,
яка оцінюється середньою
кількістю порівнянь

Якщо ж елементи поступають у порядку {1, 2, 3, 4, 5, 6, 7}, то


дерево стає витягнутим (див. рис. 68), а середню кількість порівнянь
оцінюють за формулою
100 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
а середня кількість порівнянь при пошуку, зміні та вилученні є
Q (n) = n/(n+l)*P(n) - 1.
Доведення теореми в [20].
Наслідком теореми с вираз, який показує обмеження-на кількість
порівнянь пои включенні P(nV.

Збалансовані дерева
Висота гілки дерева - це число вузлів, розташованих між
коренем і листком, включаючи і ці вузли. Висота дерева -
максимальна з висот його гілок.
Інтуїтивно зрозуміло, що якщо всі гілки дерева мають
приблизно однакову висоту, то пошук в середньому проходить
швидше, ніж у дереві з тими ж записами, але суттєво різними по
висоті гілками.
Якщо в дереві є 2n — 1 вузлів, то можна сформувати дерево, в
якому кожна гілка має висоту h. Наприклад, якщо n=15, то можна
сформувати дерево, кожна гілка якого має висоту 4. Якщо ж n=14, то
одна гілка буде висоти 3.
За оптимальне визначають дерево, гілки якого мають висоту від
h до h - 1. Оптимальне дерево вимагає багато зусиль для побудови і
супроводження.
Компромісним рішенням цієї проблеми є збалансоване дерево.
Задавши дерево, виберемо довільний з його вузлів. Цей вузол
визначає два піддерева. Висота кожного з них не повинна відрізнятись
більше ніж на 1. В такому випадку отримаємо збалансоване дерево.
Отже, бінарне дерево називається збалансованим за висотою,
якщо для кожного вузла висота піддерева з більшими ключами
відрізняється від висоти піддерева з меншими ключами не більше ніж
на 1 (приклад такого дерева на рис. 69).
Такі дерева дозволяють встановити прийнятну верхню границю
довжини пошуку, обмежену значенням log2 n.
Якщо дерево формується із записів, які поступають у випадковій
послідовності, воно переважно не буде збалансованим. Проте, в
середньому дерево буде близьким до збалансованого. Тому виникає
потреба перебудови дерева до збалансованого.
Поняття збалансованого дерева може бути поширене на дерева
довільної арності. В такому дереві кожний вузол мас однакову
кількість гілок, причому процес включення нових гілок у вузли йде
зверху вниз, а на кожному рівні — зліва направо (див. рис. 70).
Дерево називається ідеально збалансованим, якщо для кожного
його вузла кількість вузлів лівому і правому піддереві
відрізняються не більше ніж на 1 ( див. рис. 71).
Процедура включення, яка
відновлює ідеальну збалансова-
ність структури дерева, навряд чи
буде вигідною, оскільки таке
відновлення після випадкового
включення - доволі складна
операція. Але її можна спростити,
якщо дати менш строге визначення
"збалансованості". Такий недо-
сконалий критерій збалансованості
може вимагати простої перебудови
дерева при невеликому зменшенні
середньої швидкодії пошуку. Таке визначення дали Адельсон -
Вельский та Ландис, тому дерева називають АВЛ - деревами.
102 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
Алгоритм побудови ідеально збалансованого дерева
""Припустимо, що треба сформувати дерево, яке містить вузли з
числовими значеннями, які будуть прочитані з вхідного файла. Будемо
будувати дерево з п вузлами і мінімальною висотою.
Щоб досягти мінімальної висоти при заданій кількості вузлів,
треба розташувати максимально можливу кількість вузлів на всіх
рівнях, крім найнижчого. Це можна зробити дуже просто, якщо
розподіляти всі вузли, які надходять, порівну ліворуч та праворуч від
кожного вузла. В результаті побудоване дерево при даному п має
вигляд, поданий на рис.72.

Рис. 72
Правило рівномірного розподілу при відомій кількості вузлів п
найкпаїпе формулюється за допомогою рекурсії:
1. Взяти один вузол за корінь.
2. Побудувати ліве піддерево з nl = n div 2 вузлами тим самим
способом.
3. Побудувати праве піддерево з nr = n div г вузлами тим самим
способом.
Бінарні дерева часто використовуються для зображення
множини даних, елементи яких шукаються за унікальним ключем.
Якщо дерево організовано таким чином, що для кожного вузла t i всі
Частосування деревовидних структур ЮЗ
ключі в лівому піддереві менші від ключа tj, а ключі в правому
піддереві більші від ключа t i , то це дерево називають деревом пошуку.
У дереві пошуку можна знайти місце кожного ключа. Якщо дерево
ідеально збалансоване, то для пошуку серед п елементів може бути
потрібно не більше log2n порівнянь. Пошук у такому випадку
проходить по єдиному шляху від кореня до шуканого вузла.

АВЛ дерева
Дерево називається збалансованим по висоті (ЛВЛ-деревом)
тоді і лише тоді, коли для кожного вузла висота його двох піддерев
відрізняється не більше ніж на 1.
Критерій їх збалансованості виявляється найкращим для
реалізації операцій сортування та пошуку інформації. Варто
відзначити, що всі ідеально збалансовані дерева є також ABJI-
деревами.
Це визначення не лише просте, але також приводить до легко
виконуваного балансування, а середня довжина пошуку залишається
практично такою ж, як в ідеально збалансованого дерева.
Зі збалансованими деревами можна виконувати наступні
операції за 0(log 2 n) одиниць часу, навіть у найгіршому випадку:
- знайти вузол із заданим значенням;
- включити вузол із заданим значенням;
- видалити вузол із заданим значенням.
Це є прямим наслідком теореми, доведеної Адельсоном —
Вельским і Ландісом, яка стверджує, що збалансоване дерево ніколи
не буде більше ніж на 45% вище від відповідного ідеального
збалансованого дерева, незалежно від кількості вузлів.
Якщо позначити висоту збалансованого дерева з п вузлами через
hB(n), то справедливе співвідношення:

Оптимальність досягається, якщо дерево ідеально збалансоване,


при n=2 k - l. У цьому випадку оцінка найкраща.
Виникає запитання про те, яка структура найгіршого АВЛ-
збалансованого дерева?
Щоб знайти максимальну висоту h всіх збалансованих дерев з п
вузлами, візьмемо фіксоване h і спробуємо побудувати збалансоване
дерево з мінімальною кількістю вузлів.
104 9. ДЕРЕВОВИДНІ СТРУКТУРИ
Позначимо таке дерево з висотою h через T h і всі такі дерева
назвемо Т-деревами. Очевидно, що Т 0 - порожнє дерево, T 1 — дерево з
одним вузлом. Щоб побудувати дерево Т h для h>l, ми задамо корінь з
двома піддеревами, які також мають мінімальну кількість вузлів.
Отже, піддерева також є Т-деревами. Очевидно, що одне піддерево
обов'язково має висоту h-1, а другому дозволено мати висоту h-2. На
рис. 73 показано дерева Т 2 , Т 3 , Т 4 .

Оскільки принцип їх організації нагадує принцип побудови


чисел Фібоначчі, подібні дерева називаються деревами Фібоначчі. їх
визначають так:
1. Порожнє дерево є дерево Фібоначчі з висотою 0.
2. Один вузол є дерево Фібоначчі з висотою 1.
3. Якщо T h-1 та T h-2 - дерева Фібоначчі, то T h = < T h-1 , х, Th.2> є дерево
Фібоначчі з висотою h.
4. Жодні інші дерева не є деревами Фібоначчі.
Кількість вузлів в T h визначається простим рекурентним
співвідношенням:
N 0 = 0, N, = 1
N h = N h . 1 +l+N h . 2
Отже, найгіршими з ABJ1 дерев є дерева Фібоначчі.
Додавання у збалансоване дерево
Нехай дано корінь г з лівим і правим піддеревами L та R.
Припустимо, що в L включається новий вузол, викликаючи
збільшення його висоти на 1. Можливі три випадки:
'Застосування деревовидних структур 105
1. hL = hR : L та R стають нерівної висоти, але критерій
збалансованості не порушується.
2. hL < hR : L та R отримують рівну висоту, тобто баланс
покращується.
3. hL > hR : критерій збалансованості порушується, і дерево треба
перебудувати.
Для ілюстрації операції додавання у збалансоване дерево
розглянемо дерево на рис. 74. У корені знаходиться вузол зі значенням
8. Вузли з ключами 9 та 11 у таке дерево можна додати без
балансування і при цьому баланс покращується (див. рис. 75).

З такими ж значеннями ключів може бути і витягнуте дерево


(див. рис. 76), але воно не відповідає означенню збалансованих.

Розглянемо включення у
дерево, зображене на рис. 74, вузлів зі
значеннями 1, 3, 5 або 7. Кожне таке
включення вимагає балансування.
Існує лише дві суттєво різні
можливості, які вимагають індивіду-
ального підходу. Інші можуть бути
отримані симетричними перетворен-
нями цих двох.
Випадок 1 виникає при вклю-
ченні вузлів зі значенням 1 або 3.
Вони будуть п о є д н у в а т и с ь до вузла зі
значенням 2 і при цьому буде
106 9. Д Е Р Е В О В И Д Н І С Т Р У К Т У Р И
порушено баланс. Такий випадок вимагає перебалансу вання
обертанням, у якому задіяно два вузли (однократне обертання). На
рис. 77 показано як виглядає дерево до балансування і після
перебалансування.
Випадок 2 виникає при включенні вузлів зі значенням 5 або 7.
Вони будуть під'єднуватись до вузла зі значенням 6 і при цьому також
буде порушено баланс. При перебалансуванні буде задіяно три вузли
(двократне обертання). На рис. 78 показано як виглядає дерево до
балансування і після такого перебалансування.
В обох випадках перекреслені квадрати позначають під'єднаний
вузол.
іастосунання деревовидних структур 107
Прості перетворення цих двох структур відновлюють потрібний
баланс. Відзначимо, що допускаються переміщення лише у
вертикальному напрямку, в той час як відносне горизонтальне
розташування показаних вузлів і піддерев повинне залишатись без
змін.
Алгоритм включення і балансування повністю визначається
способом зберігання інформації про збалансованість дерева. Одне з
вирішень цієї проблеми полягає у зберіганні цієї інформації повністю
у самій структурі дерева. Але в цьому випадку включення торкається
показника збалансованості вузла, що призводить до надзвичайно
високих затрат. Інший варіант - зберігати показник збалансованості
разом з інформацією, пов'язаною з кожним вузлом. Показник
збалансованості можна інтерпретувати як різницю між висотами
правого та лівого піддерева hR - hL. Тоді у кожному вузлі можна
тримати, наприклад, інформацію такого виду: bal: -1..1;
Алгоритм включення:
1. Переміщатись шляхом пошуку, доки не з'ясують, що ключа, який
передбачають включити, немає в дереві.
2. Включити новий вузол і визначити новий показник
збалансованості.
3. Пройти назад шляхом пошуку і перевірити показник
збалансованості для кожного вузла.
Очікувана висота AVL-збалансованого дерева рівна
h = log(n) + с, де с - мала константа (с = 0,25). Це означає, що на
практиці AVL-збалансовані дерева ведуть себе як ідеально
збалансовані дерева, хоча з ними простіше працювати.
Балансування в середньому відбувається приблизно один раз на
два включення. Причому однократне і двократне обертання рівно
можливі.
Через складність операцій балансування вважається, що
збалансовані дерева слід використовувати лише у тому випадку, коли
пошук інформації відбувається частіше, ніж включення.

Видалення із збалансованого дерева


Ця операція ще складніша, ніж включення. Операція балансу-
вання така ж як і під час додавання елемента. Зокрема,-балансування
полягає в однократному чи двократному повороті вузла.
108 9. ДЕРЕВОВИДНІ СТРУКТУРИ
Видалення зі збалансованого дерева ґрунтується на алгоритмі
видалення у пошуковому дереві.
Простими випадками є видалення термінальних вузлів і вузлів з
одним нащадком. Якщо ж вузол, який треба видалити, має два
піддерева, знову будемо замінювати його найправішим вузлом лівого
піддерева. Але при цьому виникає проблема перебалансування.
Видалення з перебалансуванням показано на рис. 79.

Очевидно, що видалення елемента в AVL-дереві може бути


виконано за O(logn) кроків у найгіршому випадку. Проте існує
істотна різниця у виконанні процедур включення та видалення. В той
час, коли включення одного ключа може викликати щонайбільше одне
обертання двох чи трьох вузлів, видалення може вимагати обертання в
кожному вузлі вздовж шляху пошуку. Розглянемо, наприклад,
видалення найправішого вузла у дереві Фібоначчі. Видалення
довільного вузла у дереві Фібоначчі призводить до зменшення його
висоти, а видалення найправішого вузла вимагає максимальної
кількості поворотів. Отже, так отримують найневдаліше поєднання;
найгірший вибір вузла у найгіршому AVL-збалансованому дереві. У
результаті імпіричних перевірок отримано, що в той час як одине
обертання викликається приблизно двома включеннями, то під час
видалення вузла відбувається одне обертання на цілих пять видалень.
Тому видалення з AVL-збалансованого дерева таке ж просте, чи таке
ж складне, як і включення.
Крім таких дерев, існують різноманітні їх модифікації, з якими
можна ознайомитись в [9].
10. СІТКОВІ СТРУКТУРИ
Якщо у відношенні між даними породжений елемент мас
більше одного вихідного, то таке відношення не можна описати як
деревовидну структуру. Його описують у вигляді сіткової
структури. В такій структурі кожен елемент може бути зв'язаний з
довільним іншим. Це певний різновид графів. їх називають прямими
ациклічними графами. Подібно як у деревовидних структурах,
вважається, що напрямок ліній зв'язку між вузлами іде зверху вниз.
Так само, як і у випадку деревовидної структури, прпродньо говорити
про рівні сіткової структури.

Наприклад, на рис. 80 та 81 подано дві сіткові структури. Перша


має два рівні, а друга - три.
Довільна така структура може бути приведена до простішого
виду (зокрема, деревовидного) введенням надлишковості. Зокрема
сіткова структура з рис. 80 може бути зображена як сума двох дерев
(див. рис. 82), а структура з рис. 81 - деревом з продубльованим
вузлом (див. рис. 83).
110 10. СІТКОВІ СТРУКТУРИ

Ще одним прикладом структури, проміжної між деревом і


графом, є ґратка. Ґратка - це орієнтований зв'язний граф без циклів,
але на відміну від дерева, у ґратці допускається, що в одну вершину
може заходити більше одного ребра. Ґратки - це ієрархічні структури,
які мають спільні підструктури. Переважно ґратки визначають як
структури з таким обмеженням - в них є один корінь і один листок. Як
приклад ґратки можна розглянути структуру, використовувану для
зображення множини всіх підмножин, де відношення включення
зображається орієнтованим ребром (див. рис. 84).
Застосування деревовидних структур 111

Ґратки можна розглядати як особливий випадок часткової


впорядкованості. Ґратки, як і сіткові структури, можна зображати як
сукупність деревовидних структур або як дерева з надлишковістю.
11. ГРАФИ
Графи (graphs) - одне з найважливіших понять, яке
використовують для описів об'єктів багатьох областей людської
діяльності, наприклад, у географії, хімії, фізиці, електроніці,
психології, лінгвістиці та багатьох розділах математики [4, 11, 13, 18].
Графи добре підходять для моделювання схеми доріг між
населеними пунктами. Перегляд Web-каталогів передбачає
використання документів, які містять посилання на інші документи;
перехід на документи відбувається за допомогою клікання мишкою на
відповідному посиланні. Сам Інтернет - це граф, у якому вузлами є
документи, а з'єднання є зв'язками. Алгоритми обробки графів є
важливими компонентами пошукових механізмів, які дають змогу
знайти потрібну інформацію.
Графи використовують під час конструювання та налагодження
програм для дослідження їхніх властивостей. До них відносять задачі
аналізу потоку керування у програмі, задачі тестування та верифікації
програми, оцінка її складності та часу виконання. Для зображення
структури викликів величезної системи програмного забезпечення
компілятор також будує графи. Елементами у цьому випадку є різні
функції чи програмні модулі, які утворюють систему; з'єднання
ототожнюються або з можливістю того, що одна функція може
викликати іншу, або з фактичним викликом. У цьому випадку треба
виконати аналіз графа, щоб визначити, як досягнути максимальної
ефективності під час виділення системних ресурсів для конкретної
програми.

Основні поняття
Визначення. Графом G = (V, Е) називають сукупність двох
множин: скінченої непорожньої множини V вершин (vertex) і
скінченої множини Е ребер (edge), які з'єднують пари вершин. Ребра
зображаються невиорядкованими парами вершин (u,v).
Основні поняття 113
Граф зручно зображати діаграмами (кресленнями), причому
один і той самий граф можна зобразити по-різному. На діаграмах
першини зображають точками, а ребра - відрізками прямих чи кривих.
Алгоритми побудови креслень, які дотримуються різних
природних обмежень, інтенсивно досліджували, що привело до
цікавих застосувань. Одним з найпростіших обмежень є вимога того,
щоб ребра не перетинались. Планарний граф ( planar graph) належить
до тих, які можна побудувати без перетину ребер.

У графі можуть бути петлі (.self-loop) — ребра, що починаються


і закінчуються в одній вершині, а також повторювані ребра (кратні,
або паралельні). Якщо в графі немає петель і кратних ребер, то такий
граф називають простим (simple graph). Якщо граф містить кратні
ребра, то граф називають мультиграфом (multі graph).
Ребра вважаються неорієнтованими в тому сенсі, що пари (u, v)
та (v,u) вважаються одним і тим самим ребром.
114 11. Г Р А Ф И
Підграфом (subgraph) називають підмножину ребер деякого
графа (і зв'язаних з ними вершин), які утворюють граф. Якщо
виділити деяку підмножину вершин графа і всі ребра графа, які
з'єднують пари вершин з цієї підмножини, то таку підмножину
називають індукованим підграфом, асоційованим з цими вершинами.
В деяких застосуваннях треба враховувати додаткову інфор-
мацію про предметну область, і відстані між точками повинні бути
витримані у певному масштабі. Такі графи називають евклідовими
графами.
Якщо досліджувати лише властивості з'єднань у графі, то
відмітки вершин розглядаються для зручності позначень, а два графи
вважаються однаковими, якщо вони відрізняються лише мітками
вершин. Два графи називають ізоморфними (,isomorphic•), якщо можна
поміняти мітки вершин так, щоб набір ребер цього графа став
ідентичним до набору вершин другого графа. Виявлення ізоморфізму
двох графів є складною обчислювальною задачею. Складність задачі
полягає в тому, що існує V! способів позначення вершин. Приклад
ізоморфних графів зображено на рис. 87.

Іноді необхідно розглядати впорядковані пари в множині Е.


Наприклад, у графах, використовуваних у Web чи для складання розкладів.
У цьому випадку ребра називають орієнтованими (directed graphs), а
відповідні графи орієнтованими, або орографами. Під час роботи з
орографами елементи множини У зазвичай називають вузлами, а елементи
Е - дугами. Нехай d - дуга орографа d=(u,v); вузол и називається початком
(source) дуги, a v - кінцем (destination) (кажуть також, що дуга виходить з и
і входить у v). Дуги на діаграмах зображають відрізками зі стрілками.
Наприклад, нехай орієнтований граф задано множинами
Основні поняття 115

Його можна зобразити діаграмою на рис. 88.


Іноді доцільно розглядати неорієнтований граф, як орграф, у
якого є два орієнтовані ребра (по одному в кожному напрямку).
Дуже часто в практичних застосуваннях ребрам або дугам
надають певної ваги для того, щоб моделювати деякі величини,
наприклад, час, відстань, вартість. Граф з вагою (зважений граф) - цет акий граф
, ребрам чи дугам
якого поставлено у відповід-
ність деякі значення (зокре-
ма, числові).
Дві вершини u та v є
суміжними (adjacent), якщо
в G існує ребро (u,v). Про
ребро говорять, що воно
інцидентне (incident on) вер-
шинам u та v. Вершина
інцидентна дузі, якщо вона є
її кінцем.
Ребра e i та е j - суміжні, якщо в них існує спільна кінцева
вершина.
Кількість Дуг, які входять у вершину х, називають півстепенем
входу вершини х і позначають d e g + ( x ) . Кількість дуг, які виходять з
вершини х, називають півстепенем виходу вершини х і позначають
deg - ( х ) . Загальна кількість дуг, інцидентних вершині х, називають
степенем вершини і позначають deg (х). Має місце формула

Якщо deg (х) = 1, то вершина х називається кінцевою (висячою).


Якщо deg (х)=0, то вершина х - ізольована.
Має місце теорема про те, що сума степенів вершин дорівнює
подвоєній кількості ребер ( | Е | - потужність множини ребер).

Маршрутом, або шляхом (path) в графі з вершини v1 у вер-


шину v n називають скінчену послідовність в е р ш и н і W = v 1 , v 2 , ... , v n
116 11. ГРАФИ
таких, що кожна наступна вершина (після першої), є суміжною з
попередньою, тобто (v i , v i + 1 ) є Е(G) для кожного і, 1<i<n-1.
Маршрут називають замкнутим, якщо v 1 = v n ; інакше маршрут
відкритий. Маршрут називають простіш шляхом, якщо жодна
вершина (і, отже, жодне з ребер) не з'являється в ньому більше одного
разу. Циклом (cycle) називають простий шлях, у якого перша та
остання вершина одна і та ж.
х

Наприклад, для графа, зображеного на рис. 89, маємо:


маршрут и v х w у w z v w v и-,
простий шлях и v х w z;
цикл (на рисунку виділено) vxwzv.
Іноді використовують термін циклічний шлях для позначення
шляху, у якому перша і остання вершина одна і та ж. Для циклу, який
містить всі вершини графа, використовують термін контур (tour).
Еквівалентне визначення розглядає маршрут як послідовність
ребер, яка з'єднує сусідні вершини. Довжина маршруту, шляху чи
циклу визначається кількістю ребер, які їх утворюють. Наприклад,
довжина маршруту з попереднього прикладу дорівнює 10.
Кожна окрема вершина є шлях довжини 0. У простому графі
шлях повинен складатись принаймні з двох вершин, а цикл — з трьох.
Два шляхи не перетинаються, якщо вони не містять спільних
вершин.
Граф називається зв'язним (connected graph), якщо для
довільних двох вершин існує шлях з однієї вершини в іншу. Інакше
граф називають незв'язним. У незв'язному графі максимальні зв'язні
Основні поняття 117
підграфи називаються компонентами. Наприклад, на рис. 90 зобра-
жено двокомпонентний граф.
Термін максимальний зв'язний підграф означає, що не існує
шляху з вершини такого підграфа у довільну іншу вершину графа,
який би містився у графі.
Зв'язний ациклічний граф називається деревом (tree). Каркасне
дерево (spanning tree) зв'язного графа є підграф, який містить всі
першини цього графа і є деревом. Каркасним лісом (spanning forest)
графа є підграф, який містить всі вершини ірафа і при цьому є лісом.
Наприклад, на рис. 90 каркасне дерево першої компоненти ірафа
виділено.
Графи, у яких всі вершини суміжні,
називають повними графами. У цих
графах присутні всі ребра (див. рис. 91).
Повний граф називають клікою. Можна
визначити доповнення графа G
конструктивно, взявши повний граф,
який має стільки вершин, скільки
початковий граф G, та видаливши з нього
всі ребра ірафа G. Об'єднанням двох графів є граф, отриманий
об'єднанням множини ребер цих графів. Об'єднання графа та його
доповнення є повний граф. Усі ірафи, які мають V вершин є
підграфами повного графа з V вершинами. Загальну кількість графів з
V вершинами обчислюють за формулою 2V(V-1)/2.
Більшість графів з практичних задач містять невелику частину з
всіх м шливих ребер. Граф вважають насиченим (dense), якщо
кількість ребер пропорційна V 2 , і розрідженим (sparse) у
протилежному випадку.
Інформація про те, який граф - розріджений чи насичений -
важлива для вибору ефективного алгоритму
обробки цього графа.
Дводольний граф (bipartite graph) - це
граф, множину вершин якого можна розділити
на такі дві підмножини, що довільне ребро
з'єднує вершину з однієї підмножини лише з
вершиною з іншої підмножини, тобто вершини
можна розбити на дві підмножини, які не
перетинаються (див. рис. 92).
118 11. ГРАФИ
Дводольні графи можуть бути використані для зображення
моделі задач пошуку відповідності.
- Орграф називають сильнозв'язним, якщо для будь-якої пари
вершин кожну з них можна досягнути з іншої (тобто існує шлях від
однієї вершини до іншої).
Шлях називається ейлеровим, якщо він містить всі дуги графа
рівно по одному разу. Шлях називається гамільтоновим, якщо кожна
вершина входить у нього рівно один раз.
Для опрацювання графів існує великий набір різноманітних
алгоритмів, у яких графи треба певним способом представити у
пам'яті комп'ютера. Таке зображення залежить від виду графа чи
алгоритму розв'язування задачі.

Відображення графів у структури зберігання


Зображення графів у вигляді множин вершин та ребер
Граф G = ( V, Е ) може бути повністю визначений простим
переліченням елементів множин вершин V та ребер (або дуг) Е.
Простий перелік нар вершин, які відповідають ребрам неорієн-
тованого ірафа чи дугам орієнтованого, зручний особливо для
негустих графів, коли кількість ребер т значно менша за квадрат
кількості вершин. У цьому випадку має бути відомо, чи граф є
неорієнтованим чи орграфом. Такий спосіб часто використувують для
введення інформації про граф у проіраму. Наприклад, послідовність
1-2, 2-3, 1-3, 1-4, 2-5, 4-5 задає неорієнтований іраф, зображений на
рис. 93.
Очевидно, що обсяг пам'яті у цьому
випадку становить 2т. Проте, щоб
отримати множину вершин, суміж-
них з даною, у гіршому випадку
треба виконати близько т кроків.
Ситуацію можна покращити, впо-
рядкувавши вершини у лексикогра-
фічному порядку.

Основним недоліком такого зображення є незручність його


використання для графів з великими кількостями ребер.
Підображення графів у структури зберігання 119
Зображення графів у вигляді матриць
Одним з найчастіше використовуваних способів є зображення
графів у вигляді різних матриць [4, 11, 12, 16, 18].

У теорії графів класичним


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

Вершина інцидентна ребру, якщо вона є його початком або


кінцем.
Так, для графа з рис. 94 матриця інциденцій матиме вигляд:
10 11 12 13 14
1 1 1 0 0 0
2 0 1 І 1 0
3 1 0 0 1 0
4 0 0 0 0 1
5 0 0 1 0 1
Для орієнтованого графа беруть до уваги факт, що деяка
вершина є початком чи кінцем дуги. У цьому випадку елементи
матриці інциденцій визначають так:

Для ірафа з рис. 95 матриця


інциденцій матиме вигляд
120 П . ГРАФИ

(1,3) (2,1) (2,4) (2,5) (3,2) (4,5)


1 -1 1 0 0 0. 0
2. 0 -1 -1 -1 1 0
3 1 0 0 0 -1 0
4 0 0 1 0 0 -1
5 0 0 0 1 0 1
З алгоритмічної точки зору, матриця інциденцій є, напевно,
найгіршим способом зображення графів, оскільки вимагає п*т квантів
пам'яті, причому більшість з них містять нульові значення. Незручним
є і доступ до інформації, оскільки, наприклад, щоб з'ясувати, які
вершини суміжні із заданою, треба перебрати всі стовпці у рядку, який
відповідає заданій вершині, тобто у гіршому випадку виконати m
кроків.
Кращим способом зображення є матриця суміжності.
Матрицею суміжності A(G) ірафа G з n вершинами
називається квадратна матриця розміру n х n , елементи якої
визначають так:

Очевидно, що така матриця може бути логічною, де істинне


значення позначає наявність ребра, а хибне - його відсутність.
Якщо граф неорієнтований, то матриця суміжності - симетрична
відносно головної діагоналі. Наприклад, для графа з рис. 93, матриця
суміжності матиме вигляд:
1 2 3 4 5
1 0 1 1 1 0
2 1 0 1 -0 1
3 1 1 0 0 0
4 1 0 0 0 1
5 0 1 0 1 0
З матриці суміжності неорієнтованого графа можна отримати
деякі цікаві властивості графа. Зокрема гаку, що сума одиниць у
кожному рядку або в кожному стовпці дорівнює степеневі відповідної
вершини.
Якщо матрицю піднести до квадрата, то на головній діагоналі
будуть степені відповідних вершин.
Иідображення графів у структури зберігання 121
Має місце теорема про те, що елемент aij(p) р-го степеня
матриці суміжності А, дорівнює кількості маршрутів довжини р, що
з'єднують вершини vi, та vj.
Цікавими є коди, отримані з матриці суміжності [4].
Оскільки матриця А симетрична, то можна розглядати лише її
Верхню трикутну частину А'. Змінюючи нумерацію вершин, для
одного і того ж графа отримуємо різні числа. Найбільше з них
визначається для графа однозначно і називається кодом Харарі. Код
Харарі визначає граф однозначно, тому деякі задачі, наприклад, задачу
визначення ізоморфізму двох графів, можна звести до порівняння
відповідних кодів. Нумерація вершин (і матриця суміжності), яка
відповідає коду Харарі, називається канонічною і використовується під
час перелічення чи генерування графів із заданими властивостями.
Для орграфа - матриця суміжності не симетрична. Наприклад,
для орграфа, зображеного на рис. 95 матриця матиме вигляд:
1 2 3 4 5
1 0 0 1 0 0
2 1 0 0 1 1
3 0 1 0 0 0
4 0 0 0 0 1
5 0 0 0 0 0

Якщо ребра чи дуги графа мають певне навантаження, то для


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

Значення 0 - це значення,
яке не позначає ваги, а вказує на
нідсугність дуги між вершинами.
На рис. 96 зображено
навантажений орієнтований іраф
(навантаження виділено).
Матриця суміжності у
цьому випадку виглядатиме гак:
122 11. ГРАФИ

- 1 2 3 4 5
1 0 0 10 0 0
2 10 0 0 0 20
3 0 15 0 0 0
4 0 0 0 0 20
5 0 0 0 0 0

Основною перевагою матриці суміжності є те, що використо-


вуючи прямий доступ до елементів матриці, за один крок (з
константною оцінкою) можна дати відповідь на питання, чи є дві
вершини суміжними (чи існує між ними ребро).
Недоліком є те, що незалежно від того чи матриця розріджена чи
заповнена обсяг пам'яті для її збереження є n2, якщо окремий елемент
матриці зберігається в окремому кванті. Якщо взяти до уваги, що для
збереження окремого елемента використовується один біт, то можна
запакувати в один квант кілька елементів, але для опрацювання
потрібно розпаковувати їх, що вимагає додаткових затрат. Тому
використання матриці суміжності для зображення графа означає, що
алгоритм опрацювання матиме оцінку у гіршому випадку Ω ( п 2 ) [12].
Ще одним способом зображення графа у вигляді матриці є
використання матриці досяжності.
Матрицею досяжності R(G) графа G з п вершинами називають
квадратну матрицю розміру п х п, елементи якої визначають так:

Для орграфа з рис. 95 матриця досяжності матиме вигляд:


1 2 3 4 5
1 1 1 1 1 1
2 • 1 1 1 1 1
3 1 1 1 1 1
4 0 0 0 0 1
5 0 0 0 0 0
Відображення масивів у структури зберігання 123
Матриця досяжності має такі ж розміри, як матриця
суміжності, проте використовується значно рідше.

Зображення графік у вигляді списків суміжності

Стандартним зображенням, використовуваним для ненасичених


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

Щоб отримати доступ до вершин, суміжних із заданою,


достатньо вказати номер заданої вершини. Порядок, у якому
зустрічаються вершини у списках, є довільним. Додавання нового
ребра передбачає створення нової ланки і приєднаная її за
константний час. Проте додавання нової вершини означає зміну
початкового масиву вказівників. Для неорієнтованих графів ребра
фігурують у двох місцях. Так. наприклад, на рис. 97 у списку вузла 1
фігурує вузол 2 і у списку вузла 2 знаходиться вузол 1.
Якщо за назви вершин використовують позначення, відмінні від
цілочислових значень, то по-різному можна здійснювати зв'язок назв
124 11. ГРАФИ
та цілочислових значень-індексів, використовуваних для побудови
списків суміжності, і один граф може бути зображений різними
списками суміжності. Визначити, чи різні списки зображають один і
той самий граф, є доволі складною задачею.
Обсяг пам'яті, використовуваний для зображення графа у
вигляді списків суміжності, пропорційний кількості вершин плюс
кількість ребер (|V| + |Е|), що є перевагою порівняно з матрицями
суміжності, які вимагають пам'яті, пропорційної | V | 2 . Основним
недоліком такого зображення є те, що перевірка існування конкретних
ребер може вимагати часу, пропорційного |V|, на противагу
константному часу для матриці суміжності.

Зображення графів у вигляді багатозв'язних списків

Для зображення зважених графів можна використати


багатозв'язні списки, в яких не лише вершини зображають різними
елементами, але й ребра. Структура елемента, який відповідає
вершині, зображена на рис. 98, а елемента, який відповідає ребру (чи
дузі), на рис. 99. Вказівник на наступне ребро дає змогу зв'язати у
лінійний список усі ребра, які ведуть до суміжних вершин.

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


елементи-ребра, розглянемо зображення за допомогою такого списку
орієнтованого графа, зображеного на рис. 100.
Відображення масивів у структури зберігання 125

Список буде включати


чотири елементи-вершини і чотири
елемеити-дуги. Зображення списку
подано на рис. 101.
126 11. ГРАФИ
Комбіновані зображення графів
Деякі зображення графів поєднують декілька різних форм.
Зокрема, можна поєднати табличне
та епискове зображення, У такому
вигляді граф зображається двома
пов'язаними таблицями: таблиця для
вершин та таблиця для ребер.
Таблиця вершин містить номер
вершини, її мітку (якщо вона
відрізняється від номера), кількість
ребер, інцидентних до вершини (які
починаються у цій вершині), та
вказівник на елемент з таблиці ребер (вказівником може бути індекс
елемента таблиці ребер). Елемент таблиці ребер для кожного ребра
містить йог о вагу та номер (з таблиці вершин) кінцевої вершини ребра.
Таке ж зображення може бути використано для орієнтованих графів.
Наприклад, таблично-спискове зображення графа з рис. 102
подано на рис. 103.

Застосування графів
Графи є найзагальнішими структурами даних, які слугують
моделями багатьох реальних задач. Під час розв'язання багатьох
задач, які стосуються графів, доводиться систематично обходити
вершини або ребра (чи дуги в орографах). Для цього використовують
алгоритми, які називають пошуком у глибину та пошуком у ширину.
Пошук у глибину є узагальненням методу обходу дерева в
прямому порядку. Передбачають, що спочатку всі вершини графа
відмічені як невідвідані. Пошук у глибину починається з вибору деякої
Застосування графів 127
початкової вершини v, яка помічається як відвідана. Потім для кожної
вершини, суміжної з вершиною, яку не відвідували раніше,
рекурсивно застосовується пошук у глибину. Коли всі вершини,
досяжні з v, будуть відвідані, пошук закінчується. Якщо деякі
вершини залишились не відвіданими, то вибирається одна з них і
пошук повторюється. Пошук продовжується доти, доки обходом не
будуть охоплені всі вершини графа. Цей метод називається пошуком у
глибину, оскільки пошук невідвіданих вершин іде у глибину доти,
доки це можливо. Для повернення використовують стек.
Пошук у ширину називається так через те, що при досягненні
під час обходу довільної вершини v далі розглядаються всі вершини,
суміжні з v, тобто відбувається перегляд вершин „у ширину". Так
само, як при застосуванні пошуку у глибину, за допомогою пошуку у
ширину створюється каркасний ліс. При цьому відвідані вершини
поміщають у чергу.
До типових алгоритмів над графами відносять також алгоритми
пошуку шляхів, зокрема, алгоритм Дейкстри пошуку найкоротшого
шляху між двома заданими вершинами та алгоритм Флойда, пошуку
па графі найкоротшого шляху між кожною парою вершин, алгоритм
подвійного пошуку, який знаходить k перших найкоротших шляхів з
деякої вершини до всіх інших вершин початкового графа.
Алгоритми Пріма та Краскала є прикладами алгоритмів
пошуку каркасний дерев у графі.
Для моделювання задачі, яка передбачає пересилання деяких
об'єктів з одного пункту в інший, використовують потоки. Стосовно
графів потік задає спосіб пересилання деяких об'єктів (одиниць
потоку) з однієї вершини (графа) в іншу (стік) по ребрах (або дугах)
графа. Задача про максимальний потік полягає у пошуку способу
пересилання максимальної кількості одиниць потоку з джерела у стік
за умови відсутності перевищення пропускних можливостей ребер
(дуг) початкового графа. Узагальненням алгоритму розв'язку цієї
задачі є алгоритм пошуку потоку мінімальної вартості. Крім цих,
існують інші потокові алгоритми.
Окремий набір становлять алгоритми пошуку паросполук та
покриттів. Паросполукою графа називають деяку множину його ребер
(дуг), таку, що кожна вершина графа інцидентна не більше ніж одному
ребру цієї множини.
128 11. ГРАФИ
Покриттям графа називають деяку множину ребер (дуг) таку,
що кожна вершина графа інцидентна принаймні одному ребру цієї
множини.
Очевидно, що довільна підмножина паросполук графа є також
паросполукою, а довільна множина ребер, яка містить як підмножину
покриття графа, є покриттям цього графа.
Існує кілька типів поширених задач про покриття та
паросполуки, зокрема, про покриття максимальної чи мінімальної
потужності, про покриття з мінімальною вагою, про паросполуку з
максимальною вагою.
Деякі задачі зводяться до пошуку ейлерового (задача листоноші)
чи гамільтонового (задача комівояжера) циклів у графі.
У посібник не входить розгляд цих алгоритмів, а їхній опис
можна знайти у [1,2, 4, 9, 11, 12, 13, 16, 18].
Як приклад використання розглянемо аспект, який розглядають
значно рідше, а саме, графи як моделі програм, даних та процесів. Ці
питання розглядаються, зокрема, у роботі [4]. Багато задач аналізу
програм, які виникають під час оптимізації, трансляції, верифікації,
тестування значно спрощуються, якщо їх розглядати на теоретичних
графових моделях.

Керуючий граф програми


Орієнтований граф G = (V, Е) називають керуючим графом (р-
графом, графом переходів), якщо для нього виконуються такі умови:
1) граф G не містить паралельних дуг;
2) у множині вершин графа виділена одна вершина Sє V - вхід
графа;
3) у множині вершин графа виділена хоча б одна вершина t є V -
вихід графа;
4) кожна вершина х є V досяжна з S;
5) з кожної вершини хє V досяжна вершина виходу t.
Якщо в керуючому графі є кілька виходів, то цей випадок
зводиться до випадку одного, введенням фіктивного виходу,
з'єднаного дугами з кожним фактичним.
Можна вважати, що півстепінь виходу для вхідної та вихідної
вершин дорівнює 1, а для кожної вершини, відмінної від входу та
виходу, дорівнює 1 або 2.
Мирківська граф-модель програми 129
Побудова керуючого графа для конкретної програми
проводиться за простими правилами з потрібним ступенем деталізації
і, як правило, автоматично. Тривіальний розв'язок задачі побудови
полягає у зображенні кожного оператора мови (машинної команди або
будь-якого фрагмента, який призначається одиницею мови) окремою
першиною. При цьому дві вершини суміжні, якщо між відповідними
операторами є передача керування. Точніше, оператор, після
Виконання якого здійснюється передача керування, є початком дуги;
оператор, на який передасться керування — кінцем дуги, а кожна
передача керування — дугою. При такому підході розмір графа дуже
росте унаслідок появи довгих ланцюжків вершин, які відповідають
лінійним ділянкам програм. Окрему лінійну ділянку можна позначити
одною вершиною. Складніші підходи до побудови керуючих графів
полягають у виділенні квазілінійних ділянок, які також зображають
окремою вершиною. Це можливо тоді, коли немає потреби
враховувати внутрішню структуру таких ділянок.
Для прикладу розглянемо побудову керуючого графа для
фрагмента програми, яка реалізує сортування масиву методом
бульбашки. Процедура сортування матиме вигляд:
type a r r = array [1..К] of real;
procedure BUBBLE (var a:arr; N:integer);
var i, j: integer; p: real;
begin
for i:=l to N-l do
for j:=l to N-l do
if a[ j ] > a[j+l] then begin
Р:=a[j];
a[j]:=a[j+l];
a[j+l]:=p
end
end;
Керуючий граф для цієї процедури зображено на рис. 104.

Марківська граф-модель програми


Керуючий граф з вагами, де ваги дуг або вершинне фіксованими
значеннями деяких параметрів, називається марківською граф-
моделлю програми. Вершинам такого графа зазвичай ставлять у
відповідність об'єм або час виконання обчислень (або обидва
130 11. Г Р А Ф И
параметри разом). Час передачі керування вважають рівним нулю.
Кожній дузі, яка відповідає передачі керування від і-го до j-ro
оператора, надають постійного значення q ij ймовірності здійснення
передачі керування (події вибору переходів є незалежними). Для
правильно побудованої програми та її граф-моделі можна із входу
потрапити послідовно у будь-яку вершину, а з будь-якої вершини - на
вихід. Довільне обчислення задається вказанням шляху (не
обов'язково простого) s-t з вхідної вершини s до вихідної вершини t.
Ймовірність реалізації визначається як добуток відповідних
ймовірностей q ij дуг зі шляху s-t. Обмежень на конфігурацію
програми та множину реалізацій обчислень граф-модель не накладає.
Множина реалізацій обчислень є підмножиною s-t шляхів.

Півмарківська граф-модель програми


Іноді доцільно задавати час виконання операторів випадковою
величиною (наприклад, якщо відповідна вершина є об'єднанням
Аналіз та тестування програм 1 31
кількох операторів, які перериваються умовними переходами). Граф-
модель у цьому випадку буде півмарківською, причому час виконання
операторів буде характеризуватись середнім значенням μ та
σ2
дисперсією . Випадкові величини, які визначають час виконання,
вважаються взаємно-незалежними. Якщо всі дисперсії σ2 рівні нулю,
то модель перетворюється у марківську.
Основна складність побудови такої моделі полягає у визначенні
ймовірностей передачі керування. Вони можуть бути визначені або
наперед, якщо розгалуження відбувається після операцій з індексами,
або за відомими розподілами вхідних даних, або шляхом апроксимації
чи моделювання, якщо розподіл ймовірностей для вхідних даних
невідомий. Розрахунок ймовірностей може бути проведений, якщо
вони є функціями параметрів розв'язуваної задачі. Часом просте-
жується існування зв'язку між кількістю виконань операторів, числом
циклів у програмі та ймовірностями переходів.

Аналіз та тестування програм


У разі конструювання га налагодження програм виникають
задачі, які або зводяться до задач теорії графів, або використовують
такі задачі як основу для розв'язку. До них насамперед відносяться
задачі аналізу потоку керування в програмі, задачі тестування та
перевірки правильності програми, оцінки складності та часу
виконання. Зв'язок таких задач та задач теорії графів показано у табл. 1.
Шляхи i-го рівня відображають глибину вкладення оператора;
чим більший рівень вкладеності, тим важче досягнути цього оператора
і, значить, тим складніша перевірка правильності його роботи.
Шлях рівня 0 - це простий s-t шлях; шлях рівня 1 — це простий
шлях чи контур, який починається чи закінчується у вершинах шляху
рівня 0. Відповідно шлях рівня і - це простий шлях чи контур, який
починається або закінчується у вершинах шляхів меншого рівня та у
якого жодна інша вершина чи дуга не входить у шлях меншого рівня.
Шлях рівня i ставиться у відповідність найменшому з можливих
рівнів.
Розглянемо приклад побудови шляхів різних рівнів для
керуючого графа програми для задачі сортування, поданого на рис.
101. Шлях рівня 0 вміщує дві дуги (1,2) та (2,9). Шлях рівня 1 - це
132 11. ГРАФИ
дуги (2,3), (3,4), (4,8), (8,2). Шлях рівня 2 складають дуги (4,5), (5,7),
(7,4). Шлях рівня 3 - це дуги (5,6), (6,7).
Для оцінки часу виконання розглянемо марківську модель програми.
Будемо вважати, що з кожною вершиною v граф-моделі пов'язано
параметр τ i , який задає час виконання відповідного оператора. Час
виконання програми є випадкова величина, яка визначається
параметрами граф-моделі та її структурою. Її можна інтерпретувати як
час блукання точки по графу із заданими часами затримання точки у
вершинах та із заданими ймовірностями переходів. Траєкторія руху
такої точки є шлях із входу s до виходу графа t. Тому обчислення
оцінки зводиться до генерування усеможливих шляхів з s до t.
Введемо поняття шляху m-го рангу. Простий шлях s-t є шлях рангу 0.
Прості контури, які мають спільні вершини чи дуги з шляхами
нульового рангу утворюють s-t шляхи першого рангу. Аналогічно
визначають шляхи довільного рангу. З кожним таким шляхом
зв'язується випадкова величина - час його проходження блукаючою
точкою. Не дивлячись на безконечність множини шляхів, для програм
невеликого розміру можна знайти очікуваний час блукання і розподіл
цієї випадкової величини. Якщо ж час виконання оператора є, в свою
чергу, випадкова величина, то ми маємо справу з півмарківською
моделлю. Процедура розрахунку середнього значення та дисперсії
часу виконання такої програми може бути зведена до покрокової
спрощуючої заміни фрагментів графа з послідовно та паралельно
з'єднаними дугами (а також з петлями) новими фрагментами з
відповідним рекурентним перерахунком ймовірності потрапляння на
дугу q, середнього значення μ та дисперсії σ2 часу для дуги. Час
виконання операторів переноситься на дуги, які виходять з відповідної
вершини. На останньому кроці отримується граф, який складається з
двох вершин та однієї дуги, що їх з'єднує, параметри μ та σ2 якої
дають шукані характеристики графа загалом. Цей же спосіб
розрахунку можна використати і для марківської моделі, якщо
покласти всі σ2 = 0, μ рівними фіксованим значенням часу виконання
операторів.
Тестування - це перевірка правильності програми за допомогою
множини контрольних прикладів - тестів.
Аналіз та тестування програм 133
Таблиця І
Графові моделі задач системного програмування

Задача системного Відповідна задача теорії графів


програмування
Оптимізація програм Знаходження домінаторів
Перелічення бікомпонент
Перелічення контурів
Перелічення інтервалів
Перелічення гамаків
Перелічення лінійних компонент
Знаходження вкладених зон
Знаходження вкладених контурів
Інтервальне зображення графа
Перевірка Знаходження транзитивного та
правильності програм оберненого транзитивного замикання
Знаходження замикання відносно
множини вершин
Перелічення шляхів
Перелічення контурів
Визначення порядку Нумерація вершин
опрацювання операторів Побудова довгих послідовностей
при потоковому аналізі вершин
Тестування програм Знаходження покриття вершин
шляхами
Знаходження покриття дуг шляхами
Знаходження найкоротшого шляху
при наявності додаткових обмежень
Знаходження множини
фундаментальних циклів
Визначення цикломатичного числа
134 11. Г Р А Ф И
неможливо навіть для невеликих програм, то є різні стратегії
тестування.
У простому випадку задача тестування зводиться до перевірки
наперед визначеної кількості операторів у програмі. Це вимагає
побудови шляху в керуючому графі, який проходить через наперед
визначену множину вершин, якщо такий існує. ГІриродньо шукати при
цьому не просто шлях, а найкоротший шлях. Назагал варто
припускати, що кожній вершині поставлено у відповідність вагу w> 0,
яку можна інтерпретувати як складність оператора (або окремого
блоку) програми, який зображений цією вершиною.
Можна очікувати, що відшукання даних для шляху тестування
буде простішим, якщо в ньому буде міститись якнайменше вершин з
множини L вершин, які не підлягають перевірці. Це відповідає задачі
відшукання s-t-шляху з найбільшою вагою, за умови, що вершинам з
множини L надано невеликої від'ємної ваги.
Більш загальний випадок вимагає побудови тестуючих шляхів,
які покривають або всі потрібні шляхи, або всі дуги, або всі вершини
керуючого графа. Ці задачі зводяться до побудови різного виду
покриттів графа шляхами.

Цикломатична міра складності програм


Цикломатичним числом λ ( G ) неорієнтованого графа G називають
величину, обчислену за формулою λ ( G ) = m(G) - n(G) + к(G), де n -
кількість вершин, m - кількість ребер, к— кількість компонент
зв'язності.
Для зв'язного графа к = 1 і вираз для λ отримує вигляд
λ = m - n + 1 = m - (n - 1 ) , звідки випливає, що λ є кількість ребер,
які не належать до довільно вибраного каркасу.
Цикломатичне число орграфа визначають як цикломатичне
число неорієнтованого графа, який отримується заміною всіх дуг
початкового орграфа ребрами. Має місце теорема про те, що в сильно
зв'язному графі цикломатичне число дорівнює числу лінійно
незалежних контурів.
Цю теорему застосовують до керуючого графа програми так.
Нехай програма зображена своїм керуючим графом із входом s та
виходом t. Якщо з'єднати t та s дугою, то отримуємо сильно зв'язний
Цикломатична міра складності програм 135
граф, і, отже, за теоремою можна обчислити кількість лінійно
незалежних контурів. Це дає можливість виразити довільний s-t-шлях
як лінійну комбінацію незалежних контурів.
Наприклад, для керуючого графа з рис. 105 (штрихова лінія
позначає додану дугу) цикломатичне число дорівнює 10-5+1=5.

Тобто в графі існує п'ять незалежних контурів. За такі контури


можна вибрати, наприклад, контури [s, 1, 4, t, s], [1, 4, 1], [s, 1, 4, s], [s,
2, t, s], [s, 3, 2, t, s]. Тоді, наприклад, шлях [s, 1, 4, s, 1,4, 1,4, 1,4, t]
можна виразити як лінійну комбінацію
[s, 1,4, s] +*2[1, 4, l] + [s, 1,4, t, s]
Основна ідея оцінки програми через цикломатичне число
полягає у вимірюванні складності програми шляхом обчислення
лінійно незалежних шляхів v ( G ) та у контролі „розміру" програми
накладанням обмеження зверху на v(G) (замість використання чисто
фізичного розміру).
Цикломатичною складністю програми називається величина
v(G) = λ ( G ) + 2, де λ ( G ) є цикломатичне число відповідного
керуючого графа.
Деякими важливими властивостями цикломатичної складності є
такі:
• v ( G ) >= 1;
• v(G) дорівнює максимальному числу лінійно-незалежних
шляхів у графі G;
136 11. Г Р А Ф И
• зміна кількості функціональних операторів у програмі не
змінює цикломатичної складності;
• граф G має єдиний s-t-шлях тоді і лише тоді, коли v ( G ) = 1;
• додавання однієї дуги збільшує v(G) на одиницю;
• v(G) залежить лише від логічної структури керуючого графа
програми.
Цикломатична складніть є лише однією компонентою у
вимірюванні загальної складності програми. Добре структурована
програма ясніше виражена, її легше читати, вона легша для перевірки
та налагодження. З іншого боку, програма з цикломатичним числом
більше 10 складає враження ненадійної. Цикломатичну складність
небезпечно використовувати для прямого порівняння програм, тому її
варто використовувати як базу для отримання загальніших
результатів.
Цикломатична складність може бути використана для
тестування. Такий метод тестування зводиться до побудови множини
s-t-шляхів, кожний з яких містить дугу, яка не входить в жоден інший
шлях. Припустимо, що програма Р вже написана, обчислена її
цикломатична складність v і визначено кількість шляхів для
тестування р. Якщо р> v, то має виконуватись одна з таких умов:
а) має бути протестована більша кількість шляхів;
б) керуючий граф програми може бути редукований до графа зі
складністю v -р ( тобто може бути видалено v -р умовних операторів);
в) частини програми можуть бути редуковані в лінійний код.
Розглянемо приклади для пояснення цих умов. Припустимо, що
« керуючий граф (див. рис. 106) з цикломатичною складністю v ( G ) = 3 .
Припустимо, що р = 2 і за тестуючі шляхи взято шляхи [s, 1, 3, 5, t] та
[s, 2, 3, 4, t]. При цьому не перевіряються ще два можливі шляхи.
Маємо випадок, коли граф G може бути редукований до графа G1
видаленням умовного оператора 3 (див. рис. 107). Варто зауважити,
що для G1 має місце рівність v = р і складність графа G1 менша
складності G. На практиці цей підхід корисний тоді, коли доводиться
документувати керуючі графи і показувати точно різні тестові шляхи.
Часто буває, що під час порівняння кількості тестованих шляхів
із цикломатичною складністю з'являються нові шляхи для тестування,
які при іншому підході були б не зауважені. Хоча є підстава вважати,
що цикломатична складність дорівнює мінімальній кількості шляхів,
Цикломатична міра складності програм 137
які треба протестувати, на практиці використовуються методи
тестування як з більшою, так і з меншою кількістю шляхів.

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


кількість тестованих шляхів не перевищує цикломатичної складності і
при цьому гарантує, що кожна вершина проходиться принаймні раз.
Проходження кожної вершини умовного оператора відбувається
принаймні один раз по кожній з вихідних дуг, хоча не у всіх можливих
комбінаціях.
На практиці часто використовують підходи, які беруть до уваги
не лише керуючі структури, але й інформаційні зв'язки.
12. МНОЖИНИ
Множини є основними об'єктами вивчення дискретної
математики. Множина - це невпорядкована сукупність деяких
об'єктів, які називають елементами множини і над якими не визначено
жодного відношення. Над множиною можна виконувати певні
операції, які визначають функціональну специфікацію структури.
Над множинами визначено такі основні операції:

Деякі автори, зокрема [1], вводять операції, які стосуються


впорядкованих множин. У них передбачається, що елементи множини
S знаходяться у деякому лінійному порядку, який позначають
звичайними символами відношення. Над такими множинами вводять
операції.
Відображення м н о ж и н у структури зберігання 139

Багато задач, які трапляються на практиці, можна звести до


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

Відображення множин у структури зберігання


Існує два основних підходи до відображення множин в пам'ять
комп'ютера.
1. Для кожного елемента множини зберігати його опис
аналогічно до математичного способу задания множини
переліком її елементів.
2. Визначити всі потенційно можливі елементи множини, а потім
для всякої підмножини такої універсальної множини вказувати
для кожного можливого елемента, чи належить він цій
підмножині, чи ні. Цей спосіб аналогічний гіредикатній формі
задания множини у математиці.
У першому випадку множину зручно зберігати у вигляді вектора
або лінійного списку, у другому — використовують бітові вектори.
Розглянемо ці два способи для зображення множини А з
чотирьох елементів A={'d', 'u', 'k', 'а'}.
Цю множину можна зобразити вектором, для якого вказати
кількість елементів, (див. рис. 108), або ланцюговим списком (див.
рис. 109).
140

Рис. 109

У процесі відображення множини у бітовий вектор усі можливі


елементи множини треба перенумерувати, наприклад, від 1 до N. У
пам'яті виділяється поле з N бітів і належність елемента до множини
відзначається встановленням в 1 і-того біт, якщо і-тий можливий
елемент присутній у множині. Інакше цей біт встановлюється в 0.
Наприклад, для множини малих букв латинського алфавіту
вектор може складатись з 26 бітів, а множину А можна зобразити так
(див. рис. 110).

Множини можна зображати за допомогою дерев, зокрема,


збалансованих. ІДе детальніше розглядається в [1].
Список літератури 141

Список літератури
1. Ахо А., Хопкрофт Дж., Ульман Д. Структуры данных и
алгоритмы. М.: Вильяме, 2000.
2. Вирт Н. Алгоритмы + структуры данных = программы. М.: Мир,
1985 (1988).
3. ГрисД. Построение компиляторов для ЦВМ. М.: Мир, 1972.
4. Евстигнеев В.А. Применение теории графов в программировании.
М.:Наука, 1985.
5. Кнут Д. Искусство программирования. Т. 1: Основные алгоритмы.
М.: Мир, 1976.
6. Кнут Д. Искусство программирования. Т.З: Сортировка и поиск.
М.: Мир, 1978.
7. Кожевникова Г.П. Структуры данных и проектирование
эффективной вычислительной среды. Львов: Вища шк., 1986.
8. Кормен Т., Лейзер Ч., Ривест Р. Алгоритмы: построение и анализ.
М.,МЦНМО, 1999.
9. Костив О. В. Разработка и исследование оценочных базисов
прикладного сложностного анализа вычислений над деревьями:
Дисс. ... канд. физ.-мат. наук. Львов, 1985.
10. Кравець В.Л. Вступ до структур даних. К.: УМК ВО, 1989.
11. Кристофидес Н. Теория графом. Алгоритмический подход. М.:
Мир, 1978.
12. ЛипскийВ. Комбинаторика для программистов. М.: Мир, 1988.
13. Майника Э. Алгоритмы оптимизации на сетях и графах. М.: Мир,
1981.
14. Нагао М., Катаяма Т., Уэмура С. Структуры и базы данных. М.:
Мир, 1986.
15. Притула М.М., Щербина Ю.М. Алгоритми дискретної математики
та обчислювальна складність: Навч. посібн. Львів: Видавничий
центр ЛНУ ім. Івана Франка, 2002.
16. Проценко В.С.,Чаленко П.Й.,Ставровський Ф.Б. Техніка
програмування мовою Сі. К.: Либідь, 1993.
142 СПИСОК ЛІТЕРАТУРИ
17. Седжвик Р. Фундаментальные алгоритмы на С++. Ч. 5. Алгорит-
мы на графах. К.: Diasoft, 2002.
18. Седжвик Р. Фундаментальные алгоритмы на С++. 4.1-4. К.:
Diasoft, 2002.
19. Трамбле Ж., Соренсон П. Введение в структуры данных. М.:
Машиностроение, 1982.
20. Холл П. Вычислительные структуры. Введение в нечисленное
программирование. М.: Мир, 1978.
21. Цегелик Г.Г. Методы автоматической обработки информации.
Львов: Высшая шк., 1981.
143

ЗМІСТ

Вступ З
1. Багаторівневе відображення даних 6
Змістовний рівень опису даних 6
Абстрактний рівень опису даних 8
Декларований рівень даних 10
Базовий рівень даних 11
Агрегований рівень даних 12
2. Структури зберігання даних 13
Концепція типу 13
Квантування нам'яті 14
Послідовні структури зберігання 15
Спискові структури 16
Розсіяні структури 20
3. Абстрактні структури даних та їхнє зображення структурами
зберігання даних 22
4. Масиви 24
Відображення масивів у структури зберігання 26
5. Таблиці А 30
Таблиці з обчислювальними входами 36
Таблиці з прямим доступом 37
Перемішані таблиці 37
Функція розстановки (функція хешування) 43
6. Рядки : 47
Нормальні алгоритми Маркова 48
Формальні граматики 50
Операції над рядками 51
Відображення рядків у структури зберігання 52
7. Динамічні структури 56
Стеки 56
Відображення стеків у структури зберігання 60
Черги 62
Відображення черг у структури зберігання 64
Деки 67
8. Списки 69
Відображення списків у структури зберігання 71
9. Деревовидні структури і 76
Основні визначення 76
Базові оператори над деревами 83
Відображення дерев у структури зберігання 85
Послідовні структури 85
Спискові структури зберігання 88
Застосування деревовидних структур 91
Дерева сортувань 94
10. Сіткові структури 109
11. Графи 112
Основні понятгя 112
Відображення графів у структури зберігання 118
12. Множини 138
Список літератури 141
Н а в ч а л ь н е в и д а н н я

Костів Оксана

СТРУКТУРИ ДАНИХ

для студентів
факультету прикладної математики та інформатики

Редактор М. Михалюк
Корект ор В. Станкевич-Іванова
Технічний редактор С. Сеник

Підп. до друку <£06.2005. Формні 60x84/16. Папір друк. Друк на різогр.


Умови, друк. арк. 8,4. Обл.-вид. арк К.К Тираж 100прим. З а м . ^ 9 У
Видавничий центр Львівського національного університету імені
Івана Франка. 79000 Львів, вул. Дорошенки, 41

You might also like