Add bidi search
- Added g3 graph and one test
This commit is contained in:
3 files changed
+149
-12
No files matched your search
+14
-2
@@ -1,7 +1,7 @@
|
||||
import { g1, g2 } from '../tools/graph/_graph-samples.ts'
|
||||
import { g1, g2, g3 } from '../tools/graph/_graph-samples.ts'
|
||||
import { GraphSolver } from '../tools/graph/graph-solver.ts'
|
||||
import { assertEquals } from "https://deno.land/std/testing/asserts.ts"
|
||||
import { Path } from "../tools/graph/graph.ts";
|
||||
import { Path } from "../tools/graph/graph.ts"
|
||||
|
||||
// TODO: Add more tests!
|
||||
|
||||
@@ -39,6 +39,18 @@ Deno.test('GraphSolver::DFS w/ g1', () => {
|
||||
)
|
||||
})
|
||||
|
||||
Deno.test('GraphSolver::bidi', () => {
|
||||
const G = g3()
|
||||
const solver = new GraphSolver(G)
|
||||
const path = solver.bidi(G.vertices.get('A')!, G.vertices.get('E')!)
|
||||
|
||||
assertPathsEqual(
|
||||
path,
|
||||
'ABCDE'.split(''),
|
||||
'AB-BC-CD-DE'.split('-')
|
||||
)
|
||||
})
|
||||
|
||||
function assertPathsEqual<vData, eData>(
|
||||
path: Path<vData, eData> | null | undefined,
|
||||
expectedV: string[],
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
import { Graph } from './graph.ts'
|
||||
|
||||
// Box drawing characters!
|
||||
// ┌ ┬ ┐ ─│
|
||||
// ├ ┼ ┤ ╱╳╲
|
||||
// └ ┴ ┘ ╵╶╷╴
|
||||
|
||||
/**
|
||||
* Creates a undirected cyclic graph.
|
||||
* Creates an *undirected cyclic graph*
|
||||
*
|
||||
* ```plaintext
|
||||
* A
|
||||
* / \
|
||||
* C B
|
||||
* \ / \
|
||||
* \ E D
|
||||
* \|
|
||||
* F
|
||||
* ┌── B ─── D
|
||||
* │ │
|
||||
* A E ── F
|
||||
* │ │
|
||||
* └─ C ────┘
|
||||
* ```
|
||||
*/
|
||||
export function g1() {
|
||||
// A
|
||||
// / \
|
||||
// C B
|
||||
// \ / \
|
||||
// \ E D
|
||||
// \|
|
||||
// F
|
||||
const G = new Graph<null, null>({ directed: false })
|
||||
|
||||
const vertices = [
|
||||
@@ -70,3 +80,51 @@ export function g2() {
|
||||
|
||||
return G
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an *undirected weighted cyclic graph.* The numbers next to the edges are the weights.
|
||||
*
|
||||
* ```plaintext
|
||||
* 4 8 7 9
|
||||
* ┌──── B ───── C ───── D ────┐
|
||||
* │ │ 2│ ╲ │ │
|
||||
* A 11│ 7┌─── I ╲4 │14 E
|
||||
* │ │ ╱ 6│ ╲ │ │
|
||||
* └──── H ───── G ───── F ────┘
|
||||
* 8 1 2 10
|
||||
* ```
|
||||
*/
|
||||
export function g3() {
|
||||
const G = new Graph<null, number>({
|
||||
directed: false,
|
||||
weights: e => e.data
|
||||
})
|
||||
const vertices = [
|
||||
G.createVertex(null, 'A'),
|
||||
G.createVertex(null, 'B'),
|
||||
G.createVertex(null, 'C'),
|
||||
G.createVertex(null, 'D'),
|
||||
G.createVertex(null, 'E'),
|
||||
G.createVertex(null, 'F'),
|
||||
G.createVertex(null, 'G'),
|
||||
G.createVertex(null, 'H'),
|
||||
G.createVertex(null, 'I'),
|
||||
G.createVertex(null, 'J'),
|
||||
]
|
||||
|
||||
G.createEdge(vertices[0], vertices[1], 4, { id: 'AB' })
|
||||
G.createEdge(vertices[0], vertices[7], 8, { id: 'AH' })
|
||||
G.createEdge(vertices[1], vertices[2], 8, { id: 'BC' })
|
||||
G.createEdge(vertices[1], vertices[7], 11, { id: 'BH' })
|
||||
G.createEdge(vertices[2], vertices[3], 7, { id: 'CD' })
|
||||
G.createEdge(vertices[2], vertices[5], 4, { id: 'CF' })
|
||||
G.createEdge(vertices[2], vertices[8], 2, { id: 'CI' })
|
||||
G.createEdge(vertices[3], vertices[4], 9, { id: 'DE' })
|
||||
G.createEdge(vertices[3], vertices[5], 14, { id: 'DF' })
|
||||
G.createEdge(vertices[4], vertices[5], 10, { id: 'EF' })
|
||||
G.createEdge(vertices[5], vertices[6], 2, { id: 'FG' })
|
||||
G.createEdge(vertices[6], vertices[7], 1, { id: 'GH' })
|
||||
G.createEdge(vertices[6], vertices[8], 6, { id: 'GI' })
|
||||
|
||||
return G
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Graph, VertexType, EdgeType, Path, Vertex, PathType } from "./graph.ts"
|
||||
import { Queue, Stack } from "../iterables.ts"
|
||||
import { Graph, Vertex, VertexType, EdgeType, Path, PathType } from "./graph.ts"
|
||||
import { Queue, Stack } from "../structures.ts"
|
||||
|
||||
/**
|
||||
* Describes a search function for DFS and BFS.
|
||||
@@ -78,7 +78,74 @@ export class GraphSolver<vData, eData> {
|
||||
backPaths.set(w, path)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a bidirectional search on a graph. Returns the shortest path from `left` to `right`.
|
||||
* @author MindfulMinun
|
||||
* @since 2022-11-14
|
||||
*/
|
||||
bidi(left: Vertex<vData, eData>, right: Vertex<vData, eData>): Path<vData, eData> | null {
|
||||
const qL = new Queue([left])
|
||||
const qR = new Queue([right])
|
||||
|
||||
// Map a vertex to the path that led to it. Note that paths from the right
|
||||
// will be reversed so they can be concatenated with the paths from the left.
|
||||
const backL = new Map<Vertex<vData, eData>, Path<vData, eData>>()
|
||||
const backR = new Map<Vertex<vData, eData>, Path<vData, eData>>()
|
||||
|
||||
backL.set(left, this.G.createPath(left))
|
||||
backR.set(right, this.G.createPath(right))
|
||||
|
||||
const seenL = new Set<Vertex<vData, eData>>([left])
|
||||
const seenR = new Set<Vertex<vData, eData>>([right])
|
||||
|
||||
while (qL.length && qR.length) {
|
||||
const l = qL.dequeue()!
|
||||
const r = qR.dequeue()!
|
||||
|
||||
// The left side will traverse the graph normally,
|
||||
// following the direction of the arrows in the edges
|
||||
for (const E of l.adjacentEdges) {
|
||||
if (E.directed && l !== E.u) continue
|
||||
const w = E.not(l)
|
||||
if (!seenL.has(w)) {
|
||||
seenL.add(w)
|
||||
const path = backL.get(l)!.copy()
|
||||
path.addEdge(E)
|
||||
backL.set(w, path)
|
||||
qL.enqueue(w)
|
||||
}
|
||||
}
|
||||
|
||||
// For the right side, we will traverse the graph backwards
|
||||
// So, against the direction of the arrows :)
|
||||
for (const E of r.adjacentEdges) {
|
||||
// Compare against v since edges always point to v,
|
||||
// Edge u -> v
|
||||
if (E.directed && r !== E.v) continue
|
||||
const w = E.not(r)
|
||||
if (!seenR.has(w)) {
|
||||
seenR.add(w)
|
||||
const path = backR.get(r)!.copy()
|
||||
path.addEdge(E)
|
||||
backR.set(w, path)
|
||||
qR.enqueue(w)
|
||||
}
|
||||
}
|
||||
|
||||
// If we find a vertex that has been seen from both sides, then we have found a path!
|
||||
const midpoint = [...seenL].find(x => seenR.has(x))
|
||||
if (!midpoint) continue
|
||||
// console.log("Midpoint:", midpoint)
|
||||
|
||||
// Concatenate the paths from the left and right to get the shortest path
|
||||
// Flip the path from the right so it's in the correct order
|
||||
const path = backL.get(midpoint)!.copy()
|
||||
path.edges.push(...backR.get(midpoint)!.edges.reverse())
|
||||
return path
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user