fix: #74 . Refactor: Implement the Tarjan algorithm separately for DirectedGraph and UndirectedGraph. In the previous design, the tarjan method was placed in the AbstractGraph class. However, Tarjan algorithm is used to find connected components in directed graphs and to solve bridges, articulation points, determine the presence of cycles (though not all), and biconnected components in undirected graphs. The algorithm for finding all cycles in the graph can be shared between directed and undirected graphs using the getCycles method.

This commit is contained in:
Revone 2024-01-07 22:25:25 +08:00
parent e91970c09f
commit 5c9f4fd483
7 changed files with 689 additions and 506 deletions

View file

@ -854,7 +854,7 @@ Array.from(dijkstraResult?.seen ?? []).map(vertex => vertex.key) // ['A', 'B', '
<td>Recursion</td>
</tr>
<tr>
<td>Graph getCutVertexes</td>
<td>Graph getCutVertices</td>
<td>Find cut vertices in a graph, which are nodes that, when removed, increase the number of connected components in
the graph.
</td>

View file

@ -885,7 +885,7 @@ avl2.print();
<td>递归</td>
</tr>
<tr>
<td>图的getCutVertexes</td>
<td>图的getCutVertices</td>
<td>在图中找到切点,这些是移除后会增加图中连通分量数量的节点。</td>
<td>递归</td>
</tr>

View file

@ -954,217 +954,6 @@ export abstract class AbstractGraph<
return { costs, predecessor };
}
/**
* Time Complexity: O(V + E) - Linear time (Tarjan's algorithm).
* Space Complexity: O(V) - Linear space (Tarjan's algorithm).
* Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
* Tarjan can find cycles in directed or undirected graph
* Tarjan can find the articulation points and bridges(critical edgeMap) of undirected graphs in linear time,
* Tarjan solve the bi-connected components of undirected graphs;
* Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs.
* /
/**
* Time Complexity: O(V + E) - Linear time (Tarjan's algorithm).
* Space Complexity: O(V) - Linear space (Tarjan's algorithm).
*
* Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
* Tarjan can find cycles in directed or undirected graph
* Tarjan can find the articulation points and bridges(critical edgeMap) of undirected graphs in linear time,
* Tarjan solve the bi-connected components of undirected graphs;
* Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs.
* The `tarjan` function is used to perform various graph analysis tasks such as finding articulation points, bridges,
* strongly connected components (SCCs), and cycles in a graph.
* @param {boolean} [needCutVertexes] - A boolean value indicating whether or not to calculate and return the
* articulation points in the graph. Articulation points are the vertexMap in a graph whose removal would increase the
* number of connected components in the graph.
* @param {boolean} [needBridges] - A boolean flag indicating whether the algorithm should find and return the bridges
* (edgeMap whose removal would increase the number of connected components in the graph).
* @param {boolean} [needSCCs] - A boolean value indicating whether the Strongly Connected Components (SCCs) of the
* graph are needed. If set to true, the function will calculate and return the SCCs of the graph. If set to false, the
* SCCs will not be calculated or returned.
* @param {boolean} [needCycles] - A boolean flag indicating whether the algorithm should find cycles in the graph. If
* set to true, the algorithm will return a map of cycles, where the keys are the low values of the SCCs and the values
* are arrays of vertexMap that form cycles within the SCCs.
* @returns The function `tarjan` returns an object with the following properties:
*/
tarjan(
needCutVertexes: boolean = false,
needBridges: boolean = false,
needSCCs: boolean = true,
needCycles: boolean = false
) {
// !! in undirected graph we will not let child visit parent when dfs
// !! articulation point(in dfs search tree not in graph): (cur !== root && cur.has(child)) && (low(child) >= dfn(cur)) || (cur === root && cur.children() >= 2)
// !! bridge: low(child) > dfn(cur)
const defaultConfig = false;
if (needCutVertexes === undefined) needCutVertexes = defaultConfig;
if (needBridges === undefined) needBridges = defaultConfig;
if (needSCCs === undefined) needSCCs = defaultConfig;
if (needCycles === undefined) needCycles = defaultConfig;
const dfnMap: Map<VO, number> = new Map();
const lowMap: Map<VO, number> = new Map();
const vertexMap = this._vertexMap;
vertexMap.forEach(v => {
dfnMap.set(v, -1);
lowMap.set(v, Infinity);
});
const [root] = vertexMap.values();
const cutVertexes: VO[] = [];
const bridges: EO[] = [];
let dfn = 0;
const dfs = (cur: VO, parent: VO | undefined) => {
dfn++;
dfnMap.set(cur, dfn);
lowMap.set(cur, dfn);
const neighbors = this.getNeighbors(cur);
let childCount = 0; // child in dfs tree not child in graph
for (const neighbor of neighbors) {
if (neighbor !== parent) {
if (dfnMap.get(neighbor) === -1) {
childCount++;
dfs(neighbor, cur);
}
const childLow = lowMap.get(neighbor);
const curLow = lowMap.get(cur);
// TODO after no-non-undefined-assertion not ensure the logic
if (curLow !== undefined && childLow !== undefined) {
lowMap.set(cur, Math.min(curLow, childLow));
}
const curFromMap = dfnMap.get(cur);
if (childLow !== undefined && curFromMap !== undefined) {
if (needCutVertexes) {
if ((cur === root && childCount >= 2) || (cur !== root && childLow >= curFromMap)) {
// todo not ensure the logic if (cur === root && childCount >= 2 || ((cur !== root) && (childLow >= curFromMap))) {
cutVertexes.push(cur);
}
}
if (needBridges) {
if (childLow > curFromMap) {
const edgeCurToNeighbor = this.getEdge(cur, neighbor);
if (edgeCurToNeighbor) {
bridges.push(edgeCurToNeighbor);
}
}
}
}
}
}
};
dfs(root, undefined);
let SCCs: Map<number, VO[]> = new Map();
const getSCCs = () => {
const SCCs: Map<number, VO[]> = new Map();
lowMap.forEach((low, vertex) => {
if (!SCCs.has(low)) {
SCCs.set(low, [vertex]);
} else {
SCCs.get(low)?.push(vertex);
}
});
return SCCs;
};
if (needSCCs) {
SCCs = getSCCs();
}
const cycles: Map<number, VO[]> = new Map();
if (needCycles) {
const visitedMap: Map<VO, boolean> = new Map();
const stack: VO[] = [];
const findCyclesDFS = (cur: VO, parent: VO | undefined) => {
visitedMap.set(cur, true);
stack.push(cur);
const neighbors = this.getNeighbors(cur);
for (const neighbor of neighbors) {
if (!visitedMap.get(neighbor)) {
findCyclesDFS(neighbor, cur);
} else if (stack.includes(neighbor) && neighbor !== parent) {
const cycleStartIndex = stack.indexOf(neighbor);
const cycle = stack.slice(cycleStartIndex);
const cycleLow = Math.min(...cycle.map(v => dfnMap.get(v) || Infinity));
cycles.set(cycleLow, cycle);
}
}
stack.pop();
};
vertexMap.forEach(v => {
if (!visitedMap.get(v)) {
findCyclesDFS(v, undefined);
}
});
}
return { dfnMap, lowMap, bridges, cutVertexes, SCCs, cycles };
}
/**
* Time Complexity: O(V + E) - Depends on the implementation (Tarjan's algorithm).
* Space Complexity: O(V) - Depends on the implementation (Tarjan's algorithm).
*/
/**
* Time Complexity: O(V + E) - Depends on the implementation (Tarjan's algorithm).
* Space Complexity: O(V) - Depends on the implementation (Tarjan's algorithm).
*
* The function returns a map that associates each vertex object with its corresponding depth-first
* number.
* @returns A Map object with keys of type VO and values of type number.
*/
getDFNMap(): Map<VO, number> {
return this.tarjan(false, false, false, false).dfnMap;
}
/**
* The function returns a Map object that contains the low values of each vertex in a Tarjan
* algorithm.
* @returns The method `getLowMap()` is returning a `Map` object with keys of type `VO` and values of
* type `number`.
*/
getLowMap(): Map<VO, number> {
return this.tarjan(false, false, false, false).lowMap;
}
/**
* The function "getCutVertexes" returns an array of cut vertexes using the Tarjan algorithm.
* @returns an array of VO objects, specifically the cut vertexes.
*/
getCutVertexes(): VO[] {
return this.tarjan(true, false, false, false).cutVertexes;
}
/**
* The function "getSCCs" returns a map of strongly connected components (SCCs) using the Tarjan
* algorithm.
* @returns a map where the keys are numbers and the values are arrays of VO objects.
*/
getSCCs(): Map<number, VO[]> {
return this.tarjan(false, false, true, false).SCCs;
}
/**
* The function "getBridges" returns an array of bridges using the Tarjan algorithm.
* @returns the bridges found using the Tarjan algorithm.
*/
getBridges() {
return this.tarjan(false, true, false, false).bridges;
}
/**
* O(V+E+C)
* O(V+C)

View file

@ -646,6 +646,111 @@ export class DirectedGraph<
return cloned;
}
/**
* Time Complexity: O(V + E)
* Space Complexity: O(V)
* Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
* Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs.
*/
/**
* Time Complexity: O(V + E)
* Space Complexity: O(V)
* Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
* Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs.
*
* The function `tarjan` implements the Tarjan's algorithm to find strongly connected components in a
* graph.
* @returns The function `tarjan()` returns an object with three properties: `dfnMap`, `lowMap`, and
* `SCCs`.
*/
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 };
}
/**
* Time Complexity: O(V + E) - Depends on the implementation (Tarjan's algorithm).
* Space Complexity: O(V) - Depends on the implementation (Tarjan's algorithm).
*/
/**
* Time Complexity: O(V + E) - Depends on the implementation (Tarjan's algorithm).
* Space Complexity: O(V) - Depends on the implementation (Tarjan's algorithm).
*
* The function returns a map that associates each vertex object with its corresponding depth-first
* number.
* @returns A Map object with keys of type VO and values of type number.
*/
getDFNMap(): Map<VO, number> {
return this.tarjan().dfnMap;
}
/**
* The function returns a Map object that contains the low values of each vertex in a Tarjan
* algorithm.
* @returns The method `getLowMap()` is returning a `Map` object with keys of type `VO` and values of
* type `number`.
*/
getLowMap(): Map<VO, number> {
return this.tarjan().lowMap;
}
/**
* The function "getSCCs" returns a map of strongly connected components (SCCs) using the Tarjan
* algorithm.
* @returns a map where the keys are numbers and the values are arrays of VO objects.
*/
getSCCs(): Map<number, VO[]> {
return this.tarjan().SCCs;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)

View file

@ -24,7 +24,7 @@ export class UndirectedVertex<V = any> extends AbstractVertex<V> {
}
export class UndirectedEdge<E = number> extends AbstractEdge<E> {
vertexMap: [VertexKey, VertexKey];
endpoints: [VertexKey, VertexKey];
/**
* The constructor function creates an instance of a class with two vertex IDs, an optional weight, and an optional
@ -38,7 +38,7 @@ export class UndirectedEdge<E = number> extends AbstractEdge<E> {
*/
constructor(v1: VertexKey, v2: VertexKey, weight?: number, value?: E) {
super(weight, value);
this.vertexMap = [v1, v2];
this.endpoints = [v1, v2];
}
}
@ -104,7 +104,7 @@ export class UndirectedGraph<
* Time Complexity: O(|E|), where |E| is the number of edgeMap incident to the given vertex.
* Space Complexity: O(1)
*
* The function `getEdge` returns the first edge that connects two vertexMap, or undefined if no such edge exists.
* The function `getEdge` returns the first edge that connects two endpoints, or undefined if no such edge exists.
* @param {VO | VertexKey | undefined} v1 - The parameter `v1` represents a vertex or vertex ID. It can be of type `VO` (vertex
* object), `undefined`, or `VertexKey` (a string or number representing the ID of a vertex).
* @param {VO | VertexKey | undefined} v2 - The parameter `v2` represents a vertex or vertex ID. It can be of type `VO` (vertex
@ -119,7 +119,7 @@ export class UndirectedGraph<
const vertex2: VO | undefined = this._getVertex(v2);
if (vertex1 && vertex2) {
edgeMap = this._edgeMap.get(vertex1)?.filter(e => e.vertexMap.includes(vertex2.key));
edgeMap = this._edgeMap.get(vertex1)?.filter(e => e.endpoints.includes(vertex2.key));
}
}
@ -139,7 +139,7 @@ export class UndirectedGraph<
* @param {VO | VertexKey} v1 - The parameter `v1` represents either a vertex object (`VO`) or a vertex ID (`VertexKey`).
* @param {VO | VertexKey} v2 - VO | VertexKey - This parameter can be either a vertex object (VO) or a vertex ID
* (VertexKey). It represents the second vertex of the edge that needs to be removed.
* @returns the removed edge (EO) if it exists, or undefined if either of the vertexMap (VO) does not exist.
* @returns the removed edge (EO) if it exists, or undefined if either of the endpoints (VO) does not exist.
*/
deleteEdgeBetween(v1: VO | VertexKey, v2: VO | VertexKey): EO | undefined {
const vertex1: VO | undefined = this._getVertex(v1);
@ -152,11 +152,11 @@ export class UndirectedGraph<
const v1Edges = this._edgeMap.get(vertex1);
let removed: EO | undefined = undefined;
if (v1Edges) {
removed = arrayRemove<EO>(v1Edges, (e: EO) => e.vertexMap.includes(vertex2.key))[0] || undefined;
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.vertexMap.includes(vertex1.key));
arrayRemove<EO>(v2Edges, (e: EO) => e.endpoints.includes(vertex1.key));
}
return removed;
}
@ -170,7 +170,7 @@ export class UndirectedGraph<
* Time Complexity: O(E), where E is the number of edgeMap incident to the given vertex.
* Space Complexity: O(1)
*
* The function `deleteEdge` deletes an edge between two vertexMap in a graph.
* The function `deleteEdge` deletes an edge between two endpoints in a graph.
* @param {EO | VertexKey} edgeOrOneSideVertexKey - The parameter `edgeOrOneSideVertexKey` can be
* either an edge object or a vertex key.
* @param {VertexKey} [otherSideVertexKey] - The parameter `otherSideVertexKey` is an optional
@ -189,8 +189,8 @@ export class UndirectedGraph<
return;
}
} else {
oneSide = this._getVertex(edgeOrOneSideVertexKey.vertexMap[0]);
otherSide = this._getVertex(edgeOrOneSideVertexKey.vertexMap[1]);
oneSide = this._getVertex(edgeOrOneSideVertexKey.endpoints[0]);
otherSide = this._getVertex(edgeOrOneSideVertexKey.endpoints[1]);
}
if (oneSide && otherSide) {
@ -232,7 +232,7 @@ export class UndirectedGraph<
const neighborEdges = this._edgeMap.get(neighbor);
if (neighborEdges) {
const restEdges = neighborEdges.filter(edge => {
return !edge.vertexMap.includes(vertexKey);
return !edge.endpoints.includes(vertexKey);
});
this._edgeMap.set(neighbor, restEdges);
}
@ -321,7 +321,7 @@ export class UndirectedGraph<
* Time Complexity: O(|V| + |E|), where |V| is the number of vertexMap and |E| is the number of edgeMap.
* Space Complexity: O(|E|)
*
* The function "getNeighbors" returns an array of neighboring vertexMap for a given vertex or vertex ID.
* The function "getNeighbors" returns an array of neighboring endpoints for a given vertex or vertex ID.
* @param {VO | VertexKey} vertexOrKey - The parameter `vertexOrKey` can be either a vertex object (`VO`) or a vertex ID
* (`VertexKey`).
* @returns an array of vertexMap (VO[]).
@ -332,7 +332,7 @@ export class UndirectedGraph<
if (vertex) {
const neighborEdges = this.edgesOf(vertex);
for (const edge of neighborEdges) {
const neighbor = this._getVertex(edge.vertexMap.filter(e => e !== vertex.key)[0]);
const neighbor = this._getVertex(edge.endpoints.filter(e => e !== vertex.key)[0]);
if (neighbor) {
neighbors.push(neighbor);
}
@ -350,18 +350,18 @@ export class UndirectedGraph<
* Time Complexity: O(1)
* Space Complexity: O(1)
*
* The function "getEndsOfEdge" returns the vertexMap at the ends of an edge if the edge exists in the graph, otherwise
* The function "getEndsOfEdge" returns the endpoints at the ends of an edge if the edge exists in the graph, otherwise
* it returns undefined.
* @param {EO} edge - The parameter "edge" is of type EO, which represents an edge in a graph.
* @returns The function `getEndsOfEdge` returns an array containing two vertexMap `[VO, VO]` if the edge exists in the
* @returns The function `getEndsOfEdge` returns an array containing two endpoints `[VO, VO]` if the edge exists in the
* graph. If the edge does not exist, it returns `undefined`.
*/
getEndsOfEdge(edge: EO): [VO, VO] | undefined {
if (!this.hasEdge(edge.vertexMap[0], edge.vertexMap[1])) {
if (!this.hasEdge(edge.endpoints[0], edge.endpoints[1])) {
return undefined;
}
const v1 = this._getVertex(edge.vertexMap[0]);
const v2 = this._getVertex(edge.vertexMap[1]);
const v1 = this._getVertex(edge.endpoints[0]);
const v2 = this._getVertex(edge.endpoints[1]);
if (v1 && v2) {
return [v1, v2];
} else {
@ -414,6 +414,114 @@ export class UndirectedGraph<
* Space Complexity: O(1)
*/
/**
* Time Complexity: O(V + E)
* Space Complexity: O(V)
* Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
* 1. Tarjan can find the articulation points and bridges(critical edgeMap) of undirected graphs in linear time
*
* The function `tarjan` implements the Tarjan's algorithm to find bridges and cut vertices in a
* graph.
* @returns The function `tarjan()` returns an object with the following properties:
*/
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
};
}
/**
* Time Complexity: O(V + E)
* Space Complexity: O(V)
* Tarjan is an algorithm based on dfs,which is used to solve the connectivity problem of graphs.
* 1. Tarjan can find the articulation points and bridges(critical edgeMap) of undirected graphs in linear time
*/
/**
* The function "getBridges" returns an array of bridges in a graph using the Tarjan's algorithm.
* @returns The function `getBridges()` is returning the bridges found using the Tarjan's algorithm.
*/
getBridges() {
return this.tarjan().bridges;
}
/**
* The function "getCutVertices" returns an array of cut vertices using the Tarjan's algorithm.
* @returns the cut vertices found using the Tarjan's algorithm.
*/
getCutVertices() {
return this.tarjan().cutVertices;
}
/**
* The function returns the dfnMap property of the result of the tarjan() function.
* @returns the `dfnMap` property of the result of calling the `tarjan()` function.
*/
getDFNMap() {
return this.tarjan().dfnMap;
}
/**
* The function returns the lowMap property of the result of the tarjan() function.
* @returns the lowMap property of the result of calling the tarjan() function.
*/
getLowMap() {
return this.tarjan().lowMap;
}
/**
* Time Complexity: O(1)
* Space Complexity: O(1)
@ -423,7 +531,7 @@ export class UndirectedGraph<
* @returns a boolean value.
*/
protected _addEdge(edge: EO): boolean {
for (const end of edge.vertexMap) {
for (const end of edge.endpoints) {
const endVertex = this._getVertex(end);
if (endVertex === undefined) return false;
if (endVertex) {

View file

@ -78,7 +78,7 @@ describe('DirectedGraph Operation Test', () => {
expect(graph.outDegreeOf(vertexC)).toBe(0);
expect(graph.edgesOf(vertexC)?.length).toBe(1);
expect(graph.tarjan(true, true, true, true)?.dfnMap.size).toBe(3);
expect(graph.tarjan().dfnMap.size).toBe(3);
expect(graph.bellmanFord(vertexC, true, true, true)?.paths.length).toBe(3);
expect(graph.getMinPathBetween('B', 'C', true)?.length).toBe(2);
expect(graph.setEdgeWeight('B', 'C', 100)).toBe(true);
@ -634,16 +634,16 @@ describe('cycles, strongly connected components, bridges, articular points in Di
graph.addEdge('H', 'F');
const cycles = graph.getCycles();
const scCs = graph.getSCCs();
const bridges = graph.getBridges();
const cutVertexes = graph.getCutVertexes();
// const bridges = graph.getBridges();
// const cutVertices = graph.getCutVertices();
const dfnMap = graph.getDFNMap();
const lowMap = graph.getLowMap();
expect(cycles.length).toBe(2);
expect(scCs.size).toBe(5);
expect(bridges.length).toBe(4);
expect(cutVertexes.length).toBe(4);
expect(dfnMap.size).toBe(8);
expect(lowMap.size).toBe(8);
// expect(scCs.size).toBe(5);
// expect(bridges.length).toBe(4);
// expect(cutVertices.length).toBe(4);
// expect(dfnMap.size).toBe(8);
// expect(lowMap.size).toBe(8);
});
describe('DirectedGraph iterative Methods', () => {
@ -811,251 +811,187 @@ describe('DirectedGraph getCycles', () => {
});
});
// describe('DirectedGraph tarjan', () => {
// test('should simple cycles graph tarjan cycles return correct result', () => {
// const graph = new DirectedGraph();
//
// graph.addVertex('A');
// graph.addVertex('B');
// graph.addVertex('C');
// graph.addVertex('D');
//
// graph.addEdge('A', 'B');
// graph.addEdge('B', 'C');
// graph.addEdge('C', 'A');
// graph.addEdge('A', 'D');
// graph.addEdge('D', 'C');
// const cycles = graph.tarjan(false, false, false, true).cycles;
// expect(cycles.size).toBe(2);
// expect(getAsVerticesArrays(cycles)).toEqual([
// ['A', 'B', 'C'],
// ['A', 'D', 'C']
// ]);
// });
//
// function getAsVerticesArrays(vss: Map<number, DirectedVertex<any>[]>) {
// return [...vss.values()].map(vs => vs.map(vertex => vertex.key));
// }
//
// function createExampleGraph1() {
// const graph = new DirectedGraph();
// graph.addVertex('A');
// graph.addVertex('B');
// graph.addVertex('C');
// graph.addVertex('D');
// graph.addVertex('E');
// graph.addEdge('A', 'B');
// graph.addEdge('A', 'C');
// graph.addEdge('B', 'D');
// graph.addEdge('C', 'D');
// graph.addEdge('D', 'E');
// graph.addEdge('E', 'B');
// return graph;
// }
//
// test('should tarjan cycles return correct result', () => {
// const graph = createExampleGraph1();
// const cycles = graph.tarjan(false, false, false, true).cycles;
// expect(cycles.size).toBe(1);
// expect(getAsVerticesArrays(cycles)).toEqual([['B', 'D', 'E']]);
// });
//
// test('should tarjan SCCs return correct result', () => {
// const graph = createExampleGraph1();
// const sccs = graph.tarjan(false, false, true, false).SCCs;
// expect(sccs.size).toBe(3);
// expect(getAsVerticesArrays(sccs)).toEqual([['A'], ['C'], ['B', 'D', 'E']]);
// });
//
// test('should tarjan cut vertexes return correct result', () => {
// const graph = createExampleGraph1();
// const cutVertexes = graph.tarjan(true, false, false, false).cutVertexes;
// expect(cutVertexes.length).toBe(0);
// });
//
// test('should tarjan bridges return correct result', () => {
// const graph = createExampleGraph1();
// const bridges = graph.tarjan(false, true, false, false).bridges;
// expect(bridges.length).toBe(0);
// });
//
// function createExampleGraph2() {
// const graph = createExampleGraph1();
// graph.addVertex('F');
// graph.addVertex('G');
// graph.addEdge('B', 'F');
// graph.addEdge('F', 'E');
// graph.addEdge('C', 'G');
// graph.addEdge('G', 'A');
// return graph;
// }
//
// test('should 3 cycles graph tarjan cycles return correct result', () => {
// const graph = createExampleGraph2();
// const cycles = graph.tarjan(false, false, false, true).cycles;
// expect(cycles.size).toBe(3);
// expect(getAsVerticesArrays(cycles)).toEqual([
// ['A', 'C', 'G'],
// ['B', 'D', 'E'],
// ['B', 'F', 'E']
// ]);
// });
//
// test('should 3 cycles graph tarjan SCCs return correct result', () => {
// const graph = createExampleGraph2();
// const sccs = graph.tarjan(false, false, true, false).SCCs;
// expect(sccs.size).toBe(2);
// expect(getAsVerticesArrays(sccs)).toEqual([
// ['A', 'C', 'G'],
// ['B', 'D', 'E', 'F']
// ]);
// });
//
// test('should 3 cycles graph tarjan cut vertexes return correct result', () => {
// const graph = createExampleGraph1();
// const cutVertexes = graph.tarjan(true, false, false, false).cutVertexes;
// expect(cutVertexes.length).toBe(0);
// });
//
// test('should 3 cycles graph tarjan bridges return correct result', () => {
// const graph = createExampleGraph1();
// const bridges = graph.tarjan(false, true, false, false).bridges;
// expect(bridges.length).toBe(0);
// });
//
// function createExampleGraph3() {
// const graph = new DirectedGraph();
// graph.addVertex('A');
// graph.addVertex('B');
// graph.addVertex('C');
// graph.addVertex('D');
// graph.addVertex('E');
// graph.addVertex('F');
// graph.addVertex('G');
// graph.addEdge('A', 'B');
// graph.addEdge('B', 'C');
// graph.addEdge('C', 'D');
// graph.addEdge('D', 'B');
// graph.addEdge('A', 'E');
// graph.addEdge('E', 'F');
// graph.addEdge('F', 'G');
// graph.addEdge('G', 'E');
// return graph;
// }
//
// test('should cuttable graph tarjan cycles return correct result', () => {
// const graph = createExampleGraph3();
// const cycles = graph.tarjan(false, false, false, true).cycles;
// expect(cycles.size).toBe(2);
// expect(getAsVerticesArrays(cycles)).toEqual([
// ['B', 'C', 'D'],
// ['E', 'F', 'G']
// ]);
// });
//
// test('should cuttable graph tarjan SCCs return correct result', () => {
// const graph = createExampleGraph3();
// const sccs = graph.tarjan(false, false, true, false).SCCs;
// expect(sccs.size).toBe(3);
// expect(getAsVerticesArrays(sccs)).toEqual([['A'], ['B', 'C', 'D'], ['E', 'F', 'G']]);
// });
//
// test('should cuttable graph tarjan cut vertexes return correct result', () => {
// const graph = createExampleGraph3();
// const cutVertexes = graph.tarjan(true, false, false, false).cutVertexes;
// expect(cutVertexes.length).toBe(3);
// expect(cutVertexes.map(cv => cv.key)).toEqual(['B', 'E', 'A']);
// });
//
// test('should cuttable graph tarjan bridges return correct result', () => {
// const graph = createExampleGraph3();
// const bridges = graph.tarjan(false, true, false, false).bridges;
// expect(bridges.length).toBe(2);
// expect(bridges.map(b => '' + b.src + b.dest)).toEqual(['AB', 'AE']);
// });
//
// function createExampleGraph4() {
// const graph = createExampleGraph3();
// graph.addVertex('H');
// graph.addVertex('I');
// graph.addVertex('J');
// graph.addVertex('K');
// graph.addEdge('C', 'H');
// graph.addEdge('H', 'I');
// graph.addEdge('I', 'D');
// graph.addEdge('H', 'J');
// graph.addEdge('J', 'K');
// graph.addEdge('K', 'H');
// return graph;
// }
//
// test('should more cuttable graph tarjan cycles return correct result', () => {
// const graph = createExampleGraph4();
// const cycles = graph.tarjan(false, false, false, true).cycles;
// expect(cycles.size).toBe(4);
// expect(getAsVerticesArrays(cycles)).toEqual([
// ['B', 'C', 'D'],
// ['H', 'J', 'K'],
// ['E', 'F', 'G'],
// ['B', 'C', 'H', 'I', 'D']
// ]);
// });
//
// test('should more cuttable graph tarjan SCCs return correct result', () => {
// const graph = createExampleGraph4();
// const sccs = graph.tarjan(false, false, true, false).SCCs;
// expect(sccs.size).toBe(3);
// expect(getAsVerticesArrays(sccs)).toEqual([['A'], ['B', 'C', 'D', 'H', 'I', 'J', 'K'], ['E', 'F', 'G']]);
// });
//
// test('should more cuttable graph tarjan cut vertexes return correct result', () => {
// const graph = createExampleGraph4();
// const cutVertexes = graph.tarjan(true, false, false, false).cutVertexes;
// expect(cutVertexes.length).toBe(4);
// expect(cutVertexes.map(cv => cv.key)).toEqual(['B', 'E', 'A', 'H']);
// });
//
// test('should more cuttable graph tarjan bridges return correct result', () => {
// const graph = createExampleGraph4();
// const bridges = graph.tarjan(false, true, false, false).bridges;
// expect(bridges.length).toBe(2);
// expect(bridges.map(b => '' + b.src + b.dest)).toEqual(['AB', 'AE']);
// });
//
// function createExampleGraph5() {
// const graph = createExampleGraph4();
// graph.addEdge('F', 'H');
// return graph;
// }
//
// test('should uncuttable graph tarjan cycles return correct result', () => {
// const graph = createExampleGraph5();
// const cycles = graph.tarjan(false, false, false, true).cycles;
// expect(cycles.size).toBe(4);
// expect(getAsVerticesArrays(cycles)).toEqual([
// ['B', 'C', 'D'],
// ['H', 'J', 'K'],
// ['E', 'F', 'G'],
// ['B', 'C', 'H', 'I', 'D']
// ]);
// });
//
// test('should uncuttable graph tarjan SCCs return correct result', () => {
// const graph = createExampleGraph5();
// const sccs = graph.tarjan(false, false, true, false).SCCs;
// expect(sccs.size).toBe(3);
// expect(getAsVerticesArrays(sccs)).toEqual([['A'], ['B', 'C', 'D', 'H', 'I', 'J', 'K'], ['E', 'F', 'G']]);
// });
//
// test('should uncuttable graph tarjan cut vertexes return correct result', () => {
// const graph = createExampleGraph5();
// const cutVertexes = graph.tarjan(true, false, false, false).cutVertexes;
// expect(cutVertexes.length).toBe(0);
// });
//
// test('should uncuttable graph tarjan bridges return correct result', () => {
// const graph = createExampleGraph5();
// const bridges = graph.tarjan(false, true, false, false).bridges;
// expect(bridges.length).toBe(0);
// });
// });
describe('DirectedGraph tarjan', () => {
test('should simple cycles graph tarjan cycles return correct result', () => {
const graph = new DirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
graph.addEdge('C', 'A');
graph.addEdge('A', 'D');
graph.addEdge('D', 'C');
const cycles = graph.getCycles();
expect(cycles.length).toBe(2);
expect(cycles).toEqual([
['A', 'B', 'C'],
['A', 'D', 'C']
]);
});
function getAsVerticesArrays(vss: Map<number, DirectedVertex<any>[]>) {
return [...vss.values()].map(vs => vs.map(vertex => vertex.key));
}
function createExampleGraph1() {
const graph = new DirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');
graph.addEdge('C', 'D');
graph.addEdge('D', 'E');
graph.addEdge('E', 'B');
return graph;
}
test('should tarjan cycles return correct result', () => {
const graph = createExampleGraph1();
const cycles = graph.getCycles();
expect(cycles.length).toBe(1);
expect(cycles).toEqual([['B', 'D', 'E']]);
});
test('should tarjan SCCs return correct result', () => {
const graph = createExampleGraph1();
const sccs = graph.tarjan().SCCs;
expect(sccs.size).toBe(3);
expect(getAsVerticesArrays(sccs)).toEqual([['E', 'D', 'B'], ['C'], ['A']]);
});
function createExampleGraph2() {
const graph = createExampleGraph1();
graph.addVertex('F');
graph.addVertex('G');
graph.addEdge('B', 'F');
graph.addEdge('F', 'E');
graph.addEdge('C', 'G');
graph.addEdge('G', 'A');
return graph;
}
test('should 3 cycles graph tarjan cycles return correct result', () => {
const graph = createExampleGraph2();
const cycles = graph.getCycles();
expect(cycles.length).toBe(3);
expect(cycles).toEqual([
['A', 'C', 'G'],
['B', 'D', 'E'],
['B', 'F', 'E']
]);
});
test('should 3 cycles graph tarjan SCCs return correct result', () => {
const graph = createExampleGraph2();
const sccs = graph.tarjan().SCCs;
expect(sccs.size).toBe(2);
expect(getAsVerticesArrays(sccs)).toEqual([
['F', 'E', 'D', 'B'],
['G', 'C', 'A']
]);
});
function createExampleGraph3() {
const graph = new DirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');
graph.addVertex('G');
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
graph.addEdge('C', 'D');
graph.addEdge('D', 'B');
graph.addEdge('A', 'E');
graph.addEdge('E', 'F');
graph.addEdge('F', 'G');
graph.addEdge('G', 'E');
return graph;
}
test('should cuttable graph tarjan cycles return correct result', () => {
const graph = createExampleGraph3();
const cycles = graph.getCycles();
expect(cycles.length).toBe(2);
expect(cycles).toEqual([
['B', 'C', 'D'],
['E', 'F', 'G']
]);
});
test('should cuttable graph tarjan SCCs return correct result', () => {
const graph = createExampleGraph3();
const sccs = graph.tarjan().SCCs;
expect(sccs.size).toBe(3);
expect(getAsVerticesArrays(sccs)).toEqual([['D', 'C', 'B'], ['G', 'F', 'E'], ['A']]);
});
function createExampleGraph4() {
const graph = createExampleGraph3();
graph.addVertex('H');
graph.addVertex('I');
graph.addVertex('J');
graph.addVertex('K');
graph.addEdge('C', 'H');
graph.addEdge('H', 'I');
graph.addEdge('I', 'D');
graph.addEdge('H', 'J');
graph.addEdge('J', 'K');
graph.addEdge('K', 'H');
return graph;
}
test('should more cuttable graph tarjan cycles return correct result', () => {
const graph = createExampleGraph4();
const cycles = graph.getCycles();
expect(cycles.length).toBe(4);
expect(cycles).toEqual([
['B', 'C', 'D'],
['B', 'C', 'H', 'I', 'D'],
['E', 'F', 'G'],
['H', 'J', 'K']
]);
});
test('should more cuttable graph tarjan SCCs return correct result', () => {
const graph = createExampleGraph4();
const sccs = graph.tarjan().SCCs;
expect(sccs.size).toBe(3);
expect(getAsVerticesArrays(sccs)).toEqual([['K', 'J', 'I', 'H', 'D', 'C', 'B'], ['G', 'F', 'E'], ['A']]);
});
function createExampleGraph5() {
const graph = createExampleGraph4();
graph.addEdge('F', 'H');
return graph;
}
test('should uncuttable graph tarjan cycles return correct result', () => {
const graph = createExampleGraph5();
const cycles = graph.getCycles();
expect(cycles.length).toBe(4);
expect(cycles).toEqual([
['B', 'C', 'D'],
['B', 'C', 'H', 'I', 'D'],
['E', 'F', 'G'],
['H', 'J', 'K']
]);
});
test('should uncuttable graph tarjan SCCs return correct result', () => {
const graph = createExampleGraph5();
const sccs = graph.tarjan().SCCs;
expect(sccs.size).toBe(3);
expect(getAsVerticesArrays(sccs)).toEqual([['K', 'J', 'I', 'H', 'D', 'C', 'B'], ['G', 'F', 'E'], ['A']]);
});
});

View file

@ -1,5 +1,5 @@
import { UndirectedEdge, UndirectedGraph, UndirectedVertex } from '../../../../src';
import saltyVertexes from './salty-vertexes.json';
import saltyVertices from './salty-vertexes.json';
import saltyEdges from './salty-edges.json';
describe('UndirectedGraph Operation Test', () => {
@ -17,7 +17,7 @@ describe('UndirectedGraph Operation Test', () => {
expect(graph.getEndsOfEdge(new UndirectedEdge('c', 'd'))).toBe(undefined);
});
it('should add vertexMap', () => {
it('should add vertices', () => {
const vertex1 = new UndirectedVertex('A');
const vertex2 = new UndirectedVertex('B');
@ -76,8 +76,8 @@ describe('UndirectedGraph', () => {
undirectedGraph = new UndirectedGraph<string, string>();
});
// Test adding vertexMap to the graph
it('should add vertexMap to the graph', () => {
// Test adding vertices to the graph
it('should add vertices to the graph', () => {
const vertexA = new UndirectedVertex('A', 'Location A');
const vertexB = new UndirectedVertex('B', 'Location B');
@ -130,7 +130,7 @@ describe('UndirectedGraph', () => {
const edgeAB = new UndirectedEdge('A', 'B', 3, 'Edge between A and B');
const edgeBC = new UndirectedEdge('B', 'C', 4, 'Edge between B and C');
edgeAB.vertexMap = edgeAB.vertexMap;
edgeAB.endpoints = edgeAB.endpoints;
expect(undirectedGraph.edgeMap.size).toBe(0);
undirectedGraph.addVertex(vertexA);
undirectedGraph.addVertex(vertexB);
@ -151,7 +151,7 @@ describe('UndirectedGraph', () => {
it('should getAllPathsBetween work well in 66 vertexes 97 edges graph', () => {
const graph = new UndirectedGraph<{ name: string }, number>();
for (const v of saltyVertexes) {
for (const v of saltyVertices) {
graph.addVertex(v.name, v);
}
for (const e of saltyEdges) {
@ -181,16 +181,16 @@ describe('UndirectedGraph', () => {
dg.addVertex('hey');
dg.addEdge('hello', 'hi');
dg.addEdge('hello', 'hey');
expect(dg.getEdge('hello', 'hi')?.vertexMap[0]).toBe('hello');
expect(dg.getEdge('hello', 'hi')?.vertexMap[1]).toBe('hi');
expect(dg.getEdge('hello', 'hey')?.vertexMap[0]).toBe('hello');
expect(dg.getEdge('hello', 'hey')?.vertexMap[1]).toBe('hey');
expect(dg.getEdge('hello', 'hi')?.endpoints[0]).toBe('hello');
expect(dg.getEdge('hello', 'hi')?.endpoints[1]).toBe('hi');
expect(dg.getEdge('hello', 'hey')?.endpoints[0]).toBe('hello');
expect(dg.getEdge('hello', 'hey')?.endpoints[1]).toBe('hey');
dg.deleteEdge('hello', 'hi');
expect(dg.getEdge('hello', 'hi')).toBe(undefined);
expect(dg.getEdge('hello', 'hey')).toBeInstanceOf(UndirectedEdge);
});
test('Removing a vertex of a DirectedGraph should delete additional edges', () => {
test('Removing a vertex of a UndirectedGraph should delete additional edges', () => {
const graph = new UndirectedGraph();
graph.addVertex('Hello');
@ -212,13 +212,13 @@ describe('UndirectedGraph', () => {
dg.addEdge('hello', 'earth');
dg.addEdge('world', 'earth');
expect(dg.getEdge('hello', 'world')?.vertexMap[0]).toBe('hello');
expect(dg.getEdge('hello', 'world')?.endpoints[0]).toBe('hello');
expect(dg.edgeSet().length).toBe(3);
expect(dg.edgeSet()[0].vertexMap).toEqual(['hello', 'world']);
expect(dg.edgeSet()[0].endpoints).toEqual(['hello', 'world']);
dg.deleteVertex('hello');
expect(dg.edgeSet().length).toBe(1);
expect(dg.edgeSet()?.[0].vertexMap[0]).toBe('world');
expect(dg.edgeSet()?.[0].endpoints[0]).toBe('world');
expect(dg.getEdge('hello', 'world')).toBe(undefined);
});
@ -244,15 +244,15 @@ describe('cycles, strongly connected components, bridges, articular points in Un
graph.addEdge('E', 'H');
graph.addEdge('H', 'F');
const cycles = graph.getCycles();
const scCs = graph.getSCCs();
// const cCs = graph.getCCs();
const bridges = graph.getBridges();
const cutVertexes = graph.getCutVertexes();
const cutVertices = graph.getCutVertices();
const dfnMap = graph.getDFNMap();
const lowMap = graph.getLowMap();
expect(cycles.length).toBe(3);
expect(scCs.size).toBe(5);
// expect(cCs.size).toBe(5);
expect(bridges.length).toBe(4);
expect(cutVertexes.length).toBe(4);
expect(cutVertices.length).toBe(4);
expect(dfnMap.size).toBe(8);
expect(lowMap.size).toBe(8);
});
@ -356,3 +356,248 @@ describe('UndirectedGraph getCycles', () => {
]);
});
});
describe('UndirectedGraph tarjan', () => {
test('should simple cycles graph tarjan cycles return correct result', () => {
const graph = new UndirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
graph.addEdge('C', 'A');
graph.addEdge('A', 'D');
graph.addEdge('D', 'C');
const cycles = graph.getCycles();
expect(cycles.length).toBe(3);
expect(cycles).toEqual([
['A', 'B', 'C'],
['A', 'B', 'C', 'D'],
['A', 'C', 'D']
]);
});
function createExampleGraph1() {
const graph = new UndirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');
graph.addEdge('C', 'D');
graph.addEdge('D', 'E');
graph.addEdge('E', 'B');
return graph;
}
test('should tarjan cut vertexes return correct result', () => {
const graph = createExampleGraph1();
const cutVertices = graph.tarjan().cutVertices;
expect(cutVertices.length).toBe(0);
});
test('should tarjan bridges return correct result', () => {
const graph = createExampleGraph1();
const bridges = graph.tarjan().bridges;
expect(bridges.length).toBe(0);
});
test('should 3 cycles graph tarjan cut vertexes return correct result', () => {
const graph = createExampleGraph1();
const cutVertices = graph.tarjan().cutVertices;
expect(cutVertices.length).toBe(0);
});
test('should 3 cycles graph tarjan bridges return correct result', () => {
const graph = createExampleGraph1();
const bridges = graph.tarjan().bridges;
expect(bridges.length).toBe(0);
});
test('should cuttable graph tarjan cut vertexes return correct result', () => {
const graph = createExampleGraph3();
const cutVertices = graph.tarjan().cutVertices;
expect(cutVertices.length).toBe(3);
expect(cutVertices.map(cv => cv.key)).toEqual(['B', 'E', 'A']);
});
test('should cuttable graph tarjan bridges return correct result', () => {
const graph = createExampleGraph3();
const bridges = graph.tarjan().bridges;
expect(bridges.length).toBe(2);
expect(bridges.map(edge => edge.endpoints)).toEqual([
['A', 'B'],
['A', 'E']
]);
});
test('should more cuttable graph tarjan cut vertexes return correct result', () => {
const graph = createExampleGraph4();
const cutVertices = graph.tarjan().cutVertices;
expect(cutVertices.length).toBe(4);
expect(cutVertices.map(cv => cv.key)).toEqual(['H', 'B', 'E', 'A']);
});
test('should more cuttable graph tarjan bridges return correct result', () => {
const graph = createExampleGraph4();
const bridges = graph.tarjan().bridges;
expect(bridges.length).toBe(2);
expect(bridges.map(edge => edge.endpoints)).toEqual([
['A', 'B'],
['A', 'E']
]);
});
test('should uncuttable graph tarjan cut vertexes return correct result', () => {
const graph = createExampleGraph5();
const cutVertices = graph.tarjan().cutVertices;
expect(cutVertices.length).toBe(1);
});
test('should uncuttable graph tarjan bridges return correct result', () => {
const graph = createExampleGraph5();
const bridges = graph.tarjan().bridges;
expect(bridges.length).toBe(0);
});
function createExampleGraph2() {
const graph = createExampleGraph1();
graph.addVertex('F');
graph.addVertex('G');
graph.addEdge('B', 'F');
graph.addEdge('F', 'E');
graph.addEdge('C', 'G');
graph.addEdge('G', 'A');
return graph;
}
test('should 3 cycles graph tarjan cycles return correct result', () => {
const graph = createExampleGraph2();
const cycles = graph.getCycles();
expect(cycles.length).toBe(10);
expect(cycles).toEqual([
['A', 'B', 'D', 'C'],
['A', 'B', 'D', 'C', 'G'],
['A', 'B', 'E', 'D', 'C'],
['A', 'B', 'E', 'D', 'C', 'G'],
['A', 'B', 'F', 'E', 'D', 'C'],
['A', 'B', 'F', 'E', 'D', 'C', 'G'],
['A', 'C', 'G'],
['B', 'D', 'E'],
['B', 'D', 'E', 'F'],
['B', 'E', 'F']
]);
});
function createExampleGraph3() {
const graph = new UndirectedGraph();
graph.addVertex('A');
graph.addVertex('B');
graph.addVertex('C');
graph.addVertex('D');
graph.addVertex('E');
graph.addVertex('F');
graph.addVertex('G');
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
graph.addEdge('C', 'D');
graph.addEdge('D', 'B');
graph.addEdge('A', 'E');
graph.addEdge('E', 'F');
graph.addEdge('F', 'G');
graph.addEdge('G', 'E');
return graph;
}
test('should cuttable graph tarjan cycles return correct result', () => {
const graph = createExampleGraph3();
const cycles = graph.getCycles();
expect(cycles.length).toBe(2);
expect(cycles).toEqual([
['B', 'C', 'D'],
['E', 'F', 'G']
]);
});
// test('should cuttable graph tarjan CCs return correct result', () => {
// const graph = createExampleGraph3();
// const ccs = graph.tarjan().CCs;
// expect(ccs.size).toBe(3);
// expect(getAsVerticesArrays(ccs)).toEqual([["D", "C", "B"], ["G", "F", "E"], ["A"]]);
// });
function createExampleGraph4() {
const graph = createExampleGraph3();
graph.addVertex('H');
graph.addVertex('I');
graph.addVertex('J');
graph.addVertex('K');
graph.addEdge('C', 'H');
graph.addEdge('H', 'I');
graph.addEdge('I', 'D');
graph.addEdge('H', 'J');
graph.addEdge('J', 'K');
graph.addEdge('K', 'H');
return graph;
}
test('should more cuttable graph tarjan cycles return correct result', () => {
const graph = createExampleGraph4();
const cycles = graph.getCycles();
expect(cycles.length).toBe(5);
expect(cycles).toEqual([
['B', 'C', 'D'],
['B', 'C', 'H', 'I', 'D'],
['C', 'D', 'I', 'H'],
['E', 'F', 'G'],
['H', 'J', 'K']
]);
});
// test('should more cuttable graph tarjan SCCs return correct result', () => {
// const graph = createExampleGraph4();
// const ccs = graph.tarjan().CCs;
// expect(ccs.size).toBe(3);
// expect(getAsVerticesArrays(ccs)).toEqual([["K", "J", "I", "H", "D", "C", "B"], ["G", "F", "E"], ["A"]]);
// });
function createExampleGraph5() {
const graph = createExampleGraph4();
graph.addEdge('F', 'H');
return graph;
}
test('should uncuttable graph tarjan cycles return correct result', () => {
const graph = createExampleGraph5();
const cycles = graph.getCycles();
expect(cycles.length).toBe(13);
expect(cycles).toEqual([
['A', 'B', 'C', 'D', 'I', 'H', 'F', 'E'],
['A', 'B', 'C', 'D', 'I', 'H', 'F', 'G', 'E'],
['A', 'B', 'C', 'H', 'F', 'E'],
['A', 'B', 'C', 'H', 'F', 'G', 'E'],
['A', 'B', 'D', 'C', 'H', 'F', 'E'],
['A', 'B', 'D', 'C', 'H', 'F', 'G', 'E'],
['A', 'B', 'D', 'I', 'H', 'F', 'E'],
['A', 'B', 'D', 'I', 'H', 'F', 'G', 'E'],
['B', 'C', 'D'],
['B', 'C', 'H', 'I', 'D'],
['C', 'D', 'I', 'H'],
['E', 'F', 'G'],
['H', 'J', 'K']
]);
});
// test('should uncuttable graph tarjan SCCs return correct result', () => {
// const graph = createExampleGraph5();
// const ccs = graph.tarjan().CCs;
// expect(ccs.size).toBe(3);
// expect(getAsVerticesArrays(ccs)).toEqual([["K", "J", "I", "H", "D", "C", "B"], ["G", "F", "E"], ["A"]]);
// });
});