8

He buscado alto y bajo y no puedo encontrar mucho material relacionado con complejidades de tiempo de ejecución, recursividad y java.Complejidades en tiempo de ejecución para algoritmos recursivos

Actualmente estoy aprendiendo las complejidades del tiempo de ejecución y la notación de Big-O en mi clase de Algoritmos, y tengo problemas para analizar los algoritmos recursivos.

private String toStringRec(DNode d) 
{ 
    if (d == trailer) 
     return ""; 
    else 
     return d.getElement() + toStringRec(d.getNext()); 
} 

Este es un método recursivo que simplemente iterará a través de una lista doblemente enlazada e imprimirá los elementos.

Lo único que se me ocurre es que tiene una complejidad en el tiempo de ejecución de O (n), ya que el número de llamadas a métodos recursivos dependerá de la cantidad de nodos en el DList, pero todavía no lo hago Me siento cómodo con esta respuesta.

No estoy seguro de si debería dar cuenta de la adición de d y d.getNext().

¿O estoy completamente desviado y la complejidad del tiempo de ejecución es constante, ya que todo lo que hace es recuperar elementos del DNodes en el DList?

+0

Ver: http://stackoverflow.com/questions/1126388/slow-scading-concatenation-overlarge-input – dyoo

Respuesta

0

El algoritmo tiene una complejidad de tiempo de ejecución de O (n) como usted sugiere. Su lista contiene n elementos, y el algoritmo hará una cantidad de trabajo casi fija para cada elemento (el trabajo será Elemento y Acceso siguiente, más una nueva llamada aStringRec). Recuperar un Elemento de un DNode toma tiempo constante, y los tiempos constantes se descartan en notación de O grande.

Lo interesante de los métodos recursivos (en la mayoría de los casos) es que también son O (n) en la complejidad del espacio. Se crea una nueva entrada de pila (para almacenar los parámetros pasados ​​al método) para cada llamada a toStringRec, que se llama n veces.

+1

Desafortunadamente, esta explicación proporciona una conclusión incorrecta. No tiene en cuenta el costo de la concatenación de cadenas. Es decir, el costo de cada artículo ** no es constante **. Ese es un punto importante de este problema. Por favor corrige esto. – dyoo

+0

Acepto que el costo de cada artículo no es constante, pero no estoy de acuerdo en que sea O (n). El costo de 'string1 + string2' es O (m), donde m es la longitud de la Cadena resultante. Específicamente, la concatenación de dos cadenas es, en el peor de los casos, la creación de un nuevo carácter [] de longitud m y la copia de uno a uno de cada carácter a partir de las cadenas originales. Cuando se encuentra en la enésima iteración del código proporcionado, toStringRec puede devolver una cadena muy larga, pero el costo de la concatenación sigue siendo O (m). m no está directamente relacionado con n en este ejemplo, ya que 'getElement' puede devolver cadenas vacías o muy largas. – sgmorrison

+0

Supongamos que hay una longitud 'm' que es un límite superior en el tamaño de cualquier d.getElement() en particular. Entonces, el tamaño de la cadena devuelta que obtenemos de un 'toStringRec (node)' está vinculado por la longitud de la cadena que comienza en 'node'. Deje 'T (n)' ser el costo de computación. Entonces: 'T (n) dyoo

3

A primera vista, esto parece un caso clásico de tail recursion modulo cons, una generalización de la llamada de cola. Es equivalente a un bucle con el número de iteraciones.

Sin embargo, no es así de simple: lo difícil aquí es la adición de d.getElement() a una cadena en crecimiento: esto es en sí misma una operación lineal , y se repite N veces. Por lo tanto, la complejidad de su función es O(N^2).

+0

Hmm, pensé que d.getElement() debía obtener los datos almacenados en el nodo d. Necesita hacer su pregunta un poco más clara sobre esto, supongo ... –

+2

@XiaoChuanYu No, 'd.getElement()' es 'O (1)'. Es la concatenación de cadenas que sigue que es lineal. – dasblinkenlight

+0

Sí, gracias por no ignorar el costo de la concatenación de cadenas. Esto es exactamente correcto. La suma de gauss '1 + 2 + ... + n' entra en juego, y de ahí proviene el cuadrático. – dyoo

2

Si T (n) es el número de operaciones elementales (en este caso, cuando ingresamos al cuerpo de la función, cualquiera de las líneas adentro se ejecuta a lo sumo y el segundo retorno no es O (1))) ejecutado por llamar toStringRec en una lista de n elementos, entonces

T(0) = 1 - as the only things that happen is the branch instruction and a 
       return 
    T(n) = n + T(n-1) for n > 0 - as the stuff which is being done in the 
       function besides calling toStringRec is some constant time stuff and 
       concatenating strings that takes O(n) time; and we also run 
       toStringRec(d.getNet()) which takes T(n-1) time 

en este punto hemos descrito la complejidad del algoritmo. Ahora podemos calcular la forma cerrada para T, T (n) = O (n ** 2).

+0

No: las "cosas hechas en la función" no son O (1).Es un trabajo que lleva tiempo proporcional a 'n', suponiendo que el tamaño de la cadena en cada elemento no está vacío. Por lo tanto, la forma cerrada para 'T (n)' termina pareciendo 'T (n) = 1C + 2C + 3C + ... + nC' para alguna constante C. Esa es una suma de Gauss. 'T (n)' es cuadrático, no lineal. – dyoo

+0

¡Buena captura, gracias! –

2

Este es un ejemplo bastante simple, pero el truco es definir una relación de recurrencia, que es una función del tiempo de ejecución de un tamaño de entrada dado en términos de tamaños de entrada más pequeños.Para este ejemplo, suponiendo que el trabajo realizado en cada paso de toma constante de tiempo C y suponiendo que el caso base no realiza trabajo, sería:

T(0) = 0 
T(n) = C + T(n-1) 

Luego, puede resolver el tiempo de funcionamiento mediante la sustitución de encontrar una serie :

T(n) = C + T(n-1) = 2C + T(n-2) = 3C + T(n-3) = ... = nC + T(n-n) = nC + 0 = nC 

Según la definición de O, esta ecuación es O (n). Este ejemplo no es particularmente interesante, pero si observa algo como el tiempo de ejecución de mergesort u otro algoritmo de dividir y conquistar, puede obtener una mejor idea de las relaciones recurrentes.

+0

Por supuesto, en este ejemplo también puede tener sentido común: está imprimiendo cada nodo en una lista vinculada, por lo que la cantidad de impresiones que realiza crece exactamente al mismo ritmo que el tamaño de la lista. Entonces es tiempo lineal. – cjm

+1

En este ejemplo particular, no podemos suponer que el trabajo realizado en cada paso sea un tiempo constante, dada la forma en que funciona la concatenación de cadenas en Java. – dyoo

+0

Creo que está bien suponer esto porque el objetivo de este problema no es buscar la complejidad de las funciones de la biblioteca Java, sino comprender cómo se puede analizar este tipo de algoritmo recursivo en general. – cjm

0

Para tales algoritmos recursivos, generalmente es posible escribir una ecuación recursiva para calcular el orden. Es habitual mostrar la cantidad de instrucciones ejecutadas con T (n). En este ejemplo, tenemos:

T (n) = T (n - 1) + O (1)

(Suponiendo que la función getElement carreras en tiempo constante.) La solución de esta ecuación es trivialmente T (n) = O (n).

Ese fue el caso general. Sin embargo, a veces puede analizar el algoritmo sin escribir dicha ecuación. En este ejemplo, puede argumentar fácilmente que cada elemento se accede como máximo una vez, y cada vez que se realiza un trabajo de tiempo constante; por lo tanto, se necesita O (n) en general para hacer el trabajo.

Cuestiones relacionadas