Algoritmos de Grafos Avançados

Explore algoritmos avançados de grafos incluindo componentes fortemente conexas, pontes e articulações, e coloração de grafos.

Componentes Fortemente Conexas

Uma componente fortemente conexa é um subgrafo maximal onde existe um caminho dirigido entre qualquer par de vértices.

Algoritmo de Tarjan

O algoritmo de Tarjan encontra todas as componentes fortemente conexas em O(V + E).

C
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
 
#define MAX_VERTICES 1000
 
// Estrutura para lista de adjacência
typedef struct No {
    int vertice;
    struct No* proximo;
} No;
 
typedef struct {
    No* adjacentes[MAX_VERTICES];
    int numVertices;
} GrafoDirigido;
 
// Estrutura para pilha
typedef struct {
    int itens[MAX_VERTICES];
    int topo;
} Pilha;
 
// Estrutura para o algoritmo de Tarjan
typedef struct {
    int low[MAX_VERTICES];          // Menor tempo de descoberta alcançável
    int descoberta[MAX_VERTICES];   // Tempo de descoberta
    bool naPilha[MAX_VERTICES];     // Se o vértice está na pilha
    Pilha pilha;                    // Pilha para DFS
    int tempo;                      // Contador de tempo global
    int numComponentes;             // Número de componentes encontradas
} EstadoTarjan;
 
// Inicializa grafo dirigido
GrafoDirigido* criarGrafoDirigido(int numVertices) {
    GrafoDirigido* grafo = (GrafoDirigido*)malloc(sizeof(GrafoDirigido));
    grafo->numVertices = numVertices;
    
    for (int i = 0; i < numVertices; i++) {
        grafo->adjacentes[i] = NULL;
    }
    
    return grafo;
}
 
// Adiciona aresta dirigida
void adicionarArestaDirigida(GrafoDirigido* grafo, int origem, int destino) {
    No* novoNo = (No*)malloc(sizeof(No));
    novoNo->vertice = destino;
    novoNo->proximo = grafo->adjacentes[origem];
    grafo->adjacentes[origem] = novoNo;
}
 
// Funções da pilha
void inicializarPilha(Pilha* pilha) {
    pilha->topo = -1;
}
 
void empilhar(Pilha* pilha, int item) {
    pilha->itens[++pilha->topo] = item;
}
 
int desempilhar(Pilha* pilha) {
    return pilha->itens[pilha->topo--];
}
 
bool pilhaVazia(Pilha* pilha) {
    return pilha->topo == -1;
}
 
// Função auxiliar do algoritmo de Tarjan
void tarjanDFS(GrafoDirigido* grafo, int u, EstadoTarjan* estado) {
    // Define descoberta e low
    estado->descoberta[u] = estado->low[u] = ++estado->tempo;
    empilhar(&estado->pilha, u);
    estado->naPilha[u] = true;
    
    // Percorre todos os adjacentes
    No* atual = grafo->adjacentes[u];
    while (atual != NULL) {
        int v = atual->vertice;
        
        if (estado->descoberta[v] == -1) {
            // Se v não foi visitado, faz DFS recursivo
            tarjanDFS(grafo, v, estado);
            
            // Atualiza low[u] se necessário
            estado->low[u] = (estado->low[v] < estado->low[u]) ? estado->low[v] : estado->low[u];
        } else if (estado->naPilha[v]) {
            // Se v está na pilha, atualiza low[u]
            estado->low[u] = (estado->descoberta[v] < estado->low[u]) ? estado->descoberta[v] : estado->low[u];
        }
        
        atual = atual->proximo;
    }
    
    // Se u é raiz de uma SCC, desempilha todos os vértices da componente
    if (estado->low[u] == estado->descoberta[u]) {
        printf("Componente Fortemente Conexa %d: {", ++estado->numComponentes);
        
        int v;
        bool primeiro = true;
        do {
            v = desempilhar(&estado->pilha);
            estado->naPilha[v] = false;
            
            if (!primeiro) printf(", ");
            printf("%d", v);
            primeiro = false;
        } while (v != u);
        
        printf("}\n");
    }
}
 
// Algoritmo de Tarjan para encontrar SCCs
void encontrarComponentesFortementeConexas(GrafoDirigido* grafo) {
    EstadoTarjan estado;
    
    // Inicializa estruturas
    inicializarPilha(&estado.pilha);
    estado.tempo = 0;
    estado.numComponentes = 0;
    
    for (int i = 0; i < grafo->numVertices; i++) {
        estado.descoberta[i] = -1;
        estado.low[i] = -1;
        estado.naPilha[i] = false;
    }
    
    printf("=== Encontrando Componentes Fortemente Conexas ===\n");
    
    // Executa DFS para cada vértice não visitado
    for (int i = 0; i < grafo->numVertices; i++) {
        if (estado.descoberta[i] == -1) {
            tarjanDFS(grafo, i, &estado);
        }
    }
    
    printf("Total de componentes encontradas: %d\n\n", estado.numComponentes);
}

Pontes e Articulações

Pontes são arestas cuja remoção aumenta o número de componentes conexas. Articulações são vértices com a mesma propriedade.

Implementação para Encontrar Pontes:

C
// Estrutura para grafo não-dirigido
typedef struct {
    No* adjacentes[MAX_VERTICES];
    int numVertices;
    int numArestas;
} GrafoNaoDirigido;
 
// Estado para algoritmo de pontes
typedef struct {
    int descoberta[MAX_VERTICES];
    int low[MAX_VERTICES];
    int pai[MAX_VERTICES];
    bool visitado[MAX_VERTICES];
    int tempo;
    int numPontes;
} EstadoPontes;
 
// Cria grafo não-dirigido
GrafoNaoDirigido* criarGrafoNaoDirigido(int numVertices) {
    GrafoNaoDirigido* grafo = (GrafoNaoDirigido*)malloc(sizeof(GrafoNaoDirigido));
    grafo->numVertices = numVertices;
    grafo->numArestas = 0;
    
    for (int i = 0; i < numVertices; i++) {
        grafo->adjacentes[i] = NULL;
    }
    
    return grafo;
}
 
// Adiciona aresta não-dirigida
void adicionarArestaNaoDirigida(GrafoNaoDirigido* grafo, int u, int v) {
    // Adiciona v à lista de u
    No* novoNo1 = (No*)malloc(sizeof(No));
    novoNo1->vertice = v;
    novoNo1->proximo = grafo->adjacentes[u];
    grafo->adjacentes[u] = novoNo1;
    
    // Adiciona u à lista de v
    No* novoNo2 = (No*)malloc(sizeof(No));
    novoNo2->vertice = u;
    novoNo2->proximo = grafo->adjacentes[v];
    grafo->adjacentes[v] = novoNo2;
    
    grafo->numArestas++;
}
 
// Função auxiliar para encontrar pontes
void pontesDFS(GrafoNaoDirigido* grafo, int u, EstadoPontes* estado) {
    estado->visitado[u] = true;
    estado->descoberta[u] = estado->low[u] = ++estado->tempo;
    
    // Percorre todos os vértices adjacentes
    No* atual = grafo->adjacentes[u];
    while (atual != NULL) {
        int v = atual->vertice;
        
        if (!estado->visitado[v]) {
            estado->pai[v] = u;
            pontesDFS(grafo, v, estado);
            
            // Atualiza low[u]
            estado->low[u] = (estado->low[v] < estado->low[u]) ? estado->low[v] : estado->low[u];
            
            // Se low[v] > descoberta[u], então (u,v) é uma ponte
            if (estado->low[v] > estado->descoberta[u]) {
                printf("Ponte encontrada: (%d, %d)\n", u, v);
                estado->numPontes++;
            }
        } else if (v != estado->pai[u]) {
            // Aresta de retorno - atualiza low[u]
            estado->low[u] = (estado->descoberta[v] < estado->low[u]) ? estado->descoberta[v] : estado->low[u];
        }
        
        atual = atual->proximo;
    }
}
 
// Encontra todas as pontes do grafo
void encontrarPontes(GrafoNaoDirigido* grafo) {
    EstadoPontes estado;
    
    // Inicializa estruturas
    estado.tempo = 0;
    estado.numPontes = 0;
    
    for (int i = 0; i < grafo->numVertices; i++) {
        estado.descoberta[i] = -1;
        estado.low[i] = -1;
        estado.pai[i] = -1;
        estado.visitado[i] = false;
    }
    
    printf("=== Encontrando Pontes ===\n");
    
    // Executa DFS para cada componente conexa
    for (int i = 0; i < grafo->numVertices; i++) {
        if (!estado.visitado[i]) {
            pontesDFS(grafo, i, &estado);
        }
    }
    
    printf("Total de pontes encontradas: %d\n\n", estado.numPontes);
}
 
// Estado para algoritmo de articulações
typedef struct {
    int descoberta[MAX_VERTICES];
    int low[MAX_VERTICES];
    int pai[MAX_VERTICES];
    bool visitado[MAX_VERTICES];
    bool articulacao[MAX_VERTICES];
    int tempo;
    int numArticulacoes;
} EstadoArticulacoes;
 
// Função auxiliar para encontrar articulações
void articulacoesDFS(GrafoNaoDirigido* grafo, int u, EstadoArticulacoes* estado) {
    int filhos = 0;
    
    estado->visitado[u] = true;
    estado->descoberta[u] = estado->low[u] = ++estado->tempo;
    
    No* atual = grafo->adjacentes[u];
    while (atual != NULL) {
        int v = atual->vertice;
        
        if (!estado->visitado[v]) {
            filhos++;
            estado->pai[v] = u;
            articulacoesDFS(grafo, v, estado);
            
            // Atualiza low[u]
            estado->low[u] = (estado->low[v] < estado->low[u]) ? estado->low[v] : estado->low[u];
            
            // Casos para articulação:
            // 1. u é raiz da DFS e tem mais de um filho
            if (estado->pai[u] == -1 && filhos > 1) {
                estado->articulacao[u] = true;
            }
            
            // 2. u não é raiz e low[v] >= descoberta[u]
            if (estado->pai[u] != -1 && estado->low[v] >= estado->descoberta[u]) {
                estado->articulacao[u] = true;
            }
        } else if (v != estado->pai[u]) {
            // Aresta de retorno
            estado->low[u] = (estado->descoberta[v] < estado->low[u]) ? estado->descoberta[v] : estado->low[u];
        }
        
        atual = atual->proximo;
    }
}
 
// Encontra todas as articulações do grafo
void encontrarArticulacoes(GrafoNaoDirigido* grafo) {
    EstadoArticulacoes estado;
    
    // Inicializa estruturas
    estado.tempo = 0;
    estado.numArticulacoes = 0;
    
    for (int i = 0; i < grafo->numVertices; i++) {
        estado.descoberta[i] = -1;
        estado.low[i] = -1;
        estado.pai[i] = -1;
        estado.visitado[i] = false;
        estado.articulacao[i] = false;
    }
    
    printf("=== Encontrando Articulações ===\n");
    
    // Executa DFS para cada componente conexa
    for (int i = 0; i < grafo->numVertices; i++) {
        if (!estado.visitado[i]) {
            articulacoesDFS(grafo, i, &estado);
        }
    }
    
    printf("Articulações encontradas: {");
    bool primeiro = true;
    for (int i = 0; i < grafo->numVertices; i++) {
        if (estado.articulacao[i]) {
            if (!primeiro) printf(", ");
            printf("%d", i);
            estado.numArticulacoes++;
            primeiro = false;
        }
    }
    printf("}\n");
    printf("Total de articulações: %d\n\n", estado.numArticulacoes);
}
 
// Coloração de Grafos
typedef struct {
    int cores[MAX_VERTICES];
    int numCores;
    bool usado[MAX_VERTICES];
} EstadoColoracao;
 
// Algoritmo guloso para coloração de grafos
int coloracaoGulosa(GrafoNaoDirigido* grafo) {
    EstadoColoracao estado;
    
    // Inicializa cores
    for (int i = 0; i < grafo->numVertices; i++) {
        estado.cores[i] = -1;
    }
    
    printf("=== Coloração Gulosa do Grafo ===\n");
    
    // Atribui cor 0 ao primeiro vértice
    estado.cores[0] = 0;
    estado.numCores = 1;
    
    printf("Vértice 0: Cor 0\n");
    
    // Atribui cores aos demais vértices
    for (int u = 1; u < grafo->numVertices; u++) {
        // Marca cores usadas pelos adjacentes
        for (int i = 0; i < grafo->numVertices; i++) {
            estado.usado[i] = false;
        }
        
        No* atual = grafo->adjacentes[u];
        while (atual != NULL) {
            int v = atual->vertice;
            if (estado.cores[v] != -1) {
                estado.usado[estado.cores[v]] = true;
            }
            atual = atual->proximo;
        }
        
        // Encontra a menor cor disponível
        int cor;
        for (cor = 0; cor < grafo->numVertices; cor++) {
            if (!estado.usado[cor]) {
                break;
            }
        }
        
        estado.cores[u] = cor;
        if (cor >= estado.numCores) {
            estado.numCores = cor + 1;
        }
        
        printf("Vértice %d: Cor %d\n", u, cor);
    }
    
    printf("\nColoração completa:\n");
    for (int i = 0; i < grafo->numVertices; i++) {
        printf("Vértice %d: Cor %d\n", i, estado.cores[i]);
    }
    printf("Número cromático ≤ %d\n\n", estado.numCores);
    
    return estado.numCores;
}
 
// Verifica se a coloração é válida
bool verificarColoracao(GrafoNaoDirigido* grafo, int cores[]) {
    for (int u = 0; u < grafo->numVertices; u++) {
        No* atual = grafo->adjacentes[u];
        while (atual != NULL) {
            int v = atual->vertice;
            if (cores[u] == cores[v]) {
                printf("ERRO: Vértices adjacentes %d e %d têm a mesma cor %d!\n", u, v, cores[u]);
                return false;
            }
            atual = atual->proximo;
        }
    }
    return true;
}
 
// Algoritmo de Welsh-Powell para coloração
int coloracaoWelshPowell(GrafoNaoDirigido* grafo) {
    // Array para armazenar graus dos vértices
    typedef struct {
        int vertice;
        int grau;
    } VerticeGrau;
    
    VerticeGrau vertices[MAX_VERTICES];
    int cores[MAX_VERTICES];
    
    printf("=== Coloração Welsh-Powell ===\n");
    
    // Calcula graus
    for (int i = 0; i < grafo->numVertices; i++) {
        vertices[i].vertice = i;
        vertices[i].grau = 0;
        cores[i] = -1;
        
        No* atual = grafo->adjacentes[i];
        while (atual != NULL) {
            vertices[i].grau++;
            atual = atual->proximo;
        }
    }
    
    // Ordena vértices por grau (decrescente) - bubble sort simples
    for (int i = 0; i < grafo->numVertices - 1; i++) {
        for (int j = 0; j < grafo->numVertices - i - 1; j++) {
            if (vertices[j].grau < vertices[j + 1].grau) {
                VerticeGrau temp = vertices[j];
                vertices[j] = vertices[j + 1];
                vertices[j + 1] = temp;
            }
        }
    }
    
    printf("Ordem de coloração (por grau decrescente):\n");
    for (int i = 0; i < grafo->numVertices; i++) {
        printf("Vértice %d (grau %d)\n", vertices[i].vertice, vertices[i].grau);
    }
    printf("\n");
    
    int numCores = 0;
    bool usado[MAX_VERTICES];
    
    // Colore vértices na ordem de grau decrescente
    for (int i = 0; i < grafo->numVertices; i++) {
        int u = vertices[i].vertice;
        
        // Inicializa cores usadas
        for (int k = 0; k < MAX_VERTICES; k++) {
            usado[k] = false;
        }
        
        // Marca cores usadas pelos adjacentes
        No* atual = grafo->adjacentes[u];
        while (atual != NULL) {
            int v = atual->vertice;
            if (cores[v] != -1) {
                usado[cores[v]] = true;
            }
            atual = atual->proximo;
        }
        
        // Encontra menor cor disponível
        int cor;
        for (cor = 0; cor < MAX_VERTICES; cor++) {
            if (!usado[cor]) {
                break;
            }
        }
        
        cores[u] = cor;
        if (cor >= numCores) {
            numCores = cor + 1;
        }
        
        printf("Vértice %d: Cor %d\n", u, cor);
    }
    
    printf("Número de cores usado: %d\n\n", numCores);
    return numCores;
}
 
// Exemplos e função main
void exemploComponentesFortementeConexas() {
    printf("╔══════════════════════════════════════════════════════════════╗\n");
    printf("║            EXEMPLO: COMPONENTES FORTEMENTE CONEXAS          ║\n");
    printf("╚══════════════════════════════════════════════════════════════╝\n\n");
    
    GrafoDirigido* grafo = criarGrafoDirigido(5);
    
    // Cria um grafo com múltiplas SCCs
    adicionarArestaDirigida(grafo, 1, 0);
    adicionarArestaDirigida(grafo, 0, 2);
    adicionarArestaDirigida(grafo, 2, 1);
    adicionarArestaDirigida(grafo, 0, 3);
    adicionarArestaDirigida(grafo, 3, 4);
    
    printf("Grafo dirigido:\n");
    printf("1 → 0 → 2 → 1 (ciclo)\n");
    printf("0 → 3 → 4\n\n");
    
    encontrarComponentesFortementeConexas(grafo);
    
    free(grafo);
}
 
void exemploPontesArticulacoes() {
    printf("╔══════════════════════════════════════════════════════════════╗\n");
    printf("║               EXEMPLO: PONTES E ARTICULAÇÕES                ║\n");
    printf("╚══════════════════════════════════════════════════════════════╝\n\n");
    
    GrafoNaoDirigido* grafo = criarGrafoNaoDirigido(7);
    
    // Cria um grafo com pontes e articulações
    adicionarArestaNaoDirigida(grafo, 0, 1);
    adicionarArestaNaoDirigida(grafo, 0, 2);
    adicionarArestaNaoDirigida(grafo, 1, 2);
    adicionarArestaNaoDirigida(grafo, 1, 3);  // Ponte
    adicionarArestaNaoDirigida(grafo, 3, 4);
    adicionarArestaNaoDirigida(grafo, 4, 5);
    adicionarArestaNaoDirigida(grafo, 4, 6);
    adicionarArestaNaoDirigida(grafo, 5, 6);
    
    printf("Estrutura do grafo:\n");
    printf("Triângulo: 0-1-2-0\n");
    printf("Ponte: 1-3\n");
    printf("Caminho: 3-4\n");
    printf("Triângulo: 4-5-6-4\n\n");
    
    encontrarPontes(grafo);
    encontrarArticulacoes(grafo);
    
    free(grafo);
}
 
void exemploColoracao() {
    printf("╔══════════════════════════════════════════════════════════════╗\n");
    printf("║                  EXEMPLO: COLORAÇÃO                         ║\n");
    printf("╚══════════════════════════════════════════════════════════════╝\n\n");
    
    GrafoNaoDirigido* grafo = criarGrafoNaoDirigido(5);
    
    // Cria um grafo que requer múltiplas cores
    adicionarArestaNaoDirigida(grafo, 0, 1);
    adicionarArestaNaoDirigida(grafo, 0, 2);
    adicionarArestaNaoDirigida(grafo, 1, 2);
    adicionarArestaNaoDirigida(grafo, 1, 3);
    adicionarArestaNaoDirigida(grafo, 2, 3);
    adicionarArestaNaoDirigida(grafo, 3, 4);
    
    printf("Estrutura do grafo:\n");
    printf("Clique K4: 0-1-2-3 (todos conectados)\n");
    printf("Extensão: 3-4\n\n");
    
    printf("Comparando algoritmos de coloração:\n\n");
    
    int cores1 = coloracaoGulosa(grafo);
    int cores2 = coloracaoWelshPowell(grafo);
    
    printf("Resumo:\n");
    printf("• Coloração gulosa: %d cores\n", cores1);
    printf("• Welsh-Powell: %d cores\n", cores2);
    printf("• Número cromático teórico (clique K4): 4 cores\n\n");
    
    free(grafo);
}
 
int main() {
    printf("╔══════════════════════════════════════════════════════════════╗\n");
    printf("║              ALGORITMOS DE GRAFOS AVANÇADOS                 ║\n");
    printf("╚══════════════════════════════════════════════════════════════╝\n\n");
    
    exemploComponentesFortementeConexas();
    printf("\n");
    exemploPontesArticulacoes();
    printf("\n");
    exemploColoracao();
    
    printf("╔══════════════════════════════════════════════════════════════╗\n");
    printf("║                     RESUMO TEÓRICO                          ║\n");
    printf("╚══════════════════════════════════════════════════════════════╝\n\n");
    
    printf("📊 COMPLEXIDADES:\n");
    printf("• Tarjan (SCCs): O(V + E)\n");
    printf("• Pontes e Articulações: O(V + E)\n");
    printf("• Coloração Gulosa: O(V²)\n");
    printf("• Welsh-Powell: O(V² + V log V)\n\n");
    
    printf("🎯 APLICAÇÕES:\n");
    printf("• SCCs: Análise de dependências, detecção de deadlocks\n");
    printf("• Pontes: Redes críticas, pontos de falha\n");
    printf("• Articulações: Análise de vulnerabilidade\n");
    printf("• Coloração: Alocação de registradores, scheduling\n\n");
    
    printf("⚡ PROPRIEDADES:\n");
    printf("• SCCs formam um DAG quando contraídas\n");
    printf("• Pontes conectam diferentes componentes biconexas\n");
    printf("• Articulações aumentam número de componentes se removidas\n");
    printf("• Coloração é NP-completo, algoritmos são heurísticos\n");
    
    return 0;
}

Análise Detalhada dos Algoritmos

Algoritmo de Tarjan (SCCs)

  • Princípio: DFS com pilha e valores low
  • Invariante: low[u] = menor tempo de descoberta alcançável de u
  • Detecção: SCC encontrada quando low[u] == descoberta[u]

Algoritmo de Pontes

  • Condição: Aresta (u,v) é ponte se low[v] > descoberta[u]
  • Significa: Não há aresta de retorno que "pule" sobre (u,v)
  • Aplicação: Identificação de conexões críticas

Algoritmo de Articulações

  • Caso 1: Raiz da DFS com múltiplos filhos
  • Caso 2: low[v] >= descoberta[u] para não-raiz
  • Intuição: Remoção desconecta componentes

Coloração de Grafos

  • Problema: NP-completo para coloração ótima
  • Heurísticas: Gulosa, Welsh-Powell, algoritmos evolutivos
  • Limitante: Δ + 1 cores (Δ = grau máximo)

Aplicações Avançadas

1. Análise de Redes Sociais

  • SCCs: Identificação de grupos fortemente conectados
  • Pontes: Usuários que conectam comunidades diferentes
  • Coloração: Classificação de usuários em grupos

2. Análise de Sistemas

  • SCCs: Detecção de dependências circulares em software
  • Articulações: Identificação de componentes críticos
  • Coloração: Alocação de recursos sem conflitos

3. Bioinformática

  • SCCs: Redes de regulação gênica
  • Pontes: Conexões evolutivas críticas
  • Coloração: Classificação de proteínas

4. Redes de Transporte

  • Pontes: Identificação de gargalos
  • Articulações: Pontos críticos de infraestrutura
  • Coloração: Alocação de rotas sem interferência

Esta página foi útil?

Seu feedback ajuda a melhorar a documentação

Encontrou um erro ou quer contribuir?

Ajude a melhorar esta documentação editando-a no GitHub.

Editar no GitHub