Skip to content

Commit 5622c71

Browse files
authored
Merge branch 'master' into feat/add-american-flag-sort
2 parents 4c9c292 + 79dc71d commit 5622c71

File tree

7 files changed

+672
-1
lines changed

7 files changed

+672
-1
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@
127127
<plugin>
128128
<groupId>com.mebigfatguy.fb-contrib</groupId>
129129
<artifactId>fb-contrib</artifactId>
130-
<version>7.6.14</version>
130+
<version>7.6.15</version>
131131
</plugin>
132132
<plugin>
133133
<groupId>com.h3xstream.findsecbugs</groupId>
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package com.thealgorithms.geometry;
2+
3+
import java.awt.Point;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
7+
/**
8+
* The {@code WusLine} class implements Xiaolin Wu's line drawing algorithm,
9+
* which produces anti-aliased lines by varying pixel brightness
10+
* according to the line's proximity to pixel centers.
11+
*
12+
* This implementation returns the pixel coordinates along with
13+
* their associated intensity values (in range [0.0, 1.0]), allowing
14+
* rendering systems to blend accordingly.
15+
*
16+
* The algorithm works by:
17+
* - Computing a line's intersection with pixel boundaries
18+
* - Assigning intensity values based on distance from pixel centers
19+
* - Drawing pairs of pixels perpendicular to the line's direction
20+
*
21+
* Reference: Xiaolin Wu, "An Efficient Antialiasing Technique",
22+
* Computer Graphics (SIGGRAPH '91 Proceedings).
23+
*
24+
*/
25+
public final class WusLine {
26+
27+
private WusLine() {
28+
// Utility class; prevent instantiation.
29+
}
30+
31+
/**
32+
* Represents a pixel and its intensity for anti-aliased rendering.
33+
*
34+
* The intensity value determines how bright the pixel should be drawn,
35+
* with 1.0 being fully opaque and 0.0 being fully transparent.
36+
*/
37+
public static class Pixel {
38+
/** The pixel's coordinate on the screen. */
39+
public final Point point;
40+
41+
/** The pixel's intensity value, clamped to the range [0.0, 1.0]. */
42+
public final double intensity;
43+
44+
/**
45+
* Constructs a new Pixel with the given coordinates and intensity.
46+
*
47+
* @param x the x-coordinate of the pixel
48+
* @param y the y-coordinate of the pixel
49+
* @param intensity the brightness/opacity of the pixel, will be clamped to [0.0, 1.0]
50+
*/
51+
public Pixel(int x, int y, double intensity) {
52+
this.point = new Point(x, y);
53+
this.intensity = Math.clamp(intensity, 0.0, 1.0);
54+
}
55+
}
56+
57+
/**
58+
* Internal class to hold processed endpoint data.
59+
*/
60+
private static class EndpointData {
61+
final int xPixel;
62+
final int yPixel;
63+
final double yEnd;
64+
final double xGap;
65+
66+
EndpointData(int xPixel, int yPixel, double yEnd, double xGap) {
67+
this.xPixel = xPixel;
68+
this.yPixel = yPixel;
69+
this.yEnd = yEnd;
70+
this.xGap = xGap;
71+
}
72+
}
73+
74+
/**
75+
* Draws an anti-aliased line using Wu's algorithm.
76+
*
77+
* The algorithm produces smooth lines by drawing pairs of pixels at each
78+
* x-coordinate (or y-coordinate for steep lines), with intensities based on
79+
* the line's distance from pixel centers.
80+
*
81+
* @param x0 the x-coordinate of the line's start point
82+
* @param y0 the y-coordinate of the line's start point
83+
* @param x1 the x-coordinate of the line's end point
84+
* @param y1 the y-coordinate of the line's end point
85+
* @return a list of {@link Pixel} objects representing the anti-aliased line,
86+
* ordered from start to end
87+
*/
88+
public static List<Pixel> drawLine(int x0, int y0, int x1, int y1) {
89+
List<Pixel> pixels = new ArrayList<>();
90+
91+
// Determine if the line is steep (more vertical than horizontal)
92+
boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
93+
94+
if (steep) {
95+
// For steep lines, swap x and y coordinates to iterate along y-axis
96+
int temp = x0;
97+
x0 = y0;
98+
y0 = temp;
99+
100+
temp = x1;
101+
x1 = y1;
102+
y1 = temp;
103+
}
104+
105+
if (x0 > x1) {
106+
// Ensure we always draw from left to right
107+
int temp = x0;
108+
x0 = x1;
109+
x1 = temp;
110+
111+
temp = y0;
112+
y0 = y1;
113+
y1 = temp;
114+
}
115+
116+
// Calculate the line's slope
117+
double deltaX = x1 - (double) x0;
118+
double deltaY = y1 - (double) y0;
119+
double gradient = (deltaX == 0) ? 1.0 : deltaY / deltaX;
120+
121+
// Process the first endpoint
122+
EndpointData firstEndpoint = processEndpoint(x0, y0, gradient, true);
123+
addEndpointPixels(pixels, firstEndpoint, steep);
124+
125+
// Process the second endpoint
126+
EndpointData secondEndpoint = processEndpoint(x1, y1, gradient, false);
127+
addEndpointPixels(pixels, secondEndpoint, steep);
128+
129+
// Draw the main line between endpoints
130+
drawMainLine(pixels, firstEndpoint, secondEndpoint, gradient, steep);
131+
132+
return pixels;
133+
}
134+
135+
/**
136+
* Processes a line endpoint to determine its pixel coordinates and intensities.
137+
*
138+
* @param x the x-coordinate of the endpoint
139+
* @param y the y-coordinate of the endpoint
140+
* @param gradient the slope of the line
141+
* @param isStart true if this is the start endpoint, false if it's the end
142+
* @return an {@link EndpointData} object containing processed endpoint information
143+
*/
144+
private static EndpointData processEndpoint(double x, double y, double gradient, boolean isStart) {
145+
double xEnd = round(x);
146+
double yEnd = y + gradient * (xEnd - x);
147+
double xGap = isStart ? rfpart(x + 0.5) : fpart(x + 0.5);
148+
149+
int xPixel = (int) xEnd;
150+
int yPixel = (int) Math.floor(yEnd);
151+
152+
return new EndpointData(xPixel, yPixel, yEnd, xGap);
153+
}
154+
155+
/**
156+
* Adds the two endpoint pixels (one above, one below the line) to the pixel list.
157+
*
158+
* @param pixels the list to add pixels to
159+
* @param endpoint the endpoint data containing coordinates and gaps
160+
* @param steep true if the line is steep (coordinates should be swapped)
161+
*/
162+
private static void addEndpointPixels(List<Pixel> pixels, EndpointData endpoint, boolean steep) {
163+
double fractionalY = fpart(endpoint.yEnd);
164+
double complementFractionalY = rfpart(endpoint.yEnd);
165+
166+
if (steep) {
167+
pixels.add(new Pixel(endpoint.yPixel, endpoint.xPixel, complementFractionalY * endpoint.xGap));
168+
pixels.add(new Pixel(endpoint.yPixel + 1, endpoint.xPixel, fractionalY * endpoint.xGap));
169+
} else {
170+
pixels.add(new Pixel(endpoint.xPixel, endpoint.yPixel, complementFractionalY * endpoint.xGap));
171+
pixels.add(new Pixel(endpoint.xPixel, endpoint.yPixel + 1, fractionalY * endpoint.xGap));
172+
}
173+
}
174+
175+
/**
176+
* Draws the main portion of the line between the two endpoints.
177+
*
178+
* @param pixels the list to add pixels to
179+
* @param firstEndpoint the processed start endpoint
180+
* @param secondEndpoint the processed end endpoint
181+
* @param gradient the slope of the line
182+
* @param steep true if the line is steep (coordinates should be swapped)
183+
*/
184+
private static void drawMainLine(List<Pixel> pixels, EndpointData firstEndpoint, EndpointData secondEndpoint, double gradient, boolean steep) {
185+
// Start y-intersection after the first endpoint
186+
double intersectionY = firstEndpoint.yEnd + gradient;
187+
188+
// Iterate through x-coordinates between the endpoints
189+
for (int x = firstEndpoint.xPixel + 1; x < secondEndpoint.xPixel; x++) {
190+
int yFloor = (int) Math.floor(intersectionY);
191+
double fractionalPart = fpart(intersectionY);
192+
double complementFractionalPart = rfpart(intersectionY);
193+
194+
if (steep) {
195+
pixels.add(new Pixel(yFloor, x, complementFractionalPart));
196+
pixels.add(new Pixel(yFloor + 1, x, fractionalPart));
197+
} else {
198+
pixels.add(new Pixel(x, yFloor, complementFractionalPart));
199+
pixels.add(new Pixel(x, yFloor + 1, fractionalPart));
200+
}
201+
202+
intersectionY += gradient;
203+
}
204+
}
205+
206+
/**
207+
* Returns the fractional part of a number.
208+
*
209+
* @param x the input number
210+
* @return the fractional part (always in range [0.0, 1.0))
211+
*/
212+
private static double fpart(double x) {
213+
return x - Math.floor(x);
214+
}
215+
216+
/**
217+
* Returns the reverse fractional part of a number (1 - fractional part).
218+
*
219+
* @param x the input number
220+
* @return 1.0 minus the fractional part (always in range (0.0, 1.0])
221+
*/
222+
private static double rfpart(double x) {
223+
return 1.0 - fpart(x);
224+
}
225+
226+
/**
227+
* Rounds a number to the nearest integer.
228+
*
229+
* @param x the input number
230+
* @return the nearest integer value as a double
231+
*/
232+
private static double round(double x) {
233+
return Math.floor(x + 0.5);
234+
}
235+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Arrays;
5+
import java.util.Deque;
6+
import java.util.List;
7+
8+
/**
9+
* 0-1 BFS for shortest paths on graphs with edges weighted 0 or 1.
10+
*
11+
* <p>Time Complexity: O(V + E). Space Complexity: O(V).
12+
*
13+
* <p>References:
14+
* <ul>
15+
* <li>https://cp-algorithms.com/graph/01_bfs.html</li>
16+
* </ul>
17+
*/
18+
public final class ZeroOneBfs {
19+
20+
private ZeroOneBfs() {
21+
// Utility class; do not instantiate.
22+
}
23+
24+
/**
25+
* Computes shortest distances from {@code src} in a graph whose edges have weight 0 or 1.
26+
*
27+
* @param n the number of vertices, labeled {@code 0..n-1}
28+
* @param adj adjacency list; for each vertex u, {@code adj.get(u)} is a list of pairs
29+
* {@code (v, w)} where {@code v} is a neighbor and {@code w} is 0 or 1
30+
* @param src the source vertex
31+
* @return an array of distances; {@code Integer.MAX_VALUE} denotes unreachable
32+
* @throws IllegalArgumentException if {@code n < 0}, {@code src} is out of range,
33+
* or any edge has weight other than 0 or 1
34+
*/
35+
public static int[] shortestPaths(int n, List<List<int[]>> adj, int src) {
36+
if (n < 0 || src < 0 || src >= n) {
37+
throw new IllegalArgumentException("Invalid n or src");
38+
}
39+
int[] dist = new int[n];
40+
Arrays.fill(dist, Integer.MAX_VALUE);
41+
Deque<Integer> dq = new ArrayDeque<>();
42+
43+
dist[src] = 0;
44+
dq.addFirst(src);
45+
46+
while (!dq.isEmpty()) {
47+
int u = dq.pollFirst();
48+
List<int[]> edges = adj.get(u);
49+
if (edges == null) {
50+
continue;
51+
}
52+
for (int[] e : edges) {
53+
if (e == null || e.length < 2) {
54+
continue;
55+
}
56+
int v = e[0];
57+
int w = e[1];
58+
if (v < 0 || v >= n) {
59+
continue; // ignore bad edges
60+
}
61+
if (w != 0 && w != 1) {
62+
throw new IllegalArgumentException("Edge weight must be 0 or 1");
63+
}
64+
int nd = dist[u] + w;
65+
if (nd < dist[v]) {
66+
dist[v] = nd;
67+
if (w == 0) {
68+
dq.addFirst(v);
69+
} else {
70+
dq.addLast(v);
71+
}
72+
}
73+
}
74+
}
75+
return dist;
76+
}
77+
}

0 commit comments

Comments
 (0)