<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Граф — представление, обход графа (DFS, BFS): гайд и примеры на JavaScript]]></title><description><![CDATA[<p dir="auto"><strong>Граф</strong> — это структура данных, которая представляет связи между объектами. Граф состоит из <strong>вершин</strong> (узлов) и <strong>рёбер</strong> (связей между ними).</p>
<p dir="auto">Графы используются повсеместно:</p>
<ul>
<li><strong>Социальные сети</strong>: пользователи — вершины, дружба — рёбра</li>
<li><strong>Маршрутизация</strong>: города — вершины, дороги — рёбра</li>
<li><strong>Рекомендации</strong>: товары связаны по схожести</li>
<li><strong>Анализ зависимостей</strong>: модули в проекте, задачи с приоритетами</li>
<li><strong>Поиск кратчайшего пути</strong>: навигация, логистика</li>
<li><strong>Паутина интернета</strong>: веб-страницы и ссылки между ними</li>
</ul>
<h3>Типы графов</h3>
<p dir="auto"><strong>Ориентированный граф</strong> — рёбра имеют направление (A → B). <strong>Неориентированный граф</strong> — рёбра двусторонние (A <img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/2194.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--left_right_arrow" style="height:23px;width:auto;vertical-align:middle" title="↔" alt="↔" /> B). <strong>Взвешенный граф</strong> — рёбра имеют вес/стоимость. <strong>Невзвешенный граф</strong> — все рёбра одинакового веса.</p>
<hr />
<h2>Представление графа в коде</h2>
<p dir="auto">Существует три основных способа представить граф. Выбор зависит от задачи, количества вершин и плотности графа.</p>
<h3>1. Список смежности (Adjacency List) — самый эффективный</h3>
<p dir="auto">Для каждой вершины храним список её соседей. Это <strong>самый популярный способ</strong>.</p>
<pre><code class="language-javascript">// Неориентированный граф с помощью 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']
};
</code></pre>
<p dir="auto"><strong>Преимущества</strong>: компактный для разреженных графов, быстрый доступ к соседям, O(V+E) память.<br />
<strong>Недостатки</strong>: проверка наличия конкретного ребра требует линейного поиска.</p>
<h3>2. Матрица смежности (Adjacency Matrix)</h3>
<p dir="auto">Двумерный массив, где <code>matrix[i][j]</code> = 1, если есть ребро между вершиной i и j.</p>
<pre><code class="language-javascript">// Граф с 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] = вес
</code></pre>
<p dir="auto"><strong>Преимущества</strong>: быстрая проверка ребра O(1), подходит для плотных графов.<br />
<strong>Недостатки</strong>: требует O(V²) памяти, неэффективна для разреженных графов.</p>
<h3>3. Список рёбер (Edge List)</h3>
<p dir="auto">Просто список всех рёбер в виде пар вершин.</p>
<pre><code class="language-javascript">const edges = [
  ['A', 'B'],
  ['A', 'C'],
  ['B', 'D'],
  ['C', 'D']
];

// Для взвешенного графа:
const weightedEdges = [
  ['A', 'B', 5],
  ['A', 'C', 3],
  ['B', 'D', 2],
  ['C', 'D', 7]
];
</code></pre>
<p dir="auto"><strong>Используется</strong>: когда нужно обрабатывать все рёбра, в алгоритмах вроде Краскала.</p>
<hr />
<h2>Класс Graph для удобства</h2>
<p dir="auto">Создадим удобный класс для работы с графами:</p>
<pre><code class="language-javascript">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 =&gt; n.node).join(', '));
    }
  }
}
</code></pre>
<p dir="auto"><strong>Использование</strong>:</p>
<pre><code class="language-javascript">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
</code></pre>
<hr />
<h2>DFS (Depth-First Search) — Поиск в глубину</h2>
<h3>Как работает DFS</h3>
<ol>
<li>Начинаем с начальной вершины</li>
<li>Идём в глубину, посещая первого соседа</li>
<li>Затем идём в соседа этого соседа и т.д.</li>
<li>Когда соседей нет, <strong>возвращаемся</strong> (backtrack) к последней вершине и ищем другой путь</li>
<li>Повторяем, пока не посетим все вершины</li>
</ol>
<p dir="auto"><strong>Аналогия</strong>: исследование лабиринта — идёшь в одну сторону полностью, потом разворачиваешься и пробуешь другую.</p>
<h3>DFS рекурсивная реализация</h3>
<pre><code class="language-javascript">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);
  }
}
</code></pre>
<p dir="auto"><strong>Пример использования</strong>:</p>
<pre><code class="language-javascript">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']
</code></pre>
<p dir="auto"><strong>Как это работает step-by-step</strong>:</p>
<ol>
<li>Посетить A, добавить в результат</li>
<li>A имеет соседей B и C, идём к B (первому)</li>
<li>B имеет соседей A (уже посещена) и D, идём к D</li>
<li>D имеет соседей B (уже посещена), C и E, идём к C</li>
<li>C имеет соседей A (уже посещена) и D (уже посещена)</li>
<li>Возвращаемся к D, идём к E</li>
<li>E соседей нет, всё готово</li>
</ol>
<h3>DFS итеративная реализация (со стеком)</h3>
<pre><code class="language-javascript">class Graph {
  dfsIterative(startVertex) {
    const visited = new Set();
    const stack = [startVertex];
    const result = [];

    while (stack.length &gt; 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 &gt;= 0; i--) {
          if (!visited.has(neighbors[i].node)) {
            stack.push(neighbors[i].node);
          }
        }
      }
    }

    return result;
  }
}
</code></pre>
<p dir="auto"><strong>Когда использовать DFS</strong>:</p>
<ul>
<li>Поиск пути в лабиринте или графе</li>
<li>Обнаружение циклов в графе</li>
<li>Топологическая сортировка</li>
<li>Проверка связности компонент</li>
<li>Задачи с backtracking (судоку, головоломки)</li>
<li>Когда граф очень широкий (много соседей) — DFS экономит память</li>
</ul>
<hr />
<h2>BFS (Breadth-First Search) — Поиск в ширину</h2>
<h3>Как работает BFS</h3>
<ol>
<li>Начинаем с начальной вершины</li>
<li>Посещаем <strong>всех её соседей</strong> (уровень 1)</li>
<li>Затем посещаем всех соседей уровня 1 (уровень 2)</li>
<li>Затем уровень 3 и т.д.</li>
<li>Продолжаем, пока не посетим все вершины</li>
</ol>
<p dir="auto"><strong>Аналогия</strong>: расширяющиеся волны в воде — сначала ближайшие точки, потом дальше.</p>
<h3>BFS реализация (с очередью)</h3>
<pre><code class="language-javascript">class Graph {
  bfs(startVertex) {
    const visited = new Set();
    const queue = [startVertex];
    const result = [];

    visited.add(startVertex);

    while (queue.length &gt; 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;
  }
}
</code></pre>
<p dir="auto"><strong>Пример использования</strong>:</p>
<pre><code class="language-javascript">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']
</code></pre>
<p dir="auto"><strong>Как это работает step-by-step</strong>:</p>
<ol>
<li>Очередь: [A], посетить A, результат: [A]</li>
<li>Соседи A: B, C. Очередь: [B, C], посетить B, результат: [A, B]</li>
<li>Соседи B: A (уже), D. Очередь: [C, D], посетить C, результат: [A, B, C]</li>
<li>Соседи <img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/1f604.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--smile" style="height:23px;width:auto;vertical-align:middle" title="C:" alt="😄" /> A (уже), D (уже в очереди). Очередь: [D], посетить D, результат: [A, B, C, D]</li>
<li>Соседи <img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/1f627.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--anguished" style="height:23px;width:auto;vertical-align:middle" title="D:" alt="😧" /> B (уже), C (уже), E. Очередь: [E], посетить E, результат: [A, B, C, D, E]</li>
</ol>
<p dir="auto"><strong>Когда использовать BFS</strong>:</p>
<ul>
<li>Поиск кратчайшего пути в <strong>невзвешенном</strong> графе</li>
<li>Проверка, является ли граф двудольным</li>
<li>Уровневый обход дерева/графа</li>
<li>Поиск всех вершин на определённом расстоянии</li>
<li>Когда граф глубокий, а решение близко к началу</li>
</ul>
<hr />
<h2>DFS vs BFS</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Параметр</th>
<th>DFS</th>
<th>BFS</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Структура данных</strong></td>
<td>Стек (рекурсия)</td>
<td>Очередь</td>
</tr>
<tr>
<td><strong>Память</strong></td>
<td>O(h) где h — глубина</td>
<td>O(w) где w — ширина</td>
</tr>
<tr>
<td><strong>Кратчайший путь</strong></td>
<td><img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/274c.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--x" style="height:23px;width:auto;vertical-align:middle" title="❌" alt="❌" /> Не гарантирует</td>
<td>✓ Гарантирует (невзвешённый граф)</td>
</tr>
<tr>
<td><strong>Порядок посещения</strong></td>
<td>Глубоко в одну ветку</td>
<td>Слой за слоем</td>
</tr>
<tr>
<td><strong>Применение</strong></td>
<td>Циклы, топосорт, backtracking</td>
<td>Кратчайший путь, проверка расстояния</td>
</tr>
<tr>
<td><strong>Сложность</strong></td>
<td>O(V + E)</td>
<td>O(V + E)</td>
</tr>
</tbody>
</table>
<hr />
<h2>Практические примеры</h2>
<h3>Пример 1: Поиск пути между двумя вершинами</h3>
<pre><code class="language-javascript">class Graph {
  // ... предыдущий код ...

  findPath(start, end) {
    const visited = new Set();
    const path = [];

    const dfs = (vertex) =&gt; {
      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']
</code></pre>
<h3>Пример 2: Поиск кратчайшего пути (невзвешённый граф)</h3>
<pre><code class="language-javascript">class Graph {
  // ... предыдущий код ...

  shortestPath(start, end) {
    const visited = new Set();
    const queue = &lsqb;&lsqb;start, [start&rsqb;&rsqb;]; // [вершина, путь до неё]
    visited.add(start);

    while (queue.length &gt; 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&rsqb;&rsqb;);
        }
      }
    }

    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)
</code></pre>
<h3>Пример 3: Проверка наличия цикла в графе</h3>
<pre><code class="language-javascript">class Graph {
  // ... предыдущий код ...

  hasCycle() {
    const visited = new Set();
    const recursionStack = new Set();

    const dfs = (vertex, parent = null) =&gt; {
      visited.add(vertex);
      recursionStack.add(vertex);

      const neighbors = this.getNeighbors(vertex);
      for (let neighbor of neighbors) {
        const nextVertex = neighbor.node;

        // Для неориентированного графа проверяем, не родитель ли это
        if (this.isDirected &amp;&amp; visited.has(nextVertex) &amp;&amp; recursionStack.has(nextVertex)) {
          return true; // цикл найден
        }

        if (!this.isDirected &amp;&amp; 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
</code></pre>
<h3>Пример 4: Подсчёт количества связных компонент</h3>
<pre><code class="language-javascript">class Graph {
  // ... предыдущий код ...

  countConnectedComponents() {
    const visited = new Set();
    let count = 0;

    const dfs = (vertex) =&gt; {
      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]
</code></pre>
<h3>Пример 5: Поиск всех вершин на определённом расстоянии</h3>
<pre><code class="language-javascript">class Graph {
  // ... предыдущий код ...

  verticesAtDistance(start, distance) {
    const visited = new Set();
    const queue = &lsqb;&lsqb;start, 0&rsqb;&rsqb;; // [вершина, расстояние]
    const result = [];

    visited.add(start);

    while (queue.length &gt; 0) {
      const [vertex, dist] = queue.shift();

      if (dist === distance) {
        result.push(vertex);
      }

      if (dist &lt; 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']
</code></pre>
<h3>Пример 6: Топологическая сортировка (для ориентированного ацикличного графа)</h3>
<pre><code class="language-javascript">class Graph {
  // ... предыдущий код ...

  topologicalSort() {
    const visited = new Set();
    const stack = [];

    const dfs = (vertex) =&gt; {
      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
</code></pre>
<h3>Пример 7: Реальный пример — поиск маршрута между аэропортами</h3>
<pre><code class="language-javascript">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']
</code></pre>
<hr />
<h2>Сложность алгоритмов</h2>
<p dir="auto"><strong>Временная сложность</strong> обоих алгоритмов: <strong>O(V + E)</strong></p>
<ul>
<li>V — количество вершин</li>
<li>E — количество рёбер</li>
<li>Мы посещаем каждую вершину один раз и рассматриваем каждое ребро один раз</li>
</ul>
<p dir="auto"><strong>Пространственная сложность</strong>:</p>
<ul>
<li><strong>DFS</strong>: O(h) где h — глубина графа (из-за рекурсивного стека вызовов)</li>
<li><strong>BFS</strong>: O(w) где w — максимальная ширина на каждом уровне</li>
</ul>
<p dir="auto"><strong>Практический вывод</strong>: для широких графов DFS более экономен по памяти, для глубоких графов BFS.</p>
<hr />
<h2>Полный рабочий класс Graph</h2>
<pre><code class="language-javascript">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) =&gt; {
      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 &gt; 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 = &lsqb;&lsqb;start, [start&rsqb;&rsqb;];
    visited.add(start);

    while (queue.length &gt; 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&rsqb;&rsqb;);
        }
      }
    }

    return null;
  }

  hasCycle() {
    const visited = new Set();
    const recursionStack = new Set();

    const dfs = (vertex, parent = null) =&gt; {
      visited.add(vertex);
      recursionStack.add(vertex);

      const neighbors = this.getNeighbors(vertex);
      for (let neighbor of neighbors) {
        const nextVertex = neighbor.node;

        if (this.isDirected &amp;&amp; visited.has(nextVertex) &amp;&amp; recursionStack.has(nextVertex)) {
          return true;
        }

        if (!this.isDirected &amp;&amp; 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) =&gt; {
      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 =&gt; n.node).join(', '));
    }
  }
}
</code></pre>
<hr />
<h2>Заключение</h2>
<p dir="auto"><strong>DFS</strong> и <strong>BFS</strong> — это фундаментальные алгоритмы для работы с графами. Оба работают за O(V+E) и выбор между ними зависит от задачи:</p>
<ul>
<li><strong>Выбирай DFS</strong> если нужно искать пути, обнаруживать циклы или когда память важна</li>
<li><strong>Выбирай BFS</strong> если нужен кратчайший путь в невзвешённом графе или слойный обход</li>
</ul>
<p dir="auto">Практикуйся с примерами выше, экспериментируй с разными топологиями графов, и вскоре эти алгоритмы станут второй натурой.</p>
]]></description><link>https://forum.exlends.ru/topic/388/graf-predstavlenie-obhod-grafa-dfs-bfs-gajd-i-primery-na-javascript</link><generator>RSS for Node</generator><lastBuildDate>Mon, 21 Sep 2026 14:43:57 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/388.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 09 Jan 2026 20:53:08 GMT</pubDate><ttl>60</ttl></channel></rss>