An adjacency matrix represents a graph as a square table A of size n × n (with n = |V|), where each cell A[u][v] indicates whether an edge from u to v exists (and optionally stores its weight). It provides constant-time membership tests and a numerics-friendly layout at the cost of Θ(n²) memory.
What “dense” means here
If the expected number of edges m is on the order of n² (or large enough that bucket iteration dominates), a matrix can simplify logic and speed up edge tests.
Invariants
Directed vs undirected: for undirected graphs enforce symmetryA[u][v] == A[v][u].
Weighted edges: store weights in W[u][v]; use a sentinel (e.g., 0 or ∞) to mean “no edge.”
Simple vs multigraph: matrices typically encode simple graphs; multigraphs need either counts per cell or a hybrid (e.g., per-cell lists of edge ids).
Domain conventions: treat self-loops consistently (A[u][u]) and document whether 0 is a valid weight distinct from “no edge.”
Mask + weight split (alt design): keep M[u][v] ∈ {0,1} for topology and W[u][v] for weights. This avoids sentinel ambiguity when 0 is a valid weight.
Operations
Typical surface API:
hasEdge(u, v) — check membership via direct lookup.
addEdge(u, v, w?) — set A[u][v] (and A[v][u] if undirected).
removeEdge(u, v) — clear the entry to the sentinel.
degree(u) — count non-sentinel entries in row u (out-degree) or column u (in-degree).