Trilha de aprendizado · Nível 14 · Tutorial 1

Estimar tempo e memória com notação O grande

Estime como o trabalho e a memória necessária crescem com o tamanho da entrada, distinguindo limites assintóticos de tempos medidos em uma máquina.

  • Nível: Intermediário
  • Duração: 25 min
  • 9 passos
Estimar tempo e memória com notação O grande

O que você vai percorrer

  1. Definir o tamanho da entrada e o trabalho contado Escolha o que representa o tamanho de uma entrada e declare qual trabalho será contado antes de comparar algoritmos. 2 min
  2. Interpretar O grande como limite de crescimento Simplifique contagens de trabalho e use O grande para descrever crescimento, não duração exata. 2 min
  3. Reconhecer as principais ordens de crescimento Compare ordens de crescimento comuns e entenda por que reduções sucessivas pela metade produzem um custo logarítmico. 3 min
  4. Compor custos de trechos e laços Some custos de blocos e conte as execuções reais dos laços para classificar trechos de código. 3 min
  5. Incluir o trabalho das operações do Python Veja como chamadas curtas podem esconder percursos, cópias e ordenação ao estimar o tempo de uma função. 2 min
  6. Contar chamadas e profundidade na recursão Estime o trabalho total e a profundidade máxima de padrões recursivos simples, distinguindo a árvore completa de chamadas do caminho que fica ativo. 3 min
  7. Estimar a memória necessária ao mesmo tempo Separe entrada, saída e memória auxiliar para estimar quanto armazenamento uma função precisa manter simultaneamente. 3 min
  8. Distinguir pior caso, caso médio e amortização Qualifique custos de busca e de inserção ao final de listas, separando pior caso, caso médio e custo amortizado. 4 min
  9. Aplicar um roteiro completo de análise Integre a análise de tempo e memória em exemplos completos e separe estimativas assintóticas de medições reais. 4 min

O que você vai aprender

  • Definir o tamanho da entrada e as operações relevantes para analisar uma função.
  • Classificar percursos, laços aninhados e recursões simples por sua ordem de crescimento.
  • Estimar memória auxiliar, incluindo coleções temporárias e pilha de chamadas.
  • Distinguir pior caso, caso médio e custo amortizado sem interpretar O grande como duração exata.

Antes de começar

  • Construir contadores e acumuladores em laços
  • Consultar e atualizar coleções aninhadas
  • Resolver problemas com recursão e casos base
  • Controlar referências e cópias de coleções
  • Operações básicas de listas, dicionários, conjuntos e ordenação

Passo 1 de 9

Definir o tamanho da entrada e o trabalho contado

Escolha o que representa o tamanho de uma entrada e declare qual trabalho será contado antes de comparar algoritmos.

O tamanho depende do problema

Escolha a quantidade que cresce

Antes de analisar uma função, defina o tamanho relevante da entrada. Ele costuma ser uma quantidade, não o valor numérico de um argumento.

  • Uma lista: quantidade de elementos.
  • Um texto: quantidade de caracteres.
  • Uma tabela: quantidade de registros.

Por exemplo, para range(limite), se o trabalho percorre os valores produzidos, importa quantos valores são visitados. Já um argumento chamado idade=80 não significa, por si só, que a entrada tem tamanho 80.

Dimensões independentes

Duas coleções podem crescer separadamente: use uma variável para cada dimensão.

Diagrama de duas coleções independentes: uma lista curta com n elementos e outra lista mais longa com m elementos; setas destacam que n e m não precisam ter o mesmo valor.

Use n para a quantidade de itens de uma coleção e m para a de outra. Não suponha que elas crescem juntas.

Declare o trabalho observado

Conte uma operação representativa

A análise começa escolhendo uma operação que represente o trabalho, como uma comparação ou a atualização de um contador. Declare também a hipótese usada: neste início, considere que operações sobre valores de tamanho limitado têm custo constante.

Essa hipótese não vale automaticamente para todo objeto: inteiros enormes, textos muito longos ou métodos personalizados podem exigir uma análise diferente.

Exemplo: uma comparação por item

Considere n como a quantidade de valores em numeros.

python
def contar_positivos(numeros):
    total = 0
    for numero in numeros:
        if numero > 0:      # operação observada: comparação
            total += 1      # atualização do contador
    return total

Dica

Três perguntas diferentes

Contar comparações estima trabalho. Estimar armazenamento pergunta quantos valores ficam guardados ao mesmo tempo. Medir segundos ou bytes depende da máquina, da implementação e das entradas reais. Vamos manter essas perguntas separadas.

Pratique: escolha as dimensões

Associe o problema à dimensão adequada

Relacione cada situação à forma adequada de representar o tamanho da entrada.

Toque em um item e depois no par correspondente.

Declare uma hipótese

Para a função contar_positivos, escreva qual operação você contaria e uma hipótese de custo que está adotando.

Escreva pelo menos 20 caracteres (0/20).

Passo 2 de 9

Interpretar O grande como limite de crescimento

Simplifique contagens de trabalho e use O grande para descrever crescimento, não duração exata.

Um limite para entradas grandes

O que O grande expressa

Depois de contar o trabalho de uma função, usamos O grande para descrever como esse trabalho pode crescer quando a entrada fica suficientemente grande.

Dizer que um custo é O(f(n)) significa que, a partir de certo tamanho de entrada, ele não ultrapassa uma constante multiplicada por f(n). A constante representa detalhes como quantas operações semelhantes ocorrem em cada etapa; ela não muda a tendência de crescimento.

A ideia de limite superior

A curva de trabalho pode variar nos tamanhos pequenos, mas fica abaixo de uma versão ampliada da função de referência depois de certo ponto.

Gráfico conceitual: uma curva de custo irregular inicialmente passa a permanecer abaixo de uma curva de referência linear ampliada para entradas grandes.

O foco está no crescimento para entradas grandes, não em uma contagem exata para cada valor de n.

Simplificar a contagem

Exemplo

Constantes e termos menores

Se uma contagem de trabalho é 3n + 12, para entradas grandes o termo 3n domina o 12, e o fator 3 não altera a ordem de crescimento. Portanto:

3n + 12 → O(n)

Se a contagem é 2n² + 7n + 4, o termo quadrático cresce mais rápido que os demais:

2n² + 7n + 4 → O(n²)

Uma quantidade fixa, como 25, não cresce com n:

25 → O(1)

Dica

Regra prática

Em expressões de uma variável, mantenha o termo que mais cresce e descarte seus fatores constantes. Essa é uma simplificação assintótica, não uma afirmação de que os outros termos nunca são executados.

Pratique a simplificação

Termo dominante

A contagem 5n² + 20n + 9 é classificada como ____.

O que a notação não promete

Mais informativo não é o mesmo que exato

Um custo O(n) também está dentro de O(n²), pois uma função linear acaba ficando abaixo de alguma constante vezes n². Ainda assim, O(n) é o limite superior mais informativo nesse caso.

O grande também não informa automaticamente se a análise é de pior caso, nem converte custo em segundos ou bytes exatos. Máquina, implementação, dados e tamanhos pequenos influenciam medições reais.

Interpretação correta

Se uma função é O(n) e outra é O(n²), a primeira obrigatoriamente será mais rápida em qualquer computador e para toda entrada pequena.

Passo 3 de 9

Reconhecer as principais ordens de crescimento

Compare ordens de crescimento comuns e entenda por que reduções sucessivas pela metade produzem um custo logarítmico.

Seis tendências para comparar

A ordem importa quando a entrada cresce

Além de O(1), O(n) e O(n²), use estas referências para comparar tendências:

  • O(1): trabalho praticamente estável.
  • O(log n): poucas etapas extras conforme a entrada cresce.
  • O(n): uma quantidade de trabalho proporcional à entrada.
  • O(n log n): um percurso de n unidades com um fator logarítmico.
  • O(n²): trabalho parecido com comparar muitas combinações de pares.
  • O(2ⁿ): o trabalho aproximadamente dobra ao acrescentar uma unidade à entrada.

Elas descrevem crescimento assintótico, não segundos medidos no seu computador.

Como as curvas se separam

Observe que as curvas podem parecer próximas em entradas pequenas, mas se afastam muito à medida que n aumenta.

Gráfico comparando as curvas O(1), O(log n), O(n), O(n log n), O(n²) e O(2ⁿ), com a exponencial crescendo mais rapidamente e a constante permanecendo horizontal.

Para entradas grandes: O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ).

Por que reduzir pela metade é logarítmico

Conte as reduções, não os elementos

Quando cada etapa reduz o tamanho restante por um fator constante, a quantidade de etapas é logarítmica. Por exemplo, partindo de 1.024 e dividindo por 2 repetidamente:

1.024 → 512 → 256 → … → 1

São 10 reduções. Como 2¹⁰ = 1.024, essa quantidade é log₂(1.024) = 10. Em uma estimativa assintótica, escrever O(log n) basta.

Exemplo

A base não muda a ordem

Usar log₂ n, log₁₀ n ou log₃ n altera apenas um fator constante. Por isso, todas essas formas pertencem a O(log n).

O que caracteriza essa ordem é a redução repetida por uma proporção fixa, e não a base escolhida para escrever o logaritmo.

Dica

Leitura prática

Se dobrar n acrescenta aproximadamente uma etapa de redução pela metade, a tendência é logarítmica. Isso não informa a duração exata de cada etapa.

O fator logarítmico faz diferença

Entre linear e quadrático

Em O(n log n), há n unidades de trabalho, e cada uma vem acompanhada de um fator que cresce logaritmicamente.

Como referência matemática, se n = 1.024, então log₂ n = 10 e n log₂ n = 10.240 unidades de referência. Isso cresce mais que n, mas muito menos que n², que seria 1.048.576 nessa mesma entrada.

Atenção

Não transforme a tabela em cronômetro

Esses números comparam funções de referência sob hipóteses simplificadas. Uma implementação O(n²) pode terminar antes de uma O(n log n) para entradas pequenas, dependendo de constantes, dados e máquina. A medição será tratada adiante.

Verifique as tendências

Ordene por crescimento

Coloque as funções em ordem de crescimento assintótico, da menor para a maior.

  1. O(n²)
  2. O(log n)
  3. O(n)
  4. O(n log n)
  5. O(2ⁿ)
  6. O(1)

Reduções sucessivas

Uma rotina reduz um tamanho de 1.024 pela metade a cada etapa até chegar a 1. Quantas etapas de redução ela faz?

Passo 4 de 9

Compor custos de trechos e laços

Some custos de blocos e conte as execuções reais dos laços para classificar trechos de código.

Some antes de simplificar

Trechos em sequência

Quando blocos são executados um depois do outro, some seus trabalhos. Por exemplo, um percurso de n itens seguido de um percurso de m itens custa O(n + m). Não transforme isso em O(n): n e m são dimensões independentes.

Se os dois trechos dependem da mesma dimensão, como n + n, a soma é 2n e simplifica para O(n). A simplificação vem depois da contagem.

Sequência não é cruzamento

Compare os dois desenhos: percursos separados somam; cada item de uma coleção combinado com cada item da outra multiplica.

Diagrama com dois percursos independentes, um com n pontos e outro com m pontos, contrastado com uma grade de todos os pares entre n linhas e m colunas.

Dois percursos separados: n + m. Todos os pares possíveis: n vezes m.

Exemplo

Contagem antes da notação

for item in lista_a executa n vezes e for item in lista_b executa m vezes: trabalho proporcional a n + m.

Já colocar o segundo percurso dentro do primeiro executaria o corpo uma vez para cada par (item_a, item_b): n · m vezes.

O limite interno decide a contagem

Laços aninhados não são sempre quadráticos

Em um aninhamento, some o trabalho interno ao longo das iterações externas. Só multiplique diretamente quando o laço interno tem o mesmo limite em todas as voltas.

Um laço externo com n voltas e um interno com limite fixo de 3 executa cerca de 3n ações: O(n), não O(n²). Já um limite interno que cresce com a posição externa forma uma soma triangular.

Três contagens diferentes

Considere apenas as atualizações de total como trabalho constante.

python
# 1. Limite fixo: 3 atualizações para cada item
for item in valores:          # n voltas
    for tentativa in range(3):
        total += 1            # 3n atualizações -> O(n)

# 2. Grade completa: n atualizações para cada item
for esquerda in valores:      # n voltas
    for direita in valores:   # n voltas em cada volta externa
        total += 1            # n * n atualizações -> O(n²)

# 3. Triângulo: o limite interno depende de i
for i in range(n):
    for j in range(i):
        total += 1            # 0 + 1 + ... + (n - 1) -> O(n²)

A grade e o triângulo

A forma das execuções revela por que os dois últimos trechos são quadráticos, embora o triângulo não execute exatamente n² atualizações.

Comparação entre uma grade quadrada cheia de pontos de execução e uma região triangular de pontos abaixo da diagonal, representando limites internos constantes e dependentes da posição.

Grade: n × n. Triângulo: 0 + 1 + ... + (n − 1); ambos têm ordem quadrática.

Um percurso com redução interna

Linear vezes logarítmico

Se cada um dos n itens inicia uma redução sucessiva pela metade, o trabalho interno é O(log n) para cada item. Repetido n vezes, o total é O(n log n).

Conte as execuções do while; não basta olhar o número de níveis de indentação.

Reduzir pela metade para cada item

Suponha que limite seja proporcional a n e que as operações do corpo tenham custo constante.

python
passos = 0

for item in valores:          # n itens
    tamanho = len(valores)
    while tamanho > 1:        # reduz pela metade: O(log n) voltas
        tamanho //= 2
        passos += 1

# Total: n grupos de O(log n) passos -> O(n log n)

Associe a estrutura ao custo

Relacione cada descrição à ordem de crescimento mais informativa.

Toque em um item e depois no par correspondente.

Justifique pela contagem

Analise o trecho

Considere que comparacoes += 1 tem custo constante e que n é o tamanho de valores. Qual é a ordem de crescimento do trabalho? Justifique pela contagem.

comparacoes = 0
for i in range(n):
    for j in range(i):
        comparacoes += 1

Escreva pelo menos 40 caracteres (0/40).

Dica

Roteiro rápido

  1. Escolha a operação relevante. 2. Conte quantas vezes ela executa. 3. Some os trechos sequenciais ou as voltas internas. 4. Simplifique para a ordem dominante. Não classifique apenas pelo número de for.

Passo 5 de 9

Incluir o trabalho das operações do Python

Veja como chamadas curtas podem esconder percursos, cópias e ordenação ao estimar o tempo de uma função.

Linhas curtas não significam trabalho constante

Olhe além da sintaxe

A quantidade de linhas não determina a ordem de crescimento. Ao analisar uma função, considere o que cada operação executa internamente.

No modelo usual para listas, len(valores) e valores[i] são O(1): obtêm o tamanho ou um elemento por posição sem percorrer a lista. Já alvo in valores pode examinar elemento por elemento, portanto é O(n) no pior caso.

Operações visíveis e trabalho oculto

Compare uma consulta direta com uma busca que pode varrer a lista.

Diagrama comparando acesso por índice direto a um único elemento com busca de pertencimento que percorre vários elementos de uma lista até encontrar ou terminar.

Índice e tamanho usam informação direta; pertencimento em lista pode exigir uma varredura.

Dica

Hipótese do modelo

Estas classificações assumem elementos de tamanho limitado e comparações de custo constante. Comparar textos muito longos ou objetos com comparações personalizadas pode acrescentar outro custo.

Chamadas que percorrem ou copiam

Três operações, três custos

Considere itens com n elementos e k elementos na fatia.

python
tem_erro = "erro" in itens      # O(n) no pior caso
soma = sum(itens)                 # O(n)
inicio = itens[:k]                # O(k)
ordenados = sorted(itens)         # O(n log n)

O que cada chamada esconde

sum(itens) agrega todos os elementos e pode percorrê-los. A fatia itens[:k] constrói uma nova lista com k referências, então seu tempo é O(k). sorted(itens) ordena n elementos em O(n log n), supondo comparações O(1).

Uma fatia não é gratuita só porque aparece em uma expressão curta. Aqui estamos estimando tempo; a memória adicional dessa cópia será tratada depois.

Associe operação e ordem temporal

Considere uma lista com n elementos e uma fatia com k elementos.

Toque em um item e depois no par correspondente.

Uma busca escondida dentro do percurso

Duas dimensões independentes

clientes tem n elementos e bloqueados tem m elementos.

python
def contar_liberados(clientes, bloqueados):
    total = 0
    for cliente in clientes:
        if cliente not in bloqueados:
            total += 1
    return total

Conte as execuções reais

O laço externo percorre n clientes. Em cada volta, cliente not in bloqueados pode fazer uma busca linear em uma lista de m elementos. O trabalho total é O(nm), não O(n): uma expressão curta está dentro de outra repetição.

Esse é um limite de pior caso para a busca em lista. Não conclua, por essa ordem, quantos segundos a função levará em uma máquina específica.

Revise uma estimativa

Para uma lista valores de tamanho n, considere sum(valores[:k]), com k ≤ n. Qual estimativa temporal é mais informativa?

Passo 6 de 9

Contar chamadas e profundidade na recursão

Estime o trabalho total e a profundidade máxima de padrões recursivos simples, distinguindo a árvore completa de chamadas do caminho que fica ativo.

Duas contagens diferentes

Trabalho local, chamadas totais e profundidade

Ao analisar uma função recursiva, separe três perguntas:

  • Qual é o trabalho local de uma chamada? Por exemplo, uma comparação e uma soma podem ser O(1).
  • Quantas chamadas acontecem no total? Isso determina o trabalho total quando o trabalho local é constante.
  • Qual é a maior cadeia de chamadas ainda ativa? Esta é a profundidade da recursão.

A árvore de chamadas mostra todas as chamadas que ocorrerão. Já a profundidade acompanha apenas um caminho, da chamada inicial até um caso base.

Árvore completa versus caminho ativo

A mesma recursão pode criar muitos nós ao longo da execução, mas manter ativo apenas um ramo por vez.

Diagrama comparando uma árvore de chamadas com vários nós e um único caminho da raiz até uma folha destacado como caminho ativo.

Nós da árvore representam chamadas totais; o caminho destacado representa a profundidade máxima.

Uma chamada por nível

Redução de um em um

Cada chamada faz trabalho local constante e delega o restante para n - 1.

python
def contar_regressivo(n):
    if n == 0:
        return 0
    return 1 + contar_regressivo(n - 1)

Exemplo

Contagem para n = 4

As chamadas formam uma corrente:

contar_regressivo(4) → 3 → 2 → 1 → 0

Há cerca de n + 1 chamadas. Como cada uma faz O(1) de trabalho local, o tempo total é O(n). O maior caminho ativo também tem cerca de n + 1 chamadas, então a profundidade é proporcional a n.

Redução pela metade

Agora o tamanho do problema cai por um fator constante a cada chamada.

python
def contar_metades(n):
    if n <= 1:
        return 1
    return 1 + contar_metades(n // 2)

Poucos níveis ao dividir

Para n = 16, os argumentos seguem 16 → 8 → 4 → 2 → 1. Cada redução pela metade aproxima o caso base; por isso há O(log n) chamadas, tempo O(log n) e profundidade O(log n), assumindo trabalho local O(1).

Quando a chamada se ramifica

Duas chamadas sobre n − 1

Sem reutilizar resultados, cada chamada não base abre duas novas chamadas.

python
def bifurcar(n):
    if n == 0:
        return 1
    return bifurcar(n - 1) + bifurcar(n - 1)

Árvore larga, caminho ainda linear

No nível 0 há 1 chamada; no próximo, 2; depois, 4; e assim por diante. A árvore inteira tem ordem de O(2^n) nós. Com trabalho local O(1), o tempo é O(2^n).

Porém, uma chamada termina uma ramificação antes de executar a outra. Assim, o maior caminho ativo vai de n até 0 e tem profundidade O(n) — não O(2^n).

Atenção

Declare o que cada chamada faz

Essas classificações supõem que cada chamada faz apenas trabalho local constante além das chamadas recursivas. Criar uma fatia, copiar uma coleção ou percorrer parte da entrada dentro de cada chamada adiciona trabalho e pode mudar a análise.

Relacionar padrão, trabalho e profundidade

Associe cada padrão à estimativa

Considere trabalho local O(1) e ausência de cópias ou percursos extras dentro das chamadas.

Toque em um item e depois no par correspondente.

Explique as duas perspectivas

Árvore total não é pilha inteira

Por que bifurcar(n) tem trabalho total O(2^n), mas profundidade O(n)? Explique usando a diferença entre toda a árvore de chamadas e um único caminho ativo.

Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 9

Estimar a memória necessária ao mesmo tempo

Separe entrada, saída e memória auxiliar para estimar quanto armazenamento uma função precisa manter simultaneamente.

Três parcelas para descrever a memória

Informe o que cada parte ocupa

Ao analisar espaço, separe três parcelas:

  • Entrada: dados recebidos pela função. Se ela recebe uma lista com n referências, a entrada ocupa O(n).
  • Saída: dados devolvidos e materializados. Se o resultado contém k elementos, a saída ocupa O(k).
  • Memória auxiliar: espaço adicional usado durante a execução, além de entrada e saída.

Essa convenção evita esconder custos diferentes em uma única expressão. Uma função pode ter entrada O(n), saída O(1) e memória auxiliar O(1), por exemplo.

O que coexistia no pico?

Diagrama com uma lista de entrada à esquerda, uma pequena área de trabalho no centro contendo contador e acumulador, e uma coleção de saída à direita; setas mostram que as três partes podem coexistir.

Para a memória auxiliar, conte o espaço extra que está vivo no mesmo instante de maior uso — o pico simultâneo.

Dica

Não some alocações que já acabaram

Se uma estrutura temporária é criada, usada e liberada antes de outra ser criada, o espaço relevante é o maior pico entre elas, não a soma das duas. A análise de espaço pergunta: “qual é o máximo adicional mantido ao mesmo tempo?”

Constantes, temporários e cópias

Contar sem guardar os elementos

A entrada já é a lista valores. A função mantém apenas total e valor durante o percurso.

python
def contar_positivos(valores):
    total = 0
    for valor in valores:
        if valor > 0:
            total += 1
    return total

Exemplo

Estimativa do contador

Para contar_positivos(valores), se valores tem n elementos:

  • entrada: O(n);
  • saída: O(1), pois retorna um único inteiro de tamanho limitado no modelo adotado;
  • memória auxiliar: O(1), por total e pela variável do laço.

Percorrer n elementos leva tempo linear, mas não exige guardar n elementos adicionais.

Uma fatia cria outro contêiner

A fatia é criada antes de sum percorrê-la.

python
def somar_primeira_metade(valores):
    metade = valores[:len(valores) // 2]
    return sum(metade)

A fatia muda a estimativa

Com n elementos de entrada, metade tem aproximadamente n/2 referências. Logo, essa cópia rasa acrescenta memória auxiliar O(n), embora cada objeto apontado não seja duplicado por causa da fatia.

O mesmo raciocínio vale para uma cópia rasa com valores[:]: há um novo contêiner proporcional ao número de referências copiadas.

Saída materializada e pilha recursiva

Filtrar materializando o resultado

A lista pares permanece viva até o retorno.

python
def pares_positivos(valores):
    pares = []
    for valor in valores:
        if valor > 0 and valor % 2 == 0:
            pares.append(valor)
    return pares

Use k para o tamanho do resultado

Se k valores passam pelo filtro, a saída materializada ocupa O(k). Como pares é justamente o resultado devolvido, informe normalmente: entrada O(n), saída O(k) e memória auxiliar O(1), além do armazenamento da saída.

Processar cada item um por vez pode evitar uma coleção temporária, mas não faz desaparecer uma entrada que já chegou como lista materializada.

Muitas chamadas não significam uma pilha enorme

Para observar o comportamento, use valores pequenos de n, como 4 ou 5.

python
def caminhos(n):
    if n == 0:
        return 1
    return caminhos(n - 1) + caminhos(n - 1)

Total de chamadas × chamadas simultâneas

caminhos faz duas chamadas recursivas para n - 1, então o total de chamadas cresce como O(2^n). Porém, uma chamada termina antes de a outra começar: a maior cadeia ativa reduz n até zero. Se cada chamada guarda apenas estado constante, a pilha usa O(n), proporcional à profundidade máxima, e não ao total de nós da árvore de chamadas.

Verifique a estimativa

Entrada, saída e auxiliar

Considere uma entrada valores com n elementos e esta função. Se ela devolve k elementos, qual descrição está correta?

def selecionar(valores):
    selecionados = []
    for valor in valores:
        if valor > 10:
            selecionados.append(valor)
    return selecionados

Explique o pico de espaço

Analise a função abaixo para uma lista valores de tamanho n. Informe a memória da entrada, da saída e a memória auxiliar. Explique por que a fatia importa.

def total_copiado(valores):
    copia = valores[:]
    return sum(copia)

Escreva pelo menos 80 caracteres (0/80).

Passo 8 de 9

Distinguir pior caso, caso médio e amortização

Qualifique custos de busca e de inserção ao final de listas, separando pior caso, caso médio e custo amortizado.

O caso analisado muda a contagem

Busca linear não custa sempre o mesmo

Considere uma busca que percorre uma lista e para assim que encontra o alvo. Para listas com o mesmo tamanho n, o trabalho depende da entrada:

  • alvo na primeira posição: cerca de 1 comparação;
  • alvo perto do meio: cerca de n/2 comparações;
  • alvo na última posição ou ausente: até n comparações.

O pior caso é o maior custo entre entradas de mesmo tamanho: aqui, O(n). Isso não é a definição de O grande; O grande continua sendo um limite de crescimento. É preciso declarar qual caso está sendo analisado.

Posição do alvo em uma busca

Diagrama com uma lista horizontal de oito itens e três cenários de busca: alvo no primeiro item com uma comparação, no meio com quatro comparações e ausente após verificar todos os oito itens.

A mesma lista de tamanho n pode exigir quantidades diferentes de comparações.

Busca com saída antecipada

A função encerra assim que encontra o valor.

python
def contem(valores, alvo):
    for valor in valores:
        if valor == alvo:
            return True
    return False

Caso médio exige uma hipótese

Exemplo

Média não é um palpite

Se o alvo está presente e sua posição é uniformemente distribuída entre as n posições, a busca faz, em média, aproximadamente (n + 1) / 2 comparações. Portanto, o custo médio é O(n).

Essa conclusão depende da hipótese de distribuição. Se os alvos mais procurados costumam estar no início, a média observada pode ser menor. Sem declarar como as entradas são distribuídas, não há um único “caso médio” definido.

Associe a análise à descrição

Relacione cada termo à sua descrição.

Toque em um item e depois no par correspondente.

Por que append às vezes copia elementos

Capacidade extra torna a maioria das inserções barata

Uma lista dinâmica mantém espaço de capacidade além dos elementos já usados. Em geral, um append ocupa uma posição livre: custo O(1).

Quando a capacidade se esgota, a lista precisa obter um bloco maior e copiar as referências dos elementos atuais. Se havia n elementos, essa chamada específica pode custar O(n). Não é necessário conhecer a fórmula de crescimento usada pela implementação para analisar a ideia.

Redimensionamento ocasional

Sequência mostrando uma lista dinâmica quase cheia, uma inserção que preenche a última vaga e depois o crescimento para um bloco maior enquanto referências existentes são copiadas.

Há cópia no redimensionamento, mas ele não ocorre a cada append.

Exemplo

O custo da sequência

Partindo de uma lista vazia, faça N chamadas de append. Algumas são caras por causa dos redimensionamentos, mas muitas apenas ocupam uma vaga. Com crescimento de capacidade por um fator constante, o trabalho total da sequência é O(N).

Por isso, cada inserção ao final é dita O(1) amortizado: o custo total O(N) é distribuído pelas N operações. Não significa que toda chamada individual custa O(1), nem é uma média baseada em probabilidade.

Verifique a distinção

Append e amortização

Se append é O(1) amortizado, então cada chamada de append isoladamente custa O(1).

Caso médio

Dizer que uma busca linear tem custo médio O(n) exige explicitar uma hipótese sobre as entradas, como a posição do alvo.

Passo 9 de 9

Aplicar um roteiro completo de análise

Integre a análise de tempo e memória em exemplos completos e separe estimativas assintóticas de medições reais.

Um roteiro para justificar a estimativa

Da função à conclusão

Ao analisar uma função, siga este roteiro: 1. defina as dimensões da entrada; 2. declare as hipóteses de custo; 3. identifique o trabalho relevante; 4. componha as repetições; 5. simplifique a expressão; 6. informe o caso analisado e separe entrada, saída e memória auxiliar.

Considere n = len(valores_a), m = len(valores_b) e k como a quantidade de pares cuja soma supera limite. Não suponha que n e m crescem juntos.

Duas formas de contar os mesmos pares

As duas funções devolvem a quantidade de pares válidos.

python
def contar_com_lista(valores_a, valores_b, limite):
    pares_validos = []
    for a in valores_a:
        for b in valores_b:
            if a + b > limite:
                pares_validos.append((a, b))
    return len(pares_validos)


def contar_com_contador(valores_a, valores_b, limite):
    total = 0
    for a in valores_a:
        for b in valores_b:
            if a + b > limite:
                total += 1
    return total

Mesmo cruzamento, armazenamento diferente

Cada elemento de valores_a é comparado com cada elemento de valores_b.

Diagrama mostrando uma grade com n linhas e m colunas de comparações; os pares válidos destacados seguem para uma lista temporária em uma versão e para um único contador na outra.

As duas versões examinam n × m pares. Só a primeira mantém os k pares válidos simultaneamente.

Analise as duas versões

Justifique tempo e memória

Produza uma análise para as duas funções do código. Declare uma hipótese de custo constante, justifique o tempo em função de n e m e informe a memória auxiliar de cada versão em função de k. Considere que ambas devolvem apenas um inteiro e que as listas de entrada já existem.

Escreva pelo menos 180 caracteres (0/180).

Tempo total não é pilha ativa

Retomada da recursão ramificada

Em uma recursão com duas chamadas sobre n - 1, sem reutilizar resultados, a árvore pode ter trabalho total O(2n). Porém, em um instante há apenas um caminho de chamadas ativo: se cada chamada guarda estado constante, a pilha máxima é O(n), não O(2n).

Duas medidas distintas

Uma função faz duas chamadas recursivas com argumento n - 1, tem caso base em n == 0 e realiza trabalho local constante. Sem memoização, qual análise está correta?

Fechamento: estimar antes de medir

Resumo

Checklist de análise

Use a estimativa para explicar como o custo cresce; use uma medição para investigar a duração observada em um cenário concreto.

  • Escolha as dimensões relevantes, como n e m, e não presuma relação entre elas.
  • Declare hipóteses sobre o custo das operações usadas.
  • Conte execuções e simplifique o crescimento dominante, qualificando o caso analisado.
  • Informe separadamente entrada, saída e memória auxiliar; o pico depende do que está vivo ao mesmo tempo.
  • Mesma ordem de tempo não implica mesmo uso de memória nem mesma duração em segundos.
  • A seguir na trilha, você aprenderá a medir trechos de código com timeit; a notação O grande não substitui essa medição.

Análise concluída

Parabéns! Você concluiu: Estimar tempo e memória com notação O grande

Muito bem! Agora você consegue justificar uma estimativa de tempo e memória sem confundir ordem de crescimento com segundos ou bytes exatos.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso