First version.

This commit is contained in:
krahets
2026-01-20 15:08:42 +08:00
parent 2213a59ff6
commit 8071daddaa
106 changed files with 11790 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
# Графы
<u>Граф</u> -- это нелинейная структура данных, состоящая из <u>вершин (vertex)</u> и <u>ребер (edge)</u>. Граф $G$ можно абстрактно представить как множество вершин $V$ и множество ребер $E$. Ниже приведен пример графа, содержащего 5 вершин и 7 ребер:
$$
\begin{aligned}
V & = \{ 1, 2, 3, 4, 5 \} \newline
E & = \{ (1,2), (1,3), (1,5), (2,3), (2,4), (2,5), (4,5) \} \newline
G & = \{ V, E \} \newline
\end{aligned}
$$
Если рассматривать вершины как узлы, а ребра как ссылки (указатели), соединяющие узлы, то граф можно рассматривать как расширенный список. Как показано на рисунке ниже, **по сравнению с линейными отношениями (список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой** и, следовательно, являются более сложными.
![Связь между списком, деревом и графом](../assets/linkedlist_tree_graph.png)
## Основные типы и понятия графов
В зависимости от наличия направления у ребер графы делятся на <u>неориентированные (undirected graph)</u> и <u>ориентированные (directed graph)</u>, как показано на рисунке ниже.
- В неориентированном графе ребро представляет собой двустороннюю связь между двумя вершинами, например дружеские отношения в социальных сетях.
- В ориентированном графе ребро имеет направление. То есть ребра $A \rightarrow B$ и $A \leftarrow B$ независимы друг от друга, например отношения подписки--подписчики.
![Ориентированный и неориентированный графы](../assets/directed_graph.png)
Если все вершины связаны, то граф называется <u>связным (connected graph)</u>, иначе -- <u>несвязным (disconnected graph)</u>, как показано на рисунке ниже.
- В связном графе из любой вершины можно достичь любой другой вершины.
- В несвязном графе существуют по крайней мере две вершины, между которыми нет пути.
![Связный и несвязный графы](../assets/connected_graph.png)
Можно также добавить к ребрам переменную «вес», получив <u>взвешенный граф (weighted graph)</u>, как показано на рисунке ниже. Например, в мобильных играх, таких как Honor of Kings, система рассчитывает близость между игроками на основе времени совместной игры. Такую сеть близости можно представить в виде взвешенного графа.
![Взвешенный и невзвешенный графы](../assets/weighted_graph.png)
Со структурой данных графа связаны следующие основные понятия.
- <u>Смежность (adjacency)</u>: если между двумя вершинами существует ребро, они называются смежными. На рисунке выше вершины, смежные с вершиной 1, -- это вершины 2, 3 и 5.
- <u>Путь (path)</u>: последовательность ребер от вершины A до вершины B называется путем от A до B. На рисунке выше последовательность ребер 1-5-2-4 является путем от вершины 1 до вершины 4.
- <u>Степень (degree)</u>: количество ребер, присоединенных к вершине. Для ориентированного графа <u>входящая степень (in-degree)</u> показывает, сколько ребер ведет к данной вершине, а <u>исходящая степень (out-degree)</u> показывает, сколько ребер выходит из данной вершины.
## Представление графа
Графы можно представить с помощью «матрицы смежности» и «списка смежности». Рассмотрим пример с неориентированным графом.
### Матрица смежности
Пусть количество вершин графа равно $n$, <u>матрица смежности (adjacency matrix)</u> представляет граф в виде матрицы размером $n \times n$, где каждая строка (столбец) соответствует вершине, а элементы матрицы обозначают наличие ребра. Значение $1$ соответствует наличию ребра между двумя вершинами, значение $0$ -- отсутствию.
Обозначим матрицу смежности как $M$, а список вершин как $V$. Тогда элемент матрицы $M[i, j] = 1$ указывает на наличие ребра между вершинами $V[i]$ и $V[j]$, в противном случае элемент матрицы $M[i, j] = 0$.
![Представление графа с помощью матрицы смежности](../assets/adjacency_matrix.png)
Матрица смежности обладает следующими свойствами.
- В простом графе вершина не может быть соединена с самой собой, поэтому элементы на главной диагонали матрицы смежности не имеют значения.
- Для неориентированного графа ребра в обоих направлениях эквивалентны, поэтому матрица смежности симметрична относительно главной диагонали.
- Заменив элементы матрицы смежности с $1$ и $0$ на веса ребер, можно представить взвешенный граф.
Используя матрицу смежности для представления графа, можно напрямую обращаться к элементам матрицы для получения информации о ребрах, что делает операции добавления, удаления, поиска и изменения достаточно эффективными с временной сложностью $O(1)$. Однако пространственная сложность матрицы составляет $O(n^2)$, что требует значительных затрат памяти.
### Список смежности
<u>Список смежности (adjacency list)</u> представляет граф с помощью $n$ списков, где узлы списка представляют вершины. $i$-й список соответствует вершине $i$ и содержит все смежные вершины (вершины, соединенные с данной вершиной). На рисунке ниже показан пример графа, представленного с помощью списка смежности.
![Представление графа с помощью списка смежности](../assets/adjacency_list.png)
В списке смежности хранятся только существующие ребра, а общее количество ребер обычно значительно меньше $n^2$, что делает его более экономичным по памяти. Однако для поиска ребра в списке смежности необходимо просматривать список, что делает его менее эффективным по времени по сравнению с матрицей смежности.
Как видно из рисунка выше, **структура списка смежности очень похожа на цепную адресацию в хеш-таблицах, поэтому можно использовать аналогичные методы для оптимизации эффективности**. Например, если список длинный, его можно преобразовать в АВЛ-дерево или красно-черное дерево, чтобы повысить временную эффективность с $O(n)$ до $O(\log n)$. Также можно преобразовать список в хеш-таблицу, чтобы снизить временную сложность до $O(1)$.
## Типичные сценарии применения графов
Многие реальные системы можно моделировать с помощью графов, а соответствующие задачи могут быть сведены к задачам вычисления на графах.
<p align="center"> Таблица <id> &nbsp; Типичные графы в реальной жизни </p>
| | Вершина | Ребро | Задача вычисления на графе |
| ------------------- | --------------- | -------------------------------------- | ------------------------------------- |
| Социальные сети | Пользователи | Дружеские связи | Рекомендации потенциальных друзей |
| Линии метро | Станции | Связь между станциями | Рекомендации по кратчайшему маршруту |
| Солнечная система | Небесные тела | Взаимодействие гравитации между телами | Расчет орбит планет |

View File

@@ -0,0 +1,54 @@
# Основные операции с графами
Основные операции с графами можно разделить на операции с ребрами и операции с вершинами. В зависимости от способа представления (матрица смежности или список смежности) реализация будет различаться.
## Реализация на основе матрицы смежности
Ниже приведены операции для заданного неориентированного графа с количеством вершин *n*. Способы реализации показаны на рис. 9.7.
- **Добавление или удаление ребра**: достаточно изменить соответствующее ребро в матрице смежности за время *O*(1). Поскольку граф неориентированный, необходимо обновить ребра в обоих направлениях.
- **Добавление вершины**: в конец матрицы смежности добавляется строка и столбец, которые заполняются нулями. Временная сложность равна *O*(*n*).
- **Удаление вершины**: удаляется строка и столбец из матрицы смежности. В худшем случае при удалении первой строки и столбца необходимо переместить (*n* 1)² элементов влево вверх, что занимает время *O*(*n*²).
- **Инициализация**: передается *n* вершин, инициализируется список вершин vertices длиной *n* за время *O*(*n*). Инициализируется матрица смежности adjMat размером *n*×*n* за время *O*(*n*²).
=== "Инициализация матрицы смежности"
![](../assets/media/image444.jpeg)
=== "Добавление ребра"
![](../assets/media/image446.jpeg)
=== "Удаление ребра"
![](../assets/media/image448.jpeg)
=== "Добавление вершины"
![](../assets/media/image450.jpeg)
=== "Удаление вершины"
![](../assets/media/image452.jpeg)
Ниже приведен код реализации графа на основе матрицы смежности.
```src
[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{}
```
## Реализация на основе списка смежности
Ниже приведены описания операций для неориентированного графа с общим количеством вершин *n* и ребер *m*. Способы реализации показаны на рис. 9.8.
- **Добавление ребра**: достаточно добавить ребро в конец связного списка, соответствующего вершине за время *O*(1). Поскольку граф неориентированный, необходимо добавить ребра в обоих направлениях.
- **Удаление ребра**: необходимо найти и удалить указанное ребро в связном списке, соответствующем вершине, за время *O*(*m*). В неориентированном графе необходимо удалить ребра в обоих направлениях.
- **Добавление вершины**: добавляется связный список в список смежности, а новая вершина становится головным узлом списка. Требуется время *O*(1).
- **Удаление вершины**: необходимо пройтись по всему списку смежности и удалить все ребра, содержащие указанную вершину. Требуется время *O*(*n* + *m*).
- **Инициализация**: в списке смежности создается *n* вершин и *2m* ребер. Требуется время *O*(*n* + *m*).
```src
[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{}
```

View File

@@ -0,0 +1,116 @@
# Обход графа
Дерево представляет отношение «один ко многим», в то время как граф обладает большей свободой и может представлять любые отношения «многие ко многим». Таким образом, **мы можем рассматривать дерево как частный случай графа**. Очевидно, что **операция обхода дерева также является частным случаем операции обхода графа**.
Как графы, так и деревья требуют применения алгоритмов поиска для выполнения операций обхода. Способы обхода графа также можно разделить на два типа: <u>обход в ширину</u> и <u>обход в глубину</u>.
## Обход в ширину
**Обход в ширину — это способ обхода от ближайших к дальним вершинам, начиная с некоторой вершины, всегда приоритетно посещая ближайшие вершины и расширяясь слой за слоем**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, сначала обходятся все смежные вершины этой вершины, затем обходятся все смежные вершины следующей вершины и так далее, пока все вершины не будут посещены.
![Обход графа в ширину](../assets/graph_bfs.png)
### Реализация алгоритма
BFS обычно реализуется с помощью очереди, код показан ниже. Очередь обладает свойством «первым пришел — первым вышел», что соответствует идее BFS «от ближайших к дальним».
1. Добавить начальную вершину обхода `startVet` в очередь и начать цикл.
2. В каждой итерации цикла извлечь вершину из начала очереди и записать посещение, затем добавить все смежные вершины этой вершины в конец очереди.
3. Повторять шаг `2.` до тех пор, пока все вершины не будут посещены.
Чтобы предотвратить повторный обход вершин, необходимо использовать хеш-множество `visited` для записи того, какие узлы уже были посещены.
!!! tip
Хеш-множество можно рассматривать как хеш-таблицу, которая хранит только `key`, но не хранит `value`. Оно может выполнять операции добавления, удаления, поиска и изменения `key` за время $O(1)$. Благодаря уникальности `key`, хеш-множество обычно используется для удаления дубликатов данных и в других сценариях.
```src
[file]{graph_bfs}-[class]{}-[func]{graph_bfs}
```
Код довольно абстрактный, рекомендуется сопоставить его с рисунком ниже для более глубокого понимания.
=== "<1>"
![Шаги обхода графа в ширину](../assets/graph_bfs_step1.png)
=== "<2>"
![graph_bfs_step2](../assets/graph_bfs_step2.png)
=== "<3>"
![graph_bfs_step3](../assets/graph_bfs_step3.png)
=== "<4>"
![graph_bfs_step4](../assets/graph_bfs_step4.png)
=== "<5>"
![graph_bfs_step5](../assets/graph_bfs_step5.png)
=== "<6>"
![graph_bfs_step6](../assets/graph_bfs_step6.png)
=== "<7>"
![graph_bfs_step7](../assets/graph_bfs_step7.png)
=== "<8>"
![graph_bfs_step8](../assets/graph_bfs_step8.png)
=== "<9>"
![graph_bfs_step9](../assets/graph_bfs_step9.png)
=== "<10>"
![graph_bfs_step10](../assets/graph_bfs_step10.png)
=== "<11>"
![graph_bfs_step11](../assets/graph_bfs_step11.png)
!!! question "Является ли последовательность обхода в ширину уникальной?"
Нет. Обход в ширину требует только порядка обхода «от ближайших к дальним», **а порядок обхода нескольких вершин на одинаковом расстоянии может быть произвольным**. Например, на рисунке выше порядок посещения вершин $1$, $3$ может быть изменен, порядок посещения вершин $2$, $4$, $6$ также может быть произвольным.
### Анализ сложности
**Временная сложность**: Все вершины будут добавлены в очередь и извлечены из нее один раз, что требует $O(|V|)$ времени; в процессе обхода смежных вершин, поскольку граф неориентированный, все ребра будут посещены $2$ раза, что требует $O(2|E|)$ времени; в целом используется $O(|V| + |E|)$ времени.
**Пространственная сложность**: Список `res`, хеш-множество `visited`, очередь `que` могут содержать максимум $|V|$ вершин, используя $O(|V|)$ пространства.
## Обход в глубину
**Обход в глубину — это способ обхода, при котором приоритетно идут до конца, а когда пути нет, возвращаются назад**. Как показано на рисунке ниже, начиная с вершины в левом верхнем углу, посещается одна из смежных вершин текущей вершины, пока не будет достигнут конец, затем происходит возврат, снова идут до конца и возвращаются, и так далее, пока все вершины не будут обойдены.
![Обход графа в глубину](../assets/graph_dfs.png)
### Реализация алгоритма
Эта парадигма алгоритма «идти до конца и возвращаться» обычно реализуется на основе рекурсии. Подобно обходу в ширину, при обходе в глубину также необходимо использовать хеш-множество `visited` для записи посещенных вершин, чтобы избежать повторного посещения вершин.
```src
[file]{graph_dfs}-[class]{}-[func]{graph_dfs}
```
Процесс алгоритма обхода в глубину показан на рисунке ниже.
- **Прямая пунктирная линия представляет рекурсию вниз**, указывая на то, что был открыт новый рекурсивный метод для посещения новой вершины.
- **Изогнутая пунктирная линия представляет возврат назад**, указывая на то, что этот рекурсивный метод уже вернулся, возвращаясь к позиции, где был открыт этот метод.
Для более глубокого понимания рекомендуется объединить рисунок ниже с кодом и мысленно смоделировать (или нарисовать) весь процесс DFS, включая то, когда открывается и когда возвращается каждый рекурсивный метод.
=== "<1>"
![Шаги обхода графа в глубину](../assets/graph_dfs_step1.png)
=== "<2>"
![graph_dfs_step2](../assets/graph_dfs_step2.png)
=== "<3>"
![graph_dfs_step3](../assets/graph_dfs_step3.png)
=== "<4>"
![graph_dfs_step4](../assets/graph_dfs_step4.png)
=== "<5>"
![graph_dfs_step5](../assets/graph_dfs_step5.png)
=== "<6>"
![graph_dfs_step6](../assets/graph_dfs_step6.png)
=== "<7>"
![graph_dfs_step7](../assets/graph_dfs_step7.png)

View File

@@ -0,0 +1,9 @@
# Графы
![](../assets/media/image430.jpeg)
!!! abstract
В жизненном путешествии мы подобны узлам, связанным бесчисленными невидимыми рёбрами.
Каждая встреча и расставание оставляют уникальный след в этой огромной сетевой структуре.

View File

@@ -0,0 +1,31 @@
# Резюме
### Основные моменты
- Граф состоит из вершин и ребер и может быть представлен как множество вершин и множество ребер.
- По сравнению с линейными отношениями (список) и отношениями разделения (дерево), сетевые отношения (граф) обладают большей свободой и, следовательно, являются более сложными.
- В ориентированном графе ребра имеют направление, в связном графе любая вершина достижима, во взвешенном графе каждое ребро содержит переменную веса.
- Матрица смежности использует матрицу для представления графа, где каждая строка (столбец) соответствует вершине, элементы матрицы представляют ребра, используя $1$ или $0$ для обозначения наличия или отсутствия ребра между двумя вершинами. Матрица смежности очень эффективна в операциях добавления, удаления, поиска и изменения, но требует значительных затрат памяти.
- Список смежности использует несколько связных списков для представления графа, где $i$-й список соответствует вершине $i$ и содержит все смежные вершины этой вершины. Список смежности более экономичен по памяти по сравнению с матрицей смежности, но менее эффективен по времени, так как требуется просматривать список для поиска ребра.
- Когда связный список в списке смежности становится слишком длинным, его можно преобразовать в красно-черное дерево или хеш-таблицу для повышения эффективности поиска.
- С точки зрения алгоритмического подхода, матрица смежности воплощает принцип "обмена пространства на время", а список смежности воплощает принцип "обмена времени на пространство".
- Графы можно использовать для моделирования различных реальных систем, таких как социальные сети, линии метро и т. д.
- Дерево является частным случаем графа, обход дерева также является частным случаем обхода графа.
- Обход графа в ширину — это метод поиска, который расширяется слой за слоем от ближайших к дальним вершинам, обычно реализуется с помощью очереди.
- Обход графа в глубину — это метод поиска, который идет до конца, а затем возвращается назад, когда дальше идти некуда, часто реализуется на основе рекурсии.
### Вопросы и ответы
**В**: Путь — это последовательность вершин или последовательность ребер?
В разных языковых версиях Википедии определения различаются: в английской версии "путь — это последовательность ребер", а в китайской версии "путь — это последовательность вершин". Вот оригинальный текст из английской версии: In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.
В данном тексте путь рассматривается как последовательность ребер, а не последовательность вершин. Это связано с тем, что между двумя вершинами может существовать несколько ребер, и каждое ребро соответствует отдельному пути.
**В**: Будут ли в несвязном графе вершины, которые невозможно обойти?
В несвязном графе, начиная с определенной вершины, существует по крайней мере одна вершина, которую невозможно достичь. Для обхода несвязного графа необходимо установить несколько начальных точек, чтобы обойти все связные компоненты графа.
**В**: В списке смежности должен ли быть определенный порядок "всех вершин, соединенных с данной вершиной"?
Порядок может быть произвольным. Однако в практических приложениях может потребоваться сортировка по определенным правилам, например, в порядке добавления вершин или в порядке возрастания значений вершин и т. д., что помогает быстро находить вершины "с определенным экстремальным значением".