-
Book Overview & Buying
-
Table Of Contents
-
Feedback & Rating
C++ Data Structures and Algorithm Design Principles
By :
We can solve this activity using the exact algorithm from Exercise 33, Implementing the Bellman-Ford Algorithm (Part II). The potential pitfalls here are related to correctly interpreting the required task and representing the graph within the context of the problem you are actually trying to solve. Let's get started:
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
struct Edge
{
int start;
int end;
int weight;
Edge(int s, int e, int w) : start(s), end(e), weight(w) {}
};
const int UNKNOWN = INT_MAX;
vector<Edge*> edges;
int main()
{
int N;
cin >> N;
for(int i = 0; i < N * N - 1; i++)
{
string directions;
int power;
cin >> directions >> power;
……
}
return 0;
}
CURRENT CELL: (x, y) —> i
NORTH: (x, y - 1) —> i - N
SOUTH: (x, y + 1) —> i + N
EAST: (x + 1, y) —> i + 1
WEST: (x - 1, y) —> i - 1
for(int i = 0; i < N * N - 1; i++)
{
string directions;
int power;
cin >> directions >> power;
int next;
for(auto d : directions)
{
switch(d)
{
case 'N': next = i - N; break;
case 'S': next = i + N; break;
case 'E': next = i + 1; break;
case 'W': next = i - 1; break;
}
……
}
}
for(auto d : directions)
{
switch(d)
{
……
}
// Add edge with power variable's sign reversed
edges.push_back(new Edge(i, next, -power));
}
int BellmanFord(int N, vector<Edge*> edges)
{
vector<int> distance(N, UNKNOWN);
// Starting node is always index 0
distance[0] = 0;
for(int i = 0; i < N - 1; i++)
{
for(auto edge : edges)
{
if(distance[edge->start] == UNKNOWN)
{
continue;
}
if(distance[edge->start] + edge->weight < distance[edge->end])
{
distance[edge->end] = distance[edge->start] + edge->weight;
}
}
}
……
}
// Check for negative cycles
for(auto edge : edges)
{
if(distance[edge->start] == UNKNOWN)
{
continue;
}
if(distance[edge->start] + edge->weight < distance[edge->end])
{
return UNKNOWN;
}
}
return distance[N];
int result = BellmanFord(N * N, edges);
(result == UNKNOWN) ? cout << "ABORT TRAVERSAL" << endl
: cout << -1 * result << endl;
return 0;
In this activity, we will generate randomized graphs for interview tests as described in the activity brief. Follow these steps to complete the activity:
#include <iostream>
#include <vector>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <utility>
using namespace std;
const int UNKNOWN = 1e9;
struct Edge
{
int u;
int v;
int w;
Edge(int u, int v, int w)
: u(u), v(v), w(w) {}
};
struct Graph
{
int V, E;
int maxWeight = -1e9;
vector<Edge> edges;
vector<vector<int>> adj;
vector<vector<int>> weight;
Graph(int v, int e) : V(v), E(e)
{
...
}
};
Graph(int v, int e) : V(v), E(e)
{
vector<vector<bool>> used(V, vector<bool>(V, false));
adj.resize(V);
weight.resize(V, vector<int>(V, UNKNOWN));
while(e)
{
// Generate edge values
int u = rand() % V;
int v = rand() % V;
int w = rand() % 100;
if(rand() % 3 == 0)
{
w = -w;
}
// Check if the edge is valid
if(u == v || used[u][v])
{
continue;
}
// Add to edges and mark as used
edges.push_back(Edge(u, v, w));
adj[u].push_back(v);
weight[u][v] = w;
maxWeight = max(maxWeight, w);
used[u][u] = used[v][v] = used[u][v] = used[v][u] = true;
e--;
}
for(int i = 0; i < V; i++)
{
// Set V to -1 to indicate the graph is invalid
if(!used[i][i])
{
V = -1;
break;
}
}
}
enum RESULT
{
VALID,
INVALID,
INTERESTING
};
int main()
{
unsigned int seed;
int iterations, V, E;
cin >> seed;
cin >> iterations;
cin >> V >> E;
int invalid = 0;
int valid = 0;
int interesting = 0;
srand(seed);
while(iterations--)
{
Graph G(V, E);
switch(TestGraph(G))
{
case INVALID: invalid++; break;
case VALID: valid++; break;
case INTERESTING:
{
valid++;
interesting++;
break;
}
}
}
return 0;
}
RESULT TestGraph(Graph G)
{
if(G.V == -1)
{
return INVALID;
}
vector<int> distance = BellmanFord(G);
……
}
vector<int> BellmanFord(Graph G)
{
vector<int> distance(G.V + 1, UNKNOWN);
int s = G.V;
for(int i = 0; i < G.V; i++)
{
G.edges.push_back(Edge(s, i, 0));
}
distance[s] = 0;
for(int i = 0; i < G.V; i++)
{
for(auto edge : G.edges)
{
if(distance[edge.u] == UNKNOWN)
{
continue;
}
if(distance[edge.u] + edge.w < distance[edge.v])
{
distance[edge.v] = distance[edge.u] + edge.w;
}
}
}
for(auto edge : G.edges)
{
if(distance[edge.u] == UNKNOWN)
{
continue;
}
if(distance[edge.u] + edge.w < distance[edge.v])
{
return {};
}
}
return distance;
}
RESULT TestGraph(Graph G)
{
if(G.V == -1)
{
return INVALID;
}
vector<int> distance = BellmanFord(G);
if(distance.empty())
{
return VALID;
}
for(auto edge : G.edges)
{
G.weight[edge.u][edge.v] += (distance[edge.u] – distance[edge.v]);
}
double result = 0;
for(int i = 0; i < G.V; i++)
{
vector<int> shortest = Dijkstra(i, G);
}
}
vector<int> Dijkstra(int source, Graph G)
{
typedef pair<int, int> State;
priority_queue<State, vector<State>, greater<State>> Q;
vector<bool> visited(G.V, false);
vector<int> distance(G.V, UNKNOWN);
Q.push({0, source});
distance[source] = 0;
while(!Q.empty())
{
State top = Q.top();
Q.pop();
int node = top.second;
int dist = top.first;
visited[node] = true;
for(auto next : G.adj[node])
{
if(visited[next])
{
continue;
}
if(dist != UNKNOWN && distance[next] > dist + G.weight[node][next])
{
distance[next] = distance[node] + G.weight[node][next];
Q.push({distance[next], next});
}
}
}
return distance;
}
double result = 0;
for(int i = 0; i < G.V; i++)
{
vector<int> shortest = Dijkstra(i, G);
double average = 0;
int count = 0;
for(int j = 0; j < G.V; j++)
{
if(i == j || shortest[j] == UNKNOWN)
{
continue;
}
shortest[j] += (distance[j] – distance[i]);
average += shortest[j];
count++;
}
average = average / count;
result += average;
}
result = result / G.V;
double ratio = result / G.maxWeight;
return (ratio < 0.5) ? INTERESTING : VALID;
double percentInteresting = (double)interesting / valid * 100;
cout << "INVALID GRAPHS: " << invalid << endl;
cout << "PERCENT INTERESTING: " << fixed << setprecision(2) << percentInteresting << endl;
return 0;
The entire activity conforms fairly closely to the standard implementations of the algorithms we've discussed in this chapter, but with a few slight modifications.
The terms that were used in the problem description, that is, maze, rooms, teleporters, and points could, of course, just as easily have been called graph, vertices, edges, and edge weights. The condition in which a player is able to infinitely reduce their score can be redefined as a negative weight cycle. Follow these steps to complete the activity:
#include <iostream>
#include <vector>
#include <stack>
#include <climits>
struct Edge
{
int start;
int end;
int weight;
Edge(int s, int e, int w) : start(s), end(e), weight(w) {}
}
const int UNKNOWN = INT_MAX;
vector<Edge*> edges; // Collection of edge pointers
int main()
{
int V, E;
cin >> V >> E;
vector<Edge*> edges;
vector<vector<int>> adj(V + 1);
for(int i = 0; i < E; i++)
{
int u, v, w;
cin >> u >> v >> w;
edges.push_back(new Edge(u, v, w));
adj[u].push_back(v);
}
vector<int> results;
int BellmanFord(int V, int start, vector<Edge*> edges)
{
// Standard Bellman-Ford implementation
vector<int> distance(V, UNKNOWN);
distance[start] = 0;
for(int i = 0; i < V - 1; i++)
{
for(auto edge : edges)
{
if(distance[edge->start] == UNKNOWN)
{
continue;
}
if(distance[edge->start] + edge->weight < distance[edge->end])
{
distance[edge->end] = distance[edge->start] + edge->weight;
}
}
}
// Return UNKNOWN if a negative cycle is found
if(HasNegativeCycle(distance, edges))
{
return UNKNOWN;
}
int result = UNKNOWN;
for(int i = 0; i < V; i++)
{
if(i == start) continue;
result = min(result, distance[i]);
}
return result;
}
vector<int> results;
for(int i = 0; i < V; i++)
{
if(adj[i].empty())
{
results.push_back(UNKNOWN);
continue;
}
int shortest = BellmanFord(V, i, edges);
if(shortest == UNKNOWN)
{
cout << "INVALID MAZE" << endl;
return 0;
}
results.push_back(shortest);
}
for(int i = 0; i < V; i++)
{
cout << i << ": ";
(results[i] == INVALID) ? cout << "DEAD END" << endl : cout << results[i] << endl;
}
To accomplish this, we will declare two new vectors: one of type bool, named isStuck and another of type int, named inComponent. inComponent will store the index of the component each node belongs to, while isStuck will tell us whether or not the component with index i is cut off from the rest of the graph.
For the sake of simplicity, let's declare the new variables globally:
vector<bool> isStuck;
vector<int> inComponent;
int componentIndex;
Here, we can really begin to appreciate the benefits of encapsulation and object-oriented implementations of graph structures. Having to pass such a large amount of data between our functions is not only difficult to keep track of mentally, but it greatly complicates any kind of modifications we may want to make in the future (to say nothing about the headache-inducing appearance of a function call such as GetComponent(node, adj, visited, component, isStuck, inComponent, componentIndex). For the sake of example and readability, we opt to declare this data globally, but this sort of approach is highly recommended against within the context of an actual full-scale application.
isStuck.resize(V, true);
inComponent.resize(V, UNKNOWN);
componentIndex = 0;
while(!stack.empty())
{
int node = stack.top();
stack.pop();
if(!visited[node])
{
vector<int> component;
GetComponent(node, transpose, visited, component);
components.push_back(component);
componentIndex++;
}
}
component.push_back(node);
visited[node] = true;
inComponent[node] = componentIndex;
for(auto next : adj[node])
{
if(!visited[next])
{
GetComponent(next, visited, adj, component);
}
else if(inComponent[node] != inComponent[next])
{
isStuck[inComponent[next]] = false;
}
}
Essentially, we are checking to see whether each previously visited neighbor's component matches the current node's component. If their respective component IDs are different, we can conclude that the neighbor's component has a path that extends to other parts of the graph.
You may be wondering why, in a directed graph, the existence of an edge from the current node indicates that the neighboring node has an outgoing path outside of its own component. The reason this logic seems 'backward' is because it is. Remember that we are traversing the transform of the original graph, so the directions between adjacencies are all reversed!
auto components = Kosaraju(V, adj);
for(int i = 0; i < components.size(); i++)
{
if(isStuck[i])
{
for(auto node : components[i])
{
cout << node << " ";
}
cout << endl;
}
}
return 0;
Change the font size
Change margin width
Change background colour