/*
* Solution to the "Imperial Messengers" problem by Anish Laxman Ramaswamy.
*
* -------------------------------------------------------------------------------------------
*
* We can consider this as a shortest path problem. Each city represents a node in a
* graph, and each entry in the adjacency matrix given as input describes an edge. A
* number in the adjacency matrix (which denotes the time taken to travel between those
* cities) will denote the weight of that edge. An 'x' in the adjacency matrix would mean
* that there is no edge connecting those respective nodes.
*
* Thus, to find the time taken for the message to reach all the cities, we can run Dijkstra's
* algorithm on the graph of cities. This will give us the shortest paths from the capitol
* city to all the other cities. Then, the minimum time it would take for the message to
* reach all the cities will be the maximum of the path lengths from the capitol to every
* other city. This would work only because we are working under the assumption that there
* are no limits upon the number of messengers available at each city.
*
* For example, given the graph of the empire as follows (where city #1 is the capitol city):
*
* 1
* / \
* 1 / \ 3
* / \
* 2 ----- 3
* 1
*
* Dijkstra's algorithm will give us the shortest path from:
* - City #1 to city #2 = 1
* - City #1 to city #3 = 2
*
* Thus, the minimum time it will take for the message to reach all the cities will be the
* maximum of (1, 2) = 2.
*
* -------------------------------------------------------------------------------------------
*
* This solution can be broken down into the following steps:
*
* 1. Take the adjacency matrix as input, and store it.
* 2. Run Dijkstra's algorithm on the graph represented by the stored matrix.
* 3. Running step #2 would have resulted in path lengths being found for all nodes from
* the source.
* 4. Find the maximum of all the path lengths found in step #3 above. This is the answer.
*
* -------------------------------------------------------------------------------------------
*
* Time efficiency:
*
* It is common knowledge that Dijkstra's algorithm takes O(|E| + |V| log(V)) time. Given an
* adjacency matrix, this algorithm will take O(|E| + |V| log(V) + |V|) time where the last
* extra element comes from step #4 above. Thus, this algorithm takes O(|E| + |V| log(V)) time.
*
* -------------------------------------------------------------------------------------------
*
* Assumption: I am assuming that a time value in the inputted adjacency matrix cannot be
* negative, as that wouldn't really make sense given this problem because you cannot lose
* time or go back into the past while traveling from one city to another. (Dijkstra's
* algorithm would also fail if negative times were allowed.)
*
* Notes:
*
* 1. Typically, I would separate declaration from implementation by defining all
* publicly visible data structures in a header file. However, the question's PDF
* mentioned I should submit "a .c file", so I'm putting everything in here.
*
* 2. I would also have separated related implementations into separate .c files had it
* not been for the instructions in the PDF to submit "a .c file".
*
* 3. The adjacency matrix will only store integers. Whenever an 'x' is encountered in the
* input, we can simply store a negative value (say '-1') to denote that this edge does
* exist.
*
* -------------------------------------------------------------------------------------------
*
* Alternative algorithm: I had also considered finding the minimum spanning tree (MST) of
* the given graph. Since a minimum spanning tree would give me a tree where the total edge
* weight is the minimum compared to other such trees, and yet include all the nodes. After
* finding this MST, I initially thought of just traversing from the source to every leaf node,
* incrementing a path length. Once I reached a child node, I may have the minimum time it takes
* for the message to be spread throughout the graph.
*
* However, I quickly determined that this would not work for even the sample input provided.
* For example, using the above method, I would get a tree which is connected as follows:
*
* #1 ---10---> #5 ---10---> #4 ---20---> #2 ---5---> #3 ---> NULL
* ^ ^
* | |
* \_____ _____/
* |
* represent edge weights
*
* Following the above MST-based algorithm, I would wind up with path lengths as follows:
*
* #1 - #5 = 10
* #1 - #4 = 20
* #1 - #2 = 40
* #1 - #3 = 45
*
* The above would give us a minimum time of 45 to spread the message, which is incorrect.
*
* -------------------------------------------------------------------------------------------
*
* Time spent on this solution can be broken down as follows:
*
* 1. Designing the algorithm = 0.5 hours
*
* Coding each individual module
* 2. (i.e. the input module, = 0.75 hours
* Dijkstra's algorithm, etc.)
*
* 3. Integrating everything + testing = 0.25 hours
*
* 4. Documenting the code = 0.5 hours
*
* TOTAL = 2.0 hours (approx.)
*
* -------------------------------------------------------------------------------------------
*/
#include <stdio.h> // For all the IO
#include <string.h> // For string functions
#include <stdint.h> // For SIZE_MAX
#include <stdlib.h> // For all the dynamic memory stuff
#include <assert.h> // For 'assert's
#include <stdbool.h> // To use 'bool', 'true' and 'false'
/*
* By the problem definition. The following was made a preprocessor definition to make life
* easy if it was ever needed that some other city must be the capitol city.
*/
#define CAPITOL_CITY_NUMBER 1
/*
* We can define an invalid (or non-existent) edge to have a value of '-1' due to the assumption
* made above that all negative valued edges are not possible in this problem.
*/
#define INVALID_EDGE -1
// Define an adjacency matrix
typedef struct
{
int **matrix;
size_t size;
} ADJACENCY_MATRIX;
// Allocates memory for the adjacency matrix
void initializeAdjacencyMatrix(ADJACENCY_MATRIX *adjacencyMatrix)
{
assert(adjacencyMatrix
!= NULL
);
if (adjacencyMatrix->size <= 0)
{
return;
}
/*
* We know the following constraints on the adjacency matrix as specified by the problem
* statement:
* 1. matrix[i][i] = 0 for 1 <= i <= n
* 2. matrix[i][j] = matrix[j][i]
*
* Thus, it would be sufficient to allocate memory for a lower triangular matrix.
*/
size_t i = 0;
adjacencyMatrix
->matrix
= calloc(adjacencyMatrix
->size
, sizeof(*adjacencyMatrix
)); if (!adjacencyMatrix->matrix)
{
}
for (i = 1; i <= adjacencyMatrix->size; i++)
{
adjacencyMatrix
->matrix
[i
] = calloc(i
, sizeof(*(adjacencyMatrix
->matrix
[i
])) + 1); if (!adjacencyMatrix->matrix[i])
{
/*
* There may have been some allocated memory before this point, but we can treat
* this as a fatal error, and crash. So the OS will free up the memory.
*/
}
}
}
// Frees the memory allocated for the adjacency matrix
void freeAdjacencyMatrix(ADJACENCY_MATRIX *adjacencyMatrix)
{
assert(adjacencyMatrix
!= NULL
);
size_t i = 0;
for (i = 1; i <= adjacencyMatrix->size; i++)
{
free(adjacencyMatrix
->matrix
[i
]); adjacencyMatrix->matrix[i] = NULL;
}
free(adjacencyMatrix
->matrix
); adjacencyMatrix->matrix = NULL;
adjacencyMatrix->size = 0;
}
/*
* Populate the adjacency matrix from 'stdin'. This was made as a function so that
* in the future, if input was ever needed from a file or another source, only this
* function needs to be changed.
*/
void getAdjacencyMatrix(ADJACENCY_MATRIX *adjacencyMatrix)
{
assert(adjacencyMatrix
!= NULL
);
size_t i = 0;
size_t j = 0;
char numberStr[64] = { 0 };
for (i = 1; i <= adjacencyMatrix->size; i++)
{
for (j = 1; j < i; j++)
{
/*
* Read the current number. However, if the input is an 'x', we can
* store 'INVALID_EDGE' in the adjacency matrix to denote that this is
* an invalid (or non-existent) edge (since I earlier made the documented
* assumption that negative valued edges are not allowed).
*
* Ideally, I'd read a line of input using 'fgets' and parse that into
* the numbers required. Doing so would make reading the input safe from
* malicious user input. But I didn't want to waste time doing something
* that isn't directly related to the problem at hand.
*/
if (0 == strcmp(numberStr
, "x")) {
adjacencyMatrix->matrix[i][j] = INVALID_EDGE;
}
else
{
adjacencyMatrix
->matrix
[i
][j
] = atoi(numberStr
);
/*
* The following is just a sanity check. I've decided to assert this
* since I've made the documented assumption that negative values make
* no sense for this particular problem earlier.
*/
assert(adjacencyMatrix
->matrix
[i
][j
] >= 0); }
}
}
}
/*
* Using Dijkstra's algorithm, we find and store the shortest paths from the given
* source node, to all other nodes.
*
* I recreated Dijkstra's algorithm, since it was quite simple to remember. Basically,
* we scan every node exactly once while updating its neighbors with the current path
* length plus the weight of the edge leading to it ONLY if that sum is lesser than
* the value it already holds. Once we have scanned all the nodes, the remaining
* numbers stored at each node will hold the shortest path length to that node from
* the source node.
*
* This function returns an array where each element will hold a value for the shortest
* path from the source to that element (barring the 0th element, which we do not consider).
*/
size_t *findShortestPathsToAllNodes(const ADJACENCY_MATRIX adjacencyMatrix, const size_t source,
size_t *pathLengths)
{
if (adjacencyMatrix.size == 0)
{
// Nothing to do
return NULL;
}
assert(adjacencyMatrix.
matrix != NULL
); assert(source
> 0 && source
<= adjacencyMatrix.
size);
size_t i = 0;
size_t currNode = 0; /*
* Current node whose neighbors are being examined in each
* iteration of the algorithm.
*/
size_t currMinLength = 0; // Stores the minimum length in each iteration of the algorithm
bool *visited = NULL; // Storage for whether a node has been visited or not
visited
= malloc((adjacencyMatrix.
size + 1) * sizeof(*visited
)); if (!visited)
{
/*
* Fatally crash since there isn't much else we can do. All the memory allocated
* until this point should be taken care of by the OS.
*/
}
// Initially, all the nodes have not been visited, and we set all the path lengths to a maximum value
for (i = 1; i <= adjacencyMatrix.size; i++)
{
visited[i] = false;
pathLengths[i] = SIZE_MAX; // For all practical purposes, infinity
}
// The shortest path from the source to itself is 0 by definition
pathLengths[source] = 0;
// Start at the source
currNode = source;
/*
* Loop through all unvisited nodes. The first time when 'visited[currNode]' is false will
* denote the time when we have visited all the nodes. This is because 'currNode' will not
* have been updated by the last nested loop inside the following loop.
*/
while (visited[currNode] == false)
{
visited[currNode] = true;
// Loop through all the current node's possible neighbors
for (i = 1; i <= adjacencyMatrix.size; i++)
{
if (i == currNode)
{
// Node 'i' is definitely not a neighbor to 'currNode', so ignore
continue;
}
size_t possibleLengthToNodeI = 0;
/*
* Since our matrix is lower triangular, it will not have valid values for matrix[i][j]
* whenever 'i > j'. However, by definition matrix[i][j] = matrix[j][i], and in the cases
* when 'i > j', matrix[j][i] will have valid values.
*/
if (currNode < i)
{
// If the edge doesn't exist, move on
if (adjacencyMatrix.matrix[i][currNode] == INVALID_EDGE)
{
continue;
}
possibleLengthToNodeI = pathLengths[currNode] + adjacencyMatrix.matrix[i][currNode];
}
else
{
// If the edge doesn't exist, move on
if (adjacencyMatrix.matrix[currNode][i] == INVALID_EDGE)
{
continue;
}
possibleLengthToNodeI = pathLengths[currNode] + adjacencyMatrix.matrix[currNode][i];
}
/*
* If we find that adding up the current path length seen until 'currNode' and the edge
* weight from 'currNode' to its neighbor 'i' is actually lesser than the currently stored
* path length from the source node to node 'i', update it!
*/
if (possibleLengthToNodeI < pathLengths[i])
{
pathLengths[i] = possibleLengthToNodeI;
}
}
/*
* Now we need to pick our next node such that it has the least seen path length thus far.
* Additionally, this next node has to not have been visited.
*/
currMinLength = SIZE_MAX;
for (i = 1; i <= adjacencyMatrix.size; i++)
{
if (!visited[i] && pathLengths[i] < currMinLength)
{
currMinLength = pathLengths[i];
currNode = i;
}
}
}
visited = NULL;
return pathLengths;
}
// Prints the maximum value in the given array
void printMaximumValue(const size_t *array, const size_t size)
{
if (!array || size <= 0)
{
// Nothing to do
return;
}
size_t i = 0;
size_t maxValue = 0;
maxValue = array[1];
for (i = 2; i <= size; i++)
{
if (array[i] > maxValue)
{
maxValue = array[i];
}
}
}
int main(void)
{
size_t *shortestPathLengths = NULL;
ADJACENCY_MATRIX adjacencyMatrix = { 0 };
/*
* First get the number of nodes. 'scanf' is not really safe. Ideally, I'd
* read a line of input using 'fgets' and parse that into the numbers required.
* But I didn't want to waste time doing something that isn't directly related
* to the problem at hand.
*/
scanf("%u", &(adjacencyMatrix.
size));
// Allocate memory for the adjacency matrix
initializeAdjacencyMatrix(&adjacencyMatrix);
// Read the adjacency matrix from input
getAdjacencyMatrix(&adjacencyMatrix);
// Find the shortest path from the capitol city node to all other nodes
shortestPathLengths
= calloc(adjacencyMatrix.
size + 1, sizeof(*shortestPathLengths
)); if (!shortestPathLengths)
{
/*
* Fatally crash since there isn't much else we can do. All the memory allocated
* until this point should be taken care of by the OS.
*/
}
shortestPathLengths = findShortestPathsToAllNodes(adjacencyMatrix,
CAPITOL_CITY_NUMBER,
shortestPathLengths);
/*
* After we have all the shortest paths, we just need to find the maximum element
* in that shortest path array.
*/
printMaximumValue(shortestPathLengths, adjacencyMatrix.size);
/*
* Free up allocated memory. This step may not actually be necessary here if this
* code will surely reside on its own. This is because in that case, it is better
* just return instead of freeing up memory since the OS is going to do it anyway.
* However, if this gets introduced as a module in some larger code-base, then we
* have a resource leak on our hands! Yikes...
*/
freeAdjacencyMatrix(&adjacencyMatrix);
free(shortestPathLengths
); shortestPathLengths = NULL;
return 0;
}