图的搜索算法:Dijkstra 算法、Bellman-Ford 算法、Floyd-Warshall 算法

图的搜索算法:

图的搜索算法用于在图中寻找最短路径或解决其他相关问题。Dijkstra 算法、Bellman-Ford 算法和 Floyd-Warshall 算法都是常见的图搜索算法,它们在不同的情况下有着不同的应用。

1. Dijkstra 算法(迪杰斯特拉算法)

Dijkstra 算法用于解决单源最短路径问题,即从图中的一个节点到其他所有节点的最短路径。该算法采用贪心策略,每次选择当前距离最短的节点进行扩展,直到找到目标节点或者所有可达节点都被遍历。Dijkstra 算法对于权值非负的有向图或无向图适用。

代码示例:

以下代码示例中,我们首先定义了一个 dijkstra 函数,该函数接受一个图 graph 和起始节点 start 作为参数。在函数内部,我们使用优先队列来实现最小优先级队列,以便按距离进行节点的排序。

然后,我们初始化 distances 对象用于保存每个节点到起始节点的距离,并将起始节点的距离设为 0。接着,我们使用优先队列将所有节点加入到队列中,并初始化它们的距离为无穷大,除了起始节点的距离为 0。

while 循环中,我们不断地从队列中取出距离最小的节点,并标记为已访问。然后,我们遍历该节点的所有邻居节点,并更新它们的距离。如果更新后的距离比之前保存的距离小,我们就更新距离并将邻居节点加入到队列中。

最终,我们得到了每个节点到起始节点的最短距离,存储在 distances 对象中,并返回该对象。

/**
 * 使用Dijkstra算法计算图中从起点到所有其他顶点的最短路径
 *
 * @param graph 图对象,以顶点为键,以相邻顶点及其权重为值的对象
 * @param start 起点顶点
 * @returns 返回一个对象,表示从起点到所有其他顶点的最短路径
 */
function dijkstra(graph, start) {
  // 存储每个顶点到起始顶点的最短距离
  const distances = {};
  // 标记每个顶点是否已被访问
  const visited = {};
  // 使用优先队列,用于存放待访问的顶点及对应的距离
  const queue = new PriorityQueue();

  // 初始化每个顶点到起始顶点的距离
  for (let vertex in graph) {
    if (vertex === start) {
      distances[vertex] = 0;
      // 将起始顶点加入优先队列,距离为0
      queue.enqueue(vertex, 0);
    } else {
      distances[vertex] = Infinity;
      // 将其他顶点加入优先队列,距离设为无穷大
      queue.enqueue(vertex, Infinity);
    }
  }

  // 当优先队列不为空时,持续执行循环
  while (!queue.isEmpty()) {
    // 取出距离最短的顶点作为当前顶点
    const currentVertex = queue.dequeue().data;
    // 标记当前顶点为已访问
    visited[currentVertex] = true;
    // 获取当前顶点的邻居顶点
    const neighbors = graph[currentVertex];
    // 遍历邻居顶点
    for (let neighbor in neighbors) {
      // 计算从当前顶点到邻居顶点的距离
      const distance = distances[currentVertex] + neighbors[neighbor];
      // 如果计算出的距离小于邻居顶点当前的距离,则更新邻居顶点的距离
      if (distance < distances[neighbor]) {
        distances[neighbor] = distance;
        // 将邻居顶点加入优先队列,距离为计算出的距离
        queue.enqueue(neighbor, distance);
      }
    }
  }

  // 返回每个顶点到起始顶点的最短距离
  return distances;
}

// 示例图
const graph = {
  A: { B: 2, C: 4 },
  B: { D: 3 },
  C: { D: 1 },
  D: {}
};

console.log(dijkstra(graph, 'A')); // 输出:{ A: 0, B: 2, C: 4, D: 5 }

2. Bellman-Ford 算法(贝尔曼-福特算法)

Bellman-Ford 算法也用于解决单源最短路径问题,但相比 Dijkstra 算法,它对边的权值没有非负的限制,因此适用于包含负权边的图。Bellman-Ford 算法采用动态规划的思想,通过多次松弛操作来逐步求解最短路径。该算法还可以检测图中是否存在负权环路。

代码示例:

以下代码示例中,我们定义了一个 bellmanFord 函数,该函数接受一个图 graph 和起始节点 start 作为参数。在函数内部,我们首先初始化距离数组 distances 和前驱节点数组 predecessors,将所有节点的距离设为无穷大,并将起始节点的距离设为 0。

然后,我们进行 |V| - 1 次迭代,其中 |V| 是图中节点的数量。在每次迭代中,我们遍历所有边,并尝试通过当前节点更新其邻居节点的距离。如果发现某个节点的距离可以通过当前节点进行更新,我们就更新该节点的距离和前驱节点。

最后,我们再进行一次遍历,检查是否存在负权环。如果某个节点的距离可以继续减小,则说明图中存在负权环,算法无法得出正确的最短路径结果。

/**
 * Bellman-Ford 算法实现
 *
 * @param graph 图,用邻接表表示
 * @param start 起始节点
 * @returns 返回包含最短距离和前驱节点的对象,如果图中存在负权环则返回 "Graph contains a negative-weight cycle"
 */
function bellmanFord(graph, start) {
  const distances = {};
  const predecessors = {};
  const vertices = Object.keys(graph);

  // 初始化距离和前驱节点
  for (let vertex of vertices) {
    distances[vertex] = Infinity;
    predecessors[vertex] = null;
  }
  distances[start] = 0;

  // 迭代 |V| - 1 次
  for (let i = 0; i < vertices.length - 1; i++) {
    for (let u of vertices) {
      for (let v in graph[u]) {
        const weight = graph[u][v];
        // 更新距离和前驱节点
        if (distances[u] + weight < distances[v]) {
          distances[v] = distances[u] + weight;
          predecessors[v] = u;
        }
      }
    }
  }

  // 检测负权环
  for (let u of vertices) {
    for (let v in graph[u]) {
      const weight = graph[u][v];
      // 如果存在负权环,则返回错误信息
      if (distances[u] + weight < distances[v]) {
        return "Graph contains a negative-weight cycle";
      }
    }
  }

  // 返回距离和前驱节点
  return { distances, predecessors };
}

// 示例图
const graph = {
  A: { B: -1, C: 4 },
  B: { C: 3, D: 2, E: 2 },
  C: {},
  D: { B: 1, C: 5 },
  E: { D: -3 }
};

console.log(bellmanFord(graph, 'A'));

3. Floyd-Warshall 算法(弗洛伊德-沃舍尔算法)

Floyd-Warshall 算法用于解决所有节点对之间的最短路径问题,即任意两个节点之间的最短路径。该算法基于动态规划,通过一个二维数组记录所有节点对之间的最短路径长度。Floyd-Warshall 算法适用于有向图或无向图,可以处理带有负权边但不含负权环路的图。

代码示例:

以下代码示例中,我们定义了一个 floydWarshall 函数,该函数接受一个图 graph 作为参数。在函数内部,我们首先初始化距离矩阵 distances,将所有节点对之间的距离设为无穷大,除了节点到自身的距离设为 0。

然后,我们使用三重循环来动态规划求解最短路径。在每次迭代中,我们遍历所有节点对 (i, j),并尝试通过中间节点 k 更新节点对之间的距离。如果发现从节点 i 经过节点 k 再到节点 j 的路径比直接从节点 i 到节点 j 的路径更短,我们就更新距离矩阵中节点对 (i, j) 的距离。

最终,我们得到了所有节点对之间的最短路径距离,存储在 distances 矩阵中,并返回该矩阵。

这就是 Floyd-Warshall 算法的代码示例及其解释。通过该算法,我们可以在图中找到任意两个节点之间的最短路径。

/**
 * Floyd-Warshall 算法求最短路径
 *
 * @param graph 图,用邻接矩阵表示
 * @returns 返回从任意两点之间的最短路径的矩阵
 */
function floydWarshall(graph) {
  // 获取所有顶点的集合
  const vertices = Object.keys(graph);
  // 初始化距离矩阵
  const distances = {};

  // 初始化距离矩阵
  for (let u of vertices) {
    distances[u] = {};
    for (let v of vertices) {
      // 如果两个顶点相同,距离为0,否则为图中边的权重(若不存在边则为无穷大)
      distances[u][v] = (u === v) ? 0 : (graph[u][v] || Infinity);
    }
  }

  // 动态规划求解最短路径
  for (let k of vertices) {
    for (let i of vertices) {
      for (let j of vertices) {
        // 如果通过顶点k的路径更短,则更新距离
        if (distances[i][k] + distances[k][j] < distances[i][j]) {
          distances[i][j] = distances[i][k] + distances[k][j];
        }
      }
    }
  }

  // 返回最短路径矩阵
  return distances;
}

// 示例图
const graph = {
  A: { B: 3, C: 8, E: -4 },
  B: { D: 1, E: 7 },
  C: { B: 4 },
  D: { A: 2, C: -5 },
  E: { D: 6 }
};

console.log(floydWarshall(graph));

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿online

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值