JavaScript 图算法怎么实现?图遍历和最短路径有哪些常用算法?

文章导读
图是一种由节点和边组成的数据结构。节点就是顶点,连接它们的线就是边。图是非线性数据结构。
📋 目录
  1. 图的类型
  2. 图的表示
  3. 图算法
  4. Breadth First Search (BFS) 算法
  5. 深度优先搜索 (DFS) 算法
  6. 拓扑排序算法
A A

JavaScript - 图算法

图是一种由节点和边组成的数据结构。节点就是顶点,连接它们的线就是边。图是非线性数据结构。

JavaScript 中的图算法用于解决图相关问题。这些算法用于遍历图、寻找最短路径等。我们可以使用这些算法来解决寻找最短路径、寻找连通分量等问题。

图的类型

在深入本章之前,让我们先了解图的类型。

  • 有向图 : 有向图是一种边具有方向的图。换句话说,我们可以称之为 Digraph。这些边可能是单向或双向,也可能有环。箭头用于表示边的方向。
  • 无向图: 无向图与有向图完全相反,即图的边没有方向。我们也可以称之为简单图。
  • 带权图: 带权图意味着图的边具有权重,即值。它帮助我们定义顶点之间的成本、距离等。
  • 无权图: 无权图与带权图相反,即图的边没有任何权重。

图的表示

有两种方式表示图:

  • 邻接矩阵: 在这种表示中,我们使用二维数组表示图。数组的元素为 0 或 1。如果两个顶点之间有边,则置为 1,否则为 0。
  • 邻接表: 在这种表示中,我们使用链表数组表示图。数组的每个元素代表一个顶点,链表代表该顶点的边。

图算法

谈到图算法时,有很多算法可用。我们主要使用这些算法来解决图问题。下面列出了一些:

  • Breadth First Search (BFS)
  • Depth First Search (DFS)
  • Topological Sorting

Breadth First Search (BFS) 算法

此算法可用于遍历图。它对于解决许多问题非常有用。在该算法中,我们从根节点开始遍历,然后逐层向下,遍历当前层的所有节点,然后移动到下一层。我们使用 queue 数据结构来实现此算法。

算法

我们可以使用以下步骤实现 BFS:

  • 首先,创建一个 queue,并将根节点添加到 queue 中。
  • 然后创建一个 visited 数组,并将根节点标记为已访问。
  • 然后循环遍历 queue,直到它为空。
  • 然后从 queue 中出队节点并打印它。
  • 之后,获取出队节点的所有相邻节点,如果它们未被访问,则标记为已访问并入队。
  • 重复上述步骤,直到 queue 为空。

实现

以下是 JavaScript 中 BFS 算法的实现:

function BFS(graph, root) {
   let visited = [];
   let queue = [];
   queue.push(root);

   while (queue.length > 0) {
      let node = queue.shift();

      if (!visited[node]) {
         console.log(node); // 处理节点
         visited[node] = true;
      }

      // 确保 neighbours 已定义
      const neighbours = graph[node] || [];
      for (let i = 0; i < neighbours.length; i++) {
         let neighbour = neighbours[i];
         if (!visited[neighbour]) {
            queue.push(neighbour);
         }
      }
   }
}

let graph = [[1, 2], [3, 4], [5], [6], [6], [7], [8], []];
BFS(graph, 0);

以上程序的输出如下 −

以上代码的输出如下

0
2
5
7
1
4
6
8
3

深度优先搜索 (DFS) 算法

类似于 BFS,该算法也用于遍历图,但方式不同。在该算法中,我们从根节点开始,然后移动到左子节点或右子节点,深入直到到达叶子节点,然后回溯并移动到下一个子节点。

算法

我们可以使用以下步骤来实现 DFS:

  • 首先,我们需要创建一个 stack,并将根节点添加到 stack 中。
  • 然后我们将创建一个 visited 数组,并将根节点标记为已访问。
  • 然后循环遍历 stack,直到它为空。
  • 然后从 stack 中弹出节点并打印它。
  • 之后,获取弹出节点的所有相邻节点,如果它们未被访问,则将它们标记为已访问并推入 stack。
  • 重复上述步骤直到 stack 为空。

实现

以下是 JavaScript 中 DFS 算法的实现:

function DFS(graph, root) {
   let visited = [];
   let stack = [];
   stack.push(root);

   while (stack.length > 0) {
      let node = stack.pop();
      if (!visited[node]) {
         console.log(node);
         visited[node] = true;
      }

      // 如果 graph[node] 未定义,则设置默认值
      const neighbours = graph[node] || [];
      for (let i = 0; i < neighbours.length; i++) {
         let neighbour = neighbours[i];
         if (!visited[neighbour]) {
            stack.push(neighbour);
         }
      }
   }
}

let graph = [[1, 2], [3, 4], [5], [6], [6], [7], [8], []];
DFS(graph, 0);

输出

以下是上述代码的输出

0
2
5
7
1
4
6
8

拓扑排序算法

使用该算法,我们可以将图的顶点排序,使得对于从顶点 u 到顶点 v 的每条边,u 在排序中出现在 v 之前。

算法

我们可以使用以下步骤来实现 Topological Sorting:

  • 我们将创建一个 visited 数组,并将所有顶点标记为未访问。
  • 然后,我们将创建一个 stack 来存储顶点。
  • 然后我们将循环遍历所有顶点并调用递归函数。
  • 然后我们将创建一个递归函数,并将当前节点标记为已访问。
  • 然后我们将循环遍历当前节点的所有相邻节点,如果它们未被访问,则调用递归函数。
  • 然后将当前节点推入 stack。
  • 重复上述步骤直到所有顶点都被访问。
  • 最后,打印 stack。

实现

以下是 JavaScript 中 Topological Sorting 算法的实现:

function topologicalSort(graph) {
   let visited = [];
   let stack = [];
   for (let i = 0; i < graph.length; i++) {
      if (!visited[i]) {
         topologicalSortUtil(graph, i, visited, stack);
      }
   }
   while (stack.length > 0) {
      console.log(stack.pop());
   }
}

function topologicalSortUtil(graph, node, visited, stack) {
   visited[node] = true;
   const neighbours = graph[node] || [];
   for (let i = 0; i < neighbours.length; i++) {
      let neighbour = neighbours[i];
      if (!visited[neighbour]) {
         topologicalSortUtil(graph, neighbour, visited, stack);
      }
   }
   stack.push(node);
}

// 有效的 DAG
let graph = [
   [1, 2], // 节点 0 -> 1, 2
   [3],    // 节点 1 -> 3
   [3, 4], // 节点 2 -> 3, 4
   [],     // 节点 3 -> 无出边
   [5],    // 节点 4 -> 5
   []      // 节点 5 -> 无出边
];
topologicalSort(graph);

输出

以下是上述代码的输出

5
4
3
2
1
0