Algoritmos Gananciosos: Fundamentos e Aplicações em Otimização

Propriedades Fundamentais

Algoritmos gananciosos são aplicáveis apenas a problemas que demonstram duas características críticas. A propriedade de escolha gananciosa exige que decisões localmente ótimas conduzam inevitavelmente à solução global ótima. Paralelamente, a estrutura ótima de subproblemas implica que a solução ótima do problema original contenha soluções ótimas para seus subproblemas derivados.

Mecanismo Operacional

Diferente da programação dinâmica, que integra soluções de subproblemas anteriores, a abordagem gananciosa toma decisões imediatas baseadas apenas no estado atual. Este método reduz progressivamente o escopo do problema sem revisitar escolhas passadas, visando alcançar a otimização global através de sucessivas otimizações locais. Ambos os paradigmas dependem da estrutura ótima de subproblemas, mas divergem na forma de exploração do espaço de soluções.

Estratégia de Implementação

A resolução de problemas por algoritmos gananciosos envolve três fases essenciais. Primeiro, a análise do problema define estados, objetivos de otimização e restrições. Segundo, a formulação da estratégia gananciosa deetrmina como cada decisão local reduzirá o problema. Terceiro, a validação matemática comprova que a estratégia satisfaz as propriedades fundamentais, frequentemente utilizando indução ou prova por contradição. A fase crítica reside na concepção da estratégia, que pode variar desde soluções intuitivas até abordagenns não óbvias que exigem experiência algorítmica.

Exemplos Práticos

Minimização de Moedas no Troco

Para calcular o menor número de moedas (valores 1, 5, 10, 20, 50) necessárias para dar troco de valor T, seleciona-se iterativamente a maior denominação possível até zerar o valor restente.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    const vector<int> valores = {50, 20, 10, 5, 1};
    int valor_compra;
    cin >> valor_compra;
    int restante = 100 - valor_compra;
    int quantidade = 0;
    
    for (int moeda : valores) {
        quantidade += restante / moeda;
        restante %= moeda;
    }
    cout << quantidade << endl;
    return 0;
}

Cobertura Mínima de Segmentos

Dado um segmento [S, T] e N intervalos, busca-se cobrir totalmente [S, T] com o menor número de intervalos. A solução ordena os intervalos pelo ponto inicial e seleciona iterativamente o intervalo que mais se estende à direita a partir da posição atual.

#include <iostream>
#include <algorithm>
using namespace std;

struct Intervalo {
    int inicio, fim;
};

int main() {
    int s, t, n;
    cin >> s >> t >> n;
    vector<Intervalo> intervalos(n);
    
    for (int i = 0; i < n; i++) 
        cin >> intervalos[i].inicio >> intervalos[i].fim;
    
    sort(intervalos.begin(), intervalos.end(), 
        [](const Intervalo& a, const Intervalo& b) {
            return a.inicio < b.inicio;
        });
    
    int cobertura_atual = s;
    int indice = 0;
    int total_intervalos = 0;
    bool completo = false;

    while (cobertura_atual < t && indice < n) {
        int max_fim = cobertura_atual;
        // Encontra intervalo com maior extensão à direita
        while (indice < n && intervalos[indice].inicio <= cobertura_atual) {
            if (intervalos[indice].fim > max_fim) 
                max_fim = intervalos[indice].fim;
            indice++;
        }
        if (max_fim == cobertura_atual) break;
        
        total_intervalos++;
        cobertura_atual = max_fim;
        if (cobertura_atual >= t) {
            completo = true;
            break;
        }
    }
    cout << (completo ? total_intervalos : -1) << endl;
    return 0;
}

Localização Ótima de Centro de Distribuição

Para minimizar a distância total entre um depósito e N lojas posicionadas em uma reta, a mediana das coordenadas das lojas fornece a solução ótima. Este resultado explora a propriedade matemática de que a mediana minimiza a soma das distâncias absolutas.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    int quantidade_lojas;
    cin >> quantidade_lojas;
    vector<int> posicoes(quantidade_lojas);
    
    for (int i = 0; i < quantidade_lojas; i++)
        cin >> posicoes[i];
    
    sort(posicoes.begin(), posicoes.end());
    int mediana = posicoes[quantidade_lojas / 2];
    long long distancia_total = 0;
    
    for (int pos : posicoes)
        distancia_total += abs(pos - mediana);
    
    cout << distancia_total << endl;
    return 0;
}

Tags: greedy-algorithms algorithm-design c-plus-plus

Publicado em 8-12 11:50