Skip to content

Commit e4fd99b

Browse files
committed
chore: merging master
2 parents 8bea653 + 9203008 commit e4fd99b

File tree

5 files changed

+152
-79
lines changed

5 files changed

+152
-79
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# ChangeLog
2+
3+
#### 0.0.7
4+
5+
- feat: dijkstra supports finding multiple shortest paths;

packages/graph/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@antv/algorithm",
3-
"version": "0.0.5",
3+
"version": "0.0.7",
44
"description": "graph algorithm",
55
"keywords": [
66
"graph",
@@ -30,9 +30,10 @@
3030
"lint:src": "eslint --ext .ts --format=pretty \"./src\"",
3131
"prettier": "prettier -c --write \"**/*\"",
3232
"test": "npm run build:umd && jest",
33-
"test-live": "npm run build:umd && DEBUG_MODE=1 jest --watch ./tests/unit/louvain-spec.ts",
34-
"test-live:async": "npm run build:umd && DEBUG_MODE=1 jest --watch ./tests/unit/*-async-spec.ts",
35-
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx"
33+
"test-live": "npm run build:umd && DEBUG_MODE=1 jest --watch ./tests/unit/louvain-spec.ts",
34+
"test-live:async": "npm run build:umd && DEBUG_MODE=1 jest --watch ./tests/unit/*-async-spec.ts",
35+
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
36+
"cdn": "antv-bin upload -n @antv/algorithm"
3637
},
3738
"homepage": "https://g6.antv.vision",
3839
"bugs": {

packages/graph/src/dijkstra.ts

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { GraphData, NodeConfig, EdgeConfig } from "./types";
2-
import { getOutEdgesNodeId, getEdgesByNodeId } from "./util";
1+
import { GraphData, NodeConfig, EdgeConfig } from './types';
2+
import { getOutEdgesNodeId, getEdgesByNodeId } from './util';
33

44
const minVertex = (
55
D: { [key: string]: number },
66
nodes: NodeConfig[],
7-
marks: { [key: string]: boolean },
7+
marks: { [key: string]: boolean }
88
): NodeConfig => {
99
// 找出最小的点
1010
let minDis = Infinity;
@@ -17,13 +17,13 @@ const minVertex = (
1717
}
1818
}
1919
return minNode;
20-
}
20+
};
2121

2222
const dijkstra = (
2323
graphData: GraphData,
2424
source: string,
2525
directed?: boolean,
26-
weightPropertyName?: string,
26+
weightPropertyName?: string
2727
) => {
2828
const { nodes = [], edges = [] } = graphData;
2929
const nodeIds = [];
@@ -41,40 +41,65 @@ const dijkstra = (
4141
for (let i = 0; i < nodeNum; i++) {
4242
// Process the vertices
4343
const minNode = minVertex(D, nodes, marks);
44-
const minNodId = minNode.id;
45-
marks[minNodId] = true;
44+
const minNodeId = minNode.id;
45+
marks[minNodeId] = true;
4646

47-
if (D[minNodId] === Infinity) continue; // Unreachable vertices cannot be the intermediate point
47+
if (D[minNodeId] === Infinity) continue; // Unreachable vertices cannot be the intermediate point
4848

4949
let relatedEdges: EdgeConfig[] = [];
50-
if (directed) relatedEdges = getOutEdgesNodeId(minNodId, edges);
51-
else relatedEdges = getEdgesByNodeId(minNodId, edges);
50+
if (directed) relatedEdges = getOutEdgesNodeId(minNodeId, edges);
51+
else relatedEdges = getEdgesByNodeId(minNodeId, edges);
5252

53-
relatedEdges.forEach((edge) => {
53+
relatedEdges.forEach(edge => {
5454
const edgeTarget = edge.target;
5555
const edgeSource = edge.source;
56-
const w = edgeTarget === minNodId ? edgeSource : edgeTarget;
56+
const w = edgeTarget === minNodeId ? edgeSource : edgeTarget;
5757
const weight =
5858
weightPropertyName && edge[weightPropertyName]
5959
? edge[weightPropertyName]
6060
: 1;
6161
if (D[w] > D[minNode.id] + weight) {
6262
D[w] = D[minNode.id] + weight;
63-
prevs[w] = minNode.id;
63+
prevs[w] = [minNode.id];
64+
} else if (D[w] === D[minNode.id] + weight) {
65+
prevs[w].push(minNode.id);
6466
}
6567
});
6668
}
67-
const path = {};
69+
70+
prevs[source] = [source];
71+
// 每个节点存可能存在多条最短路径
72+
const allPaths = {};
6873
for (const target in D) {
69-
path[target] = [target];
70-
let prev = prevs[target];
71-
while (prev !== undefined) {
72-
path[target].unshift(prev);
73-
prev = prevs[prev];
74+
if (D[target] !== Infinity) {
75+
findAllPaths(source, target, prevs, allPaths);
7476
}
7577
}
7678

77-
return { length: D, path };
79+
// 兼容之前单路径
80+
const path = {};
81+
for (const target in allPaths) {
82+
path[target] = allPaths[target][0];
83+
}
84+
return { length: D, path, allPaths };
7885
};
7986

8087
export default dijkstra;
88+
89+
function findAllPaths(source, target, prevs, findedPaths) {
90+
if (source === target) {
91+
return [source];
92+
}
93+
if (findedPaths[target]) {
94+
return findedPaths[target];
95+
}
96+
const paths = [];
97+
for (let prev of prevs[target]) {
98+
const prevPaths = findAllPaths(source, prev, prevs, findedPaths);
99+
for (let prePath of prevPaths) {
100+
paths.push([...prePath, target]);
101+
}
102+
}
103+
findedPaths[target] = paths;
104+
return;
105+
}

packages/graph/src/find-path.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,34 @@ export const findShortestPath = (
77
start: string,
88
end: string,
99
directed?: boolean,
10-
weightPropertyName?: string,
10+
weightPropertyName?: string
1111
) => {
12-
const { length, path } = dijkstra(graphData, start, directed, weightPropertyName);
13-
return { length: length[end], path: path[end] };
12+
const { length, path, allPaths } = dijkstra(
13+
graphData,
14+
start,
15+
directed,
16+
weightPropertyName
17+
);
18+
return { length: length[end], path: path[end], allPath: allPaths[end] };
1419
};
1520

1621
export const findAllPath = (
1722
graphData: GraphData,
1823
start: string,
1924
end: string,
20-
directed?: boolean,
25+
directed?: boolean
2126
) => {
2227
if (start === end) return [[start]];
2328

24-
const { edges = [] } = graphData
29+
const { edges = [] } = graphData;
2530

2631
const visited = [start];
2732
const isVisited = { [start]: true };
2833
const stack: string[][] = []; // 辅助栈,用于存储访问过的节点的邻居节点
2934
const allPaths = [];
30-
let neighbors = directed ? getNeighbors(start, edges, 'target') :getNeighbors(start, edges);
35+
let neighbors = directed
36+
? getNeighbors(start, edges, 'target')
37+
: getNeighbors(start, edges);
3138
stack.push(neighbors);
3239

3340
while (visited.length > 0 && stack.length > 0) {
@@ -37,8 +44,10 @@ export const findAllPath = (
3744
if (child) {
3845
visited.push(child);
3946
isVisited[child] = true;
40-
neighbors = directed ? getNeighbors(child, edges, 'target') : getNeighbors(child, edges);
41-
stack.push(neighbors.filter((neighbor) => !isVisited[neighbor]));
47+
neighbors = directed
48+
? getNeighbors(child, edges, 'target')
49+
: getNeighbors(child, edges);
50+
stack.push(neighbors.filter(neighbor => !isVisited[neighbor]));
4251
}
4352
} else {
4453
const node = visited.pop();
@@ -48,7 +57,7 @@ export const findAllPath = (
4857
}
4958

5059
if (visited[visited.length - 1] === end) {
51-
const path = visited.map((node) => node);
60+
const path = visited.map(node => node);
5261
allPaths.push(path);
5362

5463
const node = visited.pop();
Lines changed: 79 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,128 @@
1-
import { findAllPath, findShortestPath } from '../../src';
1+
import { findAllPath, findShortestPath } from "../../src";
22

33
const data = {
44
nodes: [
55
{
6-
id: 'A',
7-
label: 'A',
6+
id: "A",
7+
label: "A",
88
},
99
{
10-
id: 'B',
11-
label: 'B',
10+
id: "B",
11+
label: "B",
1212
},
1313
{
14-
id: 'C',
15-
label: 'C',
14+
id: "C",
15+
label: "C",
1616
},
1717
{
18-
id: 'D',
19-
label: 'D',
18+
id: "D",
19+
label: "D",
2020
},
2121
{
22-
id: 'E',
23-
label: 'E',
22+
id: "E",
23+
label: "E",
2424
},
2525
{
26-
id: 'F',
27-
label: 'F',
26+
id: "F",
27+
label: "F",
2828
},
2929
{
30-
id: 'G',
31-
label: 'G',
30+
id: "G",
31+
label: "G",
3232
},
3333
{
34-
id: 'H',
35-
label: 'H',
34+
id: "H",
35+
label: "H",
3636
},
3737
],
3838
edges: [
3939
{
40-
source: 'A',
41-
target: 'B',
40+
source: "A",
41+
target: "B",
4242
},
4343
{
44-
source: 'B',
45-
target: 'C',
44+
source: "B",
45+
target: "C",
4646
},
4747
{
48-
source: 'C',
49-
target: 'G',
48+
source: "C",
49+
target: "G",
5050
},
5151
{
52-
source: 'A',
53-
target: 'D',
52+
source: "A",
53+
target: "D",
5454
},
5555
{
56-
source: 'A',
57-
target: 'E',
56+
source: "A",
57+
target: "E",
5858
},
5959
{
60-
source: 'E',
61-
target: 'F',
60+
source: "E",
61+
target: "F",
6262
},
6363
{
64-
source: 'F',
65-
target: 'D',
64+
source: "F",
65+
target: "D",
6666
},
6767
{
68-
source: 'D',
69-
target: 'E',
68+
source: "D",
69+
target: "E",
7070
},
7171
],
7272
};
7373

74-
describe('Shortest Path from source to target on graph', () => {
75-
it('find the shortest path', () => {
76-
const { length, path } = findShortestPath(data, 'A', 'C');
74+
describe("Shortest Path from source to target on graph", () => {
75+
it("find the shortest path", () => {
76+
const { length, path } = findShortestPath(data, "A", "C");
7777
expect(length).toBe(2);
78-
expect(path).toStrictEqual(['A', 'B', 'C']);
78+
expect(path).toStrictEqual(["A", "B", "C"]);
7979
});
8080

81-
it('find all paths', () => {
82-
const allPaths = findAllPath(data, 'A', 'E');
81+
it("find all shortest paths", () => {
82+
const { length, allPath } = findShortestPath(data, "A", "F");
83+
expect(length).toBe(2);
84+
expect(allPath[0]).toStrictEqual(["A", "E", "F"]);
85+
expect(allPath[1]).toStrictEqual(["A", "D", "F"]);
86+
87+
const {
88+
length: directedLenght,
89+
path: directedPath,
90+
allPath: directedAllPath,
91+
} = findShortestPath(data, "A", "F", true);
92+
expect(directedLenght).toBe(2);
93+
expect(directedAllPath[0]).toStrictEqual(["A", "E", "F"]);
94+
expect(directedPath).toStrictEqual(["A", "E", "F"]);
95+
});
96+
97+
it("find all paths", () => {
98+
const allPaths = findAllPath(data, "A", "E");
8399
expect(allPaths.length).toBe(3);
84-
expect(allPaths[0]).toStrictEqual(['A', 'D', 'F', 'E']);
85-
expect(allPaths[1]).toStrictEqual(['A', 'D', 'E']);
86-
expect(allPaths[2]).toStrictEqual(['A', 'E']);
100+
expect(allPaths[0]).toStrictEqual(["A", "D", "F", "E"]);
101+
expect(allPaths[1]).toStrictEqual(["A", "D", "E"]);
102+
expect(allPaths[2]).toStrictEqual(["A", "E"]);
87103
});
88104

89-
it('find all paths in directed graph', () => {
90-
const allPaths = findAllPath(data, 'A', 'E', true);
105+
it("find all paths in directed graph", () => {
106+
const allPaths = findAllPath(data, "A", "E", true);
91107
expect(allPaths.length).toStrictEqual(2);
92-
expect(allPaths[0]).toStrictEqual(['A', 'D', 'E']);
93-
expect(allPaths[1]).toStrictEqual(['A', 'E']);
108+
expect(allPaths[0]).toStrictEqual(["A", "D", "E"]);
109+
expect(allPaths[1]).toStrictEqual(["A", "E"]);
110+
});
111+
112+
it("find all shortest paths in weighted graph", () => {
113+
data.edges.forEach((edge, i) => {
114+
edge.weight = ((i % 2) + 1) * 2;
115+
if (edge.source === "F" && edge.target === "D") edge.weight = 10;
116+
});
117+
const { length, path, allPath } = findShortestPath(
118+
data,
119+
"A",
120+
"F",
121+
false,
122+
"weight"
123+
);
124+
expect(length).toBe(6);
125+
expect(allPath[0]).toStrictEqual(["A", "E", "F"]);
126+
expect(path).toStrictEqual(["A", "E", "F"]);
94127
});
95128
});

0 commit comments

Comments
 (0)