UNPKG

@ai-on-browser/data-analysis-models

Version:

Data analysis model package without any dependencies

1,990 lines (1,922 loc) 102 kB
import Matrix from './matrix.js' /** * Exception for graph class */ export class GraphException extends Error { /** * @param {string} message Error message * @param {*} value Some value */ constructor(message, value) { super(message) this.value = value this.name = 'GraphException' } } /** * Edge of graph */ export class Edge { /** * @param {number} from Index of the starting node of the edge * @param {number} to Index of the end node of the edge * @param {unknown} [value] Value of the edge * @param {boolean} [direct] `true` if the edge is direct */ constructor(from, to, value = null, direct = false) { this[0] = from this[1] = to this.value = value ?? 1 this.direct = direct this.weighted = value != null } get from() { return this[0] } get to() { return this[1] } get value0() { return this.weighted ? this.value : null } } /** * Graph class */ export default class Graph { /** * @param {number | unknown[]} [nodes] Number of nodes or values of nodes * @param {([number, number] | {0?: number, 1?: number, from?: number, to?: number, value?: unknown, direct?: boolean} | Edge)[]} [edges] Edges */ constructor(nodes = 0, edges = []) { if (Array.isArray(nodes)) { /** @private */ this._nodes = nodes } else { this._nodes = Array(nodes) } /** @private */ this._edges = edges.map( e => new Edge(e[0] | e.from, e[1] | e.to, e instanceof Edge ? e.value0 : e.value, e.direct) ) } /** * Returns graph from adjacency matrix. * @param {number[][] | boolean[][]} mat Adjacency matrix * @returns {Graph} Graph from adjacency matrix */ static fromAdjacency(mat) { const n = mat.length const e = [] for (let i = 0; i < n; i++) { for (let j = 0; j <= i; j++) { if (!mat[i][j] && !mat[j][i]) { continue } if (mat[i][j] === mat[j][i]) { e.push(new Edge(j, i, +mat[i][j])) } else { if (mat[i][j]) { e.push(new Edge(i, j, +mat[i][j], true)) } if (mat[j][i]) { e.push(new Edge(j, i, +mat[j][i], true)) } } } } return new Graph(n, e) } /** * Returns complete graph. * @param {number} n Size of the graph * @returns {Graph} Complete graph */ static complete(n) { const e = [] for (let i = 0; i < n; i++) { for (let j = 0; j < i; j++) { e.push([i, j]) } } return new Graph(n, e) } /** * Returns complete bipartite graph. * @param {number} n Size of the first group * @param {number} m Size of the second group * @returns {Graph} Complete bipartite graph */ static completeBipartite(n, m) { const e = [] for (let i = 0; i < n; i++) { for (let j = 0; j < m; j++) { e.push([i, j + n]) } } return new Graph(n + m, e) } /** * Returns cycle graph. * @param {number} n Size of the graph * @param {boolean} [direct] Direct graph or not * @returns {Graph} Cycle graph */ static cycle(n, direct = false) { if (n < 3) { throw new GraphException('Index out of bounds.') } const e = [] for (let i = 0; i < n; i++) { e.push(new Edge(i, (i + 1) % n, null, direct)) } return new Graph(n, e) } /** * Returns wheel graph. * @param {number} n Size of the graph * @returns {Graph} Wheel graph */ static wheel(n) { if (n < 4) { throw new GraphException('Index out of bounds.') } const e = [] for (let i = 1; i < n; i++) { e.push([i, (i % (n - 1)) + 1]) e.push([0, i]) } return new Graph(n, e) } /** * Returns windmill graph. * @param {number} k Size of the sub complete graph * @param {number} n Number of the sub complete graph * @returns {Graph} Windmill graph */ static windmill(k, n) { if (k === 1) { return new Graph(1) } const e = [] for (let i = 0; i < n; i++) { for (let s = 0; s < k; s++) { for (let t = 0; t < s; t++) { e.push([t === 0 ? 0 : i * (k - 1) + t, i * (k - 1) + s]) } } } return new Graph(n * (k - 1) + 1, e) } /** * Returns named graph * @param {'balaban 10 cage' | 'bidiakis cube' | 'biggs smith' | 'brinkmann' | 'bull' | 'butterfly' | 'chvatal' | 'clebsch' | 'coxeter' | 'desargues' | 'diamond' | 'durer' | 'errera' | 'folkman' | 'foster' | 'franklin' | 'frucht' | 'goldner-harary' | 'golomb' | 'gray' | 'grotzsch' | 'harries' | 'heawood' | 'herschel' | 'hoffman' | 'holt' | 'kittell' | 'markstrom' | 'mcgee' | 'meredith' | 'mobius kantor' | 'moser spindle' | 'nauru' | 'pappus' | 'petersen' | 'poussin' | 'robertson' | 'shrikhande' | 'sousselier' | 'sylvester' | 'tutte' | 'tutte coxeter' | 'wagner' | 'wells'} name Name of the graph * @returns {Graph} Named graph */ static fromName(name) { switch (name) { case 'balaban 10 cage': { const edges = [] for (let i = 0; i < 10; i++) { edges.push([i * 2, ((i + 3) % 10) * 2 + 1]) edges.push([i * 2 + 1, ((i + 3) % 10) * 2]) edges.push([i * 2, i * 3 + 20]) edges.push([i * 2 + 1, i * 3 + 22]) edges.push([i * 3 + 20, i * 3 + 21]) edges.push([i * 3 + 21, i * 3 + 22]) if (i < 5) { edges.push([i * 3 + 21, (i + 5) * 3 + 21]) } edges.push([i * 3 + 20, i * 2 + 50]) edges.push([i * 3 + 22, i * 2 + 51]) edges.push([i * 2 + 50, ((i + 1) % 10) * 2 + 50]) edges.push([i * 2 + 51, ((i + 1) % 10) * 2 + 51]) } return new Graph(70, edges) } case 'bidiakis cube': { const edges = [] for (let i = 0; i < 12; i++) { edges.push([i, (i + 1) % 12]) } edges.push([0, 8], [1, 7], [2, 6], [3, 11], [4, 10], [5, 9]) return new Graph(12, edges) } case 'biggs smith': { const edges = [] for (let i = 0; i < 17; i++) { edges.push([i, (i + 8) % 17]) edges.push([i, i + 17]) edges.push([i + 17, i + 51]) edges.push([i + 17, i + 68]) edges.push([i + 34, ((i + 4) % 17) + 34]) edges.push([i + 34, i + 68]) edges.push([i + 51, ((i + 2) % 17) + 51]) edges.push([i + 68, i + 85]) edges.push([i + 85, ((i + 1) % 17) + 85]) } return new Graph(102, edges) } case 'brinkmann': { const edges = [] for (let i = 0; i < 7; i++) { edges.push([i, (i + 3) % 7]) edges.push([i, i + 7]) edges.push([i, ((i + 5) % 7) + 7]) edges.push([i + 7, i + 14]) edges.push([i + 7, ((i + 1) % 7) + 14]) edges.push([i + 14, ((i + 2) % 7) + 14]) } return new Graph(21, edges) } case 'bull': return new Graph(5, [ [0, 1], [1, 2], [1, 3], [2, 3], [3, 4], ]) case 'butterfly': return new Graph(5, [ [0, 1], [0, 2], [1, 2], [2, 3], [2, 4], [3, 4], ]) case 'chvatal': { const edges = [] for (let i = 0; i < 4; i++) { edges.push([i, (i + 1) % 4]) edges.push([i, i * 2 + 4]) edges.push([i, ((i * 2 + 7) % 8) + 4]) edges.push([i * 2 + 4, i * 2 + 5]) edges.push([i * 2 + 4, ((i * 2 + 3) % 8) + 4]) } edges.push([4, 8], [5, 9], [6, 10], [7, 11]) return new Graph(12, edges) } case 'clebsch': { const edges = [] for (let i = 0; i < 8; i++) { edges.push([i, (i + 3) % 8]) edges.push([i, i + 8]) edges.push([i, ((i + 2) % 8) + 8]) edges.push([i + 8, ((i + 1) % 8) + 8]) } for (let i = 0; i < 4; i++) { edges.push([i, i + 4]) edges.push([i + 8, i + 12]) } return new Graph(16, edges) } case 'coxeter': { const edges = [] for (let i = 0; i < 3; i++) { edges.push([0, i * 8 + 4]) edges.push([i + 1, i * 8 + 8]) edges.push([i + 1, ((i * 8 + 15) % 24) + 4]) edges.push([i + 1, ((i * 8 + 22) % 24) + 4]) edges.push([i * 8 + 5, ((i * 8 + 19) % 24) + 4]) edges.push([i * 8 + 9, ((i * 8 + 18) % 24) + 4]) } for (let i = 0; i < 24; i++) { edges.push([i + 4, ((i + 1) % 24) + 4]) } return new Graph(28, edges) } case 'desargues': { const edges = [] for (let i = 0; i < 10; i++) { edges.push([i, (i + 3) % 10]) edges.push([i, i + 10]) edges.push([i + 10, ((i + 1) % 10) + 10]) } return new Graph(20, edges) } case 'diamond': return new Graph(4, [ [0, 1], [0, 2], [1, 2], [1, 3], [2, 3], ]) case 'durer': { const edges = [] for (let i = 0; i < 6; i++) { edges.push([i, (i + 1) % 6]) edges.push([i, i + 6]) edges.push([i + 6, ((i + 2) % 6) + 6]) } return new Graph(12, edges) } case 'dyck': { const edges = [] for (let i = 0; i < 32; i++) { edges.push([i, (i + 1) % 32]) } for (let i = 0; i < 8; i++) { edges.push([i * 4, (i * 4 + 5) % 32]) edges.push([i * 4 + 2, (i * 4 + 15) % 32]) } return new Graph(32, edges) } case 'errera': { const edges = [ [0, 1], [2, 3], [5, 6], [9, 10], [15, 16], ] for (let i = 0; i < 2; i++) { edges.push([0, 11 + i]) edges.push([0, 15 + i]) edges.push([1, 5 + i]) edges.push([1, 11 + i]) edges.push([2, 5 + i]) edges.push([2, 7 + i]) edges.push([3, 7 + i]) edges.push([3, 9 + i]) edges.push([4, 9 + i]) edges.push([4, 13 + i]) edges.push([4, 15 + i]) edges.push([5 + i, 7 + i]) edges.push([5 + i, 11 + i]) edges.push([5 + i, 13 + i]) edges.push([7 + i, 9 + i]) edges.push([7 + i, 13 + i]) edges.push([9 + i, 13 + i]) edges.push([11 + i, 13 + i]) edges.push([11 + i, 15 + i]) edges.push([13 + i, 15 + i]) } return new Graph(17, edges) } case 'folkman': { const edges = [] for (let i = 0; i < 5; i++) { for (let j = 0; j < 4; j++) { edges.push([i * 4 + j, i * 4 + ((j + 1) % 4)]) } } for (let i = 0; i < 4; i++) { edges.push([i * 4 + 2, (i + 1) * 4 + 1]) edges.push([i * 4 + 2, (i + 1) * 4 + 3]) } for (let i = 0; i < 3; i++) { edges.push([i * 4, (i + 2) * 4 + 1]) edges.push([i * 4, (i + 2) * 4 + 3]) } edges.push([1, 12], [1, 18], [3, 12], [3, 18], [5, 16], [7, 16]) return new Graph(20, edges) } case 'foster': { const edges = [] for (let i = 0; i < 15; i++) { for (let j = 0; j < 6; j++) { edges.push([i * 6 + j, (i * 6 + j + 1) % 90]) } edges.push([i * 6, ((i + 8) % 15) * 6 + 5]) edges.push([i * 6 + 1, ((i + 1) % 15) * 6 + 4]) edges.push([i * 6 + 2, ((i + 12) % 15) * 6 + 3]) } return new Graph(90, edges) } case 'franklin': { const edges = [] for (let i = 0; i < 12; i++) { edges.push([i, (i + 1) % 12]) } edges.push([0, 7], [1, 6], [2, 9], [3, 8], [4, 11], [5, 10]) return new Graph(12, edges) } case 'frucht': { const edges = [] for (let i = 0; i < 7; i++) { edges.push([i, (i + 1) % 7]) } edges.push([0, 7], [1, 8], [2, 8], [3, 9], [4, 9], [5, 10], [6, 10], [7, 10], [7, 11], [8, 11], [9, 11]) return new Graph(12, edges) } case 'goldner-harary': { const edges = [] for (let i = 0; i < 4; i++) { edges.push([0, i + 1]) edges.push([0, i + 5]) edges.push([i + 1, i + 5]) edges.push([i + 1, ((i + 3) % 4) + 5]) edges.push([i + 5, ((i + 3) % 4) + 5]) } edges.push([5, 9], [9, 7], [7, 10], [10, 5], [5, 7], [6, 9], [8, 10]) return new Graph(11, edges) } case 'golomb': { const edges = [] for (let i = 0; i < 6; i++) { edges.push([0, i + 1]) edges.push([i + 1, ((i + 1) % 6) + 1]) } for (let i = 0; i < 3; i++) { edges.push([i + 7, ((i + 1) % 3) + 7]) edges.push([i * 2 + 1, i + 7]) } return new Graph(10, edges) } case 'gray': { const edges = [] for (let i = 0; i < 54; i++) { edges.push([i, (i + 1) % 54]) } for (let i = 0; i < 9; i++) { edges.push([i * 6, ((i + 4) % 9) * 6 + 1]) edges.push([i * 6 + 2, ((i + 2) % 9) * 6 + 3]) edges.push([i * 6 + 4, ((i + 1) % 9) * 6 + 5]) } return new Graph(54, edges) } case 'grotzsch': { const edges = [] for (let i = 0; i < 5; i++) { edges.push([0, i + 1]) edges.push([i + 1, i + 6]) edges.push([i + 6, ((i + 4) % 5) + 1]) edges.push([i + 6, ((i + 2) % 5) + 6]) } return new Graph(11, edges) } case 'harries': { const edges = [] for (let i = 0; i < 10; i++) { edges.push([i, (i + 3) % 10]) edges.push([i, i * 5 + 20]) if (i % 2 === 0) { edges.push([i + 10, ((i + 1) % 10) + 10]) edges.push([i + 10, i * 5 + 22]) } else { edges.push([i + 10, ((i + 3) % 10) + 10]) edges.push([i + 10, i * 5 + 24]) } } for (let i = 0; i < 50; i++) { edges.push([i + 20, ((i + 1) % 50) + 20]) } for (let i = 0; i < 5; i++) { edges.push([i * 10 + 24, ((i * 10 + 13) % 50) + 20]) edges.push([i * 10 + 28, ((i * 10 + 17) % 50) + 20]) edges.push([i * 5 + 21, ((i * 5 + 25) % 50) + 21]) } return new Graph(70, edges) } case 'heawood': { const edges = [] for (let i = 0; i < 7; i++) { edges.push([i * 2, i * 2 + 1]) edges.push([i * 2 + 1, (i * 2 + 2) % 14]) edges.push([i * 2, (i * 2 + 5) % 14]) } return new Graph(14, edges) } case 'herschel': { const edges = [] for (let i = 0; i < 4; i++) { edges.push([0, i + 1]) edges.push([i + 1, i + 5]) edges.push([i + 1, ((i + 3) % 4) + 5]) } edges.push([5, 9], [5, 10], [6, 9], [7, 9], [7, 10], [8, 10]) return new Graph(11, edges) } case 'hoffman': { const edges = [] for (let i = 0; i < 2; i++) { edges.push([0, 6 + i]) edges.push([0, 10 + i]) edges.push([1, 8 + i]) edges.push([1, 10 + i]) edges.push([2, 6 + i]) edges.push([2, 14 + i]) edges.push([3, 8 + i]) edges.push([3, 14 + i]) edges.push([4 + i, 6 + i]) edges.push([4 + i, 10 + i]) edges.push([4 + i, 9 - i]) edges.push([4 + i, 15 - i]) edges.push([6 + i, 12 + i]) edges.push([8 + i, 12 + i]) edges.push([10 + i, 12 + i]) edges.push([12 + i, 14 + i]) } return new Graph(16, edges) } case 'holt': { const edges = [] for (let i = 0; i < 9; i++) { edges.push([i, (i + 4) % 9]) edges.push([i, ((i + 7) % 9) + 9]) edges.push([i, ((i + 1) % 9) + 18]) edges.push([i + 9, ((i + 1) % 9) + 9]) edges.push([i + 9, i + 18]) edges.push([i + 18, ((i + 2) % 9) + 18]) } return new Graph(27, edges) } case 'kittell': return new Graph(23, [ [0, 1], [0, 2], [0, 3], [0, 4], [0, 15], [0, 16], [1, 2], [1, 4], [1, 5], [1, 6], [1, 7], [1, 12], [2, 3], [2, 7], [2, 8], [3, 8], [3, 14], [3, 15], [4, 5], [4, 9], [4, 16], [4, 17], [5, 6], [5, 9], [5, 10], [6, 10], [6, 11], [6, 12], [7, 8], [7, 12], [7, 13], [8, 13], [8, 14], [9, 10], [9, 17], [9, 18], [10, 11], [10, 18], [10, 19], [11, 12], [11, 19], [11, 20], [12, 13], [12, 20], [12, 21], [13, 14], [13, 21], [14, 15], [14, 21], [15, 16], [15, 21], [16, 17], [16, 21], [16, 22], [17, 18], [17, 22], [18, 19], [18, 22], [19, 20], [19, 22], [20, 21], [20, 22], [21, 22], ]) case 'markstrom': { const edges = [] for (let i = 0; i < 3; i++) { edges.push([i * 8, i * 8 + 1]) edges.push([i * 8, i * 8 + 4]) edges.push([i * 8 + 1, i * 8 + 2]) edges.push([i * 8 + 1, i * 8 + 3]) edges.push([i * 8 + 2, i * 8 + 3]) edges.push([i * 8 + 2, ((i + 1) % 3) * 8]) edges.push([i * 8 + 3, i * 8 + 5]) edges.push([i * 8 + 4, i * 8 + 5]) edges.push([i * 8 + 4, i * 8 + 6]) edges.push([i * 8 + 5, i * 8 + 6]) edges.push([i * 8 + 6, i * 8 + 7]) edges.push([i * 8 + 7, ((i + 1) % 3) * 8 + 7]) } return new Graph(24, edges) } case 'mcgee': { const edges = [] for (let i = 0; i < 24; i++) { edges.push([i, (i + 1) % 24]) } for (let i = 0; i < 8; i++) { edges.push([i * 3 + 1, (i * 3 + 8) % 24]) } for (let i = 0; i < 4; i++) { edges.push([i * 3, i * 3 + 12]) } return new Graph(24, edges) } case 'meredith': { const edges = [] for (let i = 0; i < 5; i++) { for (let s = 0; s < 4; s++) { for (let t = 0; t < 3; t++) { edges.push([i * 14 + s, i * 14 + 4 + t]) edges.push([i * 14 + 7 + s, i * 14 + 11 + t]) } } edges.push([i * 14 + 1, i * 14 + 8]) edges.push([i * 14 + 2, i * 14 + 9]) edges.push([i * 14 + 3, ((i + 2) % 5) * 14]) edges.push([i * 14 + 10, ((i + 1) % 5) * 14 + 7]) } return new Graph(70, edges) } case 'mobius kantor': { const edges = [] for (let i = 0; i < 8; i++) { edges.push([i, (i + 3) % 8]) edges.push([i, i + 8]) edges.push([i + 8, ((i + 1) % 8) + 8]) } return new Graph(16, edges) } case 'moser spindle': return new Graph(7, [ [0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 5], [2, 5], [3, 4], [3, 6], [4, 6], [5, 6], ]) case 'nauru': { const edges = [] for (let i = 0; i < 24; i++) { edges.push([i, (i + 1) % 24]) } for (let i = 0; i < 4; i++) { edges.push([i * 6, i * 6 + 5]) edges.push([i * 6 + 2, (i * 6 + 9) % 24]) edges.push([i * 6 + 1, (i * 6 + 16) % 24]) } return new Graph(24, edges) } case 'pappus': { const edges = [] for (let i = 0; i < 6; i++) { edges.push([i, ((i + 1) % 6) + 6]) edges.push([i, ((i + 5) % 6) + 6]) edges.push([i + 6, i + 12]) edges.push([i + 12, ((i + 1) % 6) + 12]) } edges.push([0, 3], [1, 4], [2, 5]) return new Graph(18, edges) } case 'petersen': { const edges = [] for (let i = 0; i < 5; i++) { edges.push([i, (i + 1) % 5]) edges.push([i, i + 5]) edges.push([i + 5, ((i + 2) % 5) + 5]) } return new Graph(10, edges) } case 'poussin': { const edges = [ [0, 1], [1, 2], [3, 4], [5, 6], [13, 14], ] for (let i = 0; i < 2; i++) { edges.push([0, 7 + i]) edges.push([0, 13 + i]) edges.push([1, 7 + i]) edges.push([1, 9 + i]) edges.push([2, 5 + i]) edges.push([2, 9 + i]) edges.push([3, 5 + i]) edges.push([3, 11 + i]) edges.push([4, 11 + i]) edges.push([4, 13 + i]) edges.push([5 + i, 9 + i]) edges.push([5 + i, 11 + i]) edges.push([7 + i, 9 + i]) edges.push([7 + i, 13 + i]) edges.push([9 + i, 13 + i]) edges.push([9 + i, 11 + i]) edges.push([11 + i, 13 + i]) } return new Graph(15, edges) } case 'robertson': { const edges = [ [0, 2], [1, 3], ] for (let i = 0; i < 4; i++) { edges.push([i, i * 3 + 4]) edges.push([(i + 3) % 4, i * 3 + 5]) edges.push([(i + 2) % 4, i * 3 + 6]) edges.push([i * 3 + 4, i * 3 + 5]) edges.push([i * 3 + 5, i * 3 + 6]) edges.push([i * 3 + 6, ((i + 1) % 4) * 3 + 4]) } for (let i = 0; i < 3; i++) { for (let j = 0; j < 4; j++) { edges.push([i + 16, ((i * 4 + j * 3) % 12) + 4]) } } return new Graph(19, edges) } case 'shrikhande': { const edges = [] for (let i = 0; i < 4; i++) { edges.push([i * 4, ((i + 1) % 4) * 4]) edges.push([i * 4, i * 4 + 1]) edges.push([i * 4, i * 4 + 2]) edges.push([i * 4, ((i + 1) % 4) * 4 + 1]) edges.push([i * 4, ((i + 1) % 4) * 4 + 2]) edges.push([i * 4 + 1, ((i + 1) % 4) * 4 + 2]) edges.push([i * 4 + 1, i * 4 + 3]) edges.push([i * 4 + 2, ((i + 1) % 4) * 4 + 1]) edges.push([i * 4 + 2, ((i + 1) % 4) * 4 + 3]) edges.push([i * 4 + 2, ((i + 2) % 4) * 4 + 3]) edges.push([i * 4 + 3, ((i + 1) % 4) * 4 + 1]) edges.push([i * 4 + 3, ((i + 1) % 4) * 4 + 3]) } return new Graph(16, edges) } case 'sousselier': { const edges = [] for (let i = 0; i < 5; i++) { edges.push([0, i * 3 + 1]) edges.push([i * 3 + 2, ((i * 3 + 5) % 15) + 1]) } for (let i = 0; i < 15; i++) { edges.push([i + 1, ((i + 1) % 15) + 1]) } edges.push([2, 12], [5, 15]) return new Graph(16, edges) } case 'sylvester': { const edges = [] for (let i = 0; i < 6; i++) { edges.push([i * 6, i * 6 + 2]) edges.push([i * 6, i * 6 + 5]) edges.push([i * 6, ((i + 1) % 6) * 6 + 4]) edges.push([i * 6, ((i + 3) % 6) * 6 + 1]) edges.push([i * 6, ((i + 3) % 6) * 6 + 3]) edges.push([i * 6 + 1, i * 6 + 2]) edges.push([i * 6 + 1, ((i + 2) % 6) * 6 + 4]) edges.push([i * 6 + 1, ((i + 3) % 6) * 6 + 4]) edges.push([i * 6 + 1, ((i + 4) % 6) * 6 + 2]) edges.push([i * 6 + 2, ((i + 1) % 6) * 6 + 5]) edges.push([i * 6 + 2, ((i + 2) % 6) * 6 + 5]) edges.push([i * 6 + 3, i * 6 + 4]) edges.push([i * 6 + 3, ((i + 1) % 6) * 6 + 3]) edges.push([i * 6 + 3, ((i + 1) % 6) * 6 + 5]) if (i < 3) { edges.push([i * 6 + 4, ((i + 3) % 6) * 6 + 4]) edges.push([i * 6 + 5, ((i + 3) % 6) * 6 + 5]) } } return new Graph(36, edges) } case 'tutte': { const edges = [] for (let i = 0; i < 3; i++) { edges.push([0, i * 15 + 1]) edges.push([i * 15 + 1, i * 15 + 2]) edges.push([i * 15 + 1, i * 15 + 4]) edges.push([i * 15 + 2, i * 15 + 3]) edges.push([i * 15 + 2, i * 15 + 5]) edges.push([i * 15 + 3, i * 15 + 4]) edges.push([i * 15 + 3, i * 15 + 7]) edges.push([i * 15 + 4, i * 15 + 9]) edges.push([i * 15 + 5, i * 15 + 6]) edges.push([i * 15 + 5, i * 15 + 10]) edges.push([i * 15 + 6, i * 15 + 7]) edges.push([i * 15 + 6, i * 15 + 11]) edges.push([i * 15 + 7, i * 15 + 8]) edges.push([i * 15 + 8, i * 15 + 9]) edges.push([i * 15 + 8, i * 15 + 12]) edges.push([i * 15 + 9, i * 15 + 15]) edges.push([i * 15 + 10, i * 15 + 11]) edges.push([i * 15 + 10, i * 15 + 13]) edges.push([i * 15 + 11, i * 15 + 12]) edges.push([i * 15 + 12, i * 15 + 14]) edges.push([i * 15 + 13, i * 15 + 14]) edges.push([i * 15 + 14, i * 15 + 15]) edges.push([i * 15 + 15, ((i + 1) % 3) * 15 + 13]) } return new Graph(46, edges) } case 'tutte coxeter': { const edges = [] for (let i = 0; i < 30; i++) { edges.push([i, (i + 1) % 30]) } for (let i = 0; i < 5; i++) { edges.push([i * 6, ((i + 1) % 5) * 6 + 3]) edges.push([i * 6 + 1, ((i + 2) % 5) * 6 + 2]) edges.push([i * 6 + 4, ((i + 1) % 5) * 6 + 5]) } return new Graph(30, edges) } case 'wagner': { const edges = [] for (let i = 0; i < 4; i++) { edges.push([i, i + 1]) edges.push([i, i + 4]) edges.push([i + 4, (i + 5) % 8]) } return new Graph(8, edges) } case 'wells': { const edges = [] for (let i = 0; i < 8; i++) { edges.push([i * 4, ((i + 1) % 8) * 4]) edges.push([i * 4, ((i + 2) % 8) * 4 + 3]) edges.push([i * 4 + 1, ((i + 1) % 8) * 4 + 3]) edges.push([i * 4 + 1, ((i + 2) % 8) * 4]) edges.push([i * 4 + 1, ((i + 2) % 8) * 4 + 3]) edges.push([i * 4 + 1, ((i + 3) % 8) * 4 + 2]) edges.push([i * 4 + 2, ((i + 1) % 8) * 4]) edges.push([i * 4 + 2, ((i + 3) % 8) * 4 + 2]) edges.push([i * 4 + 3, ((i + 1) % 8) * 4 + 1]) edges.push([i * 4 + 3, ((i + 3) % 8) * 4 + 2]) } return new Graph(32, edges) } case 'wiener araya': { const edges = [] for (let i = 0; i < 4; i++) { edges.push([i * 10, i * 10 + 1]) edges.push([i * 10, i * 10 + 2]) edges.push([i * 10, ((i + 1) % 4) * 10]) edges.push([i * 10 + 1, i * 10 + 3]) edges.push([i * 10 + 1, i * 10 + 5]) edges.push([i * 10 + 2, i * 10 + 4]) edges.push([i * 10 + 2, ((i + 1) % 4) * 10 + 5]) edges.push([i * 10 + 3, i * 10 + 4]) edges.push([i * 10 + 3, i * 10 + 7]) edges.push([i * 10 + 4, i * 10 + 9]) edges.push([i * 10 + 5, i * 10 + 6]) edges.push([i * 10 + 6, i * 10 + 7]) edges.push([i * 10 + 6, ((i + 3) % 4) * 10 + 9]) edges.push([i * 10 + 7, i * 10 + 8]) edges.push([i * 10 + 8, i * 10 + 9]) if (i % 2 === 1) { edges.push([i * 10 + 8, ((i + 1) % 4) * 10 + 8]) edges.push([i * 10 + 8, i === 1 ? 40 : 41]) } else { edges.push([i * 10 + 9, i === 0 ? 40 : 41]) } } edges.push([40, 41]) return new Graph(42, edges) } } } /** * Number of nodes * @type {number} */ get order() { return this._nodes.length } /** * Number of edges * @type {number} */ get size() { return this._edges.length } /** * Nodes * @type {unknown[]} */ get nodes() { return this._nodes } /** * Edges * @type {Edge[]} */ get edges() { return this._edges } /** * Returns a string of DOT format. * @returns {string} String of DOT format */ toDot() { let s = this.isUndirected() ? 'graph' : 'digraph' s += ' g {\n' for (let i = 0; i < this._nodes.length; i++) { s += ` ${i} [label="${JSON.stringify(this._nodes[i] ?? i).replace('"', "'")}"];\n` } for (const e of this._edges) { s += ` ${e[0]} ${e.direct ? '->' : '--'} ${e[1]}` if (e.weighted) { s += ` [label="${JSON.stringify(e.value).replace('"', "'")}"]` } s += ';\n' } return `${s}}` } /** * Returns a string represented this graph. * @returns {string} String represented this graph */ toString() { let s = `Number of nodes: ${this._nodes.length}\n` if (this._nodes.some(v => v != null)) { s += `Node values: ${JSON.stringify(this._nodes)}\n` } s += `Number of edges: ${this._edges.length}` if (this._edges.length > 0) { s += `\nEdges` } for (const e of this._edges) { s += `\n From ${e[0]} to ${e[1]}, value: ${JSON.stringify(e.value)} (${ e.direct ? 'directed' : 'undirected' })` } return s } /** * Returns a copy of this graph. * @returns {Graph} Copied grpah */ copy() { const edges = this._edges.map(e => new Edge(e[0], e[1], e.value0, e.direct)) const nodes = this._nodes.concat() return new Graph(nodes, edges) } /** * Return degree of the node. * @overload * @param {number} k Index of target node * @param {boolean} [undirect] Count undirected edges * @param {boolean | 'in' | 'out'} [direct] Count directed edges * @returns {number} Degree of the node */ /** * Return degree of the node. * @overload * @param {number} k Index of target node * @param {'in' | 'out'} direct Count only directed edges. * @returns {number} Degree of the node */ /** * @param {number} k Index of target node * @param {boolean | 'in' | 'out'} [undirect] Count undirected edges. If `in` or `out` is specified, only direct edges are counted and `direct` parameter is ignored. * @param {boolean | 'in' | 'out'} [direct] Count directed edges * @returns {number} Degree of the node */ degree(k, undirect = true, direct = true) { if (undirect === 'in' || undirect === 'out') { direct = undirect undirect = false } let c = 0 for (const e of this._edges) { if (undirect && !e.direct && (e[0] === k || e[1] === k)) { c++ } else if (direct === true && e.direct && (e[0] === k || e[1] === k)) { c++ } else if (direct === 'in' && e.direct && e[1] === k) { c++ } else if (direct === 'out' && e.direct && e[0] === k) { c++ } } return c } /** * Return indexes of adjacency nodes. * @overload * @param {number} k Index of target node * @param {boolean} [undirect] Check undirected edges * @param {boolean | 'in' | 'out'} [direct] Check directed edges * @returns {number[]} Indexes of adjacency nodes */ /** * Return indexes of adjacency nodes. * @overload * @param {number} k Index of target node * @param {'in' | 'out'} direct Check only directed edges * @returns {number[]} Indexes of adjacency nodes */ /** * @param {number} k Index of target node * @param {boolean | 'in' | 'out'} [undirect] Check undirected edges. If `in` or `out` is specified, only direct edges are checked and `direct` parameter is ignored. * @param {boolean | 'in' | 'out'} [direct] Check directed edges * @returns {number[]} Indexes of adjacency nodes */ adjacencies(k, undirect = true, direct = true) { if (undirect === 'in' || undirect === 'out') { direct = undirect undirect = false } const nodesSet = new Set() for (const e of this._edges) { if (undirect && !e.direct && (e[0] === k || e[1] === k)) { nodesSet.add(e[0] === k ? e[1] : e[0]) } else if (direct === true && e.direct && (e[0] === k || e[1] === k)) { nodesSet.add(e[0] === k ? e[1] : e[0]) } else if (direct === 'in' && e.direct && e[1] === k) { nodesSet.add(e[0]) } else if (direct === 'out' && e.direct && e[0] === k) { nodesSet.add(e[1]) } } const nodes = [...nodesSet] nodes.sort((a, b) => a - b) return nodes } /** * Returns indexes of each components. * @returns {number[][]} Indexes of each components */ components() { const checked = Array(this._nodes.length).fill(false) const stack = [0] const comps = [] let curcomp = [] while (stack.length > 0 || checked.some(v => !v)) { if (stack.length === 0) { curcomp.sort((a, b) => a - b) comps.push(curcomp) curcomp = [] } const i = stack.length > 0 ? stack.pop() : checked.indexOf(false) if (checked[i]) { continue } checked[i] = true curcomp.push(i) for (const e of this._edges) { if (e[0] === i) { stack.push(e[1]) } if (e[1] === i) { stack.push(e[0]) } } } if (curcomp.length > 0) { curcomp.sort((a, b) => a - b) comps.push(curcomp) } return comps } /** * Returns indexes of each biconnected components. * @returns {number[][]} Indexes of each biconnected components */ biconnectedComponents() { const structure = this.toSimple().toUndirected() const notcheckedComponents = structure.components() const components = [] while (notcheckedComponents.length > 0) { const component = notcheckedComponents.pop() const subg = structure.inducedSub(component) const a = subg.articulations() if (a.length === 0) { components.push(component) continue } const sep = a[0] subg.removeNode(sep) const subcomp = subg.components() for (let i = 0; i < subcomp.length; i++) { const c = [] for (let j = 0; j < subcomp[i].length; j++) { if (subcomp[i][j] >= sep) { c.push(component[subcomp[i][j] + 1]) } else { c.push(component[subcomp[i][j]]) } } c.push(component[sep]) c.sort((a, b) => a - b) notcheckedComponents.push(c) } } return components } /** * Returns diameter of this graph. * @returns {number} Diameter */ diameter() { const sp = this.shortestPathFloydWarshall() let max_len = -Infinity for (let i = 0; i < this._nodes.length; i++) { for (let j = 0; j < this._nodes.length; j++) { if (max_len < sp[i][j].length) { max_len = sp[i][j].length } } } return max_len } /** * Returns eccentricity at k of this graph. * @param {number} k Index of target node * @returns {number} Eccentricity */ eccentricity(k) { const sp = this.shortestPathBellmanFord(k) let max_len = -Infinity for (let i = 0; i < this._nodes.length; i++) { if (max_len < sp[i].length) { max_len = sp[i].length } } return max_len } /** * Returns radius of this graph. * @returns {number} Radius */ radius() { const sp = this.shortestPathFloydWarshall() let r = Infinity for (let i = 0; i < this._nodes.length; i++) { let max_len = -Infinity for (let j = 0; j < this._nodes.length; j++) { if (max_len < sp[i][j].length) { max_len = sp[i][j].length } } if (r > max_len) { r = max_len } } return r } /** * Returns indexes of center of this graph. * @returns {number[]} Indexes of center */ center() { const sp = this.shortestPathFloydWarshall() const ecc = Array(this._nodes.length).fill(-Infinity) let r = Infinity for (let i = 0; i < this._nodes.length; i++) { for (let j = 0; j < this._nodes.length; j++) { if (ecc[i] < sp[i][j].length) { ecc[i] = sp[i][j].length } } if (r > ecc[i]) { r = ecc[i] } } const c = [] for (let i = 0; i < ecc.length; i++) { if (ecc[i] === r) { c.push(i) } } return c } /** * Returns girth of this graph. * @returns {number} Girth */ girth() { let min_loop = Infinity for (let s = 0; s < this._nodes.length; s++) { const stack = [[[s], Array(this._edges.length).fill(false)]] while (stack.length > 0) { const [path, used] = stack.shift() const i = path[path.length - 1] for (let k = 0; k < this._edges.length; k++) { if (used[k]) { continue } const e = this._edges[k] if (e[0] === i) { if (e[1] === s) { if (path.length < min_loop) { min_loop = path.length } stack.length = 0 break } else if (path.includes(e[1])) { continue } const u = used.concat() u[k] = true stack.push([path.concat(e[1]), u]) } if (!e.direct && e[1] === i) { if (e[0] === s) { if (path.length < min_loop) { min_loop = path.length } stack.length = 0 break } else if (path.includes(e[0])) { continue } const u = used.concat() u[k] = true stack.push([path.concat(e[0]), u]) } } } } return min_loop } /** * Returns index of all cliques. * @overload * @returns {number[][][]} Index of cliques */ /** * Returns index of cliques. * @overload * @param {number} k Size of clique * @returns {number[][]} Index of cliques */ /** * @param {number} [k] Size of clique * @returns {number[][] | number[][][]} Index of cliques */ clique(k) { const n = this._nodes.length if (k != null && k > n) { return [] } const c0 = Array.from({ length: n }, (_, i) => [i]) if (k === 1) { return c0 } if (n === 1) { return [c0] } const con = [] const c1 = [] for (let i = 0; i < n; i++) { con[i] = [] for (let j = 0; j < i; j++) { if (this.getEdges(i, j).length > 0) { con[i][j] = true con[j][i] = true c1.push([j, i]) } } } if (k === 2) { return c1 } const K = k ?? this._nodes.length const c = [c0, c1] for (let i = 2; i < K; i++) { const ci = [] for (let j = 0; j < c[i - 1].length; j++) { const cij = c[i - 1][j] for (let s = cij.at(-1) + 1; s < n; s++) { if (cij.every(t => con[t][s])) { ci.push(cij.concat(s)) } } } c.push(ci) } return k == null ? c : c[k - 1] } /** * Returns chromatic number of this graph. * @returns {number} Chromatic number */ chromaticNumber() { const n = this._nodes.length if (n <= 1) { return n } if (this._edges.length === 0) { return 1 } if (this.isBipartite()) { return 2 } if (this.isComplete()) { return this._nodes.length } return this.chromaticNumberWelchPowell() } /** * Returns chromatic number of this graph with Welch-Powell algorithm. * @returns {number} Chromatic number */ chromaticNumberWelchPowell() { const n = this._nodes.length const alist = this.adjacencyList() const degrees = [] for (let i = 0; i < n; i++) { degrees[i] = [i, this.degree(i)] } degrees.sort((a, b) => b[1] - a[1]) let c = 0 const colors = Array(n).fill(-1) while (true) { const i = colors.indexOf(-1) if (i < 0) { break } colors[i] = c for (let k = 0; k < n; k++) { if (colors[k] >= 0) { continue } if (alist[k].every(v => colors[v] !== c)) { colors[k] = c } } c++ } return c } /** * Returns chromatic index of this graph. * @returns {number} Chromatic index */ chromaticIndex() { const components = this.components() let maxci = 0 for (let k = 0; k < components.length; k++) { const g = this.inducedSub(components[k]) let ci = -1 if (g._edges.length <= 1) { ci = g._edges.length } else if (g.isBipartite()) { ci = 0 for (let i = 0; i < g._nodes.length; i++) { ci = Math.max(ci, g.degree(i)) } } else if (g.isComplete()) { ci = g._nodes.length % 2 === 0 ? g._nodes.length - 1 : g._nodes.length } else { throw new GraphException('Not implemented') } maxci = Math.max(maxci, ci) } return maxci } /** * Returns indexes of articulation (cut) nodes. * @returns {number[]} Indexes of articulation nodes */ articulations() { return this.articulationsLowLink() } /** * Returns indexes of articulation (cut) nodes with checking each node. * @returns {number[]} Indexes of articulation nodes */ articulationsEachNodes() { const n = this._nodes.length const a = [] for (let i = 0; i < n; i++) { const adj = this.adjacencies(i).filter(v => v !== i) if (adj.length <= 1) { continue } const checked = Array(n).fill(false) checked[i] = true const stack = [adj[0]] while (stack.length > 0) { const k = stack.pop() if (checked[k]) { continue } checked[k] = true for (const e of this._edges) { if (e[0] === k) { stack.push(e[1]) } if (e[1] === k) { stack.push(e[0]) } } } if (adj.some(k => !checked[k])) { a.push(i) } } return a } /** * Returns indexes of articulation (cut) nodes with checking lowlinks. * @returns {number[]} Indexes of articulation nodes */ articulationsLowLink() { const n = this._nodes.length if (n === 0) { return [] } const ord = Array(n).fill(-1) const low = [] const parent = Array(n).fill(-1) const alist = this.adjacencyList() let d = 0 const a = [] const search = u => { ord[u] = low[u] = d++ let cc = 0 let isArt = false for (const v of alist[u]) { if (ord[v] < 0) { parent[v] = u search(v) cc++ if (low[v] >= ord[u]) { isArt = true } low[u] = Math.min(low[u], low[v]) } else if (v !== parent[u]) { low[u] = Math.min(low[u], ord[v]) } } if ((parent[u] >= 0 && isArt) || (parent[u] < 0 && cc > 1)) { a.push(u) } } search(0) a.sort((a, b) => a - b) return a } /** * Returns edges of bridge. * @returns {Edge[]} Bridge edges */ bridges() { return this.bridgesLowLink() } /** * Returns edges of bridge with checking lowlinks. * @returns {Edge[]} Bridge edges */ bridgesLowLink() { const n = this._nodes.length if (n === 0 || this._edges.length === 0) { return [] } const ord = Array(n).fill(-1) const low = [] const parent = Array(n).fill(-1) const alist = this.adjacencyList() let d = 0 const search = u => { ord[u] = low[u] = d++ for (const v of alist[u]) { if (ord[v] < 0) { parent[v] = u search(v) low[u] = Math.min(low[u], low[v]) } else if (v !== parent[u]) { low[u] = Math.min(low[u], ord[v]) } } } search(0) const cons = Array.from({ length: n }, () => Array(n).fill(0)) for (const e of this._edges) { cons[e[0]][e[1]]++ cons[e[1]][e[0]]++ } const b = [] for (const e of this._edges) { if (cons[e[0]][e[1]] === 1 && (ord[e[0]] < low[e[1]] || ord[e[1]] < low[e[0]])) { b.push(e) } } return b } /** * Add the node. * @param {unknown} [value] Value of the node */ addNode(value) { this._nodes[this._nodes.length] = value } /** * Returns the node value. * @overload * @param {number} k Index of the node * @returns {unknown} Node value */ /** * Returns the node value. * @overload * @param {number[]} [k] Index of the node * @returns {unknown[]} Node value */ /** * @param {number | number[]} [k] Index of the node * @returns {unknown | unknown[]} Node value */ getNode(k) { if (k == null) { return this._nodes } if (Array.isArray(k)) { if (k.some(i => i < 0 || this._nodes.length <= i)) { throw new GraphException('Index out of bounds.') } return k.map(i => this._nodes[i]) } if (k < 0 || this._nodes.length <= k) { throw new GraphException('Index out of bounds.') } return this._nodes[k] } /** * Remove the node. * @param {number} k Index of the node */ removeNode(k) { if (k < 0 || this._nodes.length <= k) { throw new GraphException('Index out of bounds.') } this._nodes.splice(k, 1) for (let i = this._edges.length - 1; i >= 0; i--) { const e = this._edges[i] if (e[0] === k || e[1] === k) { this._edges.splice(i, 1) } else { if (e[0] > k) { e[0]-- } if (e[1] > k) { e[1]-- } } } } /** * Remove all nodes. */ clearNodes() { this._nodes = [] this._edges = [] } /** * Add the edge. * @param {number} from Index of the starting node of the edge * @param {number} to Index of the end node of the edge * @param {unknown} [value] Value of the edge * @param {boolean} [direct] `true` if the edge is direct */ addEdge(from, to, value = null, direct = false) { if (from < 0 || this._nodes.length <= from || to < 0 || this._nodes.length <= to) { throw new GraphException('Index out of bounds.') } this._edges.push(new Edge(from, to, value, direct)) } /** * Returns the edges. * @overload * @param {number} from Index of the starting node of the edge * @param {number} to Index of the end node of the edge * @param {boolean} [undirect] Get undirected edges or not * @param {boolean | 'forward' | 'backward'} [direct] Get directed edges or not * @returns {Edge[]} Edges between `from` and `to` */ /** * Returns the edges. * @overload * @param {number} from Index of the starting node of the edge * @param {number} to Index of the end node of the edge * @param {'forward' | 'backward'} direct Get only directed edges * @returns {Edge[]} Edges between `from` and `to` */ /** * @param {number} from Index of the starting node of the edge * @param {number} to Index of the end node of the edge * @param {boolean | 'forward' | 'backward'} [undirect] Get undirected edges or not. If `forward` or `backward` is specified, only direct edges are get and `direct` parameter is ignored. * @param {boolean | 'forward' | 'backward'} [direct] Get directed edges or not * @returns {Edge[]} Edges between `from` and `to` */ getEdges(from, to, undirect = true, direct = true) { if (from < 0 || this._nodes.length <= from || to < 0 || this._nodes.length <= to) { throw new GraphException('Index out of bounds.') } if (undirect === 'forward' || undirect === 'backward') { direct = undirect undirect = false } const edges = [] for (const e of this._edges) { if ( ((undirect && !e.direct) || (direct === true && e.direct)) && ((e[0] === from && e[1] === to) || (e[0] === to && e[1] === from)) ) { edges.push(e) } else if (direct === 'forward' && e.direct && e[0] === from && e[1] === to) { edges.push(e) } else if (direct === 'backward' && e.direct && e[0] === to && e[1] === from) { edges.push(e) } } return edges } /** * Remove the edges. * @param {number} from Index of the starting node of the edge * @param {number} to Index of the end node of the edge * @param {boolean | null} [direct] `null` to remove direct and undirect edges, `true` to remove only direct edges, `false` to remove only undirect edges. */ removeEdges(from, to, direct = null) { if (from < 0 || this._nodes.length <= from || to < 0 || this._nodes.length <= to) { throw new GraphException('Index out of bounds.') } for (let i = this._edges.length - 1; i >= 0; i--) { const e = this._edges[i] if (direct === null || !direct === !e.direct) { if ((e[0] === from && e[1] === to) || (e[1] === from && e[0] === to)) { this._edges.splice(i, 1) } } } } /** * Remove all edges. */ clearEdges() { this._edges = [] } /** * Returns adjacency matrix * @returns {number[][]} Adjacency matrix */ adjacencyMatrix() { const a = Array.from({ length: this._nodes.length }, () => Array(this._nodes.length).fill(0)) for (const e of this._edges) { if (e.direct) { a[e[0]][e[1]] += e.value } else { a[e[0]][e[1]] += e.value a[e[1]][e[0]] += e.value } } return a } /** * Returns adjacency list * @param {'both' | 'in' | 'out'} [direct] Indegree or outdegree * @returns {number[][]} Adjacency list */ adjacencyList(direct = 'both') { const n = this._nodes.length const a = Array.from({ length: n }, () => new Set()) for (const e of this._edges) { if (e[0] === e[1]) { a[e[0]].add(e[1]) } else if (e.direct && direct !== 'both') { if (direct === 'in') { a[e[1]].add(e[0]) } else { a[e[0]].add(e[1]) } } else { a[e[0]].add(e[1]) a[e[1]].add(e[0]) } } for (let i = 0; i < n; i++) { a[i] = [...a[i]] a[i].sort((a, b) => a - b) } return a } /** * Returns degree matrix. * @param {'both' | 'in' | 'out'} [direct] Indegree or outdegree * @returns {number[][]} Degree matrix */ degreeMatrix(direct = 'both') { const a = Array.from({ length: this._nodes.length }, () => Array(this._nodes.length).fill(0)) for (const e of this._edges) { if (e.direct && direct !== 'both') { if (direct === 'in') { a[e[1]][e[1]] += e.value } else { a[e[0]][e[0]] += e.value } } else { a[e[0]][e[0]] += e.value a[e[1]][e[1]] += e.value } } return a } /** * Returns laplacian matrix. * @returns {number[][]} Laplacian matrix */ laplacianMatrix() { const amat = this.adjacencyMatrix() const lm = [] for (let i = 0; i < this._nodes.length; i++) { lm[i] = amat[i].map(v => -v) for (let j = 0; j < this._nodes.length; j++) { lm[i][i] += amat[i][j] } } return lm } /** * Returns if this is null graph or not. * @returns {boolean} `true` if this is null graph */ isNull() { return this._nodes.length === 0 } /** * Returns if this is edgeless graph or not. * @returns {boolean} `true` if this is edgeless graph */ isEdgeless() { return this._edges.length === 0 } /** * Returns if this is undirected graph or not. * @returns {boolean} `true` if this is undirected graph */ isUndirected() { return this._edges.every(e => !e.direct) } /** * Returns if this is directed graph or not. * @returns {boolean} `true` if this is directed graph */ isDirected() { return this._edges.every(e => e.direct) } /** * Returns if this is mixed graph or not. * @returns {boolean} `true` if this is mixed graph */ isMixed() { let hasDirect = false let hasUndirect = false for (const e of this._edges) { if (e.direct) { hasDirect = true } else { hasUndirect = true } if (hasDirect && hasUndirect) { return true } } return false } /** * Returns if this is oriented graph or not. * @returns {boolean} `true` if this is oriented graph */ isOriented() { const n = this._nodes.length const amat = Array.from({ length: n }, () => Array(n).fill(false)) for (const e of this._edges) { if (!e.direct) { return false } if (amat[e[0]][e[1]]) { return false } amat[e[0]][e[1]] = amat[e[1]][e[0]] = true } return true } /** * Returns if this is weighted graph or not. * @returns {boolean} `true` if this is weighted graph */ isWeighted() { return this._edges.some(e => e.weighted) } /** * Returns if this is simple graph or not. * @returns {boolean} `true` if this is simple graph */ isSimple() { const alist = Array.from({ length: this._nodes.length }, () => []) for (const e of this._edges) { if (e[0] === e[1]) { return false } if (alist[e[0]].includes(e[1]) || alist[e[1]].includes(e[0])) { return false } alist[e[0]].push(e[1]) } return true } /** * Returns if this is connected graph or not. * @returns {boolean} `true` if this is connected graph */ isConnected() { const con = Array(this._nodes.length).fill(false) const stack = [0] while (stack.length > 0) { const i = stack.pop() if (con[i]) { continue } con[i] = true for (const e of this._edges) { if (e[0] === i && !con[e[1]]) { stack.push(e[1]) } if (!e.direct && e[1] === i && !con[e[0]]) { stack.push(e[0]) } } } return con.every(v => v) } /** * Returns if this is biconnected graph or not. * @returns {boolean} `true` if this is biconnected graph */ isBiconnected() { return this.articulations().length === 0 } /** * Returns if this is tree or not. * @returns {boolean} `true` if this is tree */ isTree() { return this.isConnected() && !this.hasCycle() } /** * Returns if this is forest or not. * @returns {boolean} `true` if this is forest */ isForest() { return !this.hasCycle() } /** * Returns if this is bipartite graph or not. * @returns {boolean} `true` if this is bipartite graph */ isBipartite() { const color = Array(this._nodes.length).fill(false) const stack = [[0, 1]] while (stack.length > 0 || color.some(v => !v)) { const [i, c] = stack.length > 0 ? stack.pop() : [color.indexOf(false), 1] if (color[i]) { if (color[i] === c) { continue } return false } color[i] = c for (const e of this._edges) { if (e[0] === i) { if (color[e[1]] === c) { return false } stack.push([e[1], (c % 2) + 1]) } if (!e.direct && e[1] === i) { if (color[e[0]] === c) { return false } stack.push([e[0], (c % 2) + 1]) } } } return true } /** * Returns if this is complete graph or not. * @returns {boolean} `true` if this is complete graph */ isComplete() { for (let i = 0; i < this._nodes.length; i++) { for (let j = 0; j < i; j++) { if (this.getEdges(i, j).length === 0) {