UNPKG

avl-tree-typed

Version:
468 lines (419 loc) 13.7 kB
/** * data-structure-typed * * @author Pablo Zeng * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com> * @license MIT License */ import type { GraphOptions, VertexKey } from '../../types'; import { IGraph } from '../../interfaces'; import { AbstractEdge, AbstractGraph, AbstractVertex } from './abstract-graph'; import { arrayRemove } from '../../utils'; export class UndirectedVertex<V = any> extends AbstractVertex<V> { constructor(key: VertexKey, value?: V) { super(key, value); } } export class UndirectedEdge<E = number> extends AbstractEdge<E> { endpoints: [VertexKey, VertexKey]; constructor(v1: VertexKey, v2: VertexKey, weight?: number, value?: E) { super(weight, value); this.endpoints = [v1, v2]; } } /** * Undirected graph implementation. * @template V - Vertex value type. * @template E - Edge value type. * @template VO - Concrete vertex class (extends AbstractVertex<V>). * @template EO - Concrete edge class (extends AbstractEdge<E>). * @remarks Time O(1), Space O(1) * @example examples will be generated by unit test */ export class UndirectedGraph< V = any, E = any, VO extends UndirectedVertex<V> = UndirectedVertex<V>, EO extends UndirectedEdge<E> = UndirectedEdge<E> > extends AbstractGraph<V, E, VO, EO> implements IGraph<V, E, VO, EO> { /** * Construct an undirected graph with runtime defaults. * @param options - `GraphOptions<V>` (e.g. `vertexValueInitializer`, `defaultEdgeWeight`). * @remarks Time O(1), Space O(1) */ constructor(options?: Partial<GraphOptions<V>>) { super(options); this._edgeMap = new Map<VO, EO[]>(); } protected _edgeMap: Map<VO, EO[]>; get edgeMap(): Map<VO, EO[]> { return this._edgeMap; } set edgeMap(v: Map<VO, EO[]>) { this._edgeMap = v; } /** * Construct an undirected graph from keys with value initializer `v => v`. * @template K - Vertex key type. * @param keys - Iterable of vertex keys. * @returns UndirectedGraph with all keys added. * @remarks Time O(V), Space O(V) */ static fromKeys<K extends VertexKey>( keys: Iterable<K> ): UndirectedGraph<K, any, UndirectedVertex<K>, UndirectedEdge<any>> { const g: UndirectedGraph<K, any, UndirectedVertex<K>, UndirectedEdge<any>> = new UndirectedGraph<K, any>({ vertexValueInitializer: (k: VertexKey) => k as K }); for (const k of keys) g.addVertex(k); return g; } /** * Construct an undirected graph from `[key, value]` entries. * @template V - Vertex value type. * @param entries - Iterable of `[key, value]` pairs. * @returns UndirectedGraph with all vertices added. * @remarks Time O(V), Space O(V) */ static fromEntries<V>( entries: Iterable<[VertexKey, V]> ): UndirectedGraph<V, any, UndirectedVertex<V>, UndirectedEdge<any>> { const g: UndirectedGraph<V, any, UndirectedVertex<V>, UndirectedEdge<any>> = new UndirectedGraph<V, any>(); for (const [k, v] of entries) g.addVertex(k, v); return g; } /** * Create an undirected vertex instance. Does not insert into the graph. * @param key - Vertex identifier. * @param value - Optional payload. * @returns Concrete vertex instance. * @remarks Time O(1), Space O(1) */ createVertex(key: VertexKey, value?: VO['value']): VO { return new UndirectedVertex(key, value) as VO; } /** * Create an undirected edge instance. Does not insert into the graph. * @param v1 - One endpoint key. * @param v2 - The other endpoint key. * @param weight - Edge weight; defaults to `defaultEdgeWeight`. * @param value - Edge payload. * @returns Concrete edge instance. * @remarks Time O(1), Space O(1) */ override createEdge(v1: VertexKey, v2: VertexKey, weight?: number, value?: EO['value']): EO { return new UndirectedEdge(v1, v2, weight ?? this.options.defaultEdgeWeight ?? 1, value) as EO; } /** * Get an undirected edge between two vertices, if present. * @param v1 - One vertex or key. * @param v2 - The other vertex or key. * @returns Edge instance or `undefined`. * @remarks Time O(1) avg, Space O(1) */ getEdge(v1: VO | VertexKey | undefined, v2: VO | VertexKey | undefined): EO | undefined { let edgeMap: EO[] | undefined = []; if (v1 !== undefined && v2 !== undefined) { const vertex1: VO | undefined = this._getVertex(v1); const vertex2: VO | undefined = this._getVertex(v2); if (vertex1 && vertex2) { edgeMap = this._edgeMap.get(vertex1)?.filter(e => e.endpoints.includes(vertex2.key)); } } return edgeMap ? edgeMap[0] || undefined : undefined; } /** * Delete a single undirected edge between two vertices. * @param v1 - One vertex or key. * @param v2 - The other vertex or key. * @returns Removed edge or `undefined`. * @remarks Time O(1) avg, Space O(1) */ deleteEdgeBetween(v1: VO | VertexKey, v2: VO | VertexKey): EO | undefined { const vertex1: VO | undefined = this._getVertex(v1); const vertex2: VO | undefined = this._getVertex(v2); if (!vertex1 || !vertex2) { return undefined; } const v1Edges = this._edgeMap.get(vertex1); let removed: EO | undefined = undefined; if (v1Edges) { removed = arrayRemove<EO>(v1Edges, (e: EO) => e.endpoints.includes(vertex2.key))[0] || undefined; } const v2Edges = this._edgeMap.get(vertex2); if (v2Edges) { arrayRemove<EO>(v2Edges, (e: EO) => e.endpoints.includes(vertex1.key)); } return removed; } /** * Delete an edge by instance or by a pair of keys. * @param edgeOrOneSideVertexKey - Edge instance or one endpoint vertex/key. * @param otherSideVertexKey - Required second endpoint when deleting by pair. * @returns Removed edge or `undefined`. * @remarks Time O(1) avg, Space O(1) */ deleteEdge(edgeOrOneSideVertexKey: EO | VertexKey, otherSideVertexKey?: VertexKey): EO | undefined { let oneSide: VO | undefined, otherSide: VO | undefined; if (this.isVertexKey(edgeOrOneSideVertexKey)) { if (this.isVertexKey(otherSideVertexKey)) { oneSide = this._getVertex(edgeOrOneSideVertexKey); otherSide = this._getVertex(otherSideVertexKey); } else { return; } } else { oneSide = this._getVertex(edgeOrOneSideVertexKey.endpoints[0]); otherSide = this._getVertex(edgeOrOneSideVertexKey.endpoints[1]); } if (oneSide && otherSide) { return this.deleteEdgeBetween(oneSide, otherSide); } else { return; } } /** * Delete a vertex and remove it from all neighbor lists. * @param vertexOrKey - Vertex or key. * @returns `true` if removed; otherwise `false`. * @remarks Time O(deg), Space O(1) */ deleteVertex(vertexOrKey: VO | VertexKey): boolean { let vertexKey: VertexKey; let vertex: VO | undefined; if (this.isVertexKey(vertexOrKey)) { vertex = this.getVertex(vertexOrKey); vertexKey = vertexOrKey; } else { vertex = vertexOrKey; vertexKey = this._getVertexKey(vertexOrKey); } /** * All neighbors connected via undirected edges. * @param vertexOrKey - Vertex or key. * @returns Array of neighbor vertices. * @remarks Time O(deg), Space O(deg) */ const neighbors = this.getNeighbors(vertexOrKey); if (vertex) { neighbors.forEach(neighbor => { const neighborEdges = this._edgeMap.get(neighbor); if (neighborEdges) { const restEdges = neighborEdges.filter(edge => { return !edge.endpoints.includes(vertexKey); }); this._edgeMap.set(neighbor, restEdges); } }); this._edgeMap.delete(vertex); } return this._vertexMap.delete(vertexKey); } /** * Degree of a vertex (# of incident undirected edges). * @param vertexOrKey - Vertex or key. * @returns Non-negative integer. * @remarks Time O(1) avg, Space O(1) */ degreeOf(vertexOrKey: VertexKey | VO): number { const vertex = this._getVertex(vertexOrKey); if (vertex) { return this._edgeMap.get(vertex)?.length || 0; } else { return 0; } } /** * Incident undirected edges of a vertex. * @param vertexOrKey - Vertex or key. * @returns Array of incident edges. * @remarks Time O(deg), Space O(deg) */ edgesOf(vertexOrKey: VertexKey | VO): EO[] { const vertex = this._getVertex(vertexOrKey); if (vertex) { return this._edgeMap.get(vertex) || []; } else { return []; } } /** * Unique set of undirected edges across endpoints. * @returns Array of edges. * @remarks Time O(E), Space O(E) */ edgeSet(): EO[] { const edgeSet: Set<EO> = new Set(); this._edgeMap.forEach(edgeMap => { edgeMap.forEach(edge => { edgeSet.add(edge); }); }); return [...edgeSet]; } getNeighbors(vertexOrKey: VO | VertexKey): VO[] { const neighbors: VO[] = []; const vertex = this._getVertex(vertexOrKey); if (vertex) { const neighborEdges = this.edgesOf(vertex); for (const edge of neighborEdges) { const neighbor = this._getVertex(edge.endpoints.filter(e => e !== vertex.key)[0]); if (neighbor) { neighbors.push(neighbor); } } } return neighbors; } /** * Resolve an edge's two endpoints to vertex instances. * @param edge - Edge instance. * @returns `[v1, v2]` or `undefined` if either endpoint is missing. * @remarks Time O(1), Space O(1) */ getEndsOfEdge(edge: EO): [VO, VO] | undefined { if (!this.hasEdge(edge.endpoints[0], edge.endpoints[1])) { return undefined; } const v1 = this._getVertex(edge.endpoints[0]); const v2 = this._getVertex(edge.endpoints[1]); if (v1 && v2) { return [v1, v2]; } else { return undefined; } } /** * Whether the graph has no vertices and no edges. * @remarks Time O(1), Space O(1) */ isEmpty(): boolean { return this.vertexMap.size === 0 && this.edgeMap.size === 0; } /** * Remove all vertices and edges. * @remarks Time O(V + E), Space O(1) */ clear() { this._vertexMap = new Map<VertexKey, VO>(); this._edgeMap = new Map<VO, EO[]>(); } /** * Deep clone as the same concrete class. * @returns A new graph of the same concrete class (`this` type). * @remarks Time O(V + E), Space O(V + E) */ override clone(): this { return super.clone(); } /** * Tarjan-based bridge and articulation point detection. * @returns `{ dfnMap, lowMap, bridges, cutVertices }`. * @remarks Time O(V + E), Space O(V + E) */ tarjan(): { dfnMap: Map<VO, number>; lowMap: Map<VO, number>; bridges: EO[]; cutVertices: VO[] } { const dfnMap = new Map<VO, number>(); const lowMap = new Map<VO, number>(); const bridges: EO[] = []; const cutVertices: VO[] = []; let time = 0; const dfs = (vertex: VO, parent: VO | undefined) => { dfnMap.set(vertex, time); lowMap.set(vertex, time); time++; const neighbors = this.getNeighbors(vertex); let childCount = 0; for (const neighbor of neighbors) { if (!dfnMap.has(neighbor)) { childCount++; dfs(neighbor, vertex); lowMap.set(vertex, Math.min(lowMap.get(vertex)!, lowMap.get(neighbor)!)); if (lowMap.get(neighbor)! > dfnMap.get(vertex)!) { // Found a bridge const edge = this.getEdge(vertex, neighbor); if (edge) { bridges.push(edge); } } if (parent !== undefined && lowMap.get(neighbor)! >= dfnMap.get(vertex)!) { // Found an articulation point cutVertices.push(vertex); } } else if (neighbor !== parent) { lowMap.set(vertex, Math.min(lowMap.get(vertex)!, dfnMap.get(neighbor)!)); } } if (parent === undefined && childCount > 1) { // Special case for root in DFS tree cutVertices.push(vertex); } }; for (const vertex of this.vertexMap.values()) { if (!dfnMap.has(vertex)) { dfs(vertex, undefined); } } return { dfnMap, lowMap, bridges, cutVertices }; } /** * Get bridges discovered by `tarjan()`. * @returns Array of edges that are bridges. * @remarks Time O(B), Space O(1) */ getBridges() { return this.tarjan().bridges; } /** * Get articulation points discovered by `tarjan()`. * @returns Array of cut vertices. * @remarks Time O(C), Space O(1) */ getCutVertices() { return this.tarjan().cutVertices; } /** * DFN index map computed by `tarjan()`. * @returns Map from vertex to DFN index. * @remarks Time O(V), Space O(V) */ getDFNMap() { return this.tarjan().dfnMap; } /** * LOW link map computed by `tarjan()`. * @returns Map from vertex to LOW value. * @remarks Time O(V), Space O(V) */ getLowMap() { return this.tarjan().lowMap; } /** * Internal hook to attach an undirected edge into adjacency maps. * @param edge - Edge instance. * @returns `true` if both endpoints exist; otherwise `false`. * @remarks Time O(1) avg, Space O(1) */ protected _addEdge(edge: EO): boolean { for (const end of edge.endpoints) { const endVertex = this._getVertex(end); if (endVertex === undefined) return false; if (endVertex) { const edgeMap = this._edgeMap.get(endVertex); if (edgeMap) { edgeMap.push(edge); } else { this._edgeMap.set(endVertex, [edge]); } } } return true; } }