JavaScript 数据结构和算法——寻找图的最短路径
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 16 天,点击查看活动详情。
介绍
上一篇文章当中,我们实现了图的深度遍历和广度遍历,这里我们来实现一下寻找图的最短路径算法。图的最短路径算法就是寻找起点到图当中各个顶点的最短路径。在实际应用当中,图的最短路径算法长用于在出现时寻找最短的路径。
思想
之前我们使用 BFS 来实现图的广度遍历,我们可以根据图的广度遍历来实现最短路径算法。给定一个图 G 的源顶点 v,找出每个顶点 u 和 v 之间最短路径的距离(以边的数量计算)。
广度遍历是先遍历给定节点的相邻节点,然后再遍历相邻节点的相邻节点,我们可以定义一个对象distances来保存当前节点到源节点之间的距离,同时我们还需要定义一个predecessors来保存当前节点的前溯。这样我们就拿到了当前节点到图中每一个节点的距离。
之后,我们再计算当前节点到图中每个节点的最短路径。
实现
class Graph<T> {
// 节点列表
public vertices: T[];
// 邻接表
public adjList: Map<T, T[]>;
// 是否是有向图
public isDirected: boolean;
constructor(isDirected = false) {
this.isDirected = isDirected;
//初始化邻接表
this.adjList = new Map<T, T[]>();
// 初始化节点列表
this.vertices = [];
}
/**
* 向图当中添加节点
* @param v 添加的节点
*/
addVertex(v: T) {
//先判断当前图当中是否包含节点
if (!this.vertices.includes(v)) {
this.vertices.push(v);
this.adjList.set(v, []);
}
}
/**
*
* @param v 顶点v
* @param w 顶点w
*/
addEdge(v: T, w: T) {
//如果图当中不存在v节点
if (!this.vertices.includes(v)) {
//将v节点添加到图当中
this.addVertex(v);
}
//如果图当中不存在W节点
if (!this.vertices.includes(w)) {
//将w节点添加到图当中
this.addVertex(w);
}
//如果都存在,那么对他们进行关系添加
this.adjList.get(v).push(w);
//判断是否为有向图,如果为无向图则添加v到w的关系
if (!this.isDirected) {
this.adjList.get(w).push(v);
}
}
//获取图当中的顶点
getVertices() {
return this.vertices;
}
//获取图的邻接表
getAdjList() {
return this.adjList;
}
}
const graph = new Graph<string>();
const myVertices: string[] = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
for (let i = 0; i < myVertices.length; i++) {
graph.addVertex(myVertices[i]);
}
graph.addEdge("A", "B");
graph.addEdge("A", "C");
graph.addEdge("A", "D");
graph.addEdge("C", "D");
graph.addEdge("C", "G");
graph.addEdge("D", "G");
graph.addEdge("B", "E");
graph.addEdge("B", "F");
graph.addEdge("E", "I");
graph.addEdge("D", "H");
/**
* 节点的颜色集合
*/
const NodeColors = {
WHITE: 0, //白色 未被访问
GREY: 1, // 灰色,已经被访问,但是没有被探索完
BLACK: 2, // 黑色 已经被访问了,并且被探索完
};
/**
* 初始化节点颜色
*/
function initNodeColor<T>(nodeList: string[]): { [propName: string]: number } {
const color: { [propName: string]: number } = {}; // 存放所以节点颜色的集合
for (let i = 0; i < nodeList.length; i++) {
color[nodeList[i]] = NodeColors.WHITE;
}
return color;
}
/**
*
* @param graph 需要获取最短路径的图
* @param startVertext 起点
*/
const shorttesPath = (graph: Graph<string>, startVertext: string) => {
//获取图的节点列表
const vertices = graph.getVertices();
//获取图的邻接表
const adjList = graph.getAdjList();
//初始化图党建节点的状态
const nodeColorList = initNodeColor(vertices);
//创建保存待访问节点的队列
const queue = [];
//将首节点添加到队列当中
queue.push(startVertext);
//distances 保存起点到每个节点的距离
const distances = {};
//保存每个节点的前溯节点
const predecessors = {};
//初始化节点距离和前溯节点
for (let i = 0; i < vertices.length; i++) {
distances[vertices[i]] = 0;
predecessors[vertices[i]] = null;
}
//进行广度优先搜索
while (queue.length !== 0) {
//获取队列的第一个元素
const node = queue.shift();
//从邻接表当中获取节点node相邻的元素
const neighbors = adjList.get(node);
//将节点node状态标记为灰色
nodeColorList[node] = NodeColors.GREY;
//探索节点的相邻节点
for (let i = 0; i < neighbors.length; i++) {
const adjacent = neighbors[i];
//判断当前节点是否为白色,如果不是白色表示该节点已经被访问过
if (nodeColorList[adjacent] === NodeColors.WHITE) {
//将节点标记为已访问
nodeColorList[adjacent] = NodeColors.GREY;
//将当前节点添加到队列
queue.push(adjacent);
//当前节点的距离为前溯加一
distances[adjacent] = distances[node] + 1;
//保存前溯节点
predecessors[adjacent] = node;
}
}
//将当前被探索完的节点标记为黑色
nodeColorList[node] = NodeColors.BLACK;
}
return {
distances,
predecessors,
};
};
const info = shorttesPath(graph, "A");
const fromVertex = myVertices[0];
for (let i = 1; i < myVertices.length; i++) {
const toVertex = myVertices[i];
const path = [];
for (let v = toVertex; v !== fromVertex; v = info.predecessors[v]) {
path.push(v);
}
path.push(fromVertex);
let s = path.pop();
while (!path.length) {
s += " - " + path.pop();
}
console.log(s);
}