mirror of
https://github.com/TheAlgorithms/Java.git
synced 2026-03-13 08:40:43 +08:00
Feature iterative flood fill (#6584)
* feat: FloodFill Algorithm with iterative approach - nested class Point: helper class, represents point in 2D field (x,y) - shouldSkipPixel method: helper method to validate point - floodFill method: iterative version of floodFill, uses Queue to add and poll Points and change it color if allowed * feat: tests for IterativeFloodFill -same tests as for normal floodFill and test for a big image * docs: add link to floodFill algorithm * codeStyle fix * tests: add tests for edge cases * codeStyle fix * codeStyle fix * codeStyle fix * refactor: reorganize structure and add JavaDoc - Move private methods after public methods for better readability - Add class-level JavaDoc documentation with algorithm description and links to references
This commit is contained in:
102
src/main/java/com/thealgorithms/others/IterativeFloodFill.java
Normal file
102
src/main/java/com/thealgorithms/others/IterativeFloodFill.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.thealgorithms.others;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
/**
|
||||
* Implementation of the Flood Fill algorithm using an iterative BFS (Breadth-First Search) approach.
|
||||
*
|
||||
* <p>The Flood Fill algorithm is used to fill connected areas in an image with a new color, starting from a specified point.
|
||||
* This implementation uses an iterative BFS approach with a queue
|
||||
* instead of recursion to avoid stack overflow issues with large images.</p>
|
||||
*
|
||||
* <p><b>Implementation Features:</b></p>
|
||||
* <ul>
|
||||
* <li>Supports 8-connected filling (horizontal, vertical, and diagonal directions)</li>
|
||||
* <li>Uses BFS traversal through {@link java.util.Queue}</li>
|
||||
* <li>Includes nested {@code Point} class to represent pixel coordinates</li>
|
||||
* <li>Iterative approach avoids stack overflow for large images</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Time Complexity:</b> O(M × N) where M and N are the dimensions of the image</p>
|
||||
* <p><b>Space Complexity:</b> O(M × N) in the worst case the queue stores every pixel</p>
|
||||
*
|
||||
* @see <a href="https://www.geeksforgeeks.org/dsa/flood-fill-algorithm">Flood Fill Algorithm - GeeksforGeeks</a>
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Flood_fill">Flood Fill Algorithm - Wikipedia</a>
|
||||
*/
|
||||
public final class IterativeFloodFill {
|
||||
private IterativeFloodFill() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Iteratively fill the 2D image with new color
|
||||
*
|
||||
* @param image The image to be filled
|
||||
* @param x The x co-ordinate at which color is to be filled
|
||||
* @param y The y co-ordinate at which color is to be filled
|
||||
* @param newColor The new color which to be filled in the image
|
||||
* @param oldColor The old color which is to be replaced in the image
|
||||
* @see <a href=https://www.geeksforgeeks.org/dsa/flood-fill-algorithm>FloodFill BFS<a/>
|
||||
*/
|
||||
public static void floodFill(final int[][] image, final int x, final int y, final int newColor, final int oldColor) {
|
||||
if (image.length == 0 || image[0].length == 0 || newColor == oldColor || shouldSkipPixel(image, x, y, oldColor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Queue<Point> queue = new LinkedList<>();
|
||||
queue.add(new Point(x, y));
|
||||
|
||||
int[] dx = {0, 0, -1, 1, 1, -1, 1, -1};
|
||||
int[] dy = {-1, 1, 0, 0, -1, 1, 1, -1};
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
Point currPoint = queue.poll();
|
||||
|
||||
if (shouldSkipPixel(image, currPoint.x, currPoint.y, oldColor)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
image[currPoint.x][currPoint.y] = newColor;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int curX = currPoint.x + dx[i];
|
||||
int curY = currPoint.y + dy[i];
|
||||
|
||||
if (!shouldSkipPixel(image, curX, curY, oldColor)) {
|
||||
queue.add(new Point(curX, curY));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a point in 2D space with integer coordinates.
|
||||
*/
|
||||
private static class Point {
|
||||
final int x;
|
||||
final int y;
|
||||
|
||||
Point(final int x, final int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a pixel should be skipped during flood fill operation.
|
||||
*
|
||||
* @param image The image to get boundaries
|
||||
* @param x The x co-ordinate of pixel to check
|
||||
* @param y The y co-ordinate of pixel to check
|
||||
* @param oldColor The old color which is to be replaced in the image
|
||||
* @return {@code true} if pixel should be skipped, else {@code false}
|
||||
*/
|
||||
private static boolean shouldSkipPixel(final int[][] image, final int x, final int y, final int oldColor) {
|
||||
|
||||
if (x < 0 || x >= image.length || y < 0 || y >= image[0].length || image[x][y] != oldColor) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.thealgorithms.others;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class IterativeFloodFillTest {
|
||||
|
||||
@Test
|
||||
void testForEmptyImage() {
|
||||
int[][] image = {};
|
||||
int[][] expected = {};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 4, 5, 3, 2);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForSingleElementImage() {
|
||||
int[][] image = {{1}};
|
||||
int[][] expected = {{3}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 0, 0, 3, 1);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForEmptyRow() {
|
||||
int[][] image = {{}};
|
||||
int[][] expected = {{}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 4, 5, 3, 2);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForImageOne() {
|
||||
int[][] image = {
|
||||
{0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 3, 3, 3, 3, 0, 0},
|
||||
{0, 3, 1, 1, 5, 0, 0},
|
||||
{0, 3, 1, 1, 5, 5, 3},
|
||||
{0, 3, 5, 5, 1, 1, 3},
|
||||
{0, 0, 0, 5, 1, 1, 3},
|
||||
{0, 0, 0, 3, 3, 3, 3},
|
||||
};
|
||||
|
||||
int[][] expected = {
|
||||
{0, 0, 0, 0, 0, 0, 0},
|
||||
{0, 3, 3, 3, 3, 0, 0},
|
||||
{0, 3, 2, 2, 5, 0, 0},
|
||||
{0, 3, 2, 2, 5, 5, 3},
|
||||
{0, 3, 5, 5, 2, 2, 3},
|
||||
{0, 0, 0, 5, 2, 2, 3},
|
||||
{0, 0, 0, 3, 3, 3, 3},
|
||||
};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 2, 2, 2, 1);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForImageTwo() {
|
||||
int[][] image = {
|
||||
{0, 0, 1, 1, 0, 0, 0},
|
||||
{1, 1, 3, 3, 3, 0, 0},
|
||||
{1, 3, 1, 1, 5, 0, 0},
|
||||
{0, 3, 1, 1, 5, 5, 3},
|
||||
{0, 3, 5, 5, 1, 1, 3},
|
||||
{0, 0, 0, 5, 1, 1, 3},
|
||||
{0, 0, 0, 1, 3, 1, 3},
|
||||
};
|
||||
|
||||
int[][] expected = {
|
||||
{0, 0, 2, 2, 0, 0, 0},
|
||||
{2, 2, 3, 3, 3, 0, 0},
|
||||
{2, 3, 2, 2, 5, 0, 0},
|
||||
{0, 3, 2, 2, 5, 5, 3},
|
||||
{0, 3, 5, 5, 2, 2, 3},
|
||||
{0, 0, 0, 5, 2, 2, 3},
|
||||
{0, 0, 0, 2, 3, 2, 3},
|
||||
};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 2, 2, 2, 1);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForImageThree() {
|
||||
int[][] image = {
|
||||
{1, 1, 2, 3, 1, 1, 1},
|
||||
{1, 0, 0, 1, 0, 0, 1},
|
||||
{1, 1, 1, 0, 3, 1, 2},
|
||||
};
|
||||
|
||||
int[][] expected = {
|
||||
{4, 4, 2, 3, 4, 4, 4},
|
||||
{4, 0, 0, 4, 0, 0, 4},
|
||||
{4, 4, 4, 0, 3, 4, 2},
|
||||
};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 0, 1, 4, 1);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForSameNewAndOldColor() {
|
||||
int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 0, 1, 1, 1);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForBigImage() {
|
||||
int[][] image = new int[100][100];
|
||||
|
||||
assertDoesNotThrow(() -> IterativeFloodFill.floodFill(image, 0, 0, 1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForBelowZeroX() {
|
||||
int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, -1, 1, 1, 0);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForBelowZeroY() {
|
||||
int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 1, -1, 1, 0);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForAboveBoundaryX() {
|
||||
int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 100, 1, 1, 0);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testForAboveBoundaryY() {
|
||||
int[][] image = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
int[][] expected = {{1, 1, 2}, {1, 0, 0}, {1, 1, 1}};
|
||||
|
||||
IterativeFloodFill.floodFill(image, 1, 100, 1, 0);
|
||||
assertArrayEquals(expected, image);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user