Skip to content

Commit 19a83b8

Browse files
agorinenkoAnton Gorinenko
andauthored
Деревья поиска (#12)
Co-authored-by: Anton Gorinenko <[email protected]>
1 parent dd34542 commit 19a83b8

File tree

8 files changed

+169
-10
lines changed

8 files changed

+169
-10
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
![Алгоритмы и структуры данных](img/main.jpeg)
66

7-
Данный репозиторий содержит краткое описание алгоритмов и структур данных для практикующих разработчиков. Все примеры
8-
кода представлены с использованием Python.
7+
Данный репозиторий содержит краткое описание алгоритмов и структур данных для практикующих разработчиков. Может быть
8+
использован как справочное руководство или для подготовки к техническому собеседованию. Все примеры кода представлены с
9+
использованием Python.
910

10-
Если вам понравилось содержимое, ставьте звездочку репозиторию на github.
11+
Если вам понравилось содержимое, ставьте звездочку репозиторию на github.
1112
[https://github.com/agorinenko/data-structures-and-algorithms/](https://github.com/agorinenko/data-structures-and-algorithms/)
1213

1314
Об авторе: [https://gorinenko.ru/](https://gorinenko.ru/)
@@ -34,6 +35,10 @@
3435

3536
[Куча](tutorial/heap.md) (в работе)
3637

38+
[Очередь с приоритетом](tutorial/heap.md) (в работе)
39+
40+
[Двухсторонняя очередь](tutorial/heap.md) (в работе)
41+
3742
### Алгоритмы
3843

3944
[Бинарный поиск](tutorial/binary_search.md)
@@ -60,4 +65,5 @@
6065

6166
1. [Платформа для подготовки к техническим собеседованиям](https://leetcode.com/explore/)
6267
2. [Тренировки по алгоритмам от Яндекса](https://yandex.ru/yaintern/algorithm-training_1)
63-
3. [Добрые, добрые структуры данных - курс от Сергея Балакирева](https://stepik.org/course/134212/syllabus)
68+
3. [Добрые, добрые структуры данных - курс от Сергея Балакирева](https://stepik.org/course/134212/syllabus)
69+
4. [Алгоритмы: теория и практика. Структуры данных](https://stepik.org/course/1547/syllabus)

img/graph_16.png

16.9 KB
Loading

img/graph_17.png

3.21 KB
Loading

img/tree_1.png

37.6 KB
Loading

tutorial/dfs.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ def postorder_stack_dfs(node_function: Callable, node: Optional[TreeNode] = None
266266

267267
Для всех ``pre-order``,``in-order`` и ``post-order`` обходов:
268268

269-
Временная сложность - **O(n), где n - общее количество узлов в дереве
269+
Временная сложность - **O(n)**, где n - общее количество узлов в дереве. В случае сбалансированного дерева, временная
270+
сложность составит **O(logN)**.
270271

271-
Пространственная сложность - **O(n). Пространственная сложность зависит от высоты дерева и в худшем случае равна
272+
Пространственная сложность - **O(n)**. Пространственная сложность зависит от высоты дерева и в худшем случае равна
272273
количеству узлов в нем.

tutorial/graph.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
Все виды графов изображены на рисунке ниже.
1717

18-
![img.png](../img/graph.png)
18+
![Графы](../img/graph.png)
1919

2020
Помимо вершин и ребер граф определяется следующей терминологией:
2121

@@ -32,7 +32,8 @@ V1-V3-V4-V5.
3232
**Отрицательный вес цикла** характерен только для взвешенного графа. Вес цикла — это сумма всех весов ребер цикла. Если
3333
это значение отрицательно, то можем говорить об отрицательном весе всего цикла.
3434

35-
**Связанность вершин.** Если для вершины графа имеют между собой хотя бы один путь, то мы можем называть их связанными.
35+
**Связность вершин.** Если все вершины графа имеют между собой хотя бы один путь, то мы можем называть его связным.
36+
Напротив, вершины несвязного графа могут быть разделены по группам, которые называются компонентами связности.
3637

3738
**Инцидентность** – это когда вершина V является началом или концом ребра E.
3839

@@ -48,4 +49,19 @@ in-degree валентность вершины V3 составляет 1, out-d
4849
**Кратчайший путь** - это один из всевозможных путей от одной вершины до другой с минимальным количеством ребер, т.е. с
4950
минимальной длиной.
5051

52+
## Представление графов в памяти
5153

54+
**Матрица смежности** — один из способов представить граф в памяти компьютера. Номера строк и столбцов обозначают номера
55+
вершин, если вершина i связна с вершиной j, то в ячейке матрицы (i, j) записывается значение 1, в противном случае 0.
56+
57+
![Матрица смежности](../img/graph_16.png)
58+
59+
**Список смежности** содержит в себе информацию только о связанных вершинах. Как видим, он более экономный по памяти,
60+
чем матрица смежности.
61+
62+
![Список смежности](../img/graph_17.png)
63+
64+
**Список ребер** - граф также можно представить в виде списка значений вида [(1,2), (1,3), (3,4)]. Элементы массива
65+
представляют собой ребра графа. В ненаправленном графе порядок вершин не важен, в направленном важен.
66+
67+
Матрицу и список смежности, а также список ребер можно реализовать на основе простого массива.

tutorial/intro.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
# Сложность алгоритмов
2-
In-Place Array Operations
2+
In-Place Array Operations
3+
4+
Amortized Analysis
5+
https://en.wikipedia.org/wiki/Amortized_analysis
6+
https://leetcode.com/problems/implement-queue-using-stacks/editorial/

tutorial/tree.md

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,134 @@
11
# Деревья
2-
https://leetcode.com/explore/learn/card/introduction-to-data-structure-binary-search-tree/
2+
3+
**Дерево** — это направленный граф без циклов между узлами. Каждый узел может иметь потомков и родителя. Узел без
4+
родителя называется корнем дерева и является начальным. Вершины, у которых нет потомков, называются листьями.
5+
6+
Бинарное или двоичное дерево — это разновидность дерева, в котором у каждого узла может быть не более двух потомков. При
7+
этом в левом поддереве значения меньше, чем само значение родителя, а в правом поддереве значения должны быть больше. В
8+
случае если дерево сбалансировано, его высота ``К`` будет равна **O(logN)**.
9+
10+
Важно заметить, что если при построении дерева ключи ее узлов идут в случайном порядке, то такие деревья называют
11+
**сбалансированными**. Такие рандомизированные деревья поиска обеспечивают сбалансированность только в вероятностном
12+
смысле. Они обладают следующими свойствами:
13+
14+
1) имеют небольшую высоту
15+
1) имеют хорошую заполненность уровней
16+
17+
На рисунке ниже слева приведен пример сбалансированного дерева, которое получается для
18+
ключей ``[ 9, 2, 13, 7, 1, 11, 14]``. Справа изображено **несбалансированное дерево** для упорядоченных
19+
значений ``[ 1, 2, 7, 9, 11, 13, 14]``, вырожденное в односвязный список.
20+
21+
![Сбалансированное и несбалансированное деревья](../img/tree_1.png)
22+
23+
Существует несколько методов балансировки деревьев. Рассмотрим их чуть позже, а сейчас перейдем к основным операциям,
24+
связанных с деревьями.
25+
26+
## Основные операции с деревьями
27+
28+
### Поиск в дереве
29+
30+
В общем случае, двоичное дерево является графом, поэтому к нему применимы операции обхода в ширину и глубину о котором
31+
можно почитать по ссылкам ниже.
32+
33+
[Обход графа в ширину](bfs.md)
34+
35+
[Обход графа глубину](dfs.md)
36+
37+
Однако для поиска значений узлов наиболее эффективнее было бы использовать свойство двоичных деревьев, что для любого
38+
ключа его левое поддерево содержит только меньшие ключи, а правое — только большие. Ниже представлена функция для поиска
39+
ключа в бинарном дереве.
40+
41+
```python
42+
from typing import Optional
43+
44+
45+
class TreeNode:
46+
def __init__(self, val=0, left=None, right=None):
47+
self.val = val
48+
self.left = left
49+
self.right = right
50+
51+
def __repr__(self):
52+
return str(self.val)
53+
54+
55+
def search_bst(node: Optional[TreeNode], val: int) -> Optional[TreeNode]:
56+
"""
57+
Поиск ключа в бинарном дереве
58+
:param node: узле
59+
:param val: искомое значение
60+
:return: найденный узел, None если не найдено
61+
"""
62+
if not node:
63+
return None
64+
65+
if node.val == val:
66+
return node
67+
68+
if node.val > val:
69+
return search_bst(node.left, val)
70+
71+
if node.val < val:
72+
return search_bst(node.right, val)
73+
```
74+
75+
### Добавление элемента
76+
77+
Добавление элемента происходит по следующим правилам:
78+
79+
1. Происходит поиск добавляемого ключа. Если добавляемое значение уже присутствует в дереве, то оно игнорируется. В
80+
противном случае поиск приводит нас в конечному листу дерева с каким-то значением ``A``.
81+
1. Если добавляемое значение меньше значения ``A`` в текущем узле, то новая вершина становится левым потомком, иначе –
82+
правым.
83+
84+
### Удаление элемента
85+
86+
Удаление элемента является наиболее сложной операцией с точки зрения реализации. Возможны 3 варианта действий:
87+
88+
**Узел является листом**, т.е. не имеет потомков. Тогда мы можем просто его удалить.
89+
90+
**Вершина имеет только одного ребенка.** В этом случае мы удаляем саму вершину, а ее дочернее поддерево подвешиваем к
91+
родителю.
92+
93+
**Если у узла два дочерних узла**, то нужно найти следующий за ним элемент, т.е. минимум в правом поддереве (у этого
94+
элемента не будет левого потомка). Правого потомка подвесить на место найденного элемента, а удаляемый узел заменить
95+
найденным узлом.
96+
97+
### Нахождение максимума и минимума
98+
99+
Нетрудно заметить, что на рисунке выше у дерева слева максимальным элементом является значение 14. Это **самый правый
100+
элемент**, который не имеет правого ребенка (при этом левое поддерево может существовать). Аналогично, минимальным
101+
элементом дерева является значение 1. Это **самый левый элемент**, который не имеет левого ребенка.
102+
103+
### Временная сложность операций
104+
105+
Время работы всех перечисленных операций зависит от высоты дерева ``К``, т.е. равно **O(logN)** в случае
106+
сбалансированности.
107+
108+
## Методы балансировки деревьев
109+
110+
Итак, сложность всех операций с деревом зависит от его высоты. Следовательно, для эффективной работы нам нужно стараться
111+
минимизировать его высоту. Эта операция называется балансировкой. Кратко рассмотрим некоторые виды оптимизации деревьев.
112+
113+
### АВЛ-деревья
114+
115+
АВЛ-дерево — сбалансированное двоичное дерево поиска, в котором поддерживается следующее свойство: для каждой его
116+
вершины высота её двух поддеревьев различается не более чем на 1. В процессе добавления и удаления узлов высота дочерних
117+
поддеревьев некоторых узлов может стать больше 2 или -2. Тогда следует провести ребалансировку дерева путем левого или
118+
правого поворота.
119+
120+
Подробнее [здесь](https://neerc.ifmo.ru/wiki/index.php?title=%D0%90%D0%92%D0%9B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE)
121+
и [здесь](https://habr.com/ru/articles/150732/)
122+
123+
### Красно-черное дерево
124+
125+
Красно-чёрным называется бинарное дерево, у которого каждому узлу сопоставлен дополнительный аттрибут – цвет, а также
126+
выполняются следующие свойства:
127+
128+
1. Каждый узел промаркирован красным или чёрным цветом
129+
2. Корень и конечные узлы (листья) дерева – чёрные
130+
3. У красного узла родительский узел – чёрный
131+
4. Все простые пути из любого узла x до листьев содержат одинаковое количество чёрных узлов – black-height(x)
132+
5. Чёрный узел может иметь чёрного родителя
133+
134+
Подробнее [здесь](https://neerc.ifmo.ru/wiki/index.php?title=%D0%9A%D1%80%D0%B0%D1%81%D0%BD%D0%BE-%D1%87%D0%B5%D1%80%D0%BD%D0%BE%D0%B5_%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE)

0 commit comments

Comments
 (0)