Está en la página 1de 4

TALLER: Algoritmos de Ordenamiento, Notación Asintótica y Diseño de Algoritmos

Nombre:
Anderson Alonso Luna Rodríguez

Ejercicio 1: Cuadro Comparativo de Algoritmos de Ordenamiento

Realice un cuadro comparativo entre tres algoritmos de ordenamiento: Selección, QuickSort y


MergeSort. A continuación, se presenta un ejemplo de implementación en Python para cada
algoritmo, junto con la medición de los tiempos de ejecución.

Algoritmo Código de Ejemplo Ejemplo de Entrada Tiempo de Ejecución

Selección def selection_sort(arr): [64, 34, 25, 12, 22, 11, 90] 1.0728836059570312e-
05 segundos

QuickSort def quick_sort(arr): [38, 27, 43, 3, 9, 82, 10] 1.3589859008789062e-


05 segundos

MergeSort def merge_sort(arr): [15, 23, 7, 44, 31, 8, 12] 1.52587890625e-05


segundos

Realice la medición de tiempos de ejecución para cada algoritmo utilizando diferentes conjuntos de
datos de entrada.

Ejercicio 2: Notación Asintótica

Cite un ejemplo de ejercicios que demuestren los conceptos de notación asintótica, incluyendo O
Grande (O), Omega (Ω) y Teta (Θ).

 Notación Asintótica: Describe el comportamiento en términos de límites superiores o


inferiores a medida que el tamaño de la entrada se acerca al infinito.

El comportamiento de una función o algoritmo en términos de limites superiores o


inferiores a medida que el tamaño de la entrada se acerca al infinito se puede
describir de varias maneras, dependiendo de la función o algoritmo en cuestión.
1. Límite superior asintótico finito: Si una función tiene un límite
superior asintótico finito, significa que, a medida que el tamaño
de la entrada crece hacia el infinito, la función nunca crecerá más
rápido que una constante multiplicativa fija.
2. Límite inferior asintótico finito: Si una función tiene un límite
inferior asintótico finito, significa que, a medida que el tamaño de
la entrada crece hacia el infinito, la función nunca crecerá más
lentamente que una constante multiplicativa fija.
3. Limite superior e inferior asintóticos finitos: Si una función tiene
límites superiores e inferiores asintóticos finitos, significa que su
comportamiento esta acotado por dos funciones.

 O Grande (O): Establece un límite superior en el crecimiento del tiempo o espacio en


función del peor caso.

En el peor caso ocurre cuando la lista esta inicialmente ordenada de manera


inversa, el algoritmo de burbuja debe realizar muchas comparaciones y
movimientos de elementos. Cada elemento debe ser comparado con todos los
elementos restantes en la lista, lo que da como resultado un numero total de
comparaciones y movimientos proporcional.

 Omega (Ω): Establece un límite inferior en el crecimiento del tiempo o espacio en función
del mejor caso.

En el mejor caso el elemento que estamos buscando esta justo en el centro del
arreglo, por lo que encontramos el elemento deseado en la primera comparación.
No importa cuán grande sea el arreglo. El tiempo de ejecución en el mejor caso
siempre será el mismo, es decir, una sola comparación. Por lo tanto, podemos
decir que el tiempo de ejecución en el mejor caso para la búsqueda binaria es
Ω(1), lo que significa que es un algoritmo de tiempo constante en el mejor caso.

 Teta (Θ): Establece un rango en el que el tiempo o espacio de crecimiento se encuentra


entre un límite superior e inferior.

Un ejemplo podría ser un algoritmo de recorrido lineal en un arreglo para


encontrar el valor mínimo y el valor máximo al mismo tiempo. Este algoritmo
recorre la lista una vez, llevando un registro del valor mínimo y el valor máximo a
medida que avanza. En el mejor caso y en el peor caso, el algoritmo realizará
exactamente "n" comparaciones, donde "n" es el tamaño de la lista. En el mejor
caso, el primer elemento que se encuentra podría ser tanto el mínimo como el
máximo, y en el peor caso, el mínimo y el máximo están al principio y al final de la
lista, respectivamente.
Ejercicio 3: Estrategias de Diseño Divide y Vencerás

Defina las estrategias de diseño de algoritmos "Divide y Vencerás". Esta técnica implica dividir un
problema en subproblemas más pequeños, resolverlos de manera independiente y luego combinar
sus soluciones para obtener la solución final.

Es una técnica ampliamente utilizada en informática y matemáticas que se basa en la


división de un problema en subproblemas mas pequeños, resolviendo cada subproblema
de manera independiente y luego combinando sus soluciones para obtener la solución
final del problema original.

Ejemplos:

Merge Sort: Un algoritmo de ordenación que divide una lista en mitades, ordena las
mitades por separado y luego combina las mitades ordenadas en una lista ordenada.

Quick Sort: Otro algoritmo de ordenación que divide una lista en subconjuntos mas
pequeños, ordena los subconjuntos y luego combina los subconjuntos ordenados.

Algoritmo de Strassen para la multiplicación de Matrices: Utilizado para calcular


exponenciaciones de números enteros de manera eficiente, dividiendo el problema en
subproblemas de tamaño cada vez más pequeño.

La estrategia “divide y vencerás” es fundamental para la resolución eficiente de muchos


problemas complejos y es una herramienta poderosa en el diseño de algoritmos.

Ejercicio 4: Recurrencia y Análisis de Algoritmos Recursivos

Explique qué es una recurrencia en el contexto de algoritmos y cómo se realiza el análisis de


algoritmos recursivos. Proporcione un ejemplo de un algoritmo recursivo junto con su recurrencia y
análisis.

Es una ecuación o relación matemática que se utiliza para describir el tiempo o el espacio
que consume un algoritmo recursivo en función de su tamaño de entrada.
Para analizar algoritmos recursivos, generalmente seguimos estos pasos:
1. Definir la recurrencia
2. Encontrar el caso base
3. Resolver la recurrencia
4. Análisis de la complejidad

Ejemplo:
def factorial (n):
if n == 0:
return 1
else:
return n * factorial (n – 1)
Recurrencia: La recurrencia para este algoritmo se puede definir como:
T(0) = 1 (caso base)
T(n) = n * T(n - 1) para n > 0 (caso recursivo)

Análisis: Para analizar la complejidad de este algoritmo, podemos resolver la recurrencia:

T(n) = n * T(n - 1) = n * (n - 1) * T(n - 2) = n * (n - 1) * (n - 2) * T(n - 3) = ... = n * (n - 1) * (n - 2)


* ... * 2 * 1 * T(0)
T(n) = n!
Ejercicio 5: Ejemplo de Recurrencia y Cálculo de Tiempo de Ejecución

Proporcione un ejemplo de un algoritmo con una recurrencia, donde el tiempo de ejecución


aumenta de manera exponencial. Luego, realice el cálculo del tiempo de ejecución para una
entrada específica.

El calculo de la serie de Fibonacci de manera recursiva. La serie de Fibonacci se define de


la siguiente manera: el primer y segundo termino son 0 y 1, respectivamente, y cada
termino subsiguiente es la suma de los dos términos anteriores.
def Fibonacci(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return Fibonacci (n - 1) + Fibonacci (n - 2)
Para calcular el tiempo de ejecución para una entrada especifica, como el termino 5 de la
serie de Fibonacci (n = 5), podemos seguir la ejecución del algoritmo paso a paso:
fibonacci(5) se divide en fibonacci(4) y fibonacci(3).
fibonacci(4) se divide en fibonacci(3) y fibonacci(2).
fibonacci(3) se divide en fibonacci(2) y fibonacci(1).
fibonacci(2) se divide en fibonacci(1) y fibonacci(0).

A partir de aquí, se resuelven las llamadas recursivas desde abajo hacia arriba:

fibonacci(0) devuelve 0.
fibonacci(1) devuelve 1.
fibonacci(2) = fibonacci(1) + fibonacci(0) = 1 + 0 = 1.
fibonacci(3) = fibonacci(2) + fibonacci(1) = 1 + 1 = 2.
fibonacci(4) = fibonacci(3) + fibonacci(2) = 2 + 1 = 3.
fibonacci(5) = fibonacci(4) + fibonacci(3) = 3 + 2 = 5.

El tiempo de ejecución total para calcular el término 5 de la serie de Fibonacci es 15


llamadas recursivas en este caso. Puedes ver que el número de llamadas recursivas
aumenta de manera exponencial con el valor de "n", lo que hace que el tiempo de
ejecución sea exponencial. Este algoritmo no es eficiente para calcular grandes términos
de la serie de Fibonacci debido a su crecimiento exponencial.

También podría gustarte