Backtracking, Programação Dinâmica e Flood Fill
28/05/2025
Algoritmos clássicos como backtracking, programação dinâmica e suas aplicações, além de diferentes formas de resolver o problema Flood Fill. Id: 59

Comparativo de Aplicações
Abaixo, uma tabela com exemplos clássicos do uso de Backtracking e Programação Dinâmica:
Quadro Comparativo: Aplicações de Algoritmos (fonte: chatGPT)
Flood Fill: O Problema
O Flood Fill é um algoritmo usado para preencher uma área contínua de uma matriz com uma nova cor, como a ferramenta de balde em editores de imagem. A partir de uma célula inicial, preenchermos todas as células conectadas com a mesma cor original.
Abordagens para Flood Fill
Várias estratégias podem ser utilizadas:
Comparativo de Estratégias para Flood Fill
Exemplo: FloodFill DFS recursivo
def flood_fill_dfs(imagem, x, y, cor_original, nova_cor):if x < 0 or x >= len(imagem) or y < 0 or y >= len(imagem[0]):returnif imagem[x][y] != cor_original or imagem[x][y] == nova_cor:returnimagem[x][y] = nova_cor# Visita as 4 direções: baixo, cima, direita, esquerdaflood_fill_dfs(imagem, x + 1, y, cor_original, nova_cor) # baixoflood_fill_dfs(imagem, x - 1, y, cor_original, nova_cor) # cimaflood_fill_dfs(imagem, x, y + 1, cor_original, nova_cor) # direitaflood_fill_dfs(imagem, x, y - 1, cor_original, nova_cor) # esquerda# 🌟 Exemplo de usoimagem = [[1, 1, 0, 0],[1, 0, 0, 1],[0, 0, 1, 1],[0, 1, 1, 1]]# Ponto inicial para preenchimentox_inicial, y_inicial = 0, 0# Cor original do ponto inicialcor_alvo = imagem[x_inicial][y_inicial]# Nova cor a ser preenchidanova_cor = 9# Aplica o algoritmoflood_fill_dfs(imagem, x_inicial, y_inicial, cor_alvo, nova_cor)# 📤 Resultado finalprint("Imagem após Flood Fill (DFS Recursivo):")for linha in imagem:print(linha)
Exemplo: FloodFill DFS iterativo com pilha
def flood_fill_dfs_iterativo(imagem, x, y, nova_cor):linhas = len(imagem)colunas = len(imagem[0])cor_original = imagem[x][y]if cor_original == nova_cor:returnpilha = [(x, y)]while pilha:i, j = pilha.pop()if i < 0 or i >= linhas or j < 0 or j >= colunas:continueif imagem[i][j] != cor_original:continueimagem[i][j] = nova_cor# Adiciona os vizinhos à pilha (cima, baixo, esquerda, direita)pilha.append((i + 1, j)) # baixopilha.append((i - 1, j)) # cimapilha.append((i, j + 1)) # direitapilha.append((i, j - 1)) # esquerda# 🌟 Exemplo de usoimagem = [[1, 1, 0, 0],[1, 0, 0, 1],[0, 0, 1, 1],[0, 1, 1, 1]]# Ponto de partidax_inicial, y_inicial = 0, 0nova_cor = 9# Aplicar Flood Fillflood_fill_dfs_iterativo(imagem, x_inicial, y_inicial, nova_cor)# 📤 Saída finalprint("Imagem após Flood Fill (DFS Iterativo com Pilha):")for linha in imagem:print(linha)'''Diferenciais:Evita estouro de pilha em matrizes grandes (ao contrário da versão recursiva).Pode ser facilmente adaptado para incluir mais direções (diagonais) ou restrições.'''
Exemplo: BFS com Fila
from collections import dequedef flood_fill_bfs(matrix, x, y, target_color, new_color):if matrix[x][y] != target_color:returnrows, cols = len(matrix), len(matrix[0])queue = deque([(x, y)])matrix[x][y] = new_colorwhile queue:i, j = queue.popleft()for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:ni, nj = i + dx, j + dyif 0 <= ni < rows and 0 <= nj < cols and matrix[ni][nj] == target_color:matrix[ni][nj] = new_colorqueue.append((ni, nj))
Exemplo: FloodFill Union-Find (Disjoint Set)
class UnionFind:def __init__(self, linhas, colunas):self.parent = {}self.rank = {}self.linhas = linhasself.colunas = colunasfor i in range(linhas):for j in range(colunas):self.parent[(i, j)] = (i, j)self.rank[(i, j)] = 0def find(self, pos):if self.parent[pos] != pos:self.parent[pos] = self.find(self.parent[pos]) # Path compressionreturn self.parent[pos]def union(self, pos1, pos2):root1 = self.find(pos1)root2 = self.find(pos2)if root1 == root2:returnif self.rank[root1] < self.rank[root2]:self.parent[root1] = root2else:self.parent[root2] = root1if self.rank[root1] == self.rank[root2]:self.rank[root1] += 1def flood_fill_union_find(imagem, nova_cor, start_x, start_y):linhas = len(imagem)colunas = len(imagem[0])uf = UnionFind(linhas, colunas)cor_original = imagem[start_x][start_y]# União de regiões conectadas da mesma corfor i in range(linhas):for j in range(colunas):for dx, dy in [(1, 0), (0, 1)]: # baixo e direitani, nj = i + dx, j + dyif 0 <= ni < linhas and 0 <= nj < colunas:if imagem[i][j] == imagem[ni][nj]:uf.union((i, j), (ni, nj))# Encontrar a raiz da célula de origemorigem_raiz = uf.find((start_x, start_y))# Pintar todas as células na mesma região conectadafor i in range(linhas):for j in range(colunas):if uf.find((i, j)) == origem_raiz:imagem[i][j] = nova_cor# 🌟 Exemplo de usoimagem = [[1, 1, 0, 0],[1, 0, 0, 1],[0, 0, 1, 1],[0, 1, 1, 1]]x_inicial, y_inicial = 0, 0nova_cor = 9flood_fill_union_find(imagem, nova_cor, x_inicial, y_inicial)# 📤 Saída finalprint("Imagem após Flood Fill (Union-Find):")for linha in imagem:print(linha)
Exemplo: Flood Fill com DFS Recursivo e Memoization
def flood_fill_memo(image, x, y, new_color):rows, cols = len(image), len(image[0])original_color = image[x][y]if original_color == new_color:return image # nada a fazervisited = set()def dfs(i, j):if (i < 0 or i >= rows orj < 0 or j >= cols or(i, j) in visited orimage[i][j] != original_color):returnvisited.add((i, j))image[i][j] = new_colordfs(i + 1, j)dfs(i - 1, j)dfs(i, j + 1)dfs(i, j - 1)dfs(x, y)return image# 🌟 Exemplo de usoimagem = [[1, 1, 0, 0],[1, 0, 0, 1],[0, 0, 1, 1],[0, 1, 1, 1]]nova_cor = 9x_inicial, y_inicial = 0, 0resultado = flood_fill_memo(imagem, x_inicial, y_inicial, nova_cor)print("Imagem após Flood Fill (Memoization):")for linha in resultado:print(linha)
Quando usar cada um?
✅ DFS Recursivo: quando a área a ser preenchida é pequena ou moderada.
✅ DFS Iterativo: quando há risco de recursão profunda (ex: imagens muito grandes).
✅ BFS: quando a ordem de preenchimento importa (por exemplo, espalhamento em ondas).
✅ Union-Find: quando você precisa fazer vários preenchimentos independentes de forma eficiente.
⚠️ Memoization: raramente necessária para Flood Fill puro, mas útil se há sobreposição com outro problema (como segmentação de regiões).
Conclusão
O Flood Fill é um problema clássico e prático que pode ser resolvido de várias formas, cada uma adequada a contextos específicos. Ele também é uma excelente porta de entrada para entender a diferença entre DFS, BFS, uso de memória e controle de recursão. Já algoritmos como Backtracking e Programação Dinâmica são poderosas ferramentas para problemas combinatórios e de otimização, e sua aplicação adequada depende do tipo de problema que se deseja resolver.