Estou paralisado ao analisar a complexidade de tempo do seguinte algoritmo:
def fun (r, k, d, p):
if d > p:
return r
if d = 0 and p = 0:
r <- r + k
return r
if d > 0:
fun (r, k + 1, d - 1, p)
if p > 0:
fun (r, k - 1, d, p - 1)
A chamada raiz será fun (0, 0, n, n)
e n
é do tamanho do problema.
Eu acho que: A relação de recorrência é , que é equivalente a , e então .
Minha análise está correta (sei que não é muito completa e exata)? Se houver uma falha grave, aponte-a ou mostre-me uma prova correta e completa da complexidade de tempo desse algoritmo.
d>0
ep>0
. Você não mostra o que a função retorna se atingirmos as 3a e quarta instruções if. Você queria ter umareturn
declaração após cada chamada recursiva defun
? (você queriafun (r, k + 1, d - 1, p)
serreturn fun (r, k + 1, d - 1, p)
?) Ou queria ter umareturn
declaração no final do corpo da função? Edite seu pseudocódigo para esclarecer e verifique o que isso retorna em todos os casos possíveis.d<=p
ed>0
ep>0
todos os espera. O que deveria acontecer? O algoritmo faz 2 invocações recursivas para a função? Ou invoca recursivamentefun(r, k + 1, d - 1, p)
e retorna imediatamente, sem invocar recursivamentefun(r, k - 1, d, p - 1)
? Se eu pegar seu pseudocódigo literalmente, parece que ele faz duas invocações recursivas e retorna com um valor de retorno indefinido - mas isso parece estranho e me faz pensar se há um erro de digitação / erro no pseudocódigo.Respostas:
Os únicos dois argumentos relevantes para a análise assintótica são:d e p . Esses argumentos (virtualmente) satisfazemd,p≥0 e d≤p (precisamos embaralhar a lógica da função ligeiramente para obter isso). Em cada ponto da execução, você pega o par atual(d,p) e depois recursivamente chame a função com os pares (d−1,p),(d,p−1) , evitando pares que invalidam as restrições declaradas acima.
Podemos imaginar a árvore de chamadas resultante como um caminho começando em(0,0) . Cada vez que você diminuip , adicione uma / etapa. Cada vez que você diminuid , adicione uma \ etapa. A condiçãod≤p garante que você nunca fique abaixo do eixo X. Além disso, você tem um "orçamento" den de cada etapa. O número total de folhas nesta árvore de chamadas é exatamente o número catalão , e isso nos dá uma limite inferior no tempo de execução da função.(2nn)/(n+1)=Θ(4n/n3/2)
Para obter um limite superior, observe que no caminho para cada folha passamos por nós, e isso fornece um limite superior maior que o limite inferior, ou seja, .2n 2n Θ(4n/n−−√)
Temos um limite inferior de e um limite superior em . Quais são os assintóticos exatos? Eles crescem como o número total de caminhos que não cruzam o eixo X que têm no máximo passos em cada direção. Usando o teorema da votação de Bertrand , podemos obter uma expressão exata para isso: Resta, portanto, estimar esta soma assintoticamente:Ω(4n/n3/2) O(4n/n−−√) n
fonte
Caso a caso:
fun
repete em d-1 até d ≯ 0; desde p> 0, isso é linear em d + (caso 4) .fun
recorremos em p-1 até p ≯ 0; isso é linear em p + (um dos casos 1, 2 ou 5)Começando com d = p = n> 0 atinge o caso 3, que é seguido pelo caso 4. Se n for um número inteiro, o caso final é 2, caso contrário, o caso final é 5. O tempo total para esses casos é d + p +1 ou 2n + 1.
fonte
if
s como "faça um desses", enquanto o @Yuval os considerou "considere cada um deles em ordem". O último é, é claro, o queif
s (semelse
) significa no código real; Estou acostumado com o primeiro em quase qualquer outro contexto que não seja o código real (inclusive no pseudocódigo, embora o uso no pseudocódigo seja inconsistente).return
declarações na segunda metade do código torna isso bastante confuso.