So before I explain how to implement this method, it's important to understand the original algorithm.
The original algorithm simply tells us whether a Hamiltonian path exists in a graph. It doesn't tell us how many there are.
HackerEarth has
a page on it actually. So a shoutout to them.
Setup
We will need two 2D arrays for this. Let's use the same graph from before:
Setup the adjacency matrix, sized N × N:
Vertex/Mask |
0 |
1 |
2 |
3 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
0 |
1 |
1 |
2 |
1 |
1 |
0 |
1 |
3 |
0 |
1 |
1 |
0 |
Then, set up the
dp array. This one is sized a little differently, being N × 2
N. This is formatted so the Y-axis is the
vertex, and the X-axis is the
bitmask. I'll get to that.
For now, set all values to 0. Then, for the N vertices in the graph, set dp[i][2
i] to 1.
Vertex/Mask |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
A |
B |
C |
D |
E |
F |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
So this is where we are going to consider how the bitmasks truly work.
If you have counted in binary before, you should know that 2
N combinations of bits can be stored in N bits.
The graph has 4 vertices. So there are 16 possible combinations, and we are counting "i" from 0000 to 1111.
The Algorithm
Now let's fill this table in. Loop through all masks (0 to 2
N), and for each mask, loop through each vertex (0 to N). In C++, it should look as follows:
Code (C++)
for (int i = 0; i < (1 << N); i++) {
for (int j = 0; j < N; j++) {
/* Code */
}
}
Next, check if the j
th bit is set to 1. If so, inside the second loop, let's loop through all of the vertices again (0 to N) with the variable "k".
In C++, you can check if the jth bit is is set to 1 in i by using if ((i & (1 << j))) { }
.
Inside the third loop, you are going to set dp[j][i] to 1 if the following applies:
- The kth bit is set to 1 in i
- Vertex k can go to Vertex j
By the end of the 3 loops, the table will be filled with values. If any of the bits in the last column are 1, the graph has a Hamiltonian path!
Working out a column
Now let's work out a column. We have to work from left to right, but the first 3 aren't going to change. Let's do 3. If i is 3, then it's binary representation is 0011.
This means it accounts for when vertices 1 and 2 have been visited.
Loop j from 0 to N (4).
- i = 3.
-
j = 0. 3 AND 1 = 1. Loop k from 0 to N (4).
-
k = 0.
The first bit in i (3) is 1. Vertex 0 can't go to Vertex 0. Nothing changes.
-
k = 1.
The second bit in i (3) is 1. Vertex 1 can go to Vertex 0. dp[3][0] becomes 1.
-
k = 2.
The third bit in i (3) is 0. Nothing changes.
-
k = 3.
The fourth bit in i (3) is 0. Nothing changes.
-
j = 1. 3 AND 2 = 1. Loop k from 0 to N (4).
-
k = 0.
The first bit in i (3) is 1. Vertex 0 can go to Vertex 1. dp[3][1] becomes 1.
-
k = 1.
The second bit in i (3) is 1. Vertex 1 can't go to Vertex 1. Nothing changes.
-
k = 2.
The third bit in i (3) is 0. Nothing changes.
-
k = 3.
The fourth bit in i (3) is 0. Nothing changes.
-
j = 2. 3 AND 4 = 0. We skip this vertex.
-
j = 3. 3 AND 8 = 0. We skip this vertex.
Now that dp has been adjusted to account for column 3, the table should now look like this:
Vertex/Mask |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
A |
B |
C |
D |
E |
F |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
2 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
I've highlighted the new additions in red. Now we just have to do columns 4 through 2
N...
Final answer
Here is the finished table.
Vertex/Mask |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
A |
B |
C |
D |
E |
F |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
0 |
1 |
0 |
0 |
0 |
1 |
1 |
2 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
1 |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
Remember, if any cell in the last column is 1, the graph has a Hamiltonian path. In this case, all 4 cells are 1. It definitely has at least one.
Observations
The idea of bitDP here is to tell whether a path exists with certain vertices visited.
The
mask, as mentioned earlier, goes from 0x0 (0000) to 0xF (1111).
When we look at a mask, we look at which bits are set to 1.
Those are the vertices we visited for that path. Hence why 0xF consists of paths that involve all 4 vertices being visited.
Furthermore, the Y-axis, being the
vertex tells whether there is a path that exists that ends with that vertex.
Example
Take the mask at 0x7 for example. 7 in binary is 0111, hence the first 3 vertices are visited but the fourth one is left unvisited.
Vertex/Mask |
... |
7 |
... |
0 |
... |
1 |
... |
1 |
... |
1 |
... |
2 |
... |
1 |
... |
3 |
... |
0 |
... |
Let's look at the third row (Vertex 2) of mask 7. It's set to 1.
That means there is a possible path that has only vertices 0, 1, and 2 that also ends at vertex 2.
Based on the table, we can also conclude there are possible paths that involve those same vertices that end at vertex 0 and vertex 1.