-
Notifications
You must be signed in to change notification settings - Fork 19.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Constrained Shortest Path Problem (CSPP) / Shortest Path Problem …
…with Resource Constraints (SPPRC) (#6155)
- Loading branch information
1 parent
4d667e1
commit d4b28b3
Showing
2 changed files
with
341 additions
and
0 deletions.
There are no files selected for viewing
123 changes: 123 additions & 0 deletions
123
src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package com.thealgorithms.graph; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
/** | ||
* This class implements a solution for the Constrained Shortest Path Problem (CSPP). | ||
* also known as Shortest Path Problem with Resource Constraints (SPPRC). | ||
* The goal is to find the shortest path between two nodes while ensuring that | ||
* the resource constraint is not exceeded. | ||
* | ||
* @author <a href="https://github.com/DenizAltunkapan">Deniz Altunkapan</a> | ||
*/ | ||
public class ConstrainedShortestPath { | ||
|
||
/** | ||
* Represents a graph using an adjacency list. | ||
* This graph is designed for the Constrained Shortest Path Problem (CSPP). | ||
*/ | ||
public static class Graph { | ||
|
||
private List<List<Edge>> adjacencyList; | ||
|
||
public Graph(int numNodes) { | ||
adjacencyList = new ArrayList<>(); | ||
for (int i = 0; i < numNodes; i++) { | ||
adjacencyList.add(new ArrayList<>()); | ||
} | ||
} | ||
|
||
/** | ||
* Adds an edge to the graph. | ||
* @param from the starting node | ||
* @param to the ending node | ||
* @param cost the cost of the edge | ||
* @param resource the resource required to traverse the edge | ||
*/ | ||
public void addEdge(int from, int to, int cost, int resource) { | ||
adjacencyList.get(from).add(new Edge(from, to, cost, resource)); | ||
} | ||
|
||
/** | ||
* Gets the edges that are adjacent to a given node. | ||
* @param node the node to get the edges for | ||
* @return the list of edges adjacent to the node | ||
*/ | ||
public List<Edge> getEdges(int node) { | ||
return adjacencyList.get(node); | ||
} | ||
|
||
/** | ||
* Gets the number of nodes in the graph. | ||
* @return the number of nodes | ||
*/ | ||
public int getNumNodes() { | ||
return adjacencyList.size(); | ||
} | ||
|
||
public record Edge(int from, int to, int cost, int resource) { | ||
} | ||
} | ||
|
||
private Graph graph; | ||
private int maxResource; | ||
|
||
/** | ||
* Constructs a CSPSolver with the given graph and maximum resource constraint. | ||
* | ||
* @param graph the graph representing the problem | ||
* @param maxResource the maximum allowable resource | ||
*/ | ||
public ConstrainedShortestPath(Graph graph, int maxResource) { | ||
this.graph = graph; | ||
this.maxResource = maxResource; | ||
} | ||
|
||
/** | ||
* Solves the CSP to find the shortest path from the start node to the target node | ||
* without exceeding the resource constraint. | ||
* | ||
* @param start the starting node | ||
* @param target the target node | ||
* @return the minimum cost to reach the target node within the resource constraint, | ||
* or -1 if no valid path exists | ||
*/ | ||
public int solve(int start, int target) { | ||
int numNodes = graph.getNumNodes(); | ||
int[][] dp = new int[maxResource + 1][numNodes]; | ||
|
||
// Initialize dp table with maximum values | ||
for (int i = 0; i <= maxResource; i++) { | ||
Arrays.fill(dp[i], Integer.MAX_VALUE); | ||
} | ||
dp[0][start] = 0; | ||
|
||
// Dynamic Programming: Iterate over resources and nodes | ||
for (int r = 0; r <= maxResource; r++) { | ||
for (int u = 0; u < numNodes; u++) { | ||
if (dp[r][u] == Integer.MAX_VALUE) { | ||
continue; | ||
} | ||
for (Graph.Edge edge : graph.getEdges(u)) { | ||
int v = edge.to(); | ||
int cost = edge.cost(); | ||
int resource = edge.resource(); | ||
|
||
if (r + resource <= maxResource) { | ||
dp[r + resource][v] = Math.min(dp[r + resource][v], dp[r][u] + cost); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Find the minimum cost to reach the target node | ||
int minCost = Integer.MAX_VALUE; | ||
for (int r = 0; r <= maxResource; r++) { | ||
minCost = Math.min(minCost, dp[r][target]); | ||
} | ||
|
||
return minCost == Integer.MAX_VALUE ? -1 : minCost; | ||
} | ||
} |
218 changes: 218 additions & 0 deletions
218
src/test/java/com/thealgorithms/graph/ConstrainedShortestPathTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
package com.thealgorithms.graph; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import com.thealgorithms.graph.ConstrainedShortestPath.Graph; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class ConstrainedShortestPathTest { | ||
|
||
/** | ||
* Tests a simple linear graph to verify if the solver calculates the shortest path correctly. | ||
* Expected: The minimal path cost from node 0 to node 2 should be 5 while not exceeding the resource limit. | ||
*/ | ||
@Test | ||
public void testSimpleGraph() { | ||
Graph graph = new Graph(3); | ||
graph.addEdge(0, 1, 2, 3); | ||
graph.addEdge(1, 2, 3, 2); | ||
|
||
int maxResource = 5; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(5, solver.solve(0, 2)); | ||
} | ||
|
||
/** | ||
* Tests a graph where no valid path exists due to resource constraints. | ||
* Expected: The solver should return -1, indicating no path is feasible. | ||
*/ | ||
@Test | ||
public void testNoPath() { | ||
Graph graph = new Graph(3); | ||
graph.addEdge(0, 1, 2, 6); | ||
graph.addEdge(1, 2, 3, 6); | ||
|
||
int maxResource = 5; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(-1, solver.solve(0, 2)); | ||
} | ||
|
||
/** | ||
* Tests a graph with multiple paths between source and destination. | ||
* Expected: The solver should choose the path with the minimal cost of 5, considering the resource limit. | ||
*/ | ||
@Test | ||
public void testMultiplePaths() { | ||
Graph graph = new Graph(4); | ||
graph.addEdge(0, 1, 1, 1); | ||
graph.addEdge(1, 3, 5, 2); | ||
graph.addEdge(0, 2, 2, 1); | ||
graph.addEdge(2, 3, 3, 2); | ||
|
||
int maxResource = 3; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(5, solver.solve(0, 3)); | ||
} | ||
|
||
/** | ||
* Verifies that the solver allows a path exactly matching the resource limit. | ||
* Expected: The path is valid with a total cost of 5. | ||
*/ | ||
@Test | ||
public void testExactResourceLimit() { | ||
Graph graph = new Graph(3); | ||
graph.addEdge(0, 1, 2, 3); | ||
graph.addEdge(1, 2, 3, 2); | ||
|
||
int maxResource = 5; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(5, solver.solve(0, 2)); | ||
} | ||
|
||
/** | ||
* Tests a disconnected graph where the destination node cannot be reached. | ||
* Expected: The solver should return -1, as the destination is unreachable. | ||
*/ | ||
@Test | ||
public void testDisconnectedGraph() { | ||
Graph graph = new Graph(4); | ||
graph.addEdge(0, 1, 2, 2); | ||
graph.addEdge(2, 3, 3, 2); | ||
|
||
int maxResource = 5; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(-1, solver.solve(0, 3)); | ||
} | ||
|
||
/** | ||
* Tests a graph with cycles to ensure the solver does not fall into infinite loops and correctly calculates costs. | ||
* Expected: The solver should compute the minimal path cost of 6. | ||
*/ | ||
@Test | ||
public void testGraphWithCycles() { | ||
Graph graph = new Graph(4); | ||
graph.addEdge(0, 1, 2, 1); | ||
graph.addEdge(1, 2, 3, 1); | ||
graph.addEdge(2, 0, 1, 1); | ||
graph.addEdge(1, 3, 4, 2); | ||
|
||
int maxResource = 3; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(6, solver.solve(0, 3)); | ||
} | ||
|
||
/** | ||
* Tests the solver's performance and correctness on a large linear graph with 1000 nodes. | ||
* Expected: The solver should efficiently calculate the shortest path with a cost of 999. | ||
*/ | ||
@Test | ||
public void testLargeGraphPerformance() { | ||
int nodeCount = 1000; | ||
Graph graph = new Graph(nodeCount); | ||
for (int i = 0; i < nodeCount - 1; i++) { | ||
graph.addEdge(i, i + 1, 1, 1); | ||
} | ||
|
||
int maxResource = 1000; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(999, solver.solve(0, nodeCount - 1)); | ||
} | ||
|
||
/** | ||
* Tests a graph with isolated nodes to ensure the solver recognizes unreachable destinations. | ||
* Expected: The solver should return -1 for unreachable nodes. | ||
*/ | ||
@Test | ||
public void testIsolatedNodes() { | ||
Graph graph = new Graph(5); | ||
graph.addEdge(0, 1, 2, 1); | ||
graph.addEdge(1, 2, 3, 1); | ||
|
||
int maxResource = 5; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(-1, solver.solve(0, 3)); | ||
} | ||
|
||
/** | ||
* Tests a cyclic large graph with multiple overlapping paths. | ||
* Expected: The solver should calculate the shortest path cost of 5. | ||
*/ | ||
@Test | ||
public void testCyclicLargeGraph() { | ||
Graph graph = new Graph(10); | ||
for (int i = 0; i < 9; i++) { | ||
graph.addEdge(i, (i + 1) % 10, 1, 1); | ||
} | ||
graph.addEdge(0, 5, 5, 3); | ||
|
||
int maxResource = 10; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(5, solver.solve(0, 5)); | ||
} | ||
|
||
/** | ||
* Tests a large complex graph with multiple paths and varying resource constraints. | ||
* Expected: The solver should identify the optimal path with a cost of 19 within the resource limit. | ||
*/ | ||
@Test | ||
public void testLargeComplexGraph() { | ||
Graph graph = new Graph(10); | ||
graph.addEdge(0, 1, 4, 2); | ||
graph.addEdge(0, 2, 3, 3); | ||
graph.addEdge(1, 3, 2, 1); | ||
graph.addEdge(2, 3, 5, 2); | ||
graph.addEdge(2, 4, 8, 4); | ||
graph.addEdge(3, 5, 7, 3); | ||
graph.addEdge(3, 6, 6, 2); | ||
graph.addEdge(4, 6, 3, 2); | ||
graph.addEdge(5, 7, 1, 1); | ||
graph.addEdge(6, 7, 2, 2); | ||
graph.addEdge(7, 8, 3, 1); | ||
graph.addEdge(8, 9, 2, 1); | ||
|
||
int maxResource = 10; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(19, solver.solve(0, 9)); | ||
} | ||
|
||
/** | ||
* Edge case test where the graph has only one node and no edges. | ||
* Expected: The minimal path cost is 0, as the start and destination are the same. | ||
*/ | ||
@Test | ||
public void testSingleNodeGraph() { | ||
Graph graph = new Graph(1); | ||
|
||
int maxResource = 0; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(0, solver.solve(0, 0)); | ||
} | ||
|
||
/** | ||
* Tests a graph with multiple paths but a tight resource constraint. | ||
* Expected: The solver should return -1 if no path can be found within the resource limit. | ||
*/ | ||
@Test | ||
public void testTightResourceConstraint() { | ||
Graph graph = new Graph(4); | ||
graph.addEdge(0, 1, 3, 4); | ||
graph.addEdge(1, 2, 1, 2); | ||
graph.addEdge(0, 2, 2, 2); | ||
|
||
int maxResource = 3; | ||
ConstrainedShortestPath solver = new ConstrainedShortestPath(graph, maxResource); | ||
|
||
assertEquals(2, solver.solve(0, 2)); | ||
} | ||
} |