Графы и деревья в Python: алгоритмы и практика программирования

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Разработчики, изучающие структуры данных и алгоритмы
  • Студенты и специалисты, обучающиеся программированию на Python
  • Аналитики и инженеры, работающие с графами и деревьями в бизнес-приложениях

    Структуры данных — фундамент эффективного программирования, а графы и деревья — их элитный класс. Несмотря на мощь этих инструментов, многие разработчики обходят их стороной из-за кажущейся сложности. Пора это исправить! В этом руководстве мы препарируем графы и деревья до базовых компонентов, реализуем их на Python и покажем, как решать типичные задачи — от построения социальных связей до оптимизации маршрутов. Никакой абстрактной теории — только рабочий код и практические примеры. 🚀

Хотите превратить теорию в практические навыки? Обучение Python-разработке от Skypro включает углубленное изучение структур данных, включая графы и деревья, с фокусом на реальные проекты. Вы не просто поймете концепции — вы научитесь применять их для решения бизнес-задач, от анализа данных до построения рекомендательных систем. Инвестиция в структурированное понимание алгоритмов окупается многократно в карьере разработчика.

Основы графов и деревьев в Python: теория и структуры

Графы и деревья — не просто математические абстракции, а мощные инструменты моделирования связей. Граф состоит из вершин (узлов) и ребер (связей между узлами). Формально это пара G = (V, E), где V — множество вершин, а E — множество ребер. 📊

Графы классифицируются по нескольким критериям:

  • Направленность: ориентированные (digraph) и неориентированные
  • Вес: взвешенные и невзвешенные
  • Связность: связные и несвязные
  • Цикличность: ациклические и циклические

Дерево — особый вид графа: связный, ациклический, где любые два узла соединены ровно одним путем. Корневое дерево имеет выделенный узел (корень), от которого строится иерархия.

В Python существует несколько способов представления графов:

Структура Достоинства Недостатки Применение
Матрица смежности Быстрая проверка наличия ребра O(1) Высокое потребление памяти O(V²) Плотные графы
Список смежности Экономия памяти O(V+E) Проверка наличия ребра O(E) Разреженные графы
Список ребер Простота реализации Неэффективность операций Простые алгоритмы

Для представления графа с помощью матрицы смежности используем двумерный массив:

Python
Скопировать код
# Матрица смежности для неориентированного графа
adjacency_matrix = [
[0, 1, 1, 0],
[1, 0, 1, 1],
[1, 1, 0, 1],
[0, 1, 1, 0]
]

Список смежности можно реализовать через словарь словарей (для взвешенного графа) или словарь списков:

Python
Скопировать код
# Список смежности для взвешенного ориентированного графа
adjacency_list = {
'A': {'B': 2, 'C': 1},
'B': {'C': 3, 'D': 2},
'C': {'D': 4},
'D': {}
}

Выбор структуры критически влияет на производительность алгоритмов. Для разреженных графов (где |E| << |V|²) список смежности существенно экономит память.

Пошаговый план для смены профессии

Реализация графов на Python: от простого к сложному

Александр Новиков, инженер по машинному обучению

Мы столкнулись с задачей анализа взаимодействий в крупной IT-компании. Нужно было выявить ключевых сотрудников и оптимизировать коммуникационные потоки. Традиционные методы не справлялись с масштабом.

"Когда я впервые попытался смоделировать организационные связи через табличное представление, анализ превратился в кошмар", — вспоминаю те дни. Затем я применил графовый подход:

"Мы представили каждого сотрудника как узел, а взаимодействия как рёбра с весами, отражающими частоту коммуникаций. Реализовав граф через список смежности в Python, мы смогли применить алгоритм центральности по посредничеству (betweenness centrality) и выявили 7 сотрудников, через которых проходило 80% важной информации. Реорганизация команд вокруг этих узлов сократила время принятия решений на 34%".

Именно тогда я понял: графы — это не абстрактная теория, а мощный инструмент для решения реальных бизнес-задач.

Начнем с простейшей реализации неориентированного невзвешенного графа через список смежности:

Python
Скопировать код
class Graph:
def __init__(self):
self.graph = {}

def add_vertex(self, vertex):
if vertex not in self.graph:
self.graph[vertex] = []

def add_edge(self, v1, v2):
if v1 in self.graph and v2 in self.graph:
self.graph[v1].append(v2)
self.graph[v2].append(v1) # Для неориентированного графа

def print_graph(self):
for vertex in self.graph:
print(f"{vertex} -> {' '.join(map(str, self.graph[vertex]))}")

# Пример использования
g = Graph()
for i in range(5):
g.add_vertex(i)

g.add_edge(0, 1)
g.add_edge(0, 4)
g.add_edge(1, 2)
g.add_edge(1, 3)
g.add_edge(1, 4)
g.add_edge(2, 3)
g.add_edge(3, 4)

g.print_graph()

Для реализации взвешенного ориентированного графа модифицируем код:

Python
Скопировать код
class WeightedDigraph:
def __init__(self):
self.graph = {}

def add_vertex(self, vertex):
if vertex not in self.graph:
self.graph[vertex] = {}

def add_edge(self, from_v, to_v, weight=1):
if from_v in self.graph and to_v in self.graph:
self.graph[from_v][to_v] = weight

def print_graph(self):
for vertex in self.graph:
edges = [f"{dest}({weight})" for dest, weight in self.graph[vertex].items()]
print(f"{vertex} -> {' '.join(edges)}")

# Пример использования
g = WeightedDigraph()
vertices = ['A', 'B', 'C', 'D', 'E']
for v in vertices:
g.add_vertex(v)

g.add_edge('A', 'B', 6)
g.add_edge('A', 'D', 1)
g.add_edge('B', 'C', 5)
g.add_edge('B', 'E', 2)
g.add_edge('C', 'E', 5)
g.add_edge('D', 'E', 1)

g.print_graph()

Для более сложных задач полезно реализовать дополнительные методы:

Python
Скопировать код
class AdvancedGraph:
def __init__(self, directed=False):
self.graph = {}
self.directed = directed

def add_vertex(self, vertex):
if vertex not in self.graph:
self.graph[vertex] = {}

def add_edge(self, from_v, to_v, weight=1):
if from_v not in self.graph:
self.add_vertex(from_v)
if to_v not in self.graph:
self.add_vertex(to_v)

self.graph[from_v][to_v] = weight
if not self.directed:
self.graph[to_v][from_v] = weight

def get_vertices(self):
return list(self.graph.keys())

def get_edges(self):
edges = []
for from_v in self.graph:
for to_v, weight in self.graph[from_v].items():
# Для неориентированного графа избегаем дублирования
if self.directed or from_v <= to_v:
edges.append((from_v, to_v, weight))
return edges

def get_neighbors(self, vertex):
return list(self.graph.get(vertex, {}).keys())

def has_edge(self, from_v, to_v):
return from_v in self.graph and to_v in self.graph[from_v]

При разработке графовых алгоритмов важно учитывать их временную и пространственную сложность:

Операция Матрица смежности Список смежности
Добавление вершины O(V²) O(1)
Добавление ребра O(1) O(1)
Удаление вершины O(V²) O(V+E)
Удаление ребра O(1) O(E)
Проверка смежности O(1) O(deg(v))

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

Python
Скопировать код
class SocialGraph(AdvancedGraph):
def __init__(self):
super().__init__(directed=False)
self.user_data = {}

def add_user(self, user_id, name, age=None):
self.add_vertex(user_id)
self.user_data[user_id] = {'name': name, 'age': age}

def add_friendship(self, user1, user2, strength=1):
self.add_edge(user1, user2, strength)

def get_friends(self, user_id):
return self.get_neighbors(user_id)

def get_friend_recommendations(self, user_id, max_depth=2):
"""Находит друзей друзей для рекомендаций"""
visited = {user_id}
current = set(self.get_friends(user_id))
visited.update(current)

recommendations = set()

for _ in range(max_depth – 1):
next_level = set()
for friend in current:
friends_of_friend = set(self.get_friends(friend))
next_level.update(friends_of_friend)

recommendations.update(next_level – visited)
visited.update(next_level)
current = next_level

return recommendations

Деревья в Python: представление и основные операции

Дерево — частный случай графа с иерархической структурой. Бинарное дерево ограничивает число потомков до двух, что делает его особенно эффективным для поиска и сортировки. 🌳

Базовая реализация узла бинарного дерева:

Python
Скопировать код
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None

На основе этого класса построим бинарное дерево поиска (BST), где для каждого узла все значения в левом поддереве меньше, а в правом — больше значения узла:

Python
Скопировать код
class BinarySearchTree:
def __init__(self):
self.root = None

def insert(self, value):
if not self.root:
self.root = TreeNode(value)
return

self._insert_recursive(self.root, value)

def _insert_recursive(self, node, value):
if value < node.value:
if node.left is None:
node.left = TreeNode(value)
else:
self._insert_recursive(node.left, value)
else:
if node.right is None:
node.right = TreeNode(value)
else:
self._insert_recursive(node.right, value)

def search(self, value):
return self._search_recursive(self.root, value)

def _search_recursive(self, node, value):
if node is None or node.value == value:
return node

if value < node.value:
return self._search_recursive(node.left, value)
return self._search_recursive(node.right, value)

def inorder_traversal(self):
result = []
self._inorder_recursive(self.root, result)
return result

def _inorder_recursive(self, node, result):
if node:
self._inorder_recursive(node.left, result)
result.append(node.value)
self._inorder_recursive(node.right, result)

Для реализации более сложных деревьев, таких как AVL или красно-черное дерево, потребуется добавить балансировку. Вот пример реализации АВЛ-дерева:

Python
Скопировать код
class AVLNode(TreeNode):
def __init__(self, value):
super().__init__(value)
self.height = 1

class AVLTree(BinarySearchTree):
def _get_height(self, node):
if not node:
return 0
return node.height

def _get_balance(self, node):
if not node:
return 0
return self._get_height(node.left) – self._get_height(node.right)

def _update_height(self, node):
if not node:
return
node.height = 1 + max(self._get_height(node.left), self._get_height(node.right))

def _rotate_right(self, y):
x = y.left
T2 = x.right

x.right = y
y.left = T2

self._update_height(y)
self._update_height(x)

return x

def _rotate_left(self, x):
y = x.right
T2 = y.left

y.left = x
x.right = T2

self._update_height(x)
self._update_height(y)

return y

def insert(self, value):
self.root = self._insert_recursive(self.root, value)

def _insert_recursive(self, node, value):
# Стандартная вставка BST
if not node:
return AVLNode(value)

if value < node.value:
node.left = self._insert_recursive(node.left, value)
else:
node.right = self._insert_recursive(node.right, value)

# Обновляем высоту текущего узла
self._update_height(node)

# Проверяем баланс и выполняем необходимые повороты
balance = self._get_balance(node)

# Случай левого-левого перекоса
if balance > 1 and value < node.left.value:
return self._rotate_right(node)

# Случай правого-правого перекоса
if balance < -1 and value >= node.right.value:
return self._rotate_left(node)

# Случай левого-правого перекоса
if balance > 1 and value >= node.left.value:
node.left = self._rotate_left(node.left)
return self._rotate_right(node)

# Случай правого-левого перекоса
if balance < -1 and value < node.right.value:
node.right = self._rotate_right(node.right)
return self._rotate_left(node)

return node

Операции с деревьями часто требуют рекурсивного мышления. Рассмотрим основные типы обхода дерева:

  • Inorder (левый-корень-правый): обходит сначала левое поддерево, затем корень, затем правое поддерево
  • Preorder (корень-левый-правый): сначала корень, затем левое поддерево, затем правое поддерево
  • Postorder (левый-правый-корень): сначала левое поддерево, затем правое поддерево, затем корень
  • Level order: обход по уровням сверху вниз, слева направо
Python
Скопировать код
def preorder_traversal(self):
result = []
self._preorder_recursive(self.root, result)
return result

def _preorder_recursive(self, node, result):
if node:
result.append(node.value) # Сначала корень
self._preorder_recursive(node.left, result)
self._preorder_recursive(node.right, result)

def postorder_traversal(self):
result = []
self._postorder_recursive(self.root, result)
return result

def _postorder_recursive(self, node, result):
if node:
self._postorder_recursive(node.left, result)
self._postorder_recursive(node.right, result)
result.append(node.value) # Корень в конце

def level_order_traversal(self):
if not self.root:
return []

result = []
queue = [self.root]

while queue:
node = queue.pop(0)
result.append(node.value)

if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)

return result

Екатерина Волкова, ведущий аналитик данных

Наш отдел столкнулся с проблемой оптимизации системы категорий товаров в крупном интернет-магазине. Плоская структура категорий превратилась в хаос — товары дублировались, поиск давал неточные результаты, а рекомендательная система просто не работала.

"На совещании я предложила переосмыслить структуру как иерархическое дерево, но коллеги скептически отнеслись к идее, опасаясь сложной миграции", — рассказывает Екатерина. — "Я решила провести proof-of-concept на Python".

В течение выходных я разработала прототип, используя деревья:

"Каждая категория стала узлом, товары хранились в листьях, а навигация осуществлялась через обход дерева. Я использовала алгоритм LCA (Lowest Common Ancestor) для нахождения ближайшего общего предка, что позволило создавать динамические подборки связанных товаров."

Когда в понедельник я продемонстрировала прототип, показав 30% ускорение поиска и 45% улучшение точности рекомендаций, команда была впечатлена. Через месяц мы полностью перешли на новую систему категорий, а мой подход с использованием деревьев в Python стал частью корпоративного стандарта.

Алгоритмы обхода графов и их Python-реализации

Обход графа — фундаментальная операция, на основе которой строятся более сложные алгоритмы. Два основных подхода: поиск в глубину (DFS) и поиск в ширину (BFS). 🔍

Реализация DFS:

Python
Скопировать код
def dfs(graph, start_vertex):
visited = set()

def dfs_recursive(vertex):
visited.add(vertex)
print(vertex, end=' ')

for neighbor in graph[vertex]:
if neighbor not in visited:
dfs_recursive(neighbor)

dfs_recursive(start_vertex)

# Нерекурсивная реализация DFS
def dfs_iterative(graph, start_vertex):
visited = set()
stack = [start_vertex]

while stack:
vertex = stack.pop()
if vertex not in visited:
visited.add(vertex)
print(vertex, end=' ')

# Добавляем соседей в обратном порядке,
# чтобы порядок обхода совпадал с рекурсивным
for neighbor in reversed(graph[vertex]):
if neighbor not in visited:
stack.append(neighbor)

Реализация BFS:

Python
Скопировать код
from collections import deque

def bfs(graph, start_vertex):
visited = set([start_vertex])
queue = deque([start_vertex])

while queue:
vertex = queue.popleft()
print(vertex, end=' ')

for neighbor in graph[vertex]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)

Сравнение алгоритмов обхода графа:

Характеристика DFS BFS
Структура данных Стек (или рекурсия) Очередь
Применение Топологическая сортировка, поиск циклов, компонент связности Кратчайший путь (невзвешенный), уровневый обход
Сложность по времени O(V+E) O(V+E)
Сложность по памяти O(V) в худшем случае O(V) в худшем случае

На основе BFS можно реализовать алгоритм поиска кратчайшего пути в невзвешенном графе:

Python
Скопировать код
def shortest_path(graph, start, end):
if start not in graph or end not in graph:
return None

if start == end:
return [start]

visited = {start}
queue = deque([(start, [start])])

while queue:
current, path = queue.popleft()

for neighbor in graph[current]:
if neighbor not in visited:
new_path = path + [neighbor]

if neighbor == end:
return new_path

visited.add(neighbor)
queue.append((neighbor, new_path))

return None # Путь не найден

Алгоритм Дейкстры для поиска кратчайшего пути во взвешенном графе:

Python
Скопировать код
import heapq

def dijkstra(graph, start, end):
# Расстояния до всех вершин
distances = {vertex: float('infinity') for vertex in graph}
distances[start] = 0

# Предыдущие вершины для восстановления пути
previous = {vertex: None for vertex in graph}

# Приоритетная очередь [(расстояние, вершина)]
priority_queue = [(0, start)]

while priority_queue:
current_distance, current_vertex = heapq.heappop(priority_queue)

# Если достигли конечной вершины – завершаем
if current_vertex == end:
path = []
while current_vertex:
path.append(current_vertex)
current_vertex = previous[current_vertex]
return path[::-1] # Переворачиваем путь

# Если уже нашли кратчайший путь до вершины – пропускаем
if current_distance > distances[current_vertex]:
continue

# Проверяем соседей текущей вершины
for neighbor, weight in graph[current_vertex].items():
distance = current_distance + weight

# Если нашли более короткий путь
if distance < distances[neighbor]:
distances[neighbor] = distance
previous[neighbor] = current_vertex
heapq.heappush(priority_queue, (distance, neighbor))

return None # Путь не найден

Для поиска остовного дерева минимального веса используется алгоритм Прима или Краскала. Вот реализация алгоритма Прима:

Python
Скопировать код
def prim(graph):
# Начинаем с произвольной вершины
start_vertex = next(iter(graph))

# Множество вершин, включенных в MST
mst_vertices = {start_vertex}

# Ребра в MST (вершина_1, вершина_2, вес)
mst_edges = []

# Продолжаем, пока все вершины не будут включены в MST
while len(mst_vertices) < len(graph):
min_edge = None

# Для каждой вершины в MST
for v in mst_vertices:
# Проверяем все исходящие ребра
for neighbor, weight in graph[v].items():
if neighbor not in mst_vertices:
# Если это ребро легче предыдущего – обновляем
if min_edge is None or weight < min_edge[2]:
min_edge = (v, neighbor, weight)

if min_edge is None:
break # Граф несвязный

u, v, _ = min_edge
mst_vertices.add(v)
mst_edges.append(min_edge)

return mst_edges

Для определения компонент сильной связности в ориентированном графе применяется алгоритм Косарайю:

Python
Скопировать код
def kosaraju(graph):
# Шаг 1: Выполнить DFS на оригинальном графе для определения порядка завершения
visited = set()
finish_order = []

def dfs_finish_time(vertex):
visited.add(vertex)

for neighbor in graph[vertex]:
if neighbor not in visited:
dfs_finish_time(neighbor)

finish_order.append(vertex)

for vertex in graph:
if vertex not in visited:
dfs_finish_time(vertex)

# Шаг 2: Транспонировать граф (обратить направление всех рёбер)
transposed = {vertex: [] for vertex in graph}

for vertex in graph:
for neighbor in graph[vertex]:
transposed[neighbor].append(vertex)

# Шаг 3: Выполнить DFS на транспонированном графе в порядке убывания времени завершения
visited.clear()
components = []

def dfs_component(vertex, component):
visited.add(vertex)
component.append(vertex)

for neighbor in transposed[vertex]:
if neighbor not in visited:
dfs_component(neighbor, component)

for vertex in reversed(finish_order):
if vertex not in visited:
current_component = []
dfs_component(vertex, current_component)
components.append(current_component)

return components

Библиотеки для работы с графами: NetworkX, igraph и другие

Реализация графов с нуля — ценный опыт, но в реальных проектах эффективнее использовать специализированные библиотеки. Рассмотрим наиболее популярные инструменты. 📚

NetworkX — самая популярная Python-библиотека для создания, манипулирования и анализа графов:

Python
Скопировать код
import networkx as nx
import matplotlib.pyplot as plt

# Создание графа
G = nx.Graph() # Неориентированный граф
# Или: G = nx.DiGraph() # Ориентированный граф

# Добавление узлов и рёбер
G.add_nodes_from([1, 2, 3, 4, 5])
G.add_edges_from([(1, 2), (1, 3), (2, 3), (2, 4), (3, 5), (4, 5)])

# Анализ графа
print(f"Количество узлов: {G.number_of_nodes()}")
print(f"Количество рёбер: {G.number_of_edges()}")
print(f"Степени узлов: {dict(G.degree())}")

# Алгоритмы
print(f"Центральность по посредничеству: {nx.betweenness_centrality(G)}")
print(f"Кратчайший путь от 1 до 5: {nx.shortest_path(G, 1, 5)}")

# Визуализация
nx.draw(G, with_labels=True, node_color='lightblue', 
node_size=500, font_weight='bold', font_size=14)
plt.show()

igraph — высокопроизводительная библиотека с поддержкой C:

Python
Скопировать код
from igraph import Graph
import matplotlib.pyplot as plt

# Создание графа
g = Graph(directed=False)
g.add_vertices(5) # Добавляем 5 вершин с индексами 0-4
g.add_edges([(0, 1), (0, 2), (1, 2), (1, 3), (2, 4), (3, 4)])

# Установка атрибутов
g.vs["name"] = ["A", "B", "C", "D", "E"] # Имена вершин
g.vs["color"] = ["red", "green", "blue", "yellow", "purple"]
g.es["weight"] = [1, 2, 3, 1, 4, 2] # Веса рёбер

# Анализ
print(f"Плотность графа: {g.density()}")
print(f"Диаметр графа: {g.diameter()}")
print(f"Кластерный коэффициент: {g.transitivity_undirected()}")

# Визуализация
layout = g.layout("kk") # Используем алгоритм Kamada-Kawai
fig, ax = plt.subplots()
g.plot(layout=layout, target=ax)
plt.show()

PyGraphviz — обёртка для Graphviz:

Python
Скопировать код
import pygraphviz as pgv

# Создание графа
G = pgv.AGraph(directed=True)

# Добавление узлов с атрибутами
G.add_node("A", color="red", shape="circle")
G.add_node("B", color="blue", shape="box")
G.add_node("C", color="green", shape="triangle")

# Добавление рёбер с атрибутами
G.add_edge("A", "B", weight=2, label="связь AB", color="red")
G.add_edge("A", "C", weight=1, label="связь AC", style="dashed")
G.add_edge("B", "C", weight=3, label="связь BC", penwidth=2.0)

# Установка общих атрибутов
G.graph_attr["rankdir"] = "LR"
G.node_attr["style"] = "filled"

# Сохранение и рендеринг
G.write("example.dot") # Сохранение в формате DOT
G.draw("example.png", prog="dot") # Рендеринг изображения

Сравнение основных библиотек для работы с графами:

Библиотека Преимущества Недостатки Оптимальное применение
NetworkX Простой API, богатый функционал, хорошая документация Низкая производительность на больших графах Исследование, прототипирование, малые/средние графы
igraph Высокая производительность, поддержка большого числа алгоритмов Сложнее в использовании, менее pythonic API Большие графы, когда важна скорость
PyGraphviz Мощные возможности визуализации, поддержка DOT Фокус на визуализации, не на алгоритмах Создание визуальных представлений графов
graph-tool Очень высокая производительность (C++/OpenMP) Сложная установка, крутая кривая обучения Вычислительно-интенсивные задачи на больших графах

Для специфических задач существуют специализированные библиотеки:

  • SNAP (Stanford Network Analysis Platform): для анализа больших сетей
  • NetworKit: для анализа массивных сетей с миллионами узлов и ребер
  • GraphFrames: для обработки графов на Apache Spark
  • DGL (Deep Graph Library): для графовых нейронных сетей
  • PyTorch Geometric: для графовых нейронных сетей с PyTorch

Пример использования NetworkX для анализа социального графа:

Python
Скопировать код
import networkx as nx
import matplotlib.pyplot as plt
import community as community_louvain
import numpy as np

# Создаем случайный граф, имитирующий социальную сеть
G = nx.barabasi_albert_graph(100, 5) # 100 узлов, каждый подключается к 5 существующим

# Вычисляем метрики центральности
degree_cent = nx.degree_centrality(G)
betweenness_cent = nx.betweenness_centrality(G)
closeness_cent = nx.closeness_centrality(G)

# Выявляем сообщества с помощью алгоритма Louvain
partition = community_louvain.best_partition(G)
communities = {}
for node, community_id in partition.items():
if community_id not in communities:
communities[community_id] = []
communities[community_id].append(node)

print(f"Обнаружено {len(communities)} сообществ")
for comm_id, nodes in communities.items():
print(f"Сообщество {comm_id}: {len(nodes)} узлов")

# Визуализация с учетом сообществ
pos = nx.spring_layout(G, seed=42)
plt.figure(figsize=(12, 8))

# Цвета для разных сообществ
cmap = plt.cm.get_cmap("tab20", max(partition.values()) + 1)
node_colors = [cmap(partition[node]) for node in G.nodes()]

# Размер узла зависит от центральности
node_size = [5000 * degree_cent[node] for node in G.nodes()]

nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=node_size, alpha=0.8)
nx.draw_networkx_edges(G, pos, alpha=0.2)
plt.axis("off")
plt.tight_layout()
plt.show()

Эффективная работа с графами и деревьями требует не просто знания алгоритмов, но и умения выбирать правильные инструменты. Python предоставляет обширный арсенал как для реализации с нуля, так и для использования готовых высокооптимизированных решений. Помните: выбор структуры данных определяет сложность алгоритма, а выбор библиотеки — производительность решения. Освоив представленные концепции, вы сможете моделировать сложные взаимосвязи — от маршрутизации в сетях до рекомендательных систем и анализа социальных взаимодействий.

Загрузка...