Esta página introduz o conceito de instância de um problema computacional e o conceito de tamanho de uma instância. Também discute, brevemente, a noção de consumo de tempo (ou desempenho, ou eficiência) de um algoritmo e as ideias de pior caso e melhor caso.
Uma das principais missões da análise de algoritmos é
prever o consumo de tempo de algoritmos.
Dado um algoritmo, procuramos estimar,
a relação entre o consumo de tempo do algoritmo
e o tamanho de sua entrada
.
Todo problema computacional é uma coleção de
casos particulares
que chamaremos instâncias (= instances).
Uma instância é especificada quando atribuímos valores aos parâmetros
do problema.
Em outras palavras, uma instância é especificada
por um particular conjunto de dados do problema.
[A palavra
instância
é um neologismo, importado do inglês.
Ela está sendo empregada aqui no sentido de
exemplo,
exemplar,
espécime,
amostra,
ilustração.]
Veja alguns exemplos:
Cada instância do problema é definida por dois números naturais. Por exemplo, os números 31415926535897932 e 14142135623730950488 definem uma instância.
Cada instância do problema é definida pelos valores de a, b e c. Por exemplo, os números 472, −311 e 57281 definem uma instância do problema; essa instância consiste em encontrar um número inteiro x tal que 472x² − 311x + 57281 = 0.
Cada instância do problema é definida por um número natural n e um vetor A[1 .. n] de inteiros. Por exemplo, o número 5 e o vetor (876, 145, 323, 112, 221) definem uma instância do problema; essa instância consiste em rearranjar o vetor (876, 145, 323, 112, 221) em ordem crescente.
Uma das instâncias do problema consiste em encontrar uma subsequência crescente máxima de (876, 145, 323, 112, 221).
Cada instância do problema é especificada por um digrafo. Por exemplo, o digrafo com vértices a, b, c, d e arcos ab, bc, cd, da, bd especifica uma instância do problema.
Em geral, nem toda instância de um problema tem solução. Assim, por exemplo, a instância (1, 2, 3) do problema da equação inteira de segundo grau não tem solução, pois não existe um número inteiro x tal que 1x² + 2x + 3 = 0.
Em discussões informais é
razoável confundir problema
com instância
e chamar tudo de problema
.
Mas em discussões mais técnicas é importante
distinguir os dois conceitos.
O tamanho (= size)
de uma instância
de um problema é a quantidade de dados
necessária para descrever a instância
(ou seja, o espaço de memória
necessário para especificar
os valores dos parâmetros que definem a instância).
Em geral, o tamanho de uma instância é descrito por um único
número natural, mas às vezes é mais conveniente usar
dois ou mais números.
O conceito de tamanho está sendo apresentado de maneira propositalmente vaga para que possamos escolher, em cada caso, a definição mais apropriada.
A solução de uma instância de um dado problema pode ser um número, um vetor, um valor booleano, etc., dependendo da natureza do problema. Já a solução de um problema é sempre um algoritmo.
Dizemos que um algoritmo resolve um dado problema se,
ao receber a descrição de qualquer instância do problema,
devolve uma solução da instância
ou informa que a instância não tem solução.
Essa exigência é deveras pesada,
pois obriga o algoritmo a resolver
não só as instâncias que aparecem em aplicações práticas
como também aquelas instâncias patológicas
que nem parecem razoáveis
.
Não confunda consumo de tempo com
tempo de consumo!
— um aluno
Suponha que A é um algoritmo para um certo problema. Seja t(I) a quantidade de tempo que A consome para processar uma instância I do problema. Queremos estudar o comportamento de t em função do tamanho das instâncias. É preciso lembrar que, via de regra, um problema tem muitas instâncias diferentes de um mesmo tamanho e A consome um tempo diferente para cada uma. Para cada n, sejam
T*(n) e T*(n)
o mínimo e o máximo, respectivamente,
de t(I)
para todas as instâncias I
que têm tamanho n.
Assim, T*(n) ≤
t(I) ≤
T*(n)
para toda instância I de tamanho n.
Diremos que as funções T*
e T*
medem o consumo de tempo do algoritmo A
no melhor caso (= best case)
e no pior caso (= worst case),
respectivamente.
O melhor caso corresponde
às instâncias mais fáceis
e o pior caso corresponde
às instâncias mais difíceis
.
Por exemplo, o consumo de tempo do algoritmo pode ser 200n no melhor caso e 3n² no pior caso. (Como se faz usualmente, estamos ignorando os valores pequenos de n, para os quais 200n é maior que 3n².)
Em geral, o consumo de tempo é proporcional ao número operações elementares que o algoritmo executa ao processar a instância. Tipicamente, uma operação elementar é uma operação aritmética entre variáveis do algoritmo, ou uma comparação entre duas variáveis, ou uma atribuição de valor a uma variável. Não é necessário contar todas as operações elementares: basta escolher uma operação específica de modo o consumo de tempo do algoritmo seja proporcional ao número de execuções dessa operação. No problema da ordenação, por exemplo, parece óbvio que a operação relevante é a comparação entre elementos do vetor. Já no problema da equação inteira do segundo grau, parece claro que todas as operações aritméticas que envolvem a, b e c são relevantes.
f(n) | seg | min | hora | dia | mês | ano | século |
lg n | |||||||
√n | |||||||
n | |||||||
n lg n | |||||||
n² | |||||||
n³ | |||||||
2n | |||||||
n! |
Algorithm analysis usually means
give a big-O figure
for the running time of an algorithm
.
(Of course, a big-Theta bound is even better.)
— Ian Parberry, Problems on Algorithms
We want a statement about software,
not hardware.
Suponha que A é um algoritmo para um problema P. Em termos de minutos e segundos, a quantidade de tempo que A consome para processar uma dada instância de P depende da máquina usada para executar A. Mas essa dependência se resume a uma constante multiplicativa: se A consome tempo t numa determinada máquina, consumirá tempo 2t numa máquina duas vezes mais lenta e t/2 numa máquina duas vezes mais rápida. Para eliminar a dependência da máquina, basta discutir o consumo de tempo de A a menos de constantes multiplicativas. A notação assintótica (Ο, Ω, Θ) é ideal para fazer isso.
Suponha que T* e T* são as funções que medem, em termos do tamanho das instâncias, o consumo de tempo de A no pior e no melhor caso, respectivamente.
≤, a frase
no pior casoé redundante pois T*(n) também estará em Ο(n²). Portanto, devemos dizer, simplesmente, que A consome Ο(n²) unidades de tempo.
≥, a frase
no pior casonão é redundante.
Se T*(n)
está em
Ο(n²) e em Ω(n²),
e portanto em Θ(n²),
podemos dizer que
A consome tempo
proporcional a n²
no pior caso,
embora isso constitua um abuso do significado usual de proporcional
.
no melhor casoé redundante: devemos dizer, simplesmente, que A consome Ω(n) unidades de tempo.
no melhor casonão é redundante.
o algoritmo A consome pelo menos Ο(n²) unidades de tempo.
Algoritmo (P, n) |
1 . para k crescendo de 0 até n |
2 .ooo i := k |
3 .ooo enquanto i ≥ 1 e Teste (P, i) = 0 |
4 .oooooo i := i − 1 |
5 .ooo X[k] := i |
6 . devolva X |
1 . c := 0 |
2 . i := 1 |
3 . enquanto i ≤ n |
4 .ooo para j crescendo de 1 até n |
5 .oooooo c := c + 1 |
6 .ooo i := 2 ⋅ i |
1 . a := 0 |
2 . para k := 1 até n² |
3 .ooo a := a + 1 |
4 .ooo se k ≤ n |
5 .oooooo b := Aux (k) |
Θ(f)para alguma função f simples) e justifique a tradução.
AlgoA (n) |
1 . se n = 0 |
2 .ooo devolva 0 |
3 . x := 2 ⋅ AlgoA (n − 1) + 4 |
4 . devolva x |
AlgoB (n) |
1 . se n = 0 |
2 .ooo devolva 0 |
3 . x := AlgoB (n − 1) + AlgoB (n − 1) + 4 |
4 . devolva x |