Existe uma enorme variedade de abordagens viáveis. Qual é o mais adequado depende da
- o que você está tentando mostrar,
- quantos detalhes você deseja ou precisa.
Se o algoritmo é amplamente conhecido e usado como sub-rotina, você geralmente permanece em um nível superior. Se o algoritmo for o principal objeto sob investigação, você provavelmente desejará ser mais detalhado. O mesmo pode ser dito para análises: se você precisar de um limite superior de tempo de execução aproximado, proceda de maneira diferente de quando deseja contagens precisas de instruções.
Vou dar três exemplos para o conhecido algoritmo Mergesort, que espero ilustrar isso.
Alto nível
O algoritmo Mergesort pega uma lista, divide-a em duas (aproximadamente) partes igualmente longas, repete-se nessas listas parciais e mescla os resultados (classificados) para que o resultado final seja classificado. Em listas singleton ou vazias, ele retorna a entrada.
Esse algoritmo é obviamente um algoritmo de classificação correto. Dividir a lista e mesclá-la podem ser implementadas no tempo , o que nos dá uma recorrência para o pior tempo de execução T ( n ) = 2 T ( nΘ(n). Pelo teorema do mestre, isso é avaliado comoT(n)∈Θ(nlogn).T(n)=2T(n2)+Θ(n)T(n)∈Θ(nlogn)
Nível médio
O algoritmo Mergesort é dado pelo seguinte pseudocódigo:
procedure mergesort(l : List) {
if ( l.length < 2 ) {
return l
}
left = mergesort(l.take(l.length / 2)
right = mergesort(l.drop(l.length / 2)
result = []
while ( left.length > 0 || right.length > 0 ) {
if ( right.length == 0 || (left.length > 0 && left.head <= right.head) ) {
result = left.head :: result
left = left.tail
}
else {
result = right.head :: result
right = right.tail
}
}
return result.reverse
}
mergesort
nn>1Ln+1left
right
Lwhile
result
result
left
right
L
n>1while
reverse
nwhile
nreverse
2noperações de lista - cada elemento é removido da entrada e colocado na lista de saída. Portanto, a contagem de operações cumpre a seguinte recorrência:
T(0)=T(1)T(n)=0≤T(⌈n2⌉)+T(⌊n2⌋)+7n
Tn=2k
T(0)=T(1)T(n)=0≤2T(n2)+7n
T∈Θ(nlogn)mergesort
Nível ultra baixo
Considere esta implementação (generalizada) do Mergesort em Isabelle / HOL :
types dataset = "nat * string"
fun leq :: "dataset \<Rightarrow> dataset \<Rightarrow> bool" where
"leq (kx::nat, dx) (ky, dy) = (kx \<le> ky)"
fun merge :: "dataset list \<Rightarrow> dataset list \<Rightarrow> dataset list" where
"merge [] b = b" |
"merge a [] = a" |
"merge (a # as) (b # bs) = (if leq a b then a # merge as (b # bs) else b # merge (a # as) bs)"
function (sequential) msort :: "dataset list \<Rightarrow> dataset list" where
"msort [] = []" |
"msort [x] = [x]" |
"msort l = (let mid = length l div 2 in merge (msort (take mid l)) (msort (drop mid l)))"
by pat_completeness auto
termination
apply (relation "measure length")
by simp+
Isso já inclui provas de bem-definição e rescisão. Encontre aqui uma prova de correção (quase) completa .
Para o "tempo de execução", ou seja, o número de comparações, é possível configurar uma recorrência semelhante à da seção anterior. Em vez de usar o teorema mestre e esquecer as constantes, você também pode analisá-lo para obter uma aproximação assintoticamente igual à quantidade verdadeira. Você pode encontrar a análise completa em [1]; Aqui está um esboço (não necessariamente se encaixa no código Isabelle / HOL):
Como acima, a recorrência do número de comparações é
f0=f1fn=0=f⌈n2⌉+f⌊n2⌋+en
enn
{f2mf2m+1=2fm+e2m=fm+fm+1+e2m+1
fnen
∑k=1n−1(n−k)⋅Δ∇fk=fn−nf1
Δ∇fk
W(s)=∑k≥1Δ∇fkk−s=11−2−s⋅∑k≥1Δ∇ekks=: ⊟(s)
que, juntamente com a fórmula da Perron, nos leva a
fn=nf1+n2πi∫3−i∞3+i∞⊟(s)ns(1−2−s)s(s+1)ds .
A avaliação de depende de qual caso é analisado. Fora isso, podemos - depois de alguns truques - aplicar o teorema do resíduo para obter⊟(s)
fn∼n⋅log2(n)+n⋅A(log2(n))+1
onde é uma função periódica com valores em .A[−1,−0.9]
- Transformações e assintóticos de Mellin: a recorrência da fusão de Flajolet e Golin (1992)
- Melhor caso:
Pior caso:
Caso médio:en=n-1en=n-⌊ nen=⌊n2⌋
en=n−1
en=n−⌊n2⌋⌈n2⌉+1−⌈n2⌉⌊n2⌋+1