350 Алгоритми та структури даних

Конспекти лекцій та Лабораторні роботи з дисципліни "Алгоритми та структури даних" для III курсу спеціальності 121 "Інженерія програмного забезпечення" ОКР "Фаховий молодший бакалавр" Херсонського політехнічного фахового коледжу Державного університету "Одеська політехніка"

View the Project on GitHub solidol/nmk-asd

Перелік лекцій

Динамічні структури даних. Дерева

Зв’язне представлення даних в пам’яті

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

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

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

Переваги зв’язного представлення даних:

Разом з тим зв’язне представлення не позбавлене й недоліків, основні з яких:

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

Дерева

Дерево- це граф, який характеризується наступними властивостями:

Назва„дерево” виникла з логічної еквівалентності дерево-видної структури абстрактному дереву з теорії графів. Лінія зв’язку між парою вузлів дерева називається гілкою. Ті вузли, які не посилаються ні на які інші вузли дерева,називаються листям. Вузол, що не є листком або коренем, вважається проміжним або вузлом галуження.

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

Введемо ще деякі поняття, пов’язані з деревами. Вузол X називається предком, або батьком, а вузли Y і Z називаються нащадками, або синами, їх,відповідно, між собою називають братами. Причому, лівий син є старшим сином, а правий - молодшим. Кількість піддерев даної вершини називається мірою цієї вершини.

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

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

Повне дерево містить максимально можливу кількість вузлів на кожному рівні, крім нижнього. Повні дерева мають ряд важливих властивостей.

По-перше,це найкоротші дерева, які можуть містити задану кількість вузлів. Досить корисна властивість повних дерев полягає в тому, що вони можуть бути дуже компактно записані в масивах. Якщо пронумерувати вузли в „природному” порядку,зверху вниз і зліва направо, то можна помістити елементи дерева в масив у цьому ж порядку.

Існують m-арні дерева, тобто такі дерева, в яких півміра виходу кожної вершини менша або рівна т. Якщо півміра виходу кожної вершини в точності рівна або т, або нулю, то таке дерево називається повним m-арним деревом. При т=2 такі дерева називаються відповідно бінарними, або повними бінарними.

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

Правило побудови бінарного дерева з будь-якого дерева:

Таким чином перебудувати дерево заправилом:

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

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

Описаний вище метод представлення довільних впорядкованих дерев за допомогою бінарних дерев можна узагальнити на представлення довільного впорядкованого лісу.

Правило побудови бінарного дерева з лісу: корені всіх піддерев лісу з’єднати горизонтальними зв’язками. В отриманому дереві вузли в даному прикладі будуть розташовуватися на трьох рівнях. Далі перебудовувати по раніше розглянутому плану.В результаті перетворення впорядкованого лісу в бінарне дерево виходить повне бінарне дерево з лівим і правим піддеревом.

Дерева можна представляти за допомогою зв’язних списків і масивів (або послідовнихсписків).

Частіше всього використовується зв’язне представлення дерев, так як воно дуже сильно нагадує логічне. Зв’язне зберігання полягає в тому, що задається зв’язок від батька до синів. В бінарному дереві є два покажчики, тому зручно вузол представити у вигляді структури в якій left - покажчик на ліве піддерево, right - покажчик на праве піддерево, inf - містить інформацію, яка зв’язана з вершиною і має наперед визначений тип - data.

Над деревами визначені наступні основні операції:

Потрібна вершина в дереві шукається за ключем. Пошук в бінарному дереві здійснюється таким чином.

Нехай побудовано деяке дерево і вимагається знайти вузол з ключем X. Спочатку порівнюємо з X ключ, що знаходиться в корені дерева. У разі рівності пошук закінчений і потрібно повернути покажчик на корінь в якості результату пошуку. Інакше переходимо до розгляду вершини, яка знаходиться зліва внизу, якщо ключ X менший тільки що розглянутого, або справа внизу, якщо ключ X більший щойно розглянутого. Порівнюємо ключ X з ключем, що міститься в цій вершині, і т.д.Процес завершується в одному з двох випадків:

  1. знайдена вершина, що містить ключ, рівний ключу X;
  2. в дереві відсутня вершина, до якої потрібно перейти для виконання чергового кроку пошуку.

В першому випадку повертається покажчик на знайдену вершину. В другому - покажчик на вузол, де зупинився пошук (що зручне для побудови дерева ).

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

Алгоритм пошуку потрібної вершини, взагалі кажучи, той же самий, що і при пошуку вершиниіз заданим ключем. Ця вершина буде знайдена в той момент, коли в якості чергового покажчика, який визначає гілку дерева, в якій треба продовжити пошук,виявиться покажчик NULL.

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

Бінарне дерево можна обходити трьома основними способами: низхідним, змішаним і висхідним (можливі також зворотний низхідний, зворотний змішаний і зворотний висхідний обходи). Прийняті назви методів обходу зв’язані з часом обробки кореневої вершини: До того як оброблено обидва його піддерева, після того, як оброблено ліве піддерево, але до того як оброблено праве, після того, як оброблено обидва піддерева. Використовувані назви методів відображають напрям обходу в дереві: від кореневої вершини вниз до листя - низхідний обхід; від листя вгору до кореня - висхідний обхід, і змішаний обхід - від найлівішого листка дерева через корінь до найправішого листка.

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

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

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

  1. Обробка кореневої вершини;
  2. Низхідний обхід лівого піддерева;
  3. Низхідний обхід правого піддерева.
  4. Кінець алгоритму.

Змішаний обхід можна описати таким чином:

  1. Спуститися по лівій гілці із запам’ятовуванням вершин в стеку;
  2. Якщо стек порожній те перейти до п.5;
  3. Вибрати вершину із стеку і обробити дані вершини;
  4. Якщо вершина має правого сина, то перейти до нього; перейти до п.1.
  5. Кінець алгоритму.

Рекурсивний змішаний обхід описується таким чином:

  1. Змішаний обхід лівого піддерева;
  2. Обробка кореневої вершини;
  3. Змішаний обхід правого піддерева.
  4. Кінець алгоритму.

Складність реалізації висхідного обходу полягає в тому, що на відміну від попереднього методу в цьому алгоритмі кожна вершина запам’ятовується в стеку двічі: вперше - коли обходиться ліве піддерево, і другий раз - коли обходиться праве піддерево.Таким чином, в алгоритмі необхідно розрізняти два види стекових записів: 1-йозначає, що в даний момент обходиться ліве піддерево; 2-й - що обходиться праве, тому в стеку запам’ятовується покажчик на вузол і ознаку (код-1 і код-2 відповідно).

Алгоритм висхідного обходу можна представити таким чином:

  1. Спуститися по лівій гілці із запам’ятовуванням вершини в стеку як 1-й вид стекових записів;
  2. Якщо стек порожній, то перейти до п.5;
  3. Вибрати вершину із стека, якщо це перший вид стекових записів, то повернути його в стек як 2-й вид стекових записів; перейти до правого сина; перейти до п.1, інакше перейти до п.4;
  4. Обробити дані вершини і перейти доп.2;
  5. Кінець алгоритму.

Рекурсивний змішаний обхід описується таким чином:

  1. Висхідний обхід лівого піддерева;
  2. Висхідний обхід правого піддерева;
  3. Обробка кореневої вершини.
  4. Кінець алгоритму.

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

Теми для самостійного вивчення

  1. Основи представлення дерев у пам’яті: зв’язне та масивне представлення.
  2. Реалізація основних операцій над деревами у C/C++ (додавання, видалення, пошук, обхід).
  3. Бінарні дерева пошуку: властивості, застосування, балансування.
  4. Алгоритми обходу дерев: прямий (preorder), симетричний (inorder), зворотний (postorder).
  5. Збалансовані дерева (AVL, червоно-чорні дерева): принципи роботи, переваги.
  6. Практичне застосування дерев у програмуванні (словники, індекси, парсери).

Контрольні питання

  1. Що таке дерево як структура даних? Які основні властивості дерев?
  2. Які типи дерев ви знаєте? Чим відрізняється бінарне дерево від m-арного?
  3. Як організовано зв’язне представлення дерев у пам’яті комп’ютера?
  4. Які основні операції виконуються над деревами? Як реалізується обхід дерева?
  5. Що таке бінарне дерево пошуку? Які його властивості та переваги?
  6. Як здійснюється додавання та видалення вузлів у бінарному дереві?
  7. Які існують способи обходу дерев? Для чого використовується кожен з них?
  8. Що таке повне та збалансоване дерево? Які переваги має збалансоване дерево?
  9. Як організувати очищення пам’яті для динамічних дерев у C/C++?
  10. Які практичні задачі ефективно вирішуються за допомогою дерев?