Skip to content

Commit 77b6369

Browse files
Merge branch 'master' into krishnamurthy-improve
2 parents 3d22e19 + 9a907c8 commit 77b6369

File tree

4 files changed

+337
-26
lines changed

4 files changed

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

src/main/java/com/thealgorithms/others/LowestBasePalindrome.java

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,26 @@
44
import java.util.List;
55

66
/**
7-
* @brief Class for finding the lowest base in which a given integer is a palindrome.
8-
cf. https://oeis.org/A016026
7+
* Utility class for finding the lowest base in which a given integer is a
8+
* palindrome.
9+
* <p>
10+
* A number is a palindrome in a given base if its representation in that base
11+
* reads the same
12+
* forwards and backwards. For example, 15 in base 2 is 1111, which is
13+
* palindromic.
14+
* This class provides methods to check palindromic properties and find the
15+
* smallest base
16+
* where a number becomes palindromic.
17+
* </p>
18+
* <p>
19+
* Example: The number 15 in base 2 is represented as [1,1,1,1], which is
20+
* palindromic.
21+
* The number 10 in base 3 is represented as [1,0,1], which is also palindromic.
22+
* </p>
23+
*
24+
* @see <a href="https://oeis.org/A016026">OEIS A016026 - Smallest base in which
25+
* n is palindromic</a>
26+
* @author TheAlgorithms Contributors
927
*/
1028
public final class LowestBasePalindrome {
1129
private LowestBasePalindrome() {
@@ -37,12 +55,18 @@ private static void checkNumber(int number) {
3755

3856
/**
3957
* Computes the digits of a given number in a specified base.
58+
* <p>
59+
* The digits are returned in reverse order (least significant digit first).
60+
* For example, the number 13 in base 2 produces [1,0,1,1] representing 1101 in
61+
* binary.
62+
* </p>
4063
*
41-
* @param number the number to be converted
42-
* @param base the base to be used for the conversion
43-
* @return a list of digits representing the number in the given base, with the most
44-
* significant digit at the end of the list
45-
* @throws IllegalArgumentException if the number is negative or the base is less than 2
64+
* @param number the number to be converted (must be non-negative)
65+
* @param base the base to be used for the conversion (must be greater than 1)
66+
* @return a list of digits representing the number in the given base, with the
67+
* least significant digit at the beginning of the list
68+
* @throws IllegalArgumentException if the number is negative or the base is
69+
* less than 2
4670
*/
4771
public static List<Integer> computeDigitsInBase(int number, int base) {
4872
checkNumber(number);
@@ -58,6 +82,10 @@ public static List<Integer> computeDigitsInBase(int number, int base) {
5882

5983
/**
6084
* Checks if a list of integers is palindromic.
85+
* <p>
86+
* A list is palindromic if it reads the same forwards and backwards.
87+
* For example, [1,2,1] is palindromic, but [1,2,3] is not.
88+
* </p>
6189
*
6290
* @param list the list of integers to be checked
6391
* @return {@code true} if the list is a palindrome, {@code false} otherwise
@@ -73,12 +101,29 @@ public static boolean isPalindromic(List<Integer> list) {
73101
}
74102

75103
/**
76-
* Checks if the representation of a given number in a specified base is palindromic.
104+
* Checks if the representation of a given number in a specified base is
105+
* palindromic.
106+
* <p>
107+
* This method first validates the input, then applies optimization: if the
108+
* number
109+
* ends with 0 in the given base (i.e., divisible by the base), it cannot be
110+
* palindromic
111+
* as palindromes cannot start with 0.
112+
* </p>
113+
* <p>
114+
* Examples:
115+
* - 101 in base 10 is palindromic (101)
116+
* - 15 in base 2 is palindromic (1111)
117+
* - 10 in base 3 is palindromic (101)
118+
* </p>
77119
*
78-
* @param number the number to be checked
79-
* @param base the base in which the number will be represented
80-
* @return {@code true} if the number is palindromic in the specified base, {@code false} otherwise
81-
* @throws IllegalArgumentException if the number is negative or the base is less than 2
120+
* @param number the number to be checked (must be non-negative)
121+
* @param base the base in which the number will be represented (must be
122+
* greater than 1)
123+
* @return {@code true} if the number is palindromic in the specified base,
124+
* {@code false} otherwise
125+
* @throws IllegalArgumentException if the number is negative or the base is
126+
* less than 2
82127
*/
83128
public static boolean isPalindromicInBase(int number, int base) {
84129
checkNumber(number);
@@ -89,18 +134,38 @@ public static boolean isPalindromicInBase(int number, int base) {
89134
}
90135

91136
if (number % base == 0) {
92-
// If the last digit of the number in the given base is 0, it can't be palindromic
137+
// If the last digit of the number in the given base is 0, it can't be
138+
// palindromic
93139
return false;
94140
}
95141

96142
return isPalindromic(computeDigitsInBase(number, base));
97143
}
98144

99145
/**
100-
* Finds the smallest base in which the representation of a given number is palindromic.
146+
* Finds the smallest base in which the representation of a given number is
147+
* palindromic.
148+
* <p>
149+
* This method iteratively checks bases starting from 2 until it finds one where
150+
* the number is palindromic. For any number n ≥ 2, the number is always
151+
* palindromic
152+
* in base n-1 (represented as [1, 1]), so this algorithm is guaranteed to
153+
* terminate.
154+
* </p>
155+
* <p>
156+
* Time Complexity: O(n * log(n)) in the worst case, where we check each base
157+
* and
158+
* convert the number to that base.
159+
* </p>
160+
* <p>
161+
* Examples:
162+
* - lowestBasePalindrome(15) returns 2 (15 in base 2 is 1111)
163+
* - lowestBasePalindrome(10) returns 3 (10 in base 3 is 101)
164+
* - lowestBasePalindrome(11) returns 10 (11 in base 10 is 11)
165+
* </p>
101166
*
102-
* @param number the number to be checked
103-
* @return the smallest base in which the number is a palindrome
167+
* @param number the number to be checked (must be non-negative)
168+
* @return the smallest base in which the number is a palindrome (base ≥ 2)
104169
* @throws IllegalArgumentException if the number is negative
105170
*/
106171
public static int lowestBasePalindrome(int number) {
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 static final int NUM_VERTICES = 6;
16+
private static final int MAX_EDGE_WEIGHT = 10;
17+
18+
@BeforeEach
19+
void setUp() {
20+
graph = new ArrayList<>();
21+
for (int i = 0; i < NUM_VERTICES; 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, MAX_EDGE_WEIGHT);
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, MAX_EDGE_WEIGHT);
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, MAX_EDGE_WEIGHT);
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, MAX_EDGE_WEIGHT);
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, MAX_EDGE_WEIGHT));
86+
assertThrows(IllegalArgumentException.class, () -> DialsAlgorithm.run(graph, NUM_VERTICES, MAX_EDGE_WEIGHT));
87+
}
88+
}

0 commit comments

Comments
 (0)