UNPKG

@graphty/algorithms

Version:

Graph algorithms library for browser environments implemented in TypeScript

179 lines (156 loc) 5.35 kB
import type {Graph} from "../../core/graph.js"; import type {NodeId} from "../../types/index.js"; import {bfsDistancesOnly, bfsWeightedDistances} from "../traversal/bfs-variants.js"; /** * Closeness centrality implementation * * Measures how close a node is to all other nodes in the graph. * Uses BFS to compute shortest path distances efficiently. */ /** * Closeness centrality options */ export interface ClosenessCentralityOptions { /** * Whether to normalize the centrality values (default: false) */ normalized?: boolean; /** * Use harmonic mean instead of reciprocal of sum (default: false) * Better for disconnected graphs */ harmonic?: boolean; /** * Consider only nodes within this distance (default: undefined = all nodes) */ cutoff?: number; /** * Whether to use optimized BFS implementation for large graphs */ optimized?: boolean; } /** * Calculate closeness centrality from distances */ function calculateClosenessFromDistances( distances: Map<NodeId, number>, sourceNode: NodeId, totalNodes: number, options: ClosenessCentralityOptions, ): number { if (distances.size <= 1) { return 0; // No other nodes reachable } let centrality = 0; if (options.harmonic) { // Harmonic centrality: sum of reciprocals of distances for (const [targetNode, distance] of distances) { if (targetNode !== sourceNode && distance > 0 && distance < Infinity) { centrality += 1 / distance; } } // Normalization for harmonic centrality if (options.normalized && totalNodes > 1) { centrality = centrality / (totalNodes - 1); } } else { // Standard closeness: reciprocal of sum of distances let totalDistance = 0; let reachableNodes = 0; for (const [targetNode, distance] of distances) { if (targetNode !== sourceNode && distance < Infinity) { totalDistance += distance; reachableNodes++; } } if (totalDistance > 0) { centrality = 1 / totalDistance; // Wasserman and Faust normalization for disconnected graphs if (options.normalized && totalNodes > 1) { centrality = centrality * reachableNodes / (totalNodes - 1); } } } return centrality; } /** * Calculate closeness centrality for all nodes in the graph * * Closeness centrality measures how close a node is to all other nodes * in the graph. It is the reciprocal of the sum of the shortest path * distances to all other reachable nodes. * * @param graph - The input graph * @param options - Algorithm options * @returns Centrality scores for each node * * @example * ```typescript * const graph = new Graph(); * graph.addEdge("A", "B"); * graph.addEdge("B", "C"); * * const centrality = closenessCentrality(graph); * // { A: 0.5, B: 1.0, C: 0.5 } * ``` * * Time Complexity: O(V * (V + E)) for unweighted graphs * Space Complexity: O(V) */ export function closenessCentrality( graph: Graph, options: ClosenessCentralityOptions = {}, ): Record<string, number> { const nodes = Array.from(graph.nodes()).map((node) => node.id); const centrality: Record<string, number> = {}; for (const sourceNode of nodes) { centrality[String(sourceNode)] = nodeClosenessCentrality(graph, sourceNode, options); } return centrality; } /** * Calculate closeness centrality for a specific node */ export function nodeClosenessCentrality( graph: Graph, node: NodeId, options: ClosenessCentralityOptions = {}, ): number { if (!graph.hasNode(node)) { throw new Error(`Node ${String(node)} not found in graph`); } // Use optimized BFS variant for unweighted graphs const distances = bfsDistancesOnly(graph, node, options.cutoff, options.optimized !== undefined ? {optimized: options.optimized} : {}); const totalNodes = graph.nodeCount; return calculateClosenessFromDistances(distances, node, totalNodes, options); } /** * Calculate weighted closeness centrality using Dijkstra's algorithm */ export function weightedClosenessCentrality( graph: Graph, options: ClosenessCentralityOptions = {}, ): Record<string, number> { const nodes = Array.from(graph.nodes()).map((node) => node.id); const centrality: Record<string, number> = {}; for (const sourceNode of nodes) { centrality[String(sourceNode)] = nodeWeightedClosenessCentrality(graph, sourceNode, options); } return centrality; } /** * Calculate weighted closeness centrality for a specific node using Dijkstra */ export function nodeWeightedClosenessCentrality( graph: Graph, node: NodeId, options: ClosenessCentralityOptions = {}, ): number { if (!graph.hasNode(node)) { throw new Error(`Node ${String(node)} not found in graph`); } // Use optimized weighted BFS variant (simplified Dijkstra) const distances = bfsWeightedDistances(graph, node, options.cutoff, options.optimized !== undefined ? {optimized: options.optimized} : {}); const totalNodes = graph.nodeCount; return calculateClosenessFromDistances(distances, node, totalNodes, options); }