开发 gi 的时候,要用到一些图算法,唤起了多年前那段尘封的记忆,重温了胡凡的《算法笔记》。
Dijkstra
Dijkstra(中文译为戴克斯特拉算法) 是由荷兰计算机科学家艾兹赫尔·戴克斯特拉在1956年发现的算法。Dijkstra 算法用来解决单源最短路径问题,即给定图 G 和起点 start,通过算法得到 start 节点到其他每个节点的最短距离。
该过程可用下图来模拟。
具体的算法思想可用参考胡凡的《算法笔记》 P369。
感兴趣的大哥麻烦自己找找 pdf,我只有纸质书。
Javascript 实现
来自 antv 旗下的 algorithm 库 ,https://github.com/antvis/algorithm/blob/master/packages/graph/src/dijkstra.ts
值得一提的是,在 G6 中的图既不是用邻接表也不是用邻接矩阵,而是采用了如下结果
interface IGraph {
nodes: any[];
edges: any[];
}
具体算法如下:
const minVertex = (
D: { [key: string]: number },
nodes: NodeConfig[],
marks: { [key: string]: boolean },
): NodeConfig => {
// 找出最小的点
let minDis = Infinity;
let minNode;
for (let i = 0; i < nodes.length; i++) {
const nodeId = nodes[i].id;
if (!marks[nodeId] && D[nodeId] <= minDis) {
minDis = D[nodeId];
minNode = nodes[i];
}
}
return minNode;
};
const dijkstra = (
graphData: GraphData,
source: string,
directed?: boolean,
weightPropertyName?: string,
) => {
const { nodes = [], edges = [] } = graphData;
const nodeIds = [];
const marks = {};
const D = {};
const prevs = {}; // key: 顶点, value: 顶点的前驱点数组(可能有多条等长的最短路径)
nodes.forEach((node, i) => {
const id = node.id;
nodeIds.push(id);
D[id] = Infinity;
if (id === source) D[id] = 0;
});
const nodeNum = nodes.length;
for (let i = 0; i < nodeNum; i++) {
// Process the vertices
const minNode = minVertex(D, nodes, marks);
const minNodeId = minNode.id;
marks[minNodeId] = true;
if (D[minNodeId] === Infinity) continue; // Unreachable vertices cannot be the intermediate point
let relatedEdges: EdgeConfig[] = [];
if (directed) relatedEdges = getOutEdgesNodeId(minNodeId, edges);
else relatedEdges = getEdgesByNodeId(minNodeId, edges);
relatedEdges.forEach(edge => {
const edgeTarget = edge.target;
const edgeSource = edge.source;
const w = edgeTarget === minNodeId ? edgeSource : edgeTarget;
const weight = weightPropertyName && edge[weightPropertyName] ? edge[weightPropertyName] : 1;
if (D[w] > D[minNode.id] + weight) {
D[w] = D[minNode.id] + weight;
prevs[w] = [minNode.id];
} else if (D[w] === D[minNode.id] + weight) {
prevs[w].push(minNode.id);
}
});
}
prevs[source] = [source];
// 每个节点存可能存在多条最短路径
const paths = {};
for (const target in D) {
if (D[target] !== Infinity) {
findAllPaths(source, target, prevs, paths);
}
}
// 兼容之前单路径
const path = {};
for (const target in paths) {
path[target] = paths[target][0];
}
return { length: D, path, allPath: paths };
};
c++ 实现
其中的图使用邻接表实现,参考了《算法笔记》的思路自己写了一般,摒弃了原书中 c+STL 的写法,改用 c++ 11 的新语法实现。
#include <bits/stdc++.h>
using namespace std;
class Edge
{
public:
int to;
int cost;
Edge(int to, int cost)
{
this->to = to;
this->cost = cost;
}
};
void Dijkstra(int start, vector<vector<Edge>> graph)
{
int n = graph.size();
// 距离
vector<int> dis(n, INT_MAX);
// 已经访问过的节点
vector<bool> visited(n, false);
dis[start] = 0;
for (int i = 0; i < n; i++)
{
// 寻找当前已知的最近节点
int nearestNode = -1;
int minDis = INT_MAX;
for (int j = 0; j <= n; j++)
{
if (!visited[j] && dis[j] < minDis)
{
nearestNode = j;
minDis = dis[j];
}
}
visited[nearestNode] = true;
for (Edge &edge : graph[nearestNode])
{
if (!visited[edge.to] && dis[nearestNode] + edge.cost < dis[edge.to])
{
dis[edge.to] = dis[nearestNode] + edge.cost;
}
}
}
}