在本文中,我将实现8 种图算法,探索 JavaScript 中图的搜索和组合问题(图遍历、最短路径和匹配)。
这些问题是从《Java编程面试要素》一书中借来的。本书中的解决方案是用 Java、Python 或 C++ 编写的,具体取决于书的版本。
尽管问题建模背后的逻辑与语言无关,但我在本文中提供的代码片段使用了一些 JavaScript 警告。
每个问题的每个解决方案都分为 3 个部分:解决方案概述、伪代码,最后是 JavaScript 中的实际代码。
要测试代码并查看它执行预期的操作,您可以使用Chrome 的开发工具在浏览器本身上运行代码段,或使用 NodeJS 从命令行运行它们。
图实现
图的两种最常用的表示是邻接表和邻接矩阵。
我要解决的问题是稀疏图(很少的边),邻接表方法中的顶点操作采用常数(添加一个顶点,O(1))和线性时间(删除一个顶点,O(V+E) ))。所以我会在很大程度上坚持这个实现。
让我们使用adjacency list用一个简单的无向、未加权的图实现来解决这个问题。我们将维护一个对象 (adjacencyList),该对象将包含我们图中的所有顶点作为键。这些值将是所有相邻顶点的数组。在下面的示例中,顶点 1 连接到顶点 2 和 4,因此 adjacencyList: { 1 : [ 2, 4 ] } 等其他顶点。
为了构建图形,我们有两个函数:addVertex和addEdge。addVertex 用于向列表中添加一个顶点。addEdge 用于通过将相邻顶点添加到源和目标数组来连接顶点,因为这是一个无向图。要制作有向图,我们可以简单地删除下面代码中的第 14-16 和 18 行。
在移除一个顶点之前,我们需要遍历相邻顶点的数组并移除所有可能与该顶点的连接。
使用邻接表实现的无向无权图
class Graph {
constructor() {
this.adjacencyList = {};
}
addVertex(vertex) {
if (!this.adjacencyList[vertex]) {
this.adjacencyList[vertex] = [];
}
}
addEdge(source, destination) {
if (!this.adjacencyList[source]) {
this.addVertex(source);
}
if (!this.adjacencyList[destination]) {
this.addVertex(destination);
}
this.adjacencyList[source].push(destination);
this.adjacencyList[destination].push(source);
}
removeEdge(source, destination) {
this.adjacencyList[source] = this.adjacencyList[source].filter(vertex => vertex !== destination);
this.adjacencyList[destination] = this.adjacencyList[destination].filter(vertex => vertex !== source);
}
removeVertex(vertex) {
while (this.adjacencyList[vertex]) {
const adjacentVertex = this.adjacencyList[vertex].pop();
this.removeEdge(vertex, adjacentVertex);
}
delete this.adjacencyList[vertex];
}
}
图遍历
在上一节中实现图的基础上,我们将实现图遍历:广度优先搜索和深度优先搜索。
广度优先搜索
BFS 一次访问一层节点。为了防止多次访问同一个节点,我们将维护一个访问过的对象。
由于我们需要以先进先出的方式处理节点,因此队列是要使用的数据结构的一个很好的竞争者。时间复杂度为 O(V+E)。
function BFS
Initialize an empty queue, empty 'result' array & a 'visited' map
Add the starting vertex to the queue & visited map
While Queue is not empty:
- Dequeue and store current vertex
- Push current vertex to result array
- Iterate through current vertex's adjacency list:
- For each adjacent vertex, if vertex is unvisited:
- Add vertex to visited map
- Enqueue vertex
Return result array
深度优先搜索
DFS 深度访问节点。由于我们需要以后进先出的方式处理节点,因此我们将使用堆栈。
从一个顶点开始,我们将相邻的顶点推送到我们的堆栈中。每当一个顶点被弹出时,它就会在我们的访问对象中被标记为已访问。它的相邻顶点被推入堆栈。由于我们总是弹出一个新的相邻顶点,我们的算法将始终探索一个新的级别。
我们还可以使用内部堆栈调用来递归地实现 DFS。逻辑是一样的。
时间复杂度和BFS一样,O(V+E)。
function DFS
Initialize an empty stack, empty 'result' array & a 'visited' map
Add the starting vertex to the stack & visited map
While Stack is not empty:
- Pop and store current vertex
- Push current vertex to result array
- Iterate through current vertex's adjacency list:
- For each adjacent vertex, if vertex is unvisited:
- Add vertex to visited map
- Push vertex to stack
Return result array
Graph.prototype.bfs = function(start) {
const queue = [start];
const result = [];
const visited = {};
visited[start] = true;
let currentVertex;
while (queue.length) {
currentVertex = queue.shift();
result.push(currentVertex);
this.adjacencyList[currentVertex].forEach(neighbor => {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.push(neighbor);
}
});
}
return result;
}
Graph.prototype.dfsRecursive = function(start) {
const result = [];
const visited = {};
const adjacencyList = this.adjacencyList;
(function dfs(vertex){
if (!vertex) return null;
visited[vertex] = true;
result.push(vertex);
adjacencyList[vertex].forEach(neighbor => {
if (!visited[neighbor]) {
return dfs(neighbor);
}
})
})(start);
return result;
}
Graph.prototype.dfsIterative =