Skip to content

Commit d78b82e

Browse files
agorinenkoAnton Gorinenko
andauthored
Обход графа в глубину (#11)
Co-authored-by: Anton Gorinenko <[email protected]>
1 parent 10e9c47 commit d78b82e

File tree

6 files changed

+275
-7
lines changed

6 files changed

+275
-7
lines changed

img/dfs_1.png

125 KB
Loading

img/dfs_2.png

132 KB
Loading

img/dfs_3.png

138 KB
Loading

tutorial/bfs.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
Существует два основных алгоритма обхода графов. Рассмотрим один из них.
44

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

910
Для обхода графа в ширину используется несколько техник, например с использованием очереди или метод сохранения вершин.
1011
Рассмотрим более детально алгоритм с использованием очереди. Дан граф G(V, E), требуется обойти все его вершины V.

tutorial/dfs.md

Lines changed: 270 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,272 @@
11
# Обход графа глубину
2-
https://leetcode.com/explore/learn/card/queue-stack/231/practical-application-queue/1376/
3-
https://leetcode.com/explore/learn/card/queue-stack/232/practical-application-stack/1377/
42

5-
https://blog.skillfactory.ru/glossary/bfs/
6-
https://blog.skillfactory.ru/glossary/dfs/
3+
**Depth first search (DFS)** - поиск в глубину, способ обхода или нахождения узлов в графах и деревьях. Аналогично
4+
поиску в ширину(``BFS``) алгоритм ``DFS`` также обходит каждый узел структуры, однако в случае поиска в глубину
5+
учитывается иерархия хранения данных. Другими словами, поиск в ширину не осуществляет переход на следующий, более
6+
глубокий уровень до тех пор, пока не будут обработаны все узлы текущего уровня. Напротив, при реализации поиска в
7+
глубину алгоритм не выполняет переход к следующему узлу текущего уровня, пока не достигнет самого конечного элемента
8+
поддерева обрабатываемого элемента. Также следует заметить, что поиск в глубину ищет не самый короткий, а случайный
9+
путь.
10+
11+
## Стек и DFS
12+
13+
Обычно, алгоритм DFS реализуют при помощи стека и рекурсии. Шаблон реализации DFS с использованием стека и рекурсии
14+
представлен ниже:
15+
16+
```python
17+
from typing import Optional, List, Set
18+
19+
20+
class Node:
21+
"""
22+
Graph node
23+
"""
24+
25+
def __init__(self, data: int, children: Optional[List['Node']] = None):
26+
self.data = data
27+
self.children = children
28+
29+
def __repr__(self):
30+
return str(self.data)
31+
32+
def __hash__(self):
33+
return hash(self.data)
34+
35+
def __eq__(self, other):
36+
return self.data == other.data
37+
38+
39+
def dfs(current: Node, target: int, visited: Set[Node]) -> Optional[Node]:
40+
"""
41+
Обход графа в глубину с поиском значения атрибута Node.data
42+
:param current: Начальная вершина графа.
43+
:param target: искомое значение
44+
:param visited: посещенные вершины
45+
:return: node - найденная вершина. Если не найдено, None
46+
"""
47+
if current.data == target:
48+
return current
49+
50+
if current.children:
51+
for child in current.children:
52+
if child not in visited:
53+
visited.add(child)
54+
node = dfs(child, target, visited)
55+
if node:
56+
return node
57+
58+
return None # вершина не найдена
59+
```
60+
61+
Может показаться, что мы не используем стек в данной реализации, однако здесь неявно применяется системный стек
62+
вызовов (call stack) - стек, хранящий информацию для возврата управления из функций в программу. Текущий размер стека
63+
равен глубине обхода, поэтому в худшем случае **пространственная сложность** обхода составит **O(h)**, где h -
64+
максимальная глубина графа или дерева. В случае очень большой глубины графа, при использовании шаблона выше может
65+
произойти переполнение системного стека вызова, и произойдет ошибка ``stack overflow``. Чтобы этого избежать, следует
66+
явно использовать стек в реализации:
67+
68+
```python
69+
from typing import Optional, List
70+
71+
72+
class Node:
73+
"""
74+
Graph node
75+
"""
76+
77+
def __init__(self, data: int, children: Optional[List['Node']] = None):
78+
self.data = data
79+
self.children = children
80+
81+
def __repr__(self):
82+
return str(self.data)
83+
84+
def __hash__(self):
85+
return hash(self.data)
86+
87+
def __eq__(self, other):
88+
return self.data == other.data
89+
90+
91+
def dfs(root: Node, target: int) -> Optional[Node]:
92+
"""
93+
Обход графа в глубину с поиском значения атрибута Node.data
94+
:param root: Начальная вершина графа.
95+
:param target: искомое значение
96+
:return: node - найденная вершина. Если не найдено, None
97+
"""
98+
visited = set() # посещенные вершины
99+
stack = [root]
100+
while len(stack) > 0: # пока стек не пуст
101+
current = stack.pop()
102+
103+
if current.data == target:
104+
return current
105+
106+
if current.children:
107+
for child in current.children:
108+
if child not in visited:
109+
visited.add(child)
110+
stack.append(child)
111+
112+
return None # вершина не найдена
113+
```
114+
115+
Также существует три типа обхода: ``pre-order``,``in-order`` и ``post-order``.
116+
117+
## Прямой обход (pre-order)
118+
119+
При pre-order обходе алгоритм следующий:
120+
121+
1) Обработка целевого узла
122+
2) Обход левого поддерева целевого узла
123+
3) Обход правого поддерева целевого узла
124+
125+
Ниже изображен обход бинарного дерева, где стрелками с номерами описан порядок действий
126+
127+
![img.png](../img/dfs_1.png)
128+
129+
Ниже представлены реализации рекурсивным способом ``preorder_recursive_dfs`` и с использованием
130+
стека ``preorder_stack_dfs``.
131+
132+
```python
133+
from typing import Optional, Callable
134+
135+
136+
class TreeNode:
137+
def __init__(self, val=None, left=None, right=None):
138+
self.val = val
139+
self.left = left
140+
self.right = right
141+
142+
def __repr__(self):
143+
return str(self.val)
144+
145+
146+
def preorder_recursive_dfs(node_function: Callable, node: Optional[TreeNode] = None):
147+
if node:
148+
node_function(node)
149+
150+
preorder_recursive_dfs(node_function, node.left)
151+
preorder_recursive_dfs(node_function, node.right)
152+
153+
154+
def preorder_stack_dfs(node_function: Callable, node: Optional[TreeNode] = None):
155+
stack = [node]
156+
157+
while stack:
158+
node = stack.pop()
159+
if node:
160+
node_function(node)
161+
162+
if node.right:
163+
stack.append(node.right)
164+
165+
if node.left:
166+
stack.append(node.left)
167+
```
168+
169+
## Центрированный обход (in-order)
170+
171+
При in-order обходе алгоритм следующий:
172+
173+
1) Обход левого поддерева целевого узла
174+
2) Обработка целевого узла
175+
3) Обход правого поддерева целевого узла
176+
177+
Используя данный тип обхода бинарного дерева, мы получаем данные в отсортированном виде. Ниже представлены реализации
178+
рекурсивным способом ``inorder_recursive_dfs`` и с использованием стека ``inorder_stack_dfs``.
179+
180+
![img.png](../img/dfs_2.png)
181+
182+
```python
183+
from typing import Optional, Callable
184+
185+
186+
class TreeNode:
187+
def __init__(self, val=None, left=None, right=None):
188+
self.val = val
189+
self.left = left
190+
self.right = right
191+
192+
def __repr__(self):
193+
return str(self.val)
194+
195+
196+
def inorder_recursive_dfs(node_function: Callable, node: Optional[TreeNode] = None):
197+
if node:
198+
inorder_recursive_dfs(node_function, node.left)
199+
node_function(node)
200+
inorder_recursive_dfs(node_function, node.right)
201+
202+
203+
def inorder_stack_dfs(node_function: Callable, node: Optional[TreeNode] = None):
204+
stack = []
205+
current = node
206+
while current or len(stack) > 0:
207+
while current:
208+
stack.append(current)
209+
current = current.left
210+
211+
current = stack.pop()
212+
node_function(current)
213+
214+
current = current.right
215+
```
216+
217+
## Обратный обход (post-order)
218+
219+
При post-order обходе алгоритм следующий:
220+
221+
1) Обход левого поддерева целевого узла
222+
2) Обход правого поддерева целевого узла
223+
3) Обработка целевого узла
224+
225+
![img.png](../img/dfs_3.png)
226+
227+
```python
228+
from typing import Optional, Callable
229+
230+
231+
class TreeNode:
232+
def __init__(self, val=None, left=None, right=None):
233+
self.val = val
234+
self.left = left
235+
self.right = right
236+
237+
def __repr__(self):
238+
return str(self.val)
239+
240+
241+
def postorder_recursive_dfs(node_function: Callable, node: Optional[TreeNode] = None):
242+
if node:
243+
postorder_recursive_dfs(node_function, node.left)
244+
postorder_recursive_dfs(node_function, node.right)
245+
246+
node_function(node)
247+
248+
249+
def postorder_stack_dfs(node_function: Callable, node: Optional[TreeNode] = None):
250+
stack = []
251+
last_node_visited = None
252+
while node or len(stack) > 0:
253+
if node:
254+
stack.append(node)
255+
node = node.left
256+
else:
257+
peek_node = stack[-1]
258+
if peek_node.right and last_node_visited != peek_node.right:
259+
node = peek_node.right
260+
else:
261+
node_function(peek_node)
262+
last_node_visited = stack.pop()
263+
```
264+
265+
## Сложность операций
266+
267+
Для всех ``pre-order``,``in-order`` и ``post-order`` обходов:
268+
269+
Временная сложность - **O(n), где n - общее количество узлов в дереве
270+
271+
Пространственная сложность - **O(n). Пространственная сложность зависит от высоты дерева и в худшем случае равна
272+
количеству узлов в нем.

tutorial/tree.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
# Деревья
1+
# Деревья
2+
https://leetcode.com/explore/learn/card/introduction-to-data-structure-binary-search-tree/

0 commit comments

Comments
 (0)