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:
CrodiYa
2025-10-09 20:20:19 +03:00
committed by GitHub
parent b1aa896e17
commit 16557a9b81
2 changed files with 265 additions and 0 deletions

View 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;
}
}

View File

@@ -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);
}
}