广度优先遍历(Breadth First Search, BFS)是一种图的遍历算法。它从一个节点开始,先访问起始节点,然后遍历它的所有直接相连的节点。然后对这些相连节点依次进行遍历,直到图中的所有节点都被访问一次。
广度优先遍历的主要特点是:
当涉及到查找最短路径时,最常用的算法是 Dijkstra’s Algorithm(迪克斯特拉算法)。这个算法用于查找图中从起始节点到其他所有节点的最短路径。在下面的代码示例中,将使用 JavaScript 来实现 Dijkstra’s Algorithm。
function dijkstra(graph, start) {
const distances = {};
for (const node in graph) {
distances[node] = Infinity;
}
distances[start] = 0;
const priorityQueue = new PriorityQueue();
priorityQueue.enqueue(start, 0);
while (!priorityQueue.isEmpty()) {
const { element: current_node, priority: current_distance } = priorityQueue.dequeue();
if (current_distance > distances[current_node]) {
continue;
}
for (const neighbor in graph[current_node]) {
const distance = current_distance + graph[current_node][neighbor];
if (distance < distances[neighbor]) {
distances[neighbor] = distance;
priorityQueue.enqueue(neighbor, distance);
}
}
}
return distances;
}
// 优先队列实现
class PriorityQueue {
constructor() {
this.queue = [];
}
enqueue(element, priority) {
this.queue.push({ element, priority });
this.sort();
}
dequeue() {
return this.queue.shift();
}
isEmpty() {
return this.queue.length === 0;
}
sort() {
this.queue.sort((a, b) => a.priority - b.priority);
}
}
// 示例图的邻接字典表示,权重用于表示边的距离
const graph = {
'A': { 'B': 1, 'C': 4 },
'B': { 'D': 3 },
'C': { 'D': 1 },
'D': {}
};
const startNode = 'A';
const shortestDistances = dijkstra(graph, startNode);
console.log(`Shortest distances from node ${startNode} to all other nodes:`);
console.log(shortestDistances);
在上面的代码中,使用了一个优先队列来帮助处理节点的优先级。优先队列可以确保在每次迭代中,都选择最短距离的节点作为下一个当前节点,从而确保 Dijkstra’s Algorithm 的正确性。
这个代码示例中的 graph
表示一个带权重的图的邻接字典表示。startNode
是要查找最短路径的起始节点。算法会返回一个包含所有节点到起始节点的最短距离的对象。在示例图中,从节点 A
出发,算法会返回节点 A
到其他所有节点的最短距离。
需要注意,这里的 PriorityQueue
是一个简化的实现,实际上在生产环境中可以使用更高效的优先队列实现,例如 Fibonacci Heap 或二叉堆。同时,这里假设图中没有负权边。对于包含负权边的图,需要使用 Bellman-Ford 算法来查找最短路径。
深度优先遍历(Depth First Search, DFS)是另一种图的遍历算法。与广度优先遍历不同,它从起始节点出发,尽可能深的搜索某一条路径,直到无法继续遍历为止,然后再回溯进行其他路径的遍历。
深度优先遍历的主要特点是:
// 图使用邻接表表示
const graph = {
'A': ['B','C'],
'B': ['A','D','E'],
'C': ['A','F'],
'D': ['B'],
'E': ['B','F'],
'F': ['C','E']
};
// 通过递归实现深度优先遍历
function dfs(curr, end, path, shortest) {
path.push(curr); // 加入当前节点到路径
if (curr === end) { // 到达终点
shortest = [...path]; // 更新最短路径
} else {
graph[curr].forEach(next => {
if (path.indexOf(next) === -1) { // 节点未访问过
dfs(next, end, path, shortest); // 递归
path.pop(); // 回溯
}
});
}
}
// 寻找最短路径的入口函数
function findShortestPath(start, end) {
const path = [];
let shortest = null;
dfs(start, end, path, shortest);
return shortest;
}
const shortestPath = findShortestPath('A', 'F');
console.log(shortestPath); // ['A', 'C', 'F']
区别:
算法复杂度:
广度优先遍历的使用场景和缺陷:
深度优先遍历的使用场景和缺陷:
综合比较:
广度优先遍历和深度优先遍历都有各自的优势和适用场景。广度优先遍历在查找最短路径、连通性检测等问题上表现较好,而深度优先遍历在回溯和搜索问题上更加合适。在选择算法时,需要根据具体问题的特点来决定使用哪种遍历方法。有时,两种遍历方法也可以结合使用,例如在查找图中的所有路径时,可以先用深度优先遍历找到所有可能的路径,然后再筛选出最短路径。