Перейти к содержанию

Языки программирования

Синтаксис, библиотеки, фреймворки, алгоритмы, ООП, функциональное, асинхронное, многопоточное программирование. Помощь новичкам, советы экспертов, тренды и кейсы. Решайте задачи, делитесь кодом.

89 Темы 284 Сообщения

  • 15 34
    15 Темы
    34 Сообщения
    MatatabiM
    А если не получилось с помощью кода отлавливать тех пользователей которые заблокировали бота,значит их просто нет или что-то не сработало?
  • 40 127
    40 Темы
    127 Сообщения
    AladdinA
    Граф — это структура данных, которая представляет связи между объектами. Граф состоит из вершин (узлов) и рёбер (связей между ними). Графы используются повсеместно: Социальные сети: пользователи — вершины, дружба — рёбра Маршрутизация: города — вершины, дороги — рёбра Рекомендации: товары связаны по схожести Анализ зависимостей: модули в проекте, задачи с приоритетами Поиск кратчайшего пути: навигация, логистика Паутина интернета: веб-страницы и ссылки между ними Типы графов Ориентированный граф — рёбра имеют направление (A → B). Неориентированный граф — рёбра двусторонние (A B). Взвешенный граф — рёбра имеют вес/стоимость. Невзвешенный граф — все рёбра одинакового веса. Представление графа в коде Существует три основных способа представить граф. Выбор зависит от задачи, количества вершин и плотности графа. 1. Список смежности (Adjacency List) — самый эффективный Для каждой вершины храним список её соседей. Это самый популярный способ. // Неориентированный граф с помощью Map const graph = new Map(); // Добавление рёбер graph.set('A', ['B', 'C']); graph.set('B', ['A', 'D']); graph.set('C', ['A', 'D']); graph.set('D', ['B', 'C']); // Или с помощью объекта const graphObj = { 'A': ['B', 'C'], 'B': ['A', 'D'], 'C': ['A', 'D'], 'D': ['B', 'C'] }; Преимущества: компактный для разреженных графов, быстрый доступ к соседям, O(V+E) память. Недостатки: проверка наличия конкретного ребра требует линейного поиска. 2. Матрица смежности (Adjacency Matrix) Двумерный массив, где matrix[i][j] = 1, если есть ребро между вершиной i и j. // Граф с 4 вершинами const matrix = [ [0, 1, 1, 0], // A: связана с B, C [1, 0, 0, 1], // B: связана с A, D [1, 0, 0, 1], // C: связана с A, D [0, 1, 1, 0] // D: связана с B, C ]; // Или с индексами для удобства const vertices = ['A', 'B', 'C', 'D']; // Проверка ребра A → B: matrix[0][1] === 1 // Для взвешенного графа: matrix[i][j] = вес Преимущества: быстрая проверка ребра O(1), подходит для плотных графов. Недостатки: требует O(V²) памяти, неэффективна для разреженных графов. 3. Список рёбер (Edge List) Просто список всех рёбер в виде пар вершин. const edges = [ ['A', 'B'], ['A', 'C'], ['B', 'D'], ['C', 'D'] ]; // Для взвешенного графа: const weightedEdges = [ ['A', 'B', 5], ['A', 'C', 3], ['B', 'D', 2], ['C', 'D', 7] ]; Используется: когда нужно обрабатывать все рёбра, в алгоритмах вроде Краскала. Класс Graph для удобства Создадим удобный класс для работы с графами: class Graph { constructor(isDirected = false) { this.adjacencyList = new Map(); this.isDirected = isDirected; } addVertex(vertex) { if (!this.adjacencyList.has(vertex)) { this.adjacencyList.set(vertex, []); } } addEdge(vertex1, vertex2, weight = 1) { this.addVertex(vertex1); this.addVertex(vertex2); this.adjacencyList.get(vertex1).push({ node: vertex2, weight }); // Для неориентированного графа добавляем обратное ребро if (!this.isDirected) { this.adjacencyList.get(vertex2).push({ node: vertex1, weight }); } } getNeighbors(vertex) { return this.adjacencyList.get(vertex) || []; } hasVertex(vertex) { return this.adjacencyList.has(vertex); } display() { for (let [vertex, neighbors] of this.adjacencyList) { console.log(`${vertex} →`, neighbors.map(n => n.node).join(', ')); } } } Использование: const g = new Graph(false); // неориентированный граф g.addEdge('A', 'B'); g.addEdge('A', 'C'); g.addEdge('B', 'D'); g.display(); // Вывод: // A → B, C // B → A, D // C → A // D → B DFS (Depth-First Search) — Поиск в глубину Как работает DFS Начинаем с начальной вершины Идём в глубину, посещая первого соседа Затем идём в соседа этого соседа и т.д. Когда соседей нет, возвращаемся (backtrack) к последней вершине и ищем другой путь Повторяем, пока не посетим все вершины Аналогия: исследование лабиринта — идёшь в одну сторону полностью, потом разворачиваешься и пробуешь другую. DFS рекурсивная реализация class Graph { // ... предыдущий код ... dfsRecursive(startVertex, visited = new Set(), result = []) { visited.add(startVertex); result.push(startVertex); const neighbors = this.getNeighbors(startVertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { this.dfsRecursive(neighbor.node, visited, result); } } return result; } dfs(startVertex) { return this.dfsRecursive(startVertex); } } Пример использования: const g = new Graph(false); g.addEdge('A', 'B'); g.addEdge('A', 'C'); g.addEdge('B', 'D'); g.addEdge('C', 'D'); g.addEdge('D', 'E'); console.log(g.dfs('A')); // ['A', 'B', 'D', 'C', 'E'] Как это работает step-by-step: Посетить A, добавить в результат A имеет соседей B и C, идём к B (первому) B имеет соседей A (уже посещена) и D, идём к D D имеет соседей B (уже посещена), C и E, идём к C C имеет соседей A (уже посещена) и D (уже посещена) Возвращаемся к D, идём к E E соседей нет, всё готово DFS итеративная реализация (со стеком) class Graph { dfsIterative(startVertex) { const visited = new Set(); const stack = [startVertex]; const result = []; while (stack.length > 0) { const vertex = stack.pop(); if (!visited.has(vertex)) { visited.add(vertex); result.push(vertex); const neighbors = this.getNeighbors(vertex); // Добавляем соседей в стек в обратном порядке // чтобы первого соседа обработать первым for (let i = neighbors.length - 1; i >= 0; i--) { if (!visited.has(neighbors[i].node)) { stack.push(neighbors[i].node); } } } } return result; } } Когда использовать DFS: Поиск пути в лабиринте или графе Обнаружение циклов в графе Топологическая сортировка Проверка связности компонент Задачи с backtracking (судоку, головоломки) Когда граф очень широкий (много соседей) — DFS экономит память BFS (Breadth-First Search) — Поиск в ширину Как работает BFS Начинаем с начальной вершины Посещаем всех её соседей (уровень 1) Затем посещаем всех соседей уровня 1 (уровень 2) Затем уровень 3 и т.д. Продолжаем, пока не посетим все вершины Аналогия: расширяющиеся волны в воде — сначала ближайшие точки, потом дальше. BFS реализация (с очередью) class Graph { bfs(startVertex) { const visited = new Set(); const queue = [startVertex]; const result = []; visited.add(startVertex); while (queue.length > 0) { const vertex = queue.shift(); // берём первый элемент result.push(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { visited.add(neighbor.node); queue.push(neighbor.node); // добавляем в конец } } } return result; } } Пример использования: const g = new Graph(false); g.addEdge('A', 'B'); g.addEdge('A', 'C'); g.addEdge('B', 'D'); g.addEdge('C', 'D'); g.addEdge('D', 'E'); console.log(g.bfs('A')); // ['A', 'B', 'C', 'D', 'E'] Как это работает step-by-step: Очередь: [A], посетить A, результат: [A] Соседи A: B, C. Очередь: [B, C], посетить B, результат: [A, B] Соседи B: A (уже), D. Очередь: [C, D], посетить C, результат: [A, B, C] Соседи A (уже), D (уже в очереди). Очередь: [D], посетить D, результат: [A, B, C, D] Соседи B (уже), C (уже), E. Очередь: [E], посетить E, результат: [A, B, C, D, E] Когда использовать BFS: Поиск кратчайшего пути в невзвешенном графе Проверка, является ли граф двудольным Уровневый обход дерева/графа Поиск всех вершин на определённом расстоянии Когда граф глубокий, а решение близко к началу DFS vs BFS Параметр DFS BFS Структура данных Стек (рекурсия) Очередь Память O(h) где h — глубина O(w) где w — ширина Кратчайший путь Не гарантирует ✓ Гарантирует (невзвешённый граф) Порядок посещения Глубоко в одну ветку Слой за слоем Применение Циклы, топосорт, backtracking Кратчайший путь, проверка расстояния Сложность O(V + E) O(V + E) Практические примеры Пример 1: Поиск пути между двумя вершинами class Graph { // ... предыдущий код ... findPath(start, end) { const visited = new Set(); const path = []; const dfs = (vertex) => { visited.add(vertex); path.push(vertex); if (vertex === end) { return true; // путь найден } const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { if (dfs(neighbor.node)) { return true; } } } path.pop(); // backtrack return false; }; if (dfs(start)) { return path; } return null; // пути нет } } // Использование const g = new Graph(false); g.addEdge('A', 'B'); g.addEdge('B', 'C'); g.addEdge('A', 'D'); g.addEdge('D', 'C'); console.log(g.findPath('A', 'C')); // ['A', 'B', 'C'] Пример 2: Поиск кратчайшего пути (невзвешённый граф) class Graph { // ... предыдущий код ... shortestPath(start, end) { const visited = new Set(); const queue = [[start, [start]]]; // [вершина, путь до неё] visited.add(start); while (queue.length > 0) { const [vertex, path] = queue.shift(); if (vertex === end) { return path; } const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { visited.add(neighbor.node); queue.push([neighbor.node, [...path, neighbor.node]]); } } } return null; // пути нет } } // Использование const g = new Graph(false); g.addEdge('A', 'B'); g.addEdge('B', 'C'); g.addEdge('A', 'D'); g.addEdge('D', 'E'); g.addEdge('E', 'C'); console.log(g.shortestPath('A', 'C')); // ['A', 'B', 'C'] (длина 2) // Не выберет ['A', 'D', 'E', 'C'] (длина 3) Пример 3: Проверка наличия цикла в графе class Graph { // ... предыдущий код ... hasCycle() { const visited = new Set(); const recursionStack = new Set(); const dfs = (vertex, parent = null) => { visited.add(vertex); recursionStack.add(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { const nextVertex = neighbor.node; // Для неориентированного графа проверяем, не родитель ли это if (this.isDirected && visited.has(nextVertex) && recursionStack.has(nextVertex)) { return true; // цикл найден } if (!this.isDirected && nextVertex === parent) { continue; // пропускаем обратную ссылку } if (!visited.has(nextVertex)) { if (dfs(nextVertex, vertex)) { return true; } } } recursionStack.delete(vertex); return false; }; for (let vertex of this.adjacencyList.keys()) { if (!visited.has(vertex)) { if (dfs(vertex)) { return true; } } } return false; } } // Использование const g1 = new Graph(false); g1.addEdge('A', 'B'); g1.addEdge('B', 'C'); g1.addEdge('C', 'A'); // цикл! console.log(g1.hasCycle()); // true const g2 = new Graph(false); g2.addEdge('A', 'B'); g2.addEdge('B', 'C'); console.log(g2.hasCycle()); // false Пример 4: Подсчёт количества связных компонент class Graph { // ... предыдущий код ... countConnectedComponents() { const visited = new Set(); let count = 0; const dfs = (vertex) => { visited.add(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { dfs(neighbor.node); } } }; for (let vertex of this.adjacencyList.keys()) { if (!visited.has(vertex)) { dfs(vertex); count++; } } return count; } } // Использование const g = new Graph(false); g.addEdge('A', 'B'); g.addEdge('C', 'D'); g.addEdge('E', 'F'); g.addEdge('F', 'G'); console.log(g.countConnectedComponents()); // 3 компоненты: [A,B], [C,D], [E,F,G] Пример 5: Поиск всех вершин на определённом расстоянии class Graph { // ... предыдущий код ... verticesAtDistance(start, distance) { const visited = new Set(); const queue = [[start, 0]]; // [вершина, расстояние] const result = []; visited.add(start); while (queue.length > 0) { const [vertex, dist] = queue.shift(); if (dist === distance) { result.push(vertex); } if (dist < distance) { const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { visited.add(neighbor.node); queue.push([neighbor.node, dist + 1]); } } } } return result; } } // Использование const g = new Graph(false); g.addEdge('A', 'B'); g.addEdge('A', 'C'); g.addEdge('B', 'D'); g.addEdge('C', 'E'); g.addEdge('D', 'F'); console.log(g.verticesAtDistance('A', 0)); // ['A'] console.log(g.verticesAtDistance('A', 1)); // ['B', 'C'] console.log(g.verticesAtDistance('A', 2)); // ['D', 'E'] console.log(g.verticesAtDistance('A', 3)); // ['F'] Пример 6: Топологическая сортировка (для ориентированного ацикличного графа) class Graph { // ... предыдущий код ... topologicalSort() { const visited = new Set(); const stack = []; const dfs = (vertex) => { visited.add(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { dfs(neighbor.node); } } stack.push(vertex); }; for (let vertex of this.adjacencyList.keys()) { if (!visited.has(vertex)) { dfs(vertex); } } return stack.reverse(); } } // Использование: расписание задач с зависимостями // Задача B зависит от A, задача D зависит от B и C const g = new Graph(true); g.addEdge('A', 'B'); g.addEdge('B', 'D'); g.addEdge('C', 'D'); console.log(g.topologicalSort()); // ['A', 'C', 'B', 'D'] или ['C', 'A', 'B', 'D'] // Суть: A и C выполняются первыми, потом B, потом D Пример 7: Реальный пример — поиск маршрута между аэропортами class Graph { // ... весь предыдущий код ... } // Создаём граф аэропортов const airports = new Graph(false); airports.addEdge('JFK', 'LAX'); airports.addEdge('JFK', 'ATL'); airports.addEdge('LAX', 'SFO'); airports.addEdge('ATL', 'DFW'); airports.addEdge('DFW', 'SFO'); airports.addEdge('ATL', 'MIA'); // Найти кратчайший маршрут из JFK в SFO const route = airports.shortestPath('JFK', 'SFO'); console.log(`Маршрут из JFK в SFO: ${route.join(' → ')}`); // Маршрут из JFK в SFO: JFK → LAX → SFO (2 перелёта) // Альтернатива была бы: JFK → ATL → DFW → SFO (3 перелёта) // Проверить, есть ли прямой путь const allNeighbors = airports.bfs('JFK'); console.log(`Достижимые из JFK:`, allNeighbors); // Достижимые из JFK: ['JFK', 'LAX', 'ATL', 'SFO', 'DFW', 'MIA'] Сложность алгоритмов Временная сложность обоих алгоритмов: O(V + E) V — количество вершин E — количество рёбер Мы посещаем каждую вершину один раз и рассматриваем каждое ребро один раз Пространственная сложность: DFS: O(h) где h — глубина графа (из-за рекурсивного стека вызовов) BFS: O(w) где w — максимальная ширина на каждом уровне Практический вывод: для широких графов DFS более экономен по памяти, для глубоких графов BFS. Полный рабочий класс Graph class Graph { constructor(isDirected = false) { this.adjacencyList = new Map(); this.isDirected = isDirected; } addVertex(vertex) { if (!this.adjacencyList.has(vertex)) { this.adjacencyList.set(vertex, []); } } addEdge(vertex1, vertex2, weight = 1) { this.addVertex(vertex1); this.addVertex(vertex2); this.adjacencyList.get(vertex1).push({ node: vertex2, weight }); if (!this.isDirected) { this.adjacencyList.get(vertex2).push({ node: vertex1, weight }); } } getNeighbors(vertex) { return this.adjacencyList.get(vertex) || []; } hasVertex(vertex) { return this.adjacencyList.has(vertex); } dfs(startVertex) { const visited = new Set(); const result = []; const dfsHelper = (vertex) => { visited.add(vertex); result.push(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { dfsHelper(neighbor.node); } } }; dfsHelper(startVertex); return result; } bfs(startVertex) { const visited = new Set(); const queue = [startVertex]; const result = []; visited.add(startVertex); while (queue.length > 0) { const vertex = queue.shift(); result.push(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { visited.add(neighbor.node); queue.push(neighbor.node); } } } return result; } shortestPath(start, end) { const visited = new Set(); const queue = [[start, [start]]]; visited.add(start); while (queue.length > 0) { const [vertex, path] = queue.shift(); if (vertex === end) { return path; } const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { visited.add(neighbor.node); queue.push([neighbor.node, [...path, neighbor.node]]); } } } return null; } hasCycle() { const visited = new Set(); const recursionStack = new Set(); const dfs = (vertex, parent = null) => { visited.add(vertex); recursionStack.add(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { const nextVertex = neighbor.node; if (this.isDirected && visited.has(nextVertex) && recursionStack.has(nextVertex)) { return true; } if (!this.isDirected && nextVertex === parent) { continue; } if (!visited.has(nextVertex)) { if (dfs(nextVertex, vertex)) { return true; } } } recursionStack.delete(vertex); return false; }; for (let vertex of this.adjacencyList.keys()) { if (!visited.has(vertex)) { if (dfs(vertex)) { return true; } } } return false; } countConnectedComponents() { const visited = new Set(); let count = 0; const dfs = (vertex) => { visited.add(vertex); const neighbors = this.getNeighbors(vertex); for (let neighbor of neighbors) { if (!visited.has(neighbor.node)) { dfs(neighbor.node); } } }; for (let vertex of this.adjacencyList.keys()) { if (!visited.has(vertex)) { dfs(vertex); count++; } } return count; } display() { for (let [vertex, neighbors] of this.adjacencyList) { console.log(`${vertex} →`, neighbors.map(n => n.node).join(', ')); } } } Заключение DFS и BFS — это фундаментальные алгоритмы для работы с графами. Оба работают за O(V+E) и выбор между ними зависит от задачи: Выбирай DFS если нужно искать пути, обнаруживать циклы или когда память важна Выбирай BFS если нужен кратчайший путь в невзвешённом графе или слойный обход Практикуйся с примерами выше, экспериментируй с разными топологиями графов, и вскоре эти алгоритмы станут второй натурой.
  • 14 37
    14 Темы
    37 Сообщения
    kirilljsxK
    @Aladdin Ого, вот это кстати пипец какая полезная штука, как говорится - thank u very much!
  • Стоит ли учиться на GoDot?

    5
    1 Голоса
    5 Сообщения
    137 Просмотры
    itraceI
    Можете забросать меня тапками, но по-моему, когда нет концентрации внимания и человек может бросить в любой момент, то начинать что-то серьезное не стоит,а темболее программирование
  • Как стать программистом

    14
    1 Голоса
    14 Сообщения
    482 Просмотры
    MugiwaraM
    Пользователь @Алекс44 написал в Как стать программистом: И как справляться с выгоранием? Учусь уже полгода, а ощущение, что ничего не знаю. Каждый день новые технологии, кажется, что не успеваю за трендами. Со временем все устаканится, и будешь думать так: “Как я раньше был так глуп, что такую простую вещь не понимал”. Главное продолжить в чем то развиваться. Технологии меняются не так часто, как об этом пишут и может показаться. Выбираешь то, что видишь актуально для твоих задач. Далее когда уже освоился, можно попробовать другие технологии. Но на это надо настраиваться так: я это изучаю для общего развития, чтобы лучше картину всего понимать, а также я встречу паттерны и практики, которые могу внедрить уже на текущих работах (иначе смысла гнаться за технологиями я не вижу) А про выгорание - обычно это уже у ребят, которые давно работают и делают одно и тоже, и нет творчества или исследования/роста для себя. А когда ты молодой зеленый, тащить тебя должно желание изучить, освоить навыки, это должно давать сил.
  • Что такое Lambda простыми словами

    1
    0 Голоса
    1 Сообщения
    101 Просмотры
    Нет ответов
  • Как выбрать язык программирования для стартапа: Python, Go, Rust или что-то ещё?

    10
    1
    0 Голоса
    10 Сообщения
    533 Просмотры
    kirilljsxK
    @Jspi Впервые слышу! Это что еще за чудо техники?