- •Модуль 3. Лекція 12. Дерева
- •1. Дерево як абстрактна структура даних
- •2. Обхід дерева в глибину (dfs - Depth-First Search)
- •3. Обхід дерева в ширину. Алгоритм Breath-First-Search (bfs)
- •4. Елемент управління TreeView у Windows Form
- •4.1. Створення дерева TreeView з використанням вікна властивостей
- •4.2. Програмне створення дерева і робота з ним
- •Спочатку потрібно створити форму і розмістити на ній елемент TreeView.
- •Написати метод формування дерева. Метод викликати з конструктора чи з методу Load.
- •Обхід дерева.
- •Видалення вузла.
- •Додавання (вставка) вузлів
- •Робота з листями дерева.
Модуль 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>.
Створимо універсальний клас для представлення вузла – 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];
}
}
}
Створимо клас для представлення самого дерева
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);
}
}
}
Тепер створимо програму для роботи з деревом
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();
}
}
}