Graph · 41 of 43
Number of Triangles in a Directed and Undirected Graph
Count the groups of three kids who are all friends with each other, or the three-way loops of one-way arrows.
- Simple way
- time O(V^3)
room O(V^2) - Clever way
- time O(E * sqrt(E))
room O(V + E)
The problem, simply
You get a graph: dots joined by lines. A triangle is three dots where every pair of them is joined. Count how many triangles there are.
There are two versions. With two-way lines (undirected), any three dots that are all joined count once. With one-way arrows (directed), a triangle is a loop a > b > c > a, and each loop counts once.
A story to remember it
Picture it
Five kids. The lines are friendships. There are three trios: 0, 1, 2 and 1, 2, 3 and 2, 3, 4. The badges show how many friends each kid has.
Examples
A strip of triangles
- In
undirected, V = 5, edges = 0-1, 0-2, 1-2, 1-3, 2-3, 3-4, 2-4- Out
3
The trios are 0 1 2, then 1 2 3, then 2 3 4.
Everyone knows everyone
- In
undirected, V = 4, all 6 edges- Out
4
Any 3 of the 4 dots form a triangle, and there are 4 ways to choose 3 out of 4.
One-way arrows
- In
directed, V = 4, edges = 0>1, 1>2, 2>0, 0>3, 3>1- Out
1
Only 0 to 1 to 2 and back to 0 is a loop. The trio 0, 3, 1 is joined, but the arrows 0>3, 3>1 and 0>1 do not go round in a circle.
A square has no triangles
- In
undirected, V = 4, edges = 0-1, 1-2, 2-3, 3-0- Out
0
Every dot has two friends, but those two friends never know each other.
Way 1: the simple way
Try every group of three dots and check the lines between them. Store the lines in a table (an adjacency matrix) so each check is instant.
- Undirected: for every
i < j < k, ifi-j,j-kandi-kall exist, count one. - Directed: for every ordered
i, j, kof different dots, ifi>j,j>kandk>iall exist, count one. Each loop is found 3 times (starting at each of its corners), so divide by 3.
class TrianglesBrute {
public static int countUndirected(int[][] adj) {
int n = adj.length, count = 0;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
for (int k = j + 1; k < n; k++)
if (adj[i][j] == 1 && adj[j][k] == 1 && adj[i][k] == 1) count++;
return count;
}
public static int countDirected(int[][] adj) {
int n = adj.length, count = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
if (i != j && j != k && i != k
&& adj[i][j] == 1 && adj[j][k] == 1 && adj[k][i] == 1) count++;
return count / 3; // each loop is seen from all 3 corners
}
}How long it takes: three nested loops over the dots, O(V^3). Extra room: the V by V table, O(V^2). For a big graph with few lines, almost all of that work checks pairs that are not even friends.
Way 2: the clever way
Use the shyest kid rule. Order the dots by how many lines they have (their degree), breaking ties by number. Turn every line into an arrow that points from the less popular end to the more popular end. Now every triangle has exactly one corner with two arrows going out, its shyest corner, so it is counted once.
- Count each dot's lines.
- Point every line toward the more popular end.
- For each dot
u: put a sticker on every dot its arrows point to. - For each such dot
v, followv's arrows. Every dotwyou reach that hasu's sticker closes a triangleu, v, w.
Each trio is counted by its shyest corner
01 / 07
Count friends. 0 and 4 have 2, kids 1 and 3 have 3, kid 2 has 4. From shyest to most popular: 0, 4, 1, 3, 2.
- triangles
- 0
- looking now
- checking
- done
- answer
- rejected
- ignored
For one-way arrows the order trick does not apply, because the arrows already have a direction. Instead, for every arrow a > b, look at each arrow b > c and check whether c > a exists, using a set for each dot. Each loop is found once from each of its 3 arrows, so divide by 3.
class Triangles {
// Undirected: each triangle is counted once, by its shyest corner
public static long countUndirected(int n, int[][] edges) {
int[] deg = new int[n];
for (int[] e : edges) { deg[e[0]]++; deg[e[1]]++; }
List<List<Integer>> up = new ArrayList<>();
for (int i = 0; i < n; i++) up.add(new ArrayList<>());
for (int[] e : edges) {
int a = e[0], b = e[1];
boolean aFirst = deg[a] < deg[b] || (deg[a] == deg[b] && a < b);
if (aFirst) up.get(a).add(b); else up.get(b).add(a); // point to the more popular end
}
int[] sticker = new int[n];
Arrays.fill(sticker, -1);
long count = 0;
for (int u = 0; u < n; u++) {
for (int v : up.get(u)) sticker[v] = u; // stamp u's friends
for (int v : up.get(u))
for (int w : up.get(v))
if (sticker[w] == u) count++; // u, v, w all know each other
}
return count;
}
// Directed: count loops a > b > c > a
public static long countDirected(int n, int[][] edges) {
List<Set<Integer>> out = new ArrayList<>();
for (int i = 0; i < n; i++) out.add(new HashSet<>());
for (int[] e : edges) out.get(e[0]).add(e[1]);
long count = 0;
for (int[] e : edges) {
int a = e[0], b = e[1];
if (a == b) continue;
for (int c : out.get(b))
if (c != a && c != b && out.get(c).contains(a)) count++;
}
return count / 3; // found once from each of its 3 arrows
}
}How long it takes: after the arrows point toward the popular end, every dot has at most about sqrt(2E) arrows going out. A dot with more would need that many even more popular friends, each with at least that many lines, which is more lines than exist. So the undirected count is O(E * sqrt(E)). The directed count is O(E * d), where d is the most arrows leaving one dot. Extra room: the lists and sets, O(V + E).
Another way: the matrix trick
Multiply the adjacency matrix A by itself three times. The number on the diagonal at A^3[i][i] counts the walks of 3 steps that leave dot i and come back to it. In an undirected graph each triangle gives 6 such walks (3 starting corners times 2 directions), so the answer is the diagonal sum divided by 6. In a directed graph each loop gives 3, so divide by 3. This is the classic textbook answer, but it costs O(V^3) with plain multiplication.
class TrianglesMatrix {
public static int count(int[][] adj, boolean directed) {
int n = adj.length;
int[][] a3 = multiply(multiply(adj, adj), adj);
int trace = 0;
for (int i = 0; i < n; i++) trace += a3[i][i]; // walks that come home after 3 steps
return directed ? trace / 3 : trace / 6;
}
static int[][] multiply(int[][] a, int[][] b) {
int n = a.length;
int[][] c = new int[n][n];
for (int i = 0; i < n; i++)
for (int k = 0; k < n; k++)
if (a[i][k] != 0)
for (int j = 0; j < n; j++) c[i][j] += a[i][k] * b[k][j];
return c;
}
}Watch out for
- In the directed version, three dots joined by arrows are not always a loop.
0>1,1>2,0>2is not a triangle there. - Self loops and repeated lines. Clean the input first, or they create fake triangles.
- Use
longfor the count. A graph with many lines can have far more triangles than anintholds. - Fewer than 3 dots means 0 triangles.
What interviewers ask next
- Can you count triangles that touch one given dot? Mark its friends, then count lines between two marked friends.
- How would you do it on a graph too big for one machine? Split the lines by their shyest corner and count each part separately; the degree order keeps the parts balanced.
- Is there anything faster than
O(V^3)for dense graphs? Fast matrix multiplication beats it in theory, and bitsets (64 friends per machine word) make the brute force much faster in practice. - What about the clustering of a social network? It is the number of triangles compared with the number of paths of length 2, so you need exactly this count.
Remember this
Grown-up words: the friendship table is an adjacency matrix, the number of friends is the degree, pointing lines toward the popular end is a degree ordering, the stickers are a marker array, and the diagonal sum of A^3 is the trace.