Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Ключевые особенности

- **13** разобранных тем, к каждой теме легкий конспект
- **14** разобранных тем, к каждой теме легкий конспект
- **70+** практических задач с LeetCode, тренировок от Яндекса и реальных собеседований
- Решение **к каждой задаче** на лаконичном Python с комментариями
- **800+** автоматизированных тестов для проверки решений
Expand All @@ -26,6 +26,7 @@
| 11 | Теория чисел | Продвинутые подходы | `k_number_theory` |
| 12 | 2D Динамическое программирование | Продвинутые подходы | `l_dp2` |
| 13 | Деревья | Продвинутые структуры данных | `m_trees` |
| 14 | Графы | Продвинутые структуры данных | `n_trees` |

Каждая тема содержит:

Expand Down
103 changes: 103 additions & 0 deletions src/n_graphs/ABSTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Графы

## Графы

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

## Основные понятия

- **Вершина (узел)** - это базовый элемент графа, который может представлять объект или точку интереса.
- **Ребро (дуга)** - это связь между двумя вершинами, которая может быть направленной или ненаправленной.
- **Ориентированный (направленный) граф** - это граф, в котором рёбра имеют направление, то есть они соединяют вершины в
определённом порядке.
- **Неориентированный (ненаправленный) граф** - это граф, в котором рёбра не имеют направления, то есть связь между
вершинами двусторонняя.
- **Взвешенный граф** - это граф, в котором каждому ребру присвоено значение (вес), которое может представлять
стоимость, расстояние или другую метрику.
- **Невзвешенный граф** - это граф, в котором рёбра не имеют весов, то есть все связи между вершинами равнозначны.
- **Цикл** - это последовательность вершин, в которой первая и последняя вершины совпадают, и каждая пара соседних
вершин соединена ребром.
- **Путь** - это последовательность вершин, в которой каждая пара соседних вершин соединена ребром, и первая и последняя
вершины могут быть разными.
- **Связный граф** - это граф, в котором существует путь между любой парой вершин.
- **Компонента связности** - это максимальный связный подграф, то есть подграф, в котором существует путь между любой
парой вершин, и который не может быть расширен добавлением других вершин.
- **Дерево** - это связный ациклический граф, в котором существует ровно один путь между любой парой вершин.
- **Лес** - это множество деревьев, то есть связный ациклический граф, который может состоять из нескольких компонент
связности.
- **Подграф** - это граф, который состоит из части вершин и рёбер исходного графа, то есть подграф может быть получен
путём удаления некоторых вершин и рёбер из исходного графа.
- **Изоморфизм графов** - это отношение между двумя графами, при котором существует взаимно однозначное соответствие
между их вершинами и рёбрами, сохраняющее структуру графов.

## Представления графов

Существует несколько способов представления графов в памяти компьютера. Наиболее распространённые из них:

- **Матрица смежности** - это двумерный массив, где элемент в строке `i` и столбце `j` указывает на наличие или
отсутствие ребра между вершинами `i` и `j`. Если граф взвешенный, то вместо булевого значения хранится вес ребра.
- **Список смежности** - это список, где для каждой вершины хранится список её соседей. Если граф взвешенный, то
вместо списка соседей хранится список пар (сосед, вес).
- **Список рёбер** - это список, где каждое ребро представлено парой вершин (или тройкой из двух вершин и веса), которые
оно соединяет. Этот способ удобен для хранения графов с небольшим количеством рёбер.
- **Объектно-ориентированное представление** - это способ, при котором вершины и рёбра представлены как объекты,
что позволяет использовать методы и свойства для работы с графом. Например, вершина может содержать список своих
соседей, а ребро может содержать информацию о весе.

Пример ООП-представления графа на Python:

```python
from __future__ import annotations

from dataclasses import dataclass, field


@dataclass
class Node:
key: str
children: list[Node] = field(default_factory=list)

def add_child(self, child_node: Node) -> None:
self.children.append(child_node)


# Пример графа
node_a = Node('A')
node_a.add_child(Node('B'))
node_a.add_child(Node('C'))
node_a.children[0].add_child(Node('D'))
node_a.children[0].add_child(Node('E'))
node_a.children[1].add_child(Node('F'))

# В графе могут быть циклы
node_g = Node('G')
node_a.add_child(node_g)
node_g.add_child(node_a)

# В графе могут быть изолированные вершины
node_x = Node('X')
```

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

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

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

## Обход в ширину

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

## Алгоритм Дейкстры

**Алгоритм Дейкстры** - это алгоритм для поиска кратчайшего пути от одной вершины до всех остальных вершин во
взвешенном графе с неотрицательными весами рёбер. Он работает, выбирая вершину с наименьшим расстоянием от начальной
вершины и обновляя расстояния до её соседей. Алгоритм повторяется, пока не будут обработаны все вершины.
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
```
Empty file added src/n_graphs/__init__.py
Empty file.
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
44 changes: 0 additions & 44 deletions tests/test_graphs/test_dfs.py

This file was deleted.

2 changes: 1 addition & 1 deletion tests/test_graphs/test_greed_stone.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from src.o_graphs.greed_stone import greed_stone
from src.n_graphs.greed_stone import greed_stone


@pytest.mark.parametrize(
Expand Down