Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions src/main/java/com/thealgorithms/others/IterativeFloodFill.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
163 changes: 163 additions & 0 deletions src/test/java/com/thealgorithms/others/IterativeFloodFillTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}