Skip to content

Commit

Permalink
Add dfs & tree height
Browse files Browse the repository at this point in the history
  • Loading branch information
everysoftware committed Jan 25, 2025
1 parent eae87f8 commit 9038f0e
Show file tree
Hide file tree
Showing 16 changed files with 294 additions and 280 deletions.
77 changes: 77 additions & 0 deletions src/m_trees/ABSTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Деревья

## Деревья

**Дерево** - это иерархическая структура данных, состоящая из вершин и ребер, которые их соединяют.

Деревья подобны графам, однако, между ними есть несколько ключевых отличий:

* Иерархия. Деревья имеют строгую иерархию (корень, ветви, листья). Графы не имеют явной иерархии.
* Связи. В деревьях каждая вершина (кроме корня) имеет ровно одного родителя. В графах вершина может быть связана с
любым количеством других вершин.
* Циклы. Деревья не содержат циклов. Графы могут содержать циклы.

Термины, которые используются при работе с деревьями:

| Понятие | Описание |
|-----------------|--------------------------------------------------------|
| Вершина | Элемент дерева, который содержит данные. |
| Ребро | Связь между двумя вершинами. |
| Корень | Вершина, которая не имеет родителя. |
| Листья | Вершины, которые не имеют дочерних вершин. |
| Путь | Последовательность вершин, соединенных ребрами. |
| Высота вершины | Расстояние от корня до вершины. Уровень корня равен 0. |
| Высота дерева | Длина самого длинного пути от корня до листа. |
| Степень вершины | Количество дочерних вершин. |
| Поддерево | Часть дерева, состоящая из вершины и всех ее потомков. |

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

## Обход в глубину

**DFS** (Depth-First Search) - обход в глубину - это алгоритм обхода графа или дерева, который начинается с начальной
вершины и идет вглубь каждой ветви, пока не достигнет конечной вершины.

Сложность DFS: O(N), N = V + E, где V - количество вершин, E - количество ребер.

Пример.

Дерево:

```text
A : B, C
B : D, E
C : F
```

Визуализируем дерево:

```text
A
/ \
B C
/ \ \
D E F
```

Обход в глубину начинается с вершины A и проходит по всем вершинам графа:

```text
A -> B -> D -> E -> C -> F
```

## Двоичные деревья

**Двоичное дерево** - это дерево, в котором каждая вершина имеет не более двух дочерних вершин.

## Обход двоичных деревьев

Виды обходов:

* **Inorder** (центрированный). Сначала посещаются все узлы в левом поддереве, затем корневой узел, после чего — все
узлы в правом поддереве.
* **Preorder** (прямой). Сначала посещается корневой узел, затем все узлы в левом поддереве и после этого — все узлы в
правом поддереве.
* **Postorder** (обратный). Сначала посещаются все узлы в левом поддереве, затем корневой узел и после этого — все узлы
в правом поддереве.
74 changes: 74 additions & 0 deletions src/m_trees/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Задачи по деревьям

## A. Обход двоичного дерева

| Поле | Значение |
|-----------|---------------------------------------------------|
| Сложность | Средняя |
| Источник | https://stepik.org/lesson/45970/step/1?unit=24123 |

Дано двоичное дерево. Обойти дерево с помощью DFS Inorder, DFS Preorder и DFS Postorder.

Формат входа. Первая строка содержит натуральное число n. Следующие n строк содержат информацию о вершинах дерева.
Каждая строка содержит три числа: номер вершины, номер левой дочерней вершины и номер правой дочерней вершины.
Если у вершины нет дочерних вершин, то соответствующее значение равно -1.

Формат выхода. Вывести обходы дерева в порядке DFS Inorder, DFS Preorder и DFS Postorder.

Пример.

Ввод:

```
10
0 7 2
10 -1 -1
20 -1 6
30 8 9
40 3 -1
50 -1 -1
60 1 -1
70 5 4
80 -1 -1
90 -1 -1
```

Вывод:

```
50 70 80 30 90 40 0 20 10 60
0 70 50 40 30 80 90 20 60 10
50 80 90 30 40 70 10 60 20 0
```

## B. Высота дерева

| Поле | Значение |
|-----------|---------------------------------------------------|
| Сложность | Средняя |
| Источник | https://stepik.org/lesson/41234/step/2?unit=19818 |

Дано дерево. Вершины дерева пронумерованы от 0. Требуется определить высоту дерева. Высотой
вершины называется расстояние от неё до самой удалённой вершины. Высотой дерева называется высота корня дерева.

Формат входа. Первая строка содержит натуральное число n. Вторая строка содержит n целых чисел
parent[0], ... , parent[n − 1]. Для каждого 0 ≤ i ≤ n−1, parent[i] — родитель вершины i; если parent[i] = −1,
то i является корнем. Гарантируется, что корень ровно один. Гарантируется, что данная последовательность задаёт
дерево.

Формат выхода. Высота дерева.

Пример.

Вход:

```
10
9 7 5 5 2 9 9 9 2 -1
```

Выход:

```
4
```
File renamed without changes.
2 changes: 0 additions & 2 deletions src/tba_trees/dfs.py → src/m_trees/dfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ def dfs_wrapper(tree: dict[str, list[str]], node: str, visited: set[str], result
# посещение вершины
visited.add(node)
result.append(node)

# переход к детям этой вершины
for child in tree[node]:
if child not in visited:
dfs_wrapper(tree, child, visited, result)

return result


Expand Down
44 changes: 44 additions & 0 deletions src/m_trees/tree_height.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# O(n^2)
def tree_height_naive(a: list[int], parent: int = -1) -> int:
height = 0
# Находим всех детей вершины parent.
children = [i for i, j in enumerate(a) if j == parent]
for child in children:
height = max(height, 1 + tree_height_naive(a, child))
return height


# O(n)
def tree_height_stack(a: list[int]) -> int:
root, adjacency_list = build_tree(a)
return dfs_iterative(root, adjacency_list)


# O(n)
def build_tree(a: list[int]) -> tuple[int, list[list[int]]]:
n = len(a)
root = -1
adjacency_list: list[list[int]] = [[] for _ in range(n)]
for i in range(n):
if a[i] == -1:
root = i
else:
adjacency_list[a[i]].append(i)
return root, adjacency_list


# O(n)
def dfs_iterative(root: int, adjacency_list: list[list[int]]) -> int:
n = len(adjacency_list)
# height[i] - высота дерева заканчивающегося в i-й вершине
dp = [1] * n
stack = [root]
visited = [False] * n
while stack:
node = stack.pop()
if not visited[node]:
visited[node] = True
for child in adjacency_list[node]:
stack.append(child)
dp[child] += dp[node]
return max(dp)
50 changes: 50 additions & 0 deletions src/n_graphs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Задачи по графам

## A. Камень жадности

| Поле | Значение |
|-----------|-----------------------------------------|
| Сложность | Сложная |
| Источник | https://stepik.org/lesson/287380/step/1 |

В одной из мультивселенных могущественный титан Фанос собрал все камни конечности, победил храбрую команду
Блюстителей и поверг мир во тьму. Путешествуя по благодарному миру, Фанос забрел в портовый город у границы
с одной из людских колоний, называемой Р. Местные жители поведали герою легенду о самом большом богатстве
во вселенной – седьмом камне конечности, называемом камень жадности. По слухам, камень открывался только
тому, кто посетит все поселения Р, побывав в каждом ровно один раз (кроме того, в котором начинается
путешествие - в него нужно вернуться) и заплатив при этом самую низкую цену (да, путешествия по Р тоже
имеют свою цену).

Фанос узнал, что на территории Р работает только один из камней конечности - камень времени. И работает
он специфическим образом – переносит своего владельца в последний момент времени, когда обладатель находился
вне территории Р. Более того, энергия камня жадности настолько сильна, что как только Фанос попадает в поселения
Р, **каждое свое перемещение между поселениями он делает максимально дешевым из возможных**. Однако, находясь
вне Р, Фанос все еще может использовать камень пространства, чтобы мгновенно очутиться в любом из поселений.

После столь длинной истории наша задача проста – вычислить самый дешевый путь между поселениями Р,
который удалось найти Фаносу.

Формат входа. Входные данные содержат общее количество поселений n и стоимость путешествия между любой
парой поселений в виде матрицы. Поселения нумеруются от 0 до n-1.

Формат выхода. В качестве результата необходимо вывести самый короткий путь между поселениями, который удалось
найти Фаносу, а также его стоимость. Если пути не существует, вывести "Lost".

Пример.

Ввод:

```
4
0 1 2 3
4 0 5 6
7 8 0 9
10 11 12 0
```

Вывод:

```
25
0 1 2 3 0
```
File renamed without changes.
46 changes: 46 additions & 0 deletions src/n_graphs/greed_stone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
INF = 10**20


# O(n^3)
def greed_stone(matrix: list[list[int]]) -> tuple[int, list[int]] | None:
n = len(matrix)
# Преобразуем матрицу стоимости так, чтобы стоимость перемещения из города в самого себя была равна
# бесконечности. Это помогает нам убедиться, что мы не останемся в том же городе при выборе следующего
# города для посещения.
matrix = [[(x if x != 0 else INF) for x in costs] for costs in matrix]
# Инициализируем минимальную стоимость и путь.
min_cost, min_path = INF, []
# Для каждого города мы пытаемся найти путь, начинающийся в этом городе, который посещает все города
# и возвращает нас обратно в начальный город.
for start_city in range(n):
# Инициализируем список посещенных городов и путь.
visited = [False] * n
path, cost = [start_city], 0
# Инициализируем текущий город.
city = start_city
for _ in range(n - 1):
visited[city] = True
# Выбираем следующий город, который еще не был посещен и имеет минимальную стоимость
# перемещения из текущего города.
next_city, score = min(
((c, matrix[city][c]) for c in range(n) if not visited[c]),
key=lambda x: x[1],
)
# Добавляем выбранный город в путь и увеличиваем общую стоимость на стоимость перемещения до
# выбранного города.
path.append(next_city)
cost += score
# Обновляем текущий город.
city = next_city
# После посещения всех городов мы возвращаемся в начальный город.
# Увеличиваем общую стоимость на стоимость перемещения обратно в начальный город.
cost += matrix[city][start_city]
path.append(start_city)
# Если общая стоимость пути меньше минимальной стоимости пути, который мы нашли до этого,
# обновляем минимальную стоимость и путь.
if cost < min_cost:
min_cost = cost
min_path = path
if min_cost == INF:
return None
return min_cost, min_path
Loading

0 comments on commit 9038f0e

Please sign in to comment.