Add bidi search

- Added g3 graph and one test
This commit is contained in:
mindfulminun committed 2022-11-14 03:49:14 -06:00
1 parent 14515278df
commit b83668ef07
3 files changed
+149 -12

No files matched your search

+14 -2
View File
@@ -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[],
+66 -8
View File
@@ -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
}
+69 -2
View File
@@ -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
}
}