Skip to content

Commit 8926b3f

Browse files
author
Priyanshu1303d
committed
[FEAT] Implement Dials Algorithm(Graph)
1 parent 9484c7e commit 8926b3f

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.thealgorithms.datastructures.graphs;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.HashSet;
6+
import java.util.List;
7+
import java.util.Set;
8+
/**
9+
* An implementation of Dial's Algorithm for the single-source shortest path problem.
10+
* This algorithm is an optimization of Dijkstra's algorithm and is particularly
11+
* efficient for graphs with small, non-negative integer edge weights.
12+
*
13+
* It uses a bucket queue (implemented here as a List of HashSets) to store vertices,
14+
* where each bucket corresponds to a specific distance from the source. This is more
15+
* efficient than a standard priority queue when the range of edge weights is small.
16+
*
17+
* Time Complexity: O(E + W * V), where E is the number of edges, V is the number
18+
* of vertices, and W is the maximum weight of any edge.
19+
*
20+
* @see <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm#Dial's_algorithm">Wikipedia - Dial's Algorithm</a>
21+
*/
22+
public final class DialsAlgorithm {
23+
/**
24+
* Private constructor to prevent instantiation of this utility class.
25+
*/
26+
private DialsAlgorithm() {
27+
}
28+
/**
29+
* Represents an edge in the graph, connecting to a destination vertex with a given weight.
30+
*/
31+
public static class Edge {
32+
private final int destination;
33+
private final int weight;
34+
35+
public Edge(int destination, int weight) {
36+
this.destination = destination;
37+
this.weight = weight;
38+
}
39+
40+
public int getDestination() {
41+
return destination;
42+
}
43+
44+
public int getWeight() {
45+
return weight;
46+
}
47+
}
48+
/**
49+
* Finds the shortest paths from a source vertex to all other vertices in a weighted graph.
50+
*
51+
* @param graph The graph represented as an adjacency list.
52+
* @param source The source vertex to start from (0-indexed).
53+
* @param maxEdgeWeight The maximum weight of any single edge in the graph.
54+
* @return An array of integers where the value at each index `i` is the
55+
* shortest distance from the source to vertex `i`. Unreachable vertices
56+
* will have a value of Integer.MAX_VALUE.
57+
* @throws IllegalArgumentException if the source vertex is out of bounds.
58+
*/
59+
public static int[] run(List<List<Edge>> graph, int source, int maxEdgeWeight) {
60+
int numVertices = graph.size();
61+
if (source < 0 || source >= numVertices) {
62+
throw new IllegalArgumentException("Source vertex is out of bounds.");
63+
}
64+
65+
// Initialize distances array
66+
int[] distances = new int[numVertices];
67+
Arrays.fill(distances, Integer.MAX_VALUE);
68+
distances[source] = 0;
69+
70+
// The bucket queue. Size is determined by the max possible path length.
71+
int maxPathWeight = maxEdgeWeight * (numVertices > 0 ? numVertices - 1 : 0);
72+
List<Set<Integer>> buckets = new ArrayList<>(maxPathWeight + 1);
73+
for (int i = 0; i <= maxPathWeight; i++) {
74+
buckets.add(new HashSet<>());
75+
}
76+
77+
// Add the source vertex to the first bucket
78+
buckets.get(0).add(source);
79+
80+
// Process buckets in increasing order of distance
81+
for (int d = 0; d <= maxPathWeight; d++) {
82+
// Process all vertices in the current bucket
83+
while (!buckets.get(d).isEmpty()) {
84+
// Get and remove a vertex from the current bucket
85+
int u = buckets.get(d).iterator().next();
86+
buckets.get(d).remove(u);
87+
88+
// If we've found a shorter path already, skip
89+
if (d > distances[u]) {
90+
continue;
91+
}
92+
93+
// Relax all adjacent edges
94+
for (Edge edge : graph.get(u)) {
95+
int v = edge.getDestination();
96+
int weight = edge.getWeight();
97+
98+
// If a shorter path to v is found
99+
if (distances[u] != Integer.MAX_VALUE && distances[u] + weight < distances[v]) {
100+
// If v was already in a bucket, remove it from the old one
101+
if (distances[v] != Integer.MAX_VALUE) {
102+
buckets.get(distances[v]).remove(v);
103+
}
104+
// Update distance and move v to the new bucket
105+
distances[v] = distances[u] + weight;
106+
buckets.get(distances[v]).add(v);
107+
}
108+
}
109+
}
110+
}
111+
return distances;
112+
}
113+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.thealgorithms.datastructures.graphs;
2+
3+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.DisplayName;
10+
import org.junit.jupiter.api.Test;
11+
12+
final class DialsAlgorithmTest {
13+
14+
private List<List<DialsAlgorithm.Edge>> graph;
15+
private final int numVertices = 6;
16+
private final int maxEdgeWeight = 10;
17+
18+
@BeforeEach
19+
void setUp() {
20+
graph = new ArrayList<>();
21+
for (int i = 0; i < numVertices; i++) {
22+
graph.add(new ArrayList<>());
23+
}
24+
}
25+
26+
private void addEdge(int u, int v, int weight) {
27+
graph.get(u).add(new DialsAlgorithm.Edge(v, weight));
28+
}
29+
30+
@Test
31+
@DisplayName("Test with a simple connected graph")
32+
void testSimpleGraph() {
33+
// Build graph from a standard example
34+
addEdge(0, 1, 2);
35+
addEdge(0, 2, 4);
36+
addEdge(1, 2, 1);
37+
addEdge(1, 3, 7);
38+
addEdge(2, 4, 3);
39+
addEdge(3, 5, 1);
40+
addEdge(4, 3, 2);
41+
addEdge(4, 5, 5);
42+
43+
int[] expectedDistances = {0, 2, 3, 8, 6, 9};
44+
int[] actualDistances = DialsAlgorithm.run(graph, 0, maxEdgeWeight);
45+
assertArrayEquals(expectedDistances, actualDistances);
46+
}
47+
48+
@Test
49+
@DisplayName("Test with a disconnected node")
50+
void testDisconnectedNode() {
51+
addEdge(0, 1, 5);
52+
addEdge(1, 2, 5);
53+
// Node 3, 4, 5 are disconnected
54+
55+
int[] expectedDistances = {0, 5, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
56+
int[] actualDistances = DialsAlgorithm.run(graph, 0, maxEdgeWeight);
57+
assertArrayEquals(expectedDistances, actualDistances);
58+
}
59+
60+
@Test
61+
@DisplayName("Test with source as destination")
62+
void testSourceIsDestination() {
63+
addEdge(0, 1, 10);
64+
int[] expectedDistances = {0, 10, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
65+
// Run with source 0
66+
int[] actualDistances = DialsAlgorithm.run(graph, 0, maxEdgeWeight);
67+
assertArrayEquals(expectedDistances, actualDistances);
68+
}
69+
70+
@Test
71+
@DisplayName("Test graph with multiple paths to a node")
72+
void testMultiplePaths() {
73+
addEdge(0, 1, 10);
74+
addEdge(0, 2, 3);
75+
addEdge(2, 1, 2); // Shorter path to 1 is via 2 (3+2=5)
76+
77+
int[] expectedDistances = {0, 5, 3, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
78+
int[] actualDistances = DialsAlgorithm.run(graph, 0, maxEdgeWeight);
79+
assertArrayEquals(expectedDistances, actualDistances);
80+
}
81+
82+
@Test
83+
@DisplayName("Test with an invalid source vertex")
84+
void testInvalidSource() {
85+
assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, -1, maxEdgeWeight));
86+
assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, numVertices, maxEdgeWeight));
87+
}
88+
}

0 commit comments

Comments
 (0)