Módulo 3 · Análise assintótica de algoritmos

Análise amortizada

Aula 3.3 · cerca de 15 minutos

Algumas operações são caras de vez em quando e baratas quase sempre. A análise amortizada mede o custo médio por operação no pior caso de uma sequência inteira, sem nenhuma probabilidade envolvida.

Array dinâmico (list do Python, ArrayList, Vec)

Quando o array enche, alocamos o dobro e copiamos tudo. Uma cópia custa O(n), mas quanto custam n inserções no total?

Cópias: 1 + 2 + 4 + … + n/2 + n  <  2n
Custo total de n appends  ≤  n + 2n  =  3n  ⇒  O(1) amortizado

Se o array crescesse em +k posições fixas em vez de dobrar, as cópias somariam k + 2k + … ≈ n²/(2k): O(n) amortizado por append. O crescimento geométrico é o que faz funcionar.

import sys
xs = []
ultimo = sys.getsizeof(xs)
for i in range(64):
    xs.append(i)
    agora = sys.getsizeof(xs)
    if agora != ultimo:
        print(f"len={len(xs):3}  bytes={agora}")   # realocações cada vez mais espaçadas
        ultimo = agora
Dica: O método do potencial formaliza isso: cada operação barata “deposita” crédito, e a operação cara gasta o crédito acumulado. Se o saldo nunca fica negativo, o custo amortizado é um limite válido.

Exercício 1

Um contador binário de k bits é incrementado n vezes. Qual é o custo amortizado por incremento, contando os bits invertidos?