Transitive Closure
How can we make a relation transitive?
Mathematical Definition
is transitive if .
Matrix Representation
We know that the transitive closure of a relation is when each entry of the matrix squared is greater than or equal to those in the original matrix.
Verification
For example, here is how we can verify if a relation is transitive in matrix form:
Since each entry of is greater than or equal to those in , the relation is transitive.
Here is a counter-example:
Since the entry in the last row and last column of is less than the corresponding entry in , the relation is not transitive.
Filling the matrix
But here is how we can make some relation transitive in matrix form:
We have to basically fill a direct path to all nodes reachable from a node.
This would mean in a matrix, we would fill the matrix with 1s where there is a direct path between two nodes.
Above, we have a path from B to C and from A to B, but not from A to C. So we add a direct path from A to C, giving us:
A trick to use to fill this up quickly is, when we are following a path, we will jump to row representing the node we are going to and reflect all that row's 1s to the row we are coming from.
Example: If we are in row A, and we can see a one in column B, we will jump to row B and reflect all 1s in row B to row A. And we will continue this process until we have filled all the 1s as needed.
Warshall's Algorithm
Warshall's algorithm is a method to find the transitive closure of a relation in matrix form in time complexity.
Here is how it works:
- Initialize the matrix with the relation.
- For each node , we will go through all the nodes and and check if there is a path from to and from to . If there is, we will add a direct path from to .
- Repeat step 2 for all nodes.
for (int k = 0; k < n; ++k) { // k is the node we are going through
for (int i = 0; i < n; ++i) { // i is the node we are coming from
for (int j = 0; j < n; ++j) { // j is the node we are going to
adjMatrix[i][j] |= (adjMatrix[i][k] & adjMatrix[k][j]);
}
}
}
Here's a fun optimization we can bring in languages like C and C++ where we have bit-level privileges, let us assume we can encode each row as a single integer. We can then use bitwise operations to reflect the 1s in the row to the row we are coming from.
This would reduce the time complexity to .
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
adjMatrix[i] |= (adjMatrix[i] & adjMatrix[k]);
}
}