avl-tree-typed
Version:
Standard AVL tree
591 lines (517 loc) • 17.3 kB
text/typescript
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import type { GraphOptions, TopologicalStatus, VertexKey } from '../../types';
import { AbstractEdge, AbstractGraph, AbstractVertex } from './abstract-graph';
import { IGraph } from '../../interfaces';
import { arrayRemove } from '../../utils';
export class DirectedVertex<V = any> extends AbstractVertex<V> {
constructor(key: VertexKey, value?: V) {
super(key, value);
}
}
export class DirectedEdge<E = any> extends AbstractEdge<E> {
src: VertexKey;
dest: VertexKey;
constructor(src: VertexKey, dest: VertexKey, weight?: number, value?: E) {
super(weight, value);
this.src = src;
this.dest = dest;
}
}
/**
* Directed 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 DirectedGraph<
V = any,
E = any,
VO extends DirectedVertex<V> = DirectedVertex<V>,
EO extends DirectedEdge<E> = DirectedEdge<E>
>
extends AbstractGraph<V, E, VO, EO>
implements IGraph<V, E, VO, EO>
{
/**
* Construct a directed 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);
}
protected _outEdgeMap: Map<VO, EO[]> = new Map<VO, EO[]>();
get outEdgeMap(): Map<VO, EO[]> {
return this._outEdgeMap;
}
set outEdgeMap(v: Map<VO, EO[]>) {
this._outEdgeMap = v;
}
protected _inEdgeMap: Map<VO, EO[]> = new Map<VO, EO[]>();
get inEdgeMap(): Map<VO, EO[]> {
return this._inEdgeMap;
}
set inEdgeMap(v: Map<VO, EO[]>) {
this._inEdgeMap = v;
}
/**
* Construct a directed graph from keys with value initializer `v => v`.
* @template K - Vertex key type.
* @param keys - Iterable of vertex keys.
* @returns DirectedGraph with all keys added.
* @remarks Time O(V), Space O(V)
*/
static fromKeys<K extends VertexKey>(keys: Iterable<K>): DirectedGraph<K, any, DirectedVertex<K>, DirectedEdge<any>> {
const g: DirectedGraph<K, any, DirectedVertex<K>, DirectedEdge<any>> = new DirectedGraph<K, any>({
vertexValueInitializer: (k: VertexKey) => k as K
});
for (const k of keys) g.addVertex(k);
return g;
}
/**
* Construct a directed graph from `[key, value]` entries.
* @template V - Vertex value type.
* @param entries - Iterable of `[key, value]` pairs.
* @returns DirectedGraph with all vertices added.
* @remarks Time O(V), Space O(V)
*/
static fromEntries<V>(
entries: Iterable<[VertexKey, V]>
): DirectedGraph<V, any, DirectedVertex<V>, DirectedEdge<any>> {
const g: DirectedGraph<V, any, DirectedVertex<V>, DirectedEdge<any>> = new DirectedGraph<V, any>();
for (const [k, v] of entries) g.addVertex(k, v);
return g;
}
/**
* Create a directed 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 DirectedVertex(key, value) as VO;
}
/**
* Create a directed edge instance. Does not insert into the graph.
* @param src - Source vertex key.
* @param dest - Destination vertex key.
* @param weight - Edge weight; defaults to `defaultEdgeWeight`.
* @param value - Edge payload.
* @returns Concrete edge instance.
* @remarks Time O(1), Space O(1)
*/
createEdge(src: VertexKey, dest: VertexKey, weight?: number, value?: E): EO {
return new DirectedEdge(src, dest, weight ?? this.options.defaultEdgeWeight ?? 1, value) as EO;
}
/**
* Get the unique edge from `src` to `dest`, if present.
* @param srcOrKey - Source vertex or key.
* @param destOrKey - Destination vertex or key.
* @returns Edge instance or `undefined`.
* @remarks Time O(1) avg, Space O(1)
*/
getEdge(srcOrKey: VO | VertexKey | undefined, destOrKey: VO | VertexKey | undefined): EO | undefined {
let edgeMap: EO[] = [];
if (srcOrKey !== undefined && destOrKey !== undefined) {
const src: VO | undefined = this._getVertex(srcOrKey);
const dest: VO | undefined = this._getVertex(destOrKey);
if (src && dest) {
const srcOutEdges = this._outEdgeMap.get(src);
if (srcOutEdges) {
edgeMap = srcOutEdges.filter(edge => edge.dest === dest.key);
}
}
}
return edgeMap[0] || undefined;
}
/**
* Delete edge `src -> dest` if present.
* @param srcOrKey - Source vertex or key.
* @param destOrKey - Destination vertex or key.
* @returns Removed edge or `undefined`.
* @remarks Time O(1) avg, Space O(1)
*/
deleteEdgeSrcToDest(srcOrKey: VO | VertexKey, destOrKey: VO | VertexKey): EO | undefined {
const src: VO | undefined = this._getVertex(srcOrKey);
const dest: VO | undefined = this._getVertex(destOrKey);
let removed: EO | undefined = undefined;
if (!src || !dest) {
return undefined;
}
const srcOutEdges = this._outEdgeMap.get(src);
if (srcOutEdges) {
arrayRemove<EO>(srcOutEdges, (edge: EO) => edge.dest === dest.key);
}
const destInEdges = this._inEdgeMap.get(dest);
if (destInEdges) {
removed = arrayRemove<EO>(destInEdges, (edge: EO) => edge.src === src.key)[0] || undefined;
}
return removed;
}
/**
* Delete an edge by instance or by `(srcKey, destKey)`.
* @param edgeOrSrcVertexKey - Edge instance or source vertex/key.
* @param destVertexKey - Optional destination vertex/key when deleting by pair.
* @returns Removed edge or `undefined`.
* @remarks Time O(1) avg, Space O(1)
*/
deleteEdge(edgeOrSrcVertexKey: EO | VertexKey, destVertexKey?: VertexKey): EO | undefined {
let removed: EO | undefined = undefined;
let src: VO | undefined, dest: VO | undefined;
if (this.isVertexKey(edgeOrSrcVertexKey)) {
if (this.isVertexKey(destVertexKey)) {
src = this._getVertex(edgeOrSrcVertexKey);
dest = this._getVertex(destVertexKey);
} else {
return;
}
} else {
src = this._getVertex(edgeOrSrcVertexKey.src);
dest = this._getVertex(edgeOrSrcVertexKey.dest);
}
if (src && dest) {
const srcOutEdges = this._outEdgeMap.get(src);
if (srcOutEdges && srcOutEdges.length > 0) {
arrayRemove(srcOutEdges, (edge: EO) => edge.src === src!.key && edge.dest === dest?.key);
}
const destInEdges = this._inEdgeMap.get(dest);
if (destInEdges && destInEdges.length > 0) {
removed = arrayRemove(destInEdges, (edge: EO) => edge.src === src!.key && edge.dest === dest!.key)[0];
}
}
return removed;
}
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);
}
if (vertex) {
/**
* One-step neighbors following outgoing edges.
* @param vertexOrKey - Vertex or key.
* @returns Array of neighbor vertices.
* @remarks Time O(deg_out), Space O(deg_out)
*/
const neighbors = this.getNeighbors(vertex);
for (const neighbor of neighbors) {
this.deleteEdgeSrcToDest(vertex, neighbor);
}
this._outEdgeMap.delete(vertex);
this._inEdgeMap.delete(vertex);
}
return this._vertexMap.delete(vertexKey);
}
deleteEdgesBetween(v1: VertexKey | VO, v2: VertexKey | VO): EO[] {
const removed: EO[] = [];
if (v1 && v2) {
const v1ToV2 = this.deleteEdgeSrcToDest(v1, v2);
const v2ToV1 = this.deleteEdgeSrcToDest(v2, v1);
if (v1ToV2) removed.push(v1ToV2);
if (v2ToV1) removed.push(v2ToV1);
}
return removed;
}
/**
* Incoming edges of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Array of incoming edges.
* @remarks Time O(deg_in), Space O(deg_in)
*/
incomingEdgesOf(vertexOrKey: VO | VertexKey): EO[] {
const target = this._getVertex(vertexOrKey);
if (target) {
return this.inEdgeMap.get(target) || [];
}
return [];
}
/**
* Outgoing edges of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Array of outgoing edges.
* @remarks Time O(deg_out), Space O(deg_out)
*/
outgoingEdgesOf(vertexOrKey: VO | VertexKey): EO[] {
const target = this._getVertex(vertexOrKey);
if (target) {
return this._outEdgeMap.get(target) || [];
}
return [];
}
/**
* Degree (in + out) of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Non-negative integer.
* @remarks Time O(1) avg, Space O(1)
*/
degreeOf(vertexOrKey: VertexKey | VO): number {
/**
* In-degree of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Non-negative integer.
* @remarks Time O(1) avg, Space O(1)
*/
/**
* Out-degree of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Non-negative integer.
* @remarks Time O(1) avg, Space O(1)
*/
return this.outDegreeOf(vertexOrKey) + this.inDegreeOf(vertexOrKey);
}
inDegreeOf(vertexOrKey: VertexKey | VO): number {
return this.incomingEdgesOf(vertexOrKey).length;
}
outDegreeOf(vertexOrKey: VertexKey | VO): number {
return this.outgoingEdgesOf(vertexOrKey).length;
}
/**
* All incident edges of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Array of incident edges.
* @remarks Time O(deg_in + deg_out), Space O(deg_in + deg_out)
*/
edgesOf(vertexOrKey: VertexKey | VO): EO[] {
return [...this.outgoingEdgesOf(vertexOrKey), ...this.incomingEdgesOf(vertexOrKey)];
}
getEdgeSrc(e: EO): VO | undefined {
return this._getVertex(e.src);
}
getEdgeDest(e: EO): VO | undefined {
return this._getVertex(e.dest);
}
/**
* Direct children reachable by one outgoing edge.
* @param vertex - Vertex or key.
* @returns Array of neighbor vertices.
* @remarks Time O(deg_out), Space O(deg_out)
*/
getDestinations(vertex: VO | VertexKey | undefined): VO[] {
if (vertex === undefined) {
return [];
}
const destinations: VO[] = [];
const outgoingEdges = this.outgoingEdgesOf(vertex);
for (const outEdge of outgoingEdges) {
const child = this.getEdgeDest(outEdge);
if (child) {
destinations.push(child);
}
}
return destinations;
}
/**
* Topological sort if DAG; returns `undefined` if a cycle exists.
* @param propertyName - `'key'` to map to keys; `'vertex'` to keep instances.
* @returns Array of keys/vertices, or `undefined` when cycle is found.
* @remarks Time O(V + E), Space O(V)
*/
topologicalSort(propertyName?: 'vertex' | 'key'): Array<VO | VertexKey> | undefined {
propertyName = propertyName ?? 'key';
const statusMap: Map<VO | VertexKey, TopologicalStatus> = new Map<VO | VertexKey, TopologicalStatus>();
for (const entry of this.vertexMap) {
statusMap.set(entry[1], 0);
}
let sorted: (VO | VertexKey)[] = [];
let hasCycle = false;
const dfs = (cur: VO | VertexKey) => {
statusMap.set(cur, 1);
const children = this.getDestinations(cur);
for (const child of children) {
const childStatus = statusMap.get(child);
if (childStatus === 0) {
dfs(child);
} else if (childStatus === 1) {
hasCycle = true;
}
}
statusMap.set(cur, 2);
sorted.push(cur);
};
for (const entry of this.vertexMap) {
if (statusMap.get(entry[1]) === 0) {
dfs(entry[1]);
}
}
if (hasCycle) return undefined;
if (propertyName === 'key') sorted = sorted.map(vertex => (vertex instanceof DirectedVertex ? vertex.key : vertex));
return sorted.reverse();
}
edgeSet(): EO[] {
let edgeMap: EO[] = [];
this._outEdgeMap.forEach(outEdges => {
edgeMap = [...edgeMap, ...outEdges];
});
return edgeMap;
}
getNeighbors(vertexOrKey: VO | VertexKey): VO[] {
const neighbors: VO[] = [];
const vertex = this._getVertex(vertexOrKey);
if (vertex) {
const outEdges = this.outgoingEdgesOf(vertex);
for (const outEdge of outEdges) {
const neighbor = this._getVertex(outEdge.dest);
if (neighbor) {
neighbors.push(neighbor);
}
}
}
return neighbors;
}
/**
* Resolve an edge's `[src, dest]` endpoints to vertex instances.
* @param edge - Edge instance.
* @returns `[src, dest]` or `undefined` if either endpoint is missing.
* @remarks Time O(1), Space O(1)
*/
getEndsOfEdge(edge: EO): [VO, VO] | undefined {
if (!this.hasEdge(edge.src, edge.dest)) {
return undefined;
}
const v1 = this._getVertex(edge.src);
const v2 = this._getVertex(edge.dest);
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.inEdgeMap.size === 0 && this.outEdgeMap.size === 0;
}
/**
* Remove all vertices and edges.
* @remarks Time O(V + E), Space O(1)
*/
clear() {
this._vertexMap = new Map<VertexKey, VO>();
this._inEdgeMap = new Map<VO, EO[]>();
this._outEdgeMap = 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's algorithm for strongly connected components.
* @returns `{ dfnMap, lowMap, SCCs }`.
* @remarks Time O(V + E), Space O(V + E)
*/
tarjan(): { dfnMap: Map<VO, number>; lowMap: Map<VO, number>; SCCs: Map<number, VO[]> } {
const dfnMap = new Map<VO, number>();
const lowMap = new Map<VO, number>();
const SCCs = new Map<number, VO[]>();
let time = 0;
const stack: VO[] = [];
const inStack: Set<VO> = new Set();
const dfs = (vertex: VO) => {
dfnMap.set(vertex, time);
lowMap.set(vertex, time);
time++;
stack.push(vertex);
inStack.add(vertex);
const neighbors = this.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (!dfnMap.has(neighbor)) {
dfs(neighbor);
lowMap.set(vertex, Math.min(lowMap.get(vertex)!, lowMap.get(neighbor)!));
} else if (inStack.has(neighbor)) {
lowMap.set(vertex, Math.min(lowMap.get(vertex)!, dfnMap.get(neighbor)!));
}
}
if (dfnMap.get(vertex) === lowMap.get(vertex)) {
const SCC: VO[] = [];
let poppedVertex: VO | undefined;
do {
poppedVertex = stack.pop();
inStack.delete(poppedVertex!);
SCC.push(poppedVertex!);
} while (poppedVertex !== vertex);
SCCs.set(SCCs.size, SCC);
}
};
for (const vertex of this.vertexMap.values()) {
if (!dfnMap.has(vertex)) {
dfs(vertex);
}
}
return { dfnMap, lowMap, SCCs };
}
/**
* DFN index map computed by `tarjan()`.
* @returns Map from vertex to DFN index.
* @remarks Time O(V), Space O(V)
*/
getDFNMap(): Map<VO, number> {
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(): Map<VO, number> {
return this.tarjan().lowMap;
}
/**
* Strongly connected components computed by `tarjan()`.
* @returns Map from SCC id to vertices.
* @remarks Time O(#SCC + V), Space O(V)
*/
getSCCs(): Map<number, VO[]> {
return this.tarjan().SCCs;
}
/**
* Internal hook to attach a directed edge into adjacency maps.
* @param edge - Edge instance.
* @returns `true` if inserted; otherwise `false`.
* @remarks Time O(1) avg, Space O(1)
*/
protected _addEdge(edge: EO): boolean {
if (!(this.hasVertex(edge.src) && this.hasVertex(edge.dest))) {
return false;
}
const srcVertex = this._getVertex(edge.src);
const destVertex = this._getVertex(edge.dest);
if (srcVertex && destVertex) {
const srcOutEdges = this._outEdgeMap.get(srcVertex);
if (srcOutEdges) {
srcOutEdges.push(edge);
} else {
this._outEdgeMap.set(srcVertex, [edge]);
}
const destInEdges = this._inEdgeMap.get(destVertex);
if (destInEdges) {
destInEdges.push(edge);
} else {
this._inEdgeMap.set(destVertex, [edge]);
}
return true;
} else {
return false;
}
}
}