¿Cuál es la complejidad temporal de este algoritmo? ¿Y por qué?

8

Estoy atrapado analizando la complejidad temporal del siguiente 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)

La llamada raíz será fun (0, 0, n, n), y nes el tamaño del problema.

Supongo que: la relación de recurrencia es T(n,n)=T(n1,n)+T(n,n1), que es equivalente a T(2n)=2T(2n1)T(m)=2T(m1), y entonces O(2m)O(4n).

¿Es correcto mi análisis (sé que no es muy completo y exacto)? Si tiene un defecto grave, indíquelo o muéstreme una prueba correcta y completa de la complejidad temporal de este algoritmo.

象 嘉 道
fuente
1
xando, te animo a que edites la pregunta para explicar por qué las técnicas estándar no funcionan: explica por qué fallan. (Cc: @YuvalFilmus) Por ejemplo, ¿obtienes una relación de recurrencia que es difícil de resolver, y si es así, qué recurrencia obtienes?
DW
1
En comentarios con Polyergic, me di cuenta de que el pseudocódigo no está claro: no está claro qué quieres decir con el algoritmo, cuando ambos d>0y p>0. No muestra lo que devuelve la función si alcanzamos las declaraciones if 3ra y 4ta. ¿Querías tener una returndeclaración después de cada invocación recursiva de fun? (¿querías fun (r, k + 1, d - 1, p)ser return fun (r, k + 1, d - 1, p)?) ¿O querías tener una returndeclaración al final del cuerpo de la función? Edite su pseudocódigo para aclarar y asegúrese de mostrar lo que esto devuelve en todos los casos posibles.
DW
Para decirlo de otra manera: supongamos que d<=py d>0y p>0todos sostienen. ¿Qué se supone que debe pasar? ¿El algoritmo hace 2 invocaciones recursivas a la función? ¿O invoca recursivamente fun(r, k + 1, d - 1, p)y luego regresa inmediatamente, sin invocar recursivamente fun(r, k - 1, d, p - 1)? Si tomo su pseudocódigo literalmente, parece que hace 2 invocaciones recursivas y luego regresa con un valor de retorno indefinido, pero eso parece extraño y me hace preguntarme si hay un error tipográfico / error en el pseudocódigo.
DW

Respuestas:

10

Los únicos dos argumentos relevantes para el análisis asintótico son d y p. Estos argumentos (virtualmente) satisfacend,p0 y dp(Necesitamos mezclar ligeramente la lógica en la función para obtener esto). En cada punto de la ejecución, tomas el par actual(d,p) y luego llama recursivamente a la función con los pares (d1,p),(d,p1), evitando pares que invalidan las restricciones indicadas anteriormente.

Podemos imaginar el árbol de llamadas resultante como una ruta que comienza en (0,0). Cada vez que disminuyesp, agregue un / paso. Cada vez que disminuyesd, agregue un \ step. La condicióndpgarantiza que nunca irás por debajo del eje X. Además, tiene un "presupuesto" dende cada paso El número total de hojas en este árbol de llamadas es exactamente el número catalán.(2nn)/(n+1)=Θ(4n/n3/2), y esto nos da un límite inferior en el tiempo de ejecución de la función.

Para obtener un límite superior, tenga en cuenta que en el camino a cada hoja pasamos por 2n nodos, y esto da un límite superior 2n más grande que el límite inferior, es decir, Θ(4n/n).

Tenemos un límite inferior de Ω(4n/n3/2) y un límite superior en O(4n/n). ¿Cuáles son las asintóticas exactas? Crecen como el número total de caminos que no cruzan el eje X que tienen como máximonpasos en cada dirección. Usando el teorema de la boleta de Bertrand podemos obtener una expresión exacta para esto:

0dpnpd+1p+1(p+dp).
Queda por tanto estimar esta suma asintóticamente:
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).
Yuval Filmus
fuente
1
Realmente me gusta su enfoque geométrico utilizando estos "pasos". ¿Es esa una técnica común? No lo he visto antes.
Andreas T
@AndreasT No lo llamaría una técnica común, de hecho, normalmente no se aplica. Aquí la interpretación combinatoria es bastante evidente, y nos lleva a este tipo de solución.
Yuval Filmus
0

Caso por caso:

  1. d> p : tiempo constante
  2. d = 0 ∧ p = 0 : tiempo constante
  3. d> 0 : Tenga en cuenta que d ≯ p, entonces tenemos 0 <d ≤ p, y se funrepite en d-1 hasta d ≯ 0; desde p> 0, esto es lineal en d + (caso 4) .
  4. p> 0 : Tenga en cuenta que d ≯ 0, entonces tenemos d ≤ 0 ≤ p (con d <p), y se funrepite en p-1 hasta p p 0; esto es lineal en p + (uno de los casos 1, 2 o 5)
  5. d ≤ p <0 : Indefinido; Supongo que es tiempo constante

Comenzando con d = p = n> 0 coincide con el caso 3, que es seguido por el caso 4. Si n es un número entero, el caso final es 2, de lo contrario, el caso final es 5. El tiempo total para esos casos es d + p +1 o 2n + 1.

ShadSterling
fuente
2n. ¿Supongo que rechazaste porque me concentré en el razonamiento?
ShadSterling
1
Gracias por editar la respuesta! El enigma ahora es que usted concluyó que el tiempo de ejecución esO(n), pero Yuval concluyó que el tiempo de ejecución es exponencial en norte. Esa es una diferencia bastante sustancial.
DW
1
Hmm, creo que tomé los pseudocódigos ifcomo "hacer uno de estos", mientras que @Yuval los tomó como "considere cada uno de estos en orden". Esto último es, por supuesto, lo que ifs (sin else) significa en el código real; Estoy acostumbrado a lo primero en casi cualquier otro contexto que no sea el código real (incluso en pseudocódigo, aunque el uso en pseudocódigo es inconsistente).
ShadSterling
Veo a que te refieres. La falta de returndeclaraciones en la segunda mitad del código hace que esto sea bastante confuso.
DW