avl-tree-typed
Version:
Standard AVL tree
468 lines (419 loc) • 13.7 kB
text/typescript
/**
* 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;
}
}