Algoritmos gulosos

Provas por troca para atividades, mochila fracionária, ordem de serviço e códigos de Huffman.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Com atividades A=[0,3)A=[0,3), B=[1,2)B=[1,2) e C=[2,4)C=[2,4), escolher a que começa mais cedo seleciona apenas AA. Escolher a que termina mais cedo seleciona BB e depois CC, com duas atividades em vez de uma. O exemplo mostra que a regra de escolha decide o resultado. Um algoritmo guloso fixa uma escolha sem a desfazer e resolve o que resta com a mesma regra.

Para justificar a regra, é preciso provar que existe uma solução ótima compatível com essa escolha e que o problema restante conserva a mesma estrutura. Uma instância onde a regra funciona não prova a regra. Uma instância onde falha refuta-a.

Seleção de atividades

Cada atividade tem início sis_i e fim fif_i, com si<fis_i<f_i. Uma pessoa faz atividades sem sobreposição e quer maximizar a quantidade. Usamos intervalos [si,fi)[s_i,f_i), pelo que uma atividade pode começar no instante em que a anterior termina.

A regra é ordenar por fim crescente e aceitar a primeira atividade compatível com a última escolhida. O fim mais cedo deixa pelo menos tanto tempo livre como qualquer alternativa para continuar, o que torna plausível que a escolha não prejudique o resto. A prova abaixo confirma essa intuição.

Para A=[0,3)A=[0,3), B=[1,2)B=[1,2), C=[2,4)C=[2,4), D=[3,5)D=[3,5) e E=[4,6)E=[4,6), a ordem por fim é B,A,C,D,EB,A,C,D,E. Escolhemos BB, rejeitamos AA por ser incompatível, escolhemos CC, rejeitamos DD e escolhemos EE. O resultado tem três atividades.

Prova por troca

Seja aa a atividade de fim mais cedo e OO uma solução ótima. Ordena as atividades de OO por tempo e chama bb à primeira. Como fa≤fbf_a\le f_b, substituir bb por aa mantém a compatibilidade com todas as atividades seguintes. A quantidade não muda. Existe, portanto, uma solução ótima que começa por aa.

Depois de escolher aa, o resto é o mesmo problema restrito às atividades que começam a partir de faf_a. Se o resto escolhido não fosse ótimo, podíamos substituí-lo por um melhor e melhorar a solução completa. As escolhas são ótimas por indução sobre o número de atividades.

A ordenação custa O(nlog⁡n)O(n\log n) e a passagem custa O(n)O(n). Se a entrada já estiver ordenada por fim, o custo é O(n)O(n). Empates no fim podem ser resolvidos por qualquer ordem válida, sem alterar a garantia.

Escolher o início mais cedo falha com [0,10)[0,10), [1,2)[1,2) e [2,3)[2,3): escolhe uma atividade em vez de duas. Escolher a duração mais curta também não tem esta prova. Se cada atividade tiver um valor diferente e o objetivo for somar valores, maximizar a quantidade é outro problema e pode exigir programação dinâmica.

Carga em ordem e blocos contíguos

Uma sequência de caixas com pesos [6,4,5,5][6,4,5,5] tem de ser carregada nessa ordem em camiões de capacidade 10. Só um camião recebe caixas de cada vez. Enchemos o primeiro com 6+46+4 e o segundo com 5+55+5: dois camiões. Mandar o primeiro embora depois do 6 deixaria três blocos [6][6], [4,5][4,5] e [5][5], com um camião a mais.

Assume 0<pi≤W0<p_i\le W para cada caixa, capacidade WW igual em todos os camiões, sem reordenar nem repartir caixas. A regra coloca no camião atual o maior prefixo do que falta cuja soma cabe. Quando a próxima caixa já não cabe, abre outro camião. Uma passagem custa Θ(n)\Theta(n) e guarda apenas a carga atual e o número de camiões, além das fronteiras se forem pedidas.

A prova acompanha a fronteira de cada bloco. Seja gjg_j a última caixa carregada pelos primeiros jj camiões gulosos e ojo_j a última de qualquer outra solução válida. O primeiro bloco guloso é o maior prefixo que cabe, logo g1≥o1g_1\ge o_1. Supondo gj−1≥oj−1g_{j-1}\ge o_{j-1}, há dois casos. Se gj−1≥ojg_{j-1}\ge o_j, o guloso já passou essa fronteira. Caso contrário, as caixas de gj−1+1g_{j-1}+1 até ojo_j são um sufixo do bloco jj da outra solução. Como os pesos são positivos, esse sufixo também cabe num camião. O guloso coloca pelo menos essas caixas e fica gj≥ojg_j\ge o_j.

Assim, se uma solução acaba em qq camiões, oq=no_q=n e o guloso também já acabou até ao camião qq. A regra minimiza o número de camiões. A hipótese da ordem é essencial: se pudermos reordenar [6,6,4,4][6,6,4,4], dois camiões com 6+46+4 chegam, enquanto a regra sobre essa ordem usa três.

Mochila fracionária

Aqui cada objeto é divisível. Com peso wi>0w_i>0, valor vi≥0v_i\ge0 e fração xi∈[0,1]x_i\in[0,1], queremos maximizar ∑ivixi\sum_i v_i x_i sujeito a ∑iwixi≤W\sum_i w_i x_i\le W. A densidade vi/wiv_i/w_i é o valor por unidade de peso.

Ordena por densidade decrescente. Leva cada objeto inteiro enquanto couber. Quando um não cabe, leva a fração que preenche a capacidade restante.

Com A=(6,12)A=(6,12), B=(5,9)B=(5,9), C=(5,9)C=(5,9) e capacidade 10, levamos AA inteiro e 4/54/5 de BB. O peso é 6+4=106+4=10 e o valor é 12+7,2=19,212+7{,}2=19{,}2.

A prova troca peso, não objetos inteiros. Se uma solução leva peso de um objeto menos denso enquanto ainda pode levar mais do mais denso, troca uma quantidade ε\varepsilon do primeiro para o segundo. A capacidade mantém-se e o valor não diminui. Repetindo, existe uma solução ótima que enche primeiro os objetos mais densos. A ordenação dá O(nlog⁡n)O(n\log n) tempo.

Na mochila 0-1, a troca fracionária é proibida. No mesmo exemplo, o guloso leva AA e fica com valor 12, enquanto levar BB e CC dá 18. A hipótese de divisibilidade era necessária.

Poucas exceções numa mochila 0-1

Na mochila 0-1, assume pesos positivos e valores não negativos. Se apenas kk objetos têm peso excecional e os restantes n−kn-k partilham o mesmo peso ww, enumera os 2k2^k subconjuntos excecionais. Rejeita os que excedem WW e completa cada um com os regulares de maior valor que cabem.

Ordena os regulares por valor decrescente uma vez e guarda somas de prefixos. Para capacidade restante RR, cabem q=min⁡(n−k,⌊R/w⌋)q=\min(n-k,\lfloor R/w\rfloor) objetos. A soma dos primeiros qq valores dá em O(1)O(1) a melhor conclusão desse ramo: trocar um escolhido por outro de maior valor conserva o peso e não piora o resultado. Com leitura da entrada e até kk operações por subconjunto, o custo é O(nlog⁡(n+1)+2k(k+1))O(n\log(n+1)+2^k(k+1)). Para kk fixo, o fator exponencial é constante.

Se os regulares tiverem valor igual, mas pesos diferentes, a escolha muda: ordena-os por peso crescente e leva o maior prefixo que cabe. Qualquer seleção de qq objetos pesa pelo menos tanto como os qq mais leves. Encontrar o maior prefixo pelas somas acumuladas custa O(log⁡(n+1))O(\log(n+1)) por ramo, por pesquisa binária. O total é O(nlog⁡(n+1)+2k(k+log⁡(n+1)))O(n\log(n+1)+2^k(k+\log(n+1))).

Quando peso e valor dos regulares são ambos iguais, já não há ordenação: calcula diretamente quantos cabem. A pesquisa pode custar O(2k(k+1))O(2^k(k+1)) depois de ler e confirmar os nn objetos. O custo total inclui Ω(n)\Omega(n). Uma tabela em WW não é necessária nestes casos. Se outra parte regular exigisse DP em capacidade binária, o resultado poderia ser apenas pseudopolinomial.

Trocos e condições do sistema

Escolher a maior moeda que cabe pode falhar mesmo com stock ilimitado e moeda de valor 1. Com denominações 1, 3 e 4 e alvo 6, escolhe 4+1+14+1+1, três moedas. O ótimo é 3+33+3, duas.

Um sistema é canónico se esse guloso devolve o mínimo de moedas para todo o alvo com stock ilimitado. Não basta as denominações estarem ordenadas. Mesmo um sistema canónico ilimitado pode precisar de outra estratégia quando há stocks finitos. Um algoritmo deve distinguir “não consegui completar com esta regra” de “não existe solução”.

Ordem de serviço

Há nn clientes disponíveis desde o início, um servidor e tempos positivos pip_i. Sem interrupções, queremos minimizar a soma dos tempos de conclusão. É equivalente a minimizar a espera total, porque a soma dos tempos de serviço é fixa.

A regra é servir por duração crescente. Para durações 4, 1 e 3, a ordem inicial tem conclusões 4, 5 e 8, soma 17. A ordem 1, 3, 4 tem conclusões 1, 4 e 8, soma 13.

Para provar, considera dois clientes adjacentes aa e bb, depois de um prefixo de duração PP. Na ordem a,ba,b, a contribuição é (P+pa)+(P+pa+pb)(P+p_a)+(P+p_a+p_b). Na ordem b,ab,a, é (P+pb)+(P+pb+pa)(P+p_b)+(P+p_b+p_a). A primeira menos a segunda é pa−pbp_a-p_b. Se pa>pbp_a>p_b, trocar reduz o total e não muda os clientes posteriores. Eliminar inversões produz a ordem crescente.

A duração total até terminar todos é sempre ∑pi\sum p_i, pelo que não é esse total que a regra minimiza. Com tempos de chegada, prioridades ou pesos de espera, o modelo e a prova mudam.

Huffman e códigos sem prefixos

Um código binário é sem prefixos se o código de nenhum símbolo é prefixo do código de outro. Podemos representá-lo por uma árvore binária com símbolos nas folhas. A profundidade de uma folha é o comprimento do seu código.

Para frequências fif_i, queremos minimizar ∑ifiℓi\sum_i f_i\ell_i, o número total de bits. Huffman retira repetidamente da heap os dois nós de menor frequência, cria um pai com a soma das frequências e volta a inseri-lo.

Com frequências a:5a:5, b:7b:7, c:10c:10, d:15d:15, junta 5 e 7 em 12. Junta 10 e 12 em 22. Junta 15 e 22 em 37.

Um código é d=0d=0, c=10c=10, a=110a=110, b=111b=111. O custo é 15⋅1+10⋅2+5⋅3+7⋅3=7115\cdot1+10\cdot2+5\cdot3+7\cdot3=71 bits. Somar os pesos dos nós criados também dá 12+22+37=7112+22+37=71, pois cada fusão acrescenta um bit aos símbolos abaixo dela.

Por que juntar os dois mínimos

Numa árvore ótima, escolhe duas folhas irmãs de profundidade máxima. Trocar símbolos para colocar ali os dois menos frequentes não aumenta o custo: se uma frequência menor passa para uma profundidade maior enquanto a maior sobe, a variação é não positiva. Existe, assim, uma árvore ótima em que os dois mínimos são irmãos.

Contrai os dois numa folha de frequência fa+fbf_a+f_b. O custo da árvore original é o custo da contraída mais fa+fbf_a+f_b. Se a árvore contraída não fosse ótima, substituí-la por uma melhor melhoraria a original. A fusão é uma escolha segura e reduz o problema.

A heap dá O(nlog⁡n)O(n\log n) tempo e O(n)O(n) espaço. Empates podem produzir códigos diferentes com o mesmo custo. Com um único símbolo, a árvore teria profundidade zero. Um formato de ficheiro real deve definir como representar repetições desse símbolo, por exemplo com um código de um bit e comprimento explícito. A árvore sozinha não define todo o formato de compressão.

Exercícios

Escolher e justificar atividades

As atividades são A=[0,4), B=[1,3), C=[3,5), D=[4,8), E=[5,7) e F=[7,9). Escolhe o maior número de atividades com a regra do fim mais cedo e justifica a primeira escolha por troca.

Pista

Ordena pelos fins e aceita inícios iguais ao fim anterior.

Mais uma pista

A primeira escolha é B. Depois C, E e F são consecutivamente compatíveis.

Solução

A ordem de fins é B,A,C,E,D,F. Escolhemos B,C,E,F, quatro atividades. Se uma solução ótima começa por outra atividade Q, substituir Q por B não prejudica as seguintes, pois B termina até ao fim de Q, ou seja conserva a quantidade e cria uma solução ótima compatível com B. Repetimos o argumento no conjunto de atividades que começam a partir de 3.

Confere a tua resposta:

  • Escolhi B,C,E,F.
  • Ordenei por fim, não início.
  • Justifiquei a troca e o subproblema restante.

Erros frequentes

Escolher A por começar primeiro perde oportunidades. Usar início estritamente maior que o fim rejeita atividades compatíveis sob os intervalos semiabertos do enunciado.

Voltar à explicação
Valor com objetos divisíveis

Uma mochila tem capacidade 8. Há um objeto de peso 4 e valor 12 e outro de peso 6 e valor 12, ambos divisíveis. Qual é o valor máximo?

Pista

As densidades são 3 e 2 unidades de valor por unidade de peso.

Mais uma pista

Depois do primeiro objeto, sobram 4 de capacidade, que levam 4/6 do segundo.

Solução

Leva o primeiro inteiro, valor 12, e dois terços do segundo, valor 8. O total é 20 e o peso é 8. Uma troca de peso do segundo para o primeiro nunca diminui valor enquanto o primeiro não estiver completo, o que justifica preencher nessa ordem.

Erros frequentes

Comparar só valores empata objetos com densidades diferentes. Na versão 0-1 não poderíamos levar dois terços e este valor seria inválido.

Voltar à explicação
Mochila com pesos excecionais

Uma mochila 0-1 tem capacidade 8. Os regulares são R1=(2,6), R2=(2,5) e R3=(2,2); os excecionais são E=(5,11) e F=(3,4). Cada par é peso e valor. Compara os quatro subconjuntos excecionais, completa cada um de forma ótima e encontra a melhor seleção. Generaliza para n-k objetos de igual peso positivo e k exceções, com valores não negativos e k constante.

Solução

Ordenamos os regulares por valor: R1,R2,R3.

ExcecionaisCapacidade restanteRegularesValor total
Nenhum8R1,R2,R313
E3R117
F5R1,R215
E,F0Nenhum15

Vence E+R1, peso 7 e valor 17. Entre objetos de igual peso, trocar um escolhido por outro de maior valor conserva a capacidade e melhora o resultado.

Ordenamos os regulares uma vez e guardamos somas de prefixos. Para cada seleção excecional viável, cabem q=min⁡(n−k,⌊Wresto/w⌋)q=\min(n-k,\lfloor W_{resto}/w\rfloor) regulares. Consultamos o valor dos primeiros q. Contando até k objetos por subconjunto, o custo é O(nlog⁡n+2k(k+1))O(n\log n+2^k(k+1)), além da reconstrução. Para k fixo, é polinomial, sem tabela em W.

Confere a tua resposta:

  • Comparei as quatro seleções excecionais.
  • Completei pelos maiores valores regulares.
  • Obtive E e R1, valor 17, e derivei o custo.
Voltar à explicação
Encher o bloco ou fechar mais cedo

Camiões de capacidade 10 recebem as caixas de pesos [6,6,4,4] na ordem dada. Cada camião recebe um bloco contíguo; nenhuma caixa pode ser repartida. Executa a regra de encher até a próxima caixa não caber. Fechar um camião mais cedo pode reduzir a quantidade necessária? Justifica para qualquer sequência de pesos positivos que caibam individualmente. Depois admite reordenação: qual é o mínimo nesta instância e que hipótese da prova desapareceu?

Pista
Compara a última caixa transportada depois de j camiões em duas soluções.
Solução

A regra produz [6], [6,4] e [4], três camiões. Para ordem fixa, seja gjg_j a fronteira gulosa e ojo_j a de uma solução válida. O maior prefixo admissível dá g1≥o1g_1\ge o_1. Se gj−1≥oj−1g_{j-1}\ge o_{j-1} e ainda não chegou a ojo_j, o segmento desde gj−1+1g_{j-1}+1 até ojo_j é um sufixo do bloco jj da outra solução. Esse sufixo tem peso no máximo 10. O guloso inclui pelo menos esse segmento, ficando gj≥ojg_j\ge o_j. Por indução, nenhuma solução acaba antes do guloso.

Com reordenação, [6,4] e [6,4] dão dois camiões. O peso total 20 exige pelo menos dois, logo são ótimos. Os blocos deixam de ser segmentos da mesma sequência; comparar as suas fronteiras pelo índice original já não justifica o descarte.

Confere a tua resposta:

  • Obtive os blocos [6], [6,4] e [4] na ordem dada.
  • Provei o avanço das fronteiras para ordem fixa.
  • Dei dois blocos de peso 10 quando a reordenação é permitida.
Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.