Introdução: Por Que Não Existem Loops Temporais em um Pedigree
\n\nImagine: você está escrevendo um programa para construir uma árvore genealógica. Tudo funciona perfeitamente até que um usuário adiciona uma relação: "Ivan é pai de Pedro, Pedro é pai de Ivan". O programa trava, a recursão entra em loop infinito e o usuário recebe um literal Stack Overflow. Soa familiar? Essa pergunta é uma das mais populares no Stack Overflow (milhares de visualizações), e por um bom motivo.
\n\nNeste artigo, vamos detalhar como ciclos em uma árvore genealógica funcionam internamente. Mostrarei como detectar e lidar com tais situações em C++, para que seu software de árvore genealógica não trave, mas funcione como um relógio. Você aprenderá não apenas teoria dos grafos, mas também terá código pronto para uso que pode adaptar imediatamente para seu projeto.
\n\n\n\n\nImportante: O artigo foi escrito para o site educacional pythonlib.ru, mas o código é inteiramente em C++ — um ótimo exemplo de como algoritmos transcendem as fronteiras das linguagens.
\n
Por Que Ciclos São um Problema para Software de Genealogia?
\n\nUma árvore genealógica é um grafo direcionado. Cada nó é uma pessoa, cada aresta é um relacionamento (pai → filho). Na vida real, ciclos não podem existir: ninguém pode ser seu próprio ancestral. Mas em um programa, um usuário pode acidentalmente criar tal vínculo:
\n\n- \n
- Alice é mãe de Bob \n
- Bob é pai de Alice \n
Se seu algoritmo de travessia (por exemplo, para exibir todos os ancestrais) não verificar ciclos, ele iterará infinitamente sobre Alice → Bob → Alice → Bob... Isso leva a:
\n- \n
- Congelamento do programa \n
- Estouro de pilha (stack overflow) \n
- Consumo infinito de memória \n
Portanto, a detecção de ciclos em um grafo é uma tarefa fundamental para qualquer aplicação séria de genealogia.
\n\nEstrutura de Dados: Como Armazenar uma Árvore Genealógica em C++
\n\nAntes de discutir algoritmos, precisamos decidir uma representação. Sugiro usar uma lista de adjacência — é eficiente e intuitiva.
\n\n#include <iostream>\n#include <unordered_map>\n#include <vector>\n#include <string>\n#include <stack>\n\nclass FamilyTree {\nprivate:\n // Cada pessoa é um nó com um nome único (ID)\n // Arestas: pai -> filho\n std::unordered_map<std::string, std::vector<std::string>> parentToChild;\n \npublic:\n void addRelation(const std::string& parent, const std::string& child) {\n parentToChild[parent].push_back(child);\n // Se o filho ainda não está no grafo, adiciona uma lista vazia\n if (parentToChild.find(child) == parentToChild.end()) {\n parentToChild[child] = {};\n }\n }\n \n // ... métodos para travessia\n};\n\n\nAqui, cada chave é o nome de uma pessoa, e o valor é uma lista de seus filhos. Esta é uma estrutura de dados para árvore genealógica mínima, mas funcional. Agora vamos para a parte principal.
\n\nComo Detectar um Ciclo: Algoritmo de Busca em Profundidade (DFS)
\n\nA maneira padrão de encontrar um ciclo em um grafo direcionado é uma Busca em Profundidade (DFS) modificada. A ideia é simples: marcamos os vértices com três cores:
\n- \n
- Branco (0) — não visitado \n
- Cinza (1) — em processo de travessia (na pilha de recursão atual) \n
- Preto (2) — totalmente processado \n
Se durante a DFS encontrarmos um vértice com cor cinza — isso é um ciclo. Vamos implementar isso em C++.
\n\n#include <unordered_map>\n#include <string>\n#include <vector>\n\nenum class Color { WHITE, GRAY, BLACK };\n\nclass CycleDetector {\nprivate:\n std::unordered_map<std::string, std::vector<std::string>>& graph;\n std::unordered_map<std::string, Color> color;\n \n bool