Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
31
Добавлен:
15.01.2021
Размер:
289.09 Кб
Скачать

177

Модуль 3. Лекція 12. Дерева

1. Дерево як абстрактна структура даних

2. Обхід дерева в глибину (DFS - Depth-First Search)

3. Обхід дерева в ширину. Алгоритм Breath-First-Search (BFS)

4. Елемент управління TreeView для Windows Form

1. Дерево як абстрактна структура даних

Деревоподібна структура даних, або дерево, є нелінійною структурою, що зображує ієрархічні зв'язки типу «предок-нащадок»: компонент-предок може мати багато нащадків, хоча для кожного компонента-нащадка визначено не більше одного предка.

Приклад – ієрархія класів, структура компанії, інші ієрархічні структури даних.

Рис. 1. Дерево, яке зображає структуру групи проекту

Визначення 1.

Дерево з базовим типом Т - це або порожня структура, або вузол типу Т, з яким зв'язана скінченна кількість дерев із базовим типом Т, що їх називають під-деревами. Під-дерева (гілки) будь-якого вузла не перетинаються, тобто не мають спільних вузлів.

Вузол дерева, який не має предків, називається коренем. Серед будь-якої пари безпосередньо зв'язаних вузлів дерева можна виділити предка та нащадка. Вважається, що корінь дерева розташований на першому рівні (іноді відлік рахують від нуля). Кожний вузол-нащадок рівня k має предка на рівні k - 1. Максимальний рівень дерева називається його глибиною або висотою. Наприклад, на рис. 2 зображено дерево, глибина якого дорівнює 2. Вузол дерева, який не має нащадків, називається листком. Кількість безпосередніх нащадків вузла називається його ступенем. Максимальна ступінь вузла у певному дереві називається ступенем дерева.

Рис. 2. Дерево

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

2. Обхід дерева в глибину (dfs - Depth-First Search)

Виділяються декілька методів обходу в глибину, найбільш популярні з них це:

- прямий

- симетричний

- зворотний обхід.

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

Приклади: дерева https://ru.wikipedia.org/wiki/Обход дерева

Прямий обхід

F, B, A, D, C, E, G, I, H.

Симетричний обхід

(центрований)

A, B, C, D, E, F, G, H, I.

Зворотний обхід

A, C, E, D, B, H, I, G, F.

Приклад 1. Реалізація структури дерево на мові C#.

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

Кожний вузол рекурсивно визначається через самого себе. Кожний вузол дерева (TreeNode<T>) містить список дочірніх вузлів, які є також вузлами. Саме дерево є окремим класом Tree<T>, яке може бути пустим, або містити кореневий вузол. Клас Tree<T> реалізує базові операції над деревом, такі як створення і обхід дерева.

Рис. 3. Дерево чисел

У якості базової структури для зберігання вузлів використано клас List<T>.

  1. Створимо універсальний клас для представлення вузла – TreeNode.

using System;

using System.Collections.Generic;

namespace Lab12_1

{

//Представлення вузла дерева

// T – тип значення у вузлах

public class TreeNode<T>

{ // Містить значення у вузлах

public T Value { get; set; }

// вказує чи має поточний вузол предка

private bool hasParent;

// Містить нащадків вузла (нуль або більше)

private List<TreeNode<T>> children;

//конструктор класу - створює вузол

//value – значення вузла

public TreeNode(T value)

{

if (value == null)

{

throw new ArgumentNullException("Cannot insert null value!");

}

Value = value;

children = new List<TreeNode<T>>();

}

//кількість нащадків вузла - властивість

public int ChildrenCount

{

get

{

return children.Count;

}

}

//додавання до вузла нащадка - child

public void AddChild(TreeNode<T> child)

{

if (child == null)

{

throw new ArgumentNullException("Cannot insert null value!");

}

if (child.hasParent)

{

throw new ArgumentException("The node already has a parent!");

}

child.hasParent = true;

children.Add(child); //додаємо вузол у список

}

//Отримання нащадка вузла за вказаним індексом

//повертає нащадка, який знаходиться за вказаним індексом

public TreeNode<T> GetChild(int index)

{

return children[index];

}

}

}

  1. Створимо клас для представлення самого дерева

namespace Lab12_1

{

// Представляє дерево як структуру даних

// T - параметр типу значення вузлів

public class Tree<T>

{ // The root of the tree

private TreeNode<T> root; //перший вузол - корінь дерева

// перший конструктор - створює дерево з кореневим вузлом

// value - вузол типу T

public Tree(T value)

{

if (value == null)

{

throw new ArgumentNullException("Cannot insert null value!");

}

root = new TreeNode<T>(value);

}

// другий констуктор - створює підлеглі під-дерева

/// value - значення кореневого вузла

/// children - нащадки кореневого вузла

public Tree(T value, params Tree<T>[] children)

: this(value)

{

foreach (Tree<T> child in children)

{

root.AddChild(child.root);

}

}

// Корінь дерева або нуль, якщо дерево пусте - властивість

public TreeNode<T> Root

{

get

{

return root;

}

}

// Обхід дерева в глибину (DFS - Depth-First Search)

// root - корінь дерева, який відвідується

// spaces - пробіли для відображення вкладеності рівнів дерева

private void PrintDFS(TreeNode<T> root, string spaces)

{

if (this.root == null)

{

return;

}

Console.WriteLine(spaces + root.Value);

TreeNode<T> child = null;

for (int i = 0; i < root.ChildrenCount; i++)

{

child = root.GetChild(i);

PrintDFS(child, spaces + " ");

}

}

public void TraverseDFS()

{

PrintDFS(root, string.Empty);

}

}

}

  1. Тепер створимо програму для роботи з деревом

namespace Lab12_1

{

class Program

{

static void Main(string[] args)

{

// Створення дерева

Tree<int> tree = new Tree<int>(7,

new Tree<int>(19,

new Tree<int>(1),

new Tree<int>(12),

new Tree<int>(31)),

new Tree<int>(21),

new Tree<int>(14,

new Tree<int>(23),

new Tree<int>(6))

);

// Обхід дерева вгибину і виведення на консоль

tree.TraverseDFS();

Console.ReadKey();

}

}

}

Результат:

Алгоритм прямого обходу (пошуку) в глибину (DFS - Depth-First Search)

У класі Tree <T> реалізований метод TraverseDFS (), який викликає закритий метод PrintDFS (TreeNode <T> корінь, рядок пробілів), який проходить по дереву в глибину і виводить на консоль його елементи з додаванням пробілів для зсуву елементів праворуч для того, щоб відобразити структуру дерева. Алгоритм пошуку в глибину має на меті відвідати кожен з вузлів дерева точно один раз.

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

Ми можемо описати алгоритм пошуку в глибину наступними простими кроками:

1. Відвідати поточний вузол, починаючи з кореневого (наприклад, вивести його на консоль або обробити певним чином).

2. Послідовно рекурсивно відвідувати кожен з дочірніх вузлів поточних вузлів (проходити під-дерева поточного вузла). Це може бути зроблено за допомогою рекурсивного виклику того ж методу для кожного дочірнього вузла.

Розглянемо ще приклад створення дерева

Приклад 2. Створення дерева, яке представляє структуру МНТУ. Рівні дерева рахуються від 0. В цьому прикладі для обходу дерева також використовується прямий пошук в глибину.

Корінь дерева – МНТУ, на рівні 1 розташовані Департаменти та факультети, на рівні 2 – кафедри.

Для представлення дерева створимо 2 універсальні класи: GenericTree<T> для представлення дерева і вузлів, і похідний клас GenericTreeNext, в якому призначаються значення вузлів. Значеннями є назви вузлів (текстові поля).

namespace Lab12_Tree_2

{

public class GenericTree<T> where T : GenericTree<T>

{

List<T> children; //список вузлів

public GenericTree() //конструктор класу

{

children = new List<T>(); //створення колекції вузлів дерева

}

public virtual void AddChild(T newChild)

{

children.Add(newChild); //додавання вузла

}

public void Traverse(Action<int, T> visitor)

{

traverse(0, visitor);

}

protected virtual void traverse(int depth, Action<int, T> visitor)

{

//обхід дерева - рекурсивний метод

visitor(depth, (T)this);

foreach (T child in children)

child.traverse(depth + 1, visitor);

}

}

public class GenericTreeNext : GenericTree<GenericTreeNext>

{

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

public string Name { get; set; } // значення вузла

public GenericTreeNext(string name)

{

Name = name;

}

}

}

В програмі створюється об’єкт дерева та виконується формування його вузлів.

namespace Lab12_Tree_2

{

class Program

{

static void Main(string[] args)

{

GenericTreeNext tree = new GenericTreeNext("Корінь дерева_МНТУ");

tree.AddChild(new GenericTreeNext("Перший_рівень Департаменти"));

GenericTreeNext inter = new GenericTreeNext("Перший рівень Факультет КННІ");

inter.AddChild(new GenericTreeNext("кафедра КНІС"));

inter.AddChild(new GenericTreeNext("кафедра математики"));

tree.AddChild(inter);

GenericTreeNext inter1 = new GenericTreeNext("Перший рівень Факультет економіки і менеджменту");

inter1.AddChild(new GenericTreeNext("кафедра міжнародної економіки"));

inter1.AddChild(new GenericTreeNext("кафедра Облік і аудит"));

tree.AddChild(inter1);

tree.AddChild(new GenericTreeNext("Перший рівень Факультет фізреабілітації"));

tree.Traverse(NodeWorker);

Console.ReadKey();

}

static void NodeWorker(int depth, GenericTreeNext node)

{// виведення вузлів дерева на консоль

Console.WriteLine("{0}{1}: {2}", String.Join(" ", new string[depth + 1]), depth, node.Name);

}

}

}

Результат.

Приклад 3. Створення дерева з використанням LinkedList

namespace Lab12_3

{

public class TreeNode<T>

{

public T Data { get; set; }

public TreeNode<T> Parent { get; set; }

public ICollection<TreeNode<T>> Children { get; set; }

public TreeNode(T data)

{

Data = data;

Children = new LinkedList<TreeNode<T>>();

}

public TreeNode<T> AddChild(T child)

{

TreeNode<T> childNode = new TreeNode<T>(child) { Parent = this };

Children.Add(childNode);

PrintNode(childNode);

return childNode;

}

public void PrintNode(TreeNode <T> childNode)

{

Console.WriteLine(childNode.Data);

}

}

}

В програмі створюється об’єкт дерева та виконується формування його вузлів.

namespace Lab12_3

{

class Program

{

static void Main(string[] args)

{

TreeNode<string> root = new TreeNode<string>("root");

{

TreeNode<string> node0 = root.AddChild("node0");

TreeNode<string> node1 = root.AddChild("node1");

TreeNode<string> node2 = root.AddChild("node2");

{

TreeNode<string> node20 = node2.AddChild(null);

TreeNode<string> node21 = node2.AddChild("node21");

{

TreeNode<string> node210 = node21.AddChild("node210");

TreeNode<string> node211 = node21.AddChild("node211");

}

}

TreeNode<string> node3 = root.AddChild("node3");

{

TreeNode<string> node30 = node3.AddChild("node30");

}

}

Console.ReadKey();

}

}

}

Приклад 4. Обхід дерева каталогів файлової системи в глибину Алгоритм Depth-First-Search (DFS)

using System;

using System.IO;

namespace TraverserDir

{

/// <summary>

/// Sample class, which traverses recursively given directory

/// based on the Depth-First-Search (DFS) algorithm

/// </summary>

public static class DirectoryTraverserDFS

{ /// <summary>

/// Traverses and prints given directory recursively

/// </summary>

/// <param name="dir">the directory to be traversed</param>

/// <param name="spaces">the spaces used for representation

/// of the parent-child relation</param>

private static void TraverseDir(DirectoryInfo dir, string spaces)

{

// Visit the current directory

Console.WriteLine(spaces + dir.FullName);

DirectoryInfo[] children = dir.GetDirectories();

// For each child go and visit its sub-tree

foreach (DirectoryInfo child in children)

{

TraverseDir(child, spaces + " ");

}

}

/// <summary>

/// Traverses and prints given directory recursively

/// </summary>

/// <param name="directoryPath">the path to the directory

/// which should be traversed</param>

static void TraverseDir(string directoryPath)

{

TraverseDir(new DirectoryInfo(directoryPath), string.Empty);

}

static void Main(string[] args)

{

TraverseDir("D:\\МНТУ\\For_КПІ-71\\Алгоритми");

Console.ReadKey();

}

}

}