图的搜索算法:
图的搜索算法用于在图中寻找最短路径或解决其他相关问题。Dijkstra 算法、Bellman-Ford 算法和 Floyd-Warshall 算法都是常见的图搜索算法,它们在不同的情况下有着不同的应用。
1. Dijkstra 算法(迪杰斯特拉算法)
Dijkstra 算法用于解决单源最短路径问题,即从图中的一个节点到其他所有节点的最短路径。该算法采用贪心策略,每次选择当前距离最短的节点进行扩展,直到找到目标节点或者所有可达节点都被遍历。Dijkstra 算法对于权值非负的有向图或无向图适用。
代码示例:
以下代码示例中,我们首先定义了一个 dijkstra
函数,该函数接受一个图 graph
和起始节点 start
作为参数。在函数内部,我们使用优先队列来实现最小优先级队列,以便按距离进行节点的排序。
然后,我们初始化 distances
对象用于保存每个节点到起始节点的距离,并将起始节点的距离设为 0。接着,我们使用优先队列将所有节点加入到队列中,并初始化它们的距离为无穷大,除了起始节点的距离为 0。
在 while
循环中,我们不断地从队列中取出距离最小的节点,并标记为已访问。然后,我们遍历该节点的所有邻居节点,并更新它们的距离。如果更新后的距离比之前保存的距离小,我们就更新距离并将邻居节点加入到队列中。
最终,我们得到了每个节点到起始节点的最短距离,存储在 distances
对象中,并返回该对象。
javascript
/**
* 使用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| 是图中节点的数量。在每次迭代中,我们遍历所有边,并尝试通过当前节点更新其邻居节点的距离。如果发现某个节点的距离可以通过当前节点进行更新,我们就更新该节点的距离和前驱节点。
最后,我们再进行一次遍历,检查是否存在负权环。如果某个节点的距离可以继续减小,则说明图中存在负权环,算法无法得出正确的最短路径结果。
javascript
/**
* 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 算法的代码示例及其解释。通过该算法,我们可以在图中找到任意两个节点之间的最短路径。
javascript
/**
* 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));