The reachability of pages can be represented as a graph:
We always start at page 1 We end on a page with 0 entry. This is a classic shortest path problem. It is solved using the breath first search graph traversal algorithm |
The graph can be represented by an adjacency list:
page[1] = {2, 3, 6} // page[i] = vector of pages reachable from page i
page[2] = {4}
page[3] = {1}
page[4] = {5, 7}
page[5] = {}
page[6] = {5}
page[7] = {}
|
C++ code to read in the input:
vector<int> page[10001]; // page[i] = list of reachable pages
for ( int i = 1; i <= N; i++ )
{
int K;
cin >> K;
for ( int j = 0; j < K; j++ )
{
cin >> x;
page[i].push_back(x);
}
}
|
This is the generic breath first search algorithm:
void bfs()
{
int i, j;
while ( myQueue != empty )
{
i = myQueue.remove(); // Get next node to visit
visited[i] = true; // Mark node i as "visited"
/* ----------------------------------------------------------
We reached node i... Something wonderful may happen at i
---------------------------------------------------------- */
cout << i << endl; // Change accordingly
/* -------------------------------------------------------------
How you find nodes j will depend on how you represent the graph
------------------------------------------------------------- */
for ( each node j that can be reached from node i )
{
if ( ! visited[j] )
{
myQueue.push(j); // Visit node j
} // When you loop back up, myQueue.pop() will visit the node!
}
}
}
|
Adapt the queue operations to C++ syntax:
void bfs()
{
int i, j;
while ( ! myQueue.empty() )
{
i = myQueue.front(); myQueue.pop();
visited[i] = true; // Mark node i as "visited"
/* ----------------------------------------------------------
We reached node i... Something wonderful may happen at i
---------------------------------------------------------- */
cout << i << endl; // Change accordingly
/* -------------------------------------------------------------
How you find nodes j will depend on how you represent the graph
------------------------------------------------------------- */
for ( each node j that can be reached from node i )
{
if ( ! visited[j] )
{
myQueue.push(j); // Visit node j
} // When you loop back up, myQueue.pop() will visit the node!
}
}
}
|
How do we visit the neighbors ??
void bfs()
{
int i, j;
while ( ! myQueue.empty() )
{
i = myQueue.front(); myQueue.pop();
visited[i] = true; // Mark node i as "visited"
/* ----------------------------------------------------------
We reached node i... Something wonderful may happen at i
---------------------------------------------------------- */
cout << i << endl; // Change accordingly
/* -------------------------------------------------------------
How you find nodes j will depend on how you represent the graph
------------------------------------------------------------- */
for ( each node j that can be reached from node i )
{
if ( ! visited[j] )
{
myQueue.push(j); // Visit node j
} // When you loop back up, myQueue.pop() will visit the node!
}
}
}
|
Use the nodes listed in the adjacency list for page[i]:
void bfs()
{
int i, j;
while ( ! myQueue.empty() )
{
i = myQueue.front(); myQueue.pop();
visited[i] = true; // Mark node i as "visited"
/* ----------------------------------------------------------
We reached node i... Something wonderful may happen at i
---------------------------------------------------------- */
cout << i << endl; // Change accordingly
/* -------------------------------------------------------------
How you find nodes j will depend on how you represent the graph
------------------------------------------------------------- */
for ( int j = 0; j < page[i].size(); j++ )
{
if ( ! visited[ page[i][j] ] )
{
myQueue.push( page[i][j] ); // Visit page page[i][j]
} // When you loop back up, myQueue.pop() will visit the node!
}
}
}
|
Demo: CCC/2018/j5-ver1.cc
The output printed by j5-ver1.cc:
|
1
Push(2)
Push(3)
Push(6)
2
Push(4)
3
6
Push(5)
4
Push(5)
Push(7)
5
5
7
|
First, the closest nodes will be visited:
|
1
Push(2)
Push(3)
Push(6)
2
Push(4)
3
6
Push(5)
4
Push(5)
Push(7)
5
5
7
|
First, the next layer of nodes will be visited:
|
1
Push(2)
Push(3)
Push(6)
2
Push(4)
3
6
Push(5)
4
Push(5)
Push(7)
5
5
7
|
We visit
node 5 with
distance = 3
That is the shortest path !!!
We need to maintain
distance information
with each page !!!
Version 2: store page distance in another (parallel) queue distances:
void bfs()
{
int i, j, dist;
while ( ! myQueue.empty() )
{
i = myQueue.front(); myQueue.pop();
dist = distances.front(); distances.pop();
visited[i] = true; // Mark node i as "visited"
/* ----------------------------------------------------------
We reached node i... Something wonderful may happen at i
---------------------------------------------------------- */
cout << i << dist << endl; // Change accordingly
for ( int j = 0; j < page[i].size(); j++ )
{
if ( ! visited[ page[i][j] ] )
{
myQueue.push( page[i][j] ); // Visit page page[i][j]
distances.push( dist + 1 );
} // When you loop back up, myQueue.pop() will visit the node!
}
}
}
|
Demo: CCC/2018/j5-ver2.cc
The output printed by j5-ver2.cc:
|
1(dist = 1)
push(2) 2
push(3) 2
push(6) 2
2(dist = 2)
push(4) 3
3(dist = 2)
6(dist = 2)
push(5) 3
4(dist = 3)
push(5) 4
push(7) 4
5(dist = 3)
5(dist = 4)
7(dist = 4)
|
Notice that the algorithm has found the shortest path length = 3 !!!
void bfs()
{
int j;
while ( ! myQueue.empty() )
{
int i;
int dist;
/* ----------------------------------
Visit next node
---------------------------------- */
i = myQueue.front(); myQueue.pop();
dist = distances.front(); distances.pop();
visited[i] = true; // Mark node i as "visited"
/* ---------------------------------------------------------------
We reached node i... Something wonderful may happen at i
I print node i out - Replace with code that solves your problem
--------------------------------------------------------------- */
cout << ">> " << i << endl; // Debug use
/* --------------------------------------------
Check if page is terminal
-------------------------------------------- */
if ( page[i].size() == 0 ) // Terminal page
{
if ( dist < minPathLen )
{
minPathLen = dist ;
cout << " ** new shortest path length: " << minPathLen << endl;
}
}
/* ---------------------------------------------------------------
Visit pages reachable from page[i] that you have NOT visited
--------------------------------------------------------------- */
for ( int j = 0; j < page[i].size(); j++ )
{
if ( ! visited[ page[i][j] ] )
{
cout << " push(" << page[i][j] << ") " << dist+1 << endl;
myQueue.push( page[i][j] );
distances.push( dist + 1 );
}
}
}
}
|
Demo: CCC/2018/j5.cc