Qual é a complexidade temporal desse algoritmo? E porque?


8

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 .T(n,n)=T(n1,n)+T(n,n1)T(2n)=2T(2n1)T(m)=2T(m1)O(2m)O(4n)

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.


1
xando, encorajo você a editar a pergunta para explicar por que as técnicas padrão não funcionam: explique por que elas falham. (Cc: @YuvalFilmus) Por exemplo, você obtém uma relação de recorrência difícil de resolver e, em caso afirmativo, qual é a recorrência?
DW

1
Em comentários à Polyergic, percebi que o pseudocódigo não é claro: não está claro o que você quer dizer com o algoritmo, quando ambos d>0e p>0. Você não mostra o que a função retorna se atingirmos as 3a e quarta instruções if. Você queria ter uma returndeclaração após cada chamada recursiva de fun? (você queria fun (r, k + 1, d - 1, p)ser return fun (r, k + 1, d - 1, p)?) Ou queria ter uma returndeclaraçã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.
DW

Para dizê-lo de outra forma: suponhamos d<=pe d>0e p>0todos os espera. O que deveria acontecer? O algoritmo faz 2 invocações recursivas para a função? Ou invoca recursivamente fun(r, k + 1, d - 1, p)e retorna imediatamente, sem invocar recursivamente fun(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.
DW

Respostas:


10

Os únicos dois argumentos relevantes para a análise assintótica são: d e p. Esses argumentos (virtualmente) satisfazemd,p0 e dp(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 (d1,p),(d,p1), 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çãodpgarante que você nunca fique abaixo do eixo X. Além disso, você tem um "orçamento" dende 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, .2n2nΘ(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

0dpnpd+1p+1(p+dp).
0dpn(p+dp)0dpndp+1(p+dd)=0dpn(p+dp)0dpn(p+dp+1)=p=0n(2p+1p+1)p=0n(2p+1p+2)=p=0n1p+1(2p+2p)=Θ(p=0n4pp3/2)=Θ(4nn3/2).

1
Eu realmente gosto da sua abordagem geométrica usando esses "passos". Essa é uma técnica comum? Eu nunca vi isso antes.
Andreas T

@ AndreasT Eu não diria que é uma técnica comum, na verdade normalmente não se aplica. Aqui a interpretação combinatória é bastante evidente e leva a esse tipo de solução.
Yuval Filmus

0

Caso a caso:

  1. d> p : tempo constante
  2. d = 0 ∧ p = 0 : tempo constante
  3. d> 0 : Observe que d ≯ p, então temos 0 <d ≤ p, e se funrepete em d-1 até d ≯ 0; desde p> 0, isso é linear em d + (caso 4) .
  4. p> 0 : Observe que d ≯ 0, então temos d ≤ 0 ≤ p (com d <p) e funrecorremos em p-1 até p ≯ 0; isso é linear em p + (um dos casos 1, 2 ou 5)
  5. d ≤ p <0 : Indefinido; Estou assumindo que este é tempo constante

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.


2n. Acho que você votou mal porque me concentrei no raciocínio?
ShadSterling

1
Obrigado por editar a resposta! O quebra-cabeça agora é que você concluiu que o tempo de execução éO(n), mas Yuval concluiu que o tempo de execução é exponencial em n. Essa é uma diferença substancial.
DW

1
Hmm, acho que peguei o pseudocódigo ifs como "faça um desses", enquanto o @Yuval os considerou "considere cada um deles em ordem". O último é, é claro, o que ifs (sem else) 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).
ShadSterling

Eu vejo o que você quer dizer. A falta de returndeclarações na segunda metade do código torna isso bastante confuso.
DW
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.