Suggest an editImprove this articleRefine the answer for “How can a graph be stored in memory?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)A graph in memory is usually stored as: 1) an adjacency matrix (O(V^2), fast edge checks), 2) an adjacency list (O(V+E), fast neighbor iteration, convenient additions/removals), 3) an edge list (O(E), simple to load and store), 4) compressed CSR/CSC structures for large sparse graphs. The choice depends on the graph's density, the required operations (edge check, neighbor traversal, updates), and memory constraints.Shown above the full answer for quick recall.Answer (EN)Image## Short answer A graph in memory is usually stored as: 1) an adjacency matrix (O(V^2), fast edge checks), 2) an adjacency list (O(V+E), fast neighbor iteration, convenient additions/removals), 3) an edge list (O(E), simple to load and store), 4) compressed CSR/CSC structures for large sparse graphs. The choice depends on the graph's density, the required operations (edge check, neighbor traversal, updates), and memory constraints. ## Detailed answer ### Main storage methods - Adjacency matrix: a two-dimensional V×V array. Memory O(V^2). Edge-existence check O(1). Neighbor iteration O(V). Good for dense graphs and frequent edge checks. - Adjacency list: a list/set of neighbors for each vertex. Memory O(V+E). Edge check O(deg(u)) or O(1) with a Set. Neighbor iteration O(deg(u)). The best choice for sparse and dynamic graphs. - Edge list: an array of pairs/triples (u, v, w). Memory O(E). Convenient for storage, serialization, and edge-based algorithms. Edge check O(E) (without indexing). - CSR/CSC: compressed formats for sparse graphs. Memory close to O(V+E). Excellent neighbor iteration and cache-friendly traversal. Updating online is hard/expensive. ### Adjacency Matrix A two-dimensional V×V array, where element matrix[u][v] is 0/1 or the edge weight. For an undirected graph the matrix is symmetric. - Memory: O(V^2). Inefficient for large sparse graphs. - Edge check u→v: O(1). Neighbor iteration: O(V). - Adding a vertex: O(V^2) (reallocation). Adding an edge: O(1). - Suits dense graphs and tasks with frequent edge-existence checks. ``` const n = 5; // number of vertices const matrix = Array.from({ length: n }, () => Array(n).fill(0)); function addEdge(u, v, w = 1, directed = false) { matrix[u][v] = w; // 1 or the weight if (!directed) matrix[v][u] = w; } function hasEdge(u, v) { return matrix[u][v] !== 0; } function neighbors(u) { const res = []; for (let v = 0; v < n; v++) if (matrix[u][v] !== 0) res.push(v); return res; } addEdge(0, 1); addEdge(0, 3); console.log(hasEdge(0, 1)); // true console.log(neighbors(0)); // [1, 3] ``` ### Adjacency List For each vertex we keep a collection of its neighbors: an array, a Set, or a Map (for weighted graphs). Scales well for sparse graphs and supports dynamic changes. - Memory: O(V+E). - Edge check: O(deg(u)) with an array, O(1) with a Set/Map. - Neighbor iteration: O(deg(u)). Adding/removing an edge: amortized O(1). ``` // Unweighted graph: Map<number, Set<number>> const g = new Map(); function addVertex(u) { if (!g.has(u)) g.set(u, new Set()); } function addEdge(u, v, directed = false) { addVertex(u); addVertex(v); g.get(u).add(v); if (!directed) g.get(v).add(u); } function hasEdge(u, v) { return g.has(u) && g.get(u).has(v); } function neighbors(u) { return g.get(u) ? [...g.get(u)] : []; } function removeEdge(u, v, directed = false) { if (g.has(u)) g.get(u).delete(v); if (!directed && g.has(v)) g.get(v).delete(u); } // Example usage addEdge(0, 1); addEdge(0, 2); console.log(hasEdge(0, 1)); // true console.log(neighbors(0)); // [1, 2] ``` ``` // Weighted graph: Map<number, Map<number, number>> const wg = new Map(); function addWeightedEdge(u, v, w, directed = false) { if (!wg.has(u)) wg.set(u, new Map()); if (!wg.has(v)) wg.set(v, new Map()); wg.get(u).set(v, w); if (!directed) wg.get(v).set(u, w); } function weight(u, v) { return wg.has(u) ? wg.get(u).get(v) : undefined; // undefined = no edge } addWeightedEdge(1, 3, 2.5); console.log(weight(1, 3)); // 2.5 ``` ### Edge List We store an array of edges, each edge a pair (u, v) or a triple (u, v, w). A simple format for data exchange and certain algorithms (for example, sorting by weight for Kruskal's). - Memory: O(E). - Edge check: O(E), without building indexes. - Iterating over neighbors requires indexing/grouping. ``` // Edge list: [[u, v, w?]] const edges = [ [0, 1], [0, 2], [2, 3] ]; // For fast neighbor access, group into an adjacency list on load const adj = new Map(); for (const [u, v] of edges) { if (!adj.has(u)) adj.set(u, []); adj.get(u).push(v); } console.log(adj.get(0)); // [1, 2] ``` ### CSR/CSC (Compressed Sparse Row/Column) Compressed structures for sparse graphs (by analogy with sparse matrices). CSR stores all of a vertex's neighbors consecutively in a single array plus an offsets array for fast access. - Memory: close to O(V+E), good memory locality. - Ideal for traversals and computations on large graphs. Updates (adding/removing) are inconvenient, a rebuild is required. ``` // Building CSR from an edge list function buildCSR(n, edges, directed = false) { const tmp = Array.from({ length: n }, () => []); for (const [u, v] of edges) { tmp[u].push(v); if (!directed) tmp[v].push(u); } const offsets = new Uint32Array(n + 1); let total = 0; for (let i = 0; i < n; i++) { offsets[i] = total; total += tmp[i].length; } offsets[n] = total; const neighbors = new Uint32Array(total); let idx = 0; for (let i = 0; i < n; i++) { for (const v of tmp[i]) neighbors[idx++] = v; } return { offsets, neighbors }; } function neighborsCSR(csr, u) { const { offsets, neighbors } = csr; return neighbors.subarray(offsets[u], offsets[u + 1]); } const csr = buildCSR(5, [[0,1],[0,2],[2,3]]); console.log([...neighborsCSR(csr, 0)]); // [1, 2] ``` ### Incidence matrix and other variants - Incidence matrix: V×E, useful for certain theoretical/linear-algebra problems. Memory O(V·E), rarely used in practice for large graphs. - Object model (nodes/edges as objects with references): convenient for complex attributes, but more expensive in memory and cache. ### Comparison by operations (averaged) - Memory: matrix O(V^2) > adjacency list O(V+E) ≈ CSR O(V+E) > edge list O(E) (without indexes). - Edge check u→v: matrix O(1), adjacency list O(deg(u)) or O(1) with a Set/Map, CSR O(log deg(u)) with binary search (if sorted) or O(deg(u)). - Neighbor iteration: adjacency list/CSR O(deg(u)), faster and more cache-friendly, matrix O(V). - Updates: adjacency list, cheap; matrix, cheap for edges, expensive for vertices; CSR, expensive (rebuild). ### How to choose a structure - Dense graph (many edges) with edge-existence checks that matter: adjacency matrix. - Sparse graph, frequent traversals and updates: adjacency list (Set/Map). - Very large sparse graph, few or no updates, traversal performance matters: CSR/CSC. - Simple storage/exchange: edge list. ### Specifics: directed, weighted, multigraphs - Directed: store only u→v (do not duplicate v→u). In the matrix, elements are asymmetric. - Weighted: numeric weights in the matrix, a Map<neighbor, weight> or an array of { to, w } objects in the adjacency list. - Multigraphs: multiple edges are allowed. In the adjacency list use an array (not a Set), or keep a count/list of edges between a pair of vertices. - Loops (u=u): supported by all structures; diagonal elements in the matrix. ### Example: a typical graph for web tasks (BFS/DFS traversals) ``` // Adjacency list with a Set: convenient and fast for checks const G = new Map(); const dir = false; // undirected function v(u){ if(!G.has(u)) G.set(u, new Set()); } function add(u,v){ v(u); v(v); G.get(u).add(v); if(!dir) G.get(v).add(u); } // Build a small graph add(0,1); add(0,2); add(1,3); add(2,3); add(3,4); function bfs(start){ const q = [start], seen = new Set([start]), order = []; while(q.length){ const u = q.shift(); order.push(u); for(const v of G.get(u) || []) if(!seen.has(v)){ seen.add(v); q.push(v); } } return order; } function dfs(start){ const st = [start], seen = new Set(), order = []; while(st.length){ const u = st.pop(); if(seen.has(u)) continue; seen.add(u); order.push(u); for(const v of G.get(u) || []) if(!seen.has(v)) st.push(v); } return order; } console.log('BFS:', bfs(0)); // for example, [0,1,2,3,4] console.log('DFS:', dfs(0)); ``` ### Summary - There is no single correct representation: choose it for the task and the constraints. - Matrix, for dense graphs and frequent edge checks; adjacency list, a universal and dynamic choice; edge list, a simple data format; CSR/CSC, for large sparse graphs and high-performance traversal.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.