Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Luis Oyarzun
Análisis y Diseño de Algoritmos
Resultado de aprendizaje de la asignatura
Diseñar algoritmos eficientes y confiables por medio de la investigación de métodos para analizar
cuidadosamente la estructura de un problema dentro de un marco matemático, buscando a menudo la
posibilidad de disminuir drásticamente los recursos.
Contenido
Resultado de aprendizaje de la asignatura .................................................................................................. 3
Unidad 1: Algoritmos Básicos ...................................................................................................................... 1
Algoritmos ............................................................................................................................................... 1
Ejemplos .............................................................................................................................................. 1
¿Qué tipos de problemas son resueltos por los algoritmos? ............................................................... 2
Diferencia entre algoritmos: ................................................................................................................... 2
¿Cómo funciona el método de inserción? ........................................................................................... 3
Elaboración de un pseudocódigo: ....................................................................................................... 3
Bucle invariante: .................................................................................................................................. 4
Analizando algoritmos ......................................................................................................................... 5
Diferencias entre el mejor, el esperado y el peor caso de un algoritmo. ............................................ 7
Futuros análisis .................................................................................................................................... 8
Orden de crecimiento.......................................................................................................................... 8
Clase de complejidad: Crecimiento de las funciones ............................................................................. 13
Notación asintótica ........................................................................................................................... 14
Unidad 2: Técnicas de diseño de algoritmos. ............................................................................................ 21
Unidad 3: ................................................................................................................................................... 21
Unidad 1: Algoritmos Básicos
Algoritmos
Un algoritmo es una secuencia de pasos bien definidos dentro de un
orden lógico, el cual permite resolver un problema, este debe ser Algoritmo
analizado de tal forma, que tanto sus entradas como salidas deben
Es una secuencia
ser bien definidas, porque este algoritmo necesita tomar un valor o de pasos
conjunto de valores como entrada y producir o devolver algún valor
computacionales
o conjunto de valores como salida. Es decir que el algoritmo es una que transforman
secuencia de pasos computacionales que transforman la entrada en
la entrada en la
la salida. Es importante entender que todo algoritmo siempre debe salida.
concluir su proceso, este no debe ser interrumpido porque todo dato
que ingresa y es procesado hasta convertirse en una salida, es
almacenado en memoria, y solo al terminar o finalizar el algoritmo
este dato almacenado será eliminado de la memoria.
Ejemplos
Como un ejemplo básico para entender cómo
funciona un algoritmo, vamos a preparar un
jugo de naranja:
1. Inicio
2. Verificar si hay naranjas
3. Si no hay naranjas, entonces comprar
naranjas
4. Cortar las naranjas en partes iguales
5. Exprimir las naranjas
6. Verter el jugo de naranja en un vaso
7. ¿Desea agregar agua?
a. Si desea agregar agua
i. Debe agregar agua y azúcar
ii. Luego revolver y servir
b. Caso contrario, servir
8. Finalizar
números en la entrada de forma ascendente, de ahí que la entrada sería una secuencia
2 de “n” elementos no ordenados, tal que 〈𝑎1 , 𝑎2 , … , 𝑎𝑛 〉 y en la salida debe existir una
permutación de los “n” elementos 〈𝑎′1 , 𝑎′ 2 , … , 𝑎′ 𝑛 〉, tal que 𝑎′1 ≤ 𝑎′ 2 ≤ ⋯ ≤ 𝑎′𝑛 .
Entrada Salida
• ⟨𝑎1 , 𝑎2 , … , 𝑎𝑛 ⟩ • Permutación
⟨𝑎′1 , 𝑎′2 , … , 𝑎′𝑛 ⟩ ,
tal que 𝑎′1 ≤
𝑎′ 2 ≤ ⋯ ≤ 𝑎′𝑛
Aterricemos este ejemplo con algo real, suponga que, en la secuencia de ENTRADA, se
ingresan los siguientes seis números:
31 41 59 26 41 50 26 31 41 41 58 59
Entrada Salida
Con esto claro, vamos primero a analizar que significa estar frente a un mejor, a un
esperado o un peor caso de comportamiento, recuerde, esto se refiere a la eficiencia
de un algoritmo en relación con la cantidad de recursos computacionales que necesita
este para poder ejecutarse dentro de un entorno informático y llegar al objetivo para
el cual fue diseñado (mientras haya una menor cantidad de recursos consumidos,
mayor será la eficiencia del algoritmo), entonces:
Elaboración de un pseudocódigo:
Para elaborar un código, se debe comenzar elaborando un pseudocódigo y para
Pseudocódigo
elaborar el seudocódigo, se debe definir el algoritmo del proceso a realizar. Para
elaborar un algoritmo se debe conocer como se realiza el proceso de forma manual. Es la escritura del
código en un
Primero vamos a definir el nombre de nuestro proceso y lo llamaremos
lenguaje natural
𝑂𝑟𝑑𝑒𝑛𝑎_𝐼𝑛𝑠𝑒𝑟𝑐𝑖𝑜𝑛(), el cual va a recibir como parámetro un arreglo que llamaremos
o de alto nivel de
“𝐴”, este arreglo contiene una secuencia de “𝑛” números los cuales serán ordenados.
forma compacta
La idea principal es que el ordenamiento sea realizado en el mismo arreglo “𝐴” y el
e informal de
apoyo de una variable temporal llamada “𝑡𝑒𝑚𝑝” con el espacio suficiente como para
cómo debe
almacenar un solo dato a la vez. El tamaño del arreglo será almacenado en
funcionar el
𝐴. 𝑙𝑜𝑛𝑔𝑖𝑡𝑢𝑑.
algoritmo
Imaginemos que el arreglo 𝐴[1 … 𝑛], contiene los elementos 𝐴[5, 2, 4, 6, 1, 3], donde implementado.
cada elemento del arreglo esta en un casillero, cada uno contado del 1 al 6.
Como una
recomendación,
5 2 4 6 1 3 2 5 4 6 1 3 2 4 5 6 1 3 se debe respetar
la estructura del
lenguaje que se
está utilizando.
1 2 3 4 5 6 1 2 4 5 6 3 2 4 5 6 1 3
Luis Oyarzun
Ordena_Insercion(A)
4 1 desde j = 2 hasta A.longitud
2 temp = A[j]
3 //Inserta A[j] en la secuencia ordenada A[1 … j – 1]
4 i = j – 1
5 mientras i > 0 y A[i] > temp
6 A[i+1] = A[i]
7 i = i – 1
8 A[i + 1] = temp
Bucle invariante:
Propiedad de 𝐴[1 … 𝑗 − 1], el valor de “𝑗” indica la posición del número que se está
insertando. Al comienzo de cada iteración para el bucle desde (for) de las líneas 1 a la
7, el sub arreglo de 𝐴[1 … 𝑗 − 1] consiste de los elementos originales en 𝐴[1 … 𝑗 − 1]
pero ordenados, mientras que los elementos restantes que no están ordenados se
almacenan en el sub arreglo 𝐴[𝑗 + 1 … 𝑛].
Tenga en cuenta la similitud con la inducción matemática, donde para demostrar que
una propiedad es válida, demuestra un caso base y un paso inductivo. Aquí, mostrar
que el invariante se cumple antes de la primera iteración corresponde al caso base, y
mostrar que el invariante se mantiene de una iteración a otra iteración
correspondiente al paso inductivo.
Analizando algoritmos
Analizar un algoritmo ha llegado a significar predecir los recursos que requiere el
algoritmo. Ocasionalmente, los recursos como la memoria, el ancho de banda de
comunicación o el hardware de la computadora son la principal preocupación, pero la
mayoría de las veces es el tiempo computacional lo que queremos medir.
Generalmente, al analizar varios algoritmos candidatos para un problema, podemos
identificar el más eficiente. Dicho análisis puede indicar más de un candidato viable,
pero a menudo podemos descartar varios algoritmos inferiores en el proceso.
Estrictamente hablando, debemos definir con precisión las instrucciones del modelo
RAM y sus costos. Sin embargo, hacerlo sería tedioso y proporcionaría poca
información sobre el diseño y análisis de algoritmos. Entonces para hacerlo, debemos
tener cuidado de no abusar del modelo RAM. Por ejemplo, ¿qué pasa si una RAM tiene
una instrucción que ordena? Entonces podríamos clasificar en una sola instrucción. Tal
RAM no sería realista, ya que las computadoras reales no tienen tales instrucciones.
Por tanto, a través de este curso veremos cómo se diseñan las computadoras reales.
El modelo de RAM contiene instrucciones que se encuentran comúnmente en
computadoras reales: aritmética (como sumar, restar, multiplicar, dividir, resto, piso,
techo), movimiento de datos (cargar, almacenar, copiar) y control (bifurcación
condicional e incondicional, llamada y retorno de subrutina). Cada una de estas
instrucciones requiere una cantidad constante de tiempo.
Los tipos de datos en el modelo RAM son enteros y de coma flotante (para almacenar
números reales). Aunque normalmente no nos preocupamos por la precisión en este
curso, en algunas aplicaciones la precisión es crucial. También asumimos un límite en
el tamaño de cada palabra de datos. Por ejemplo, cuando trabajamos con entradas de
Luis Oyarzun
tamaño “n”, normalmente asumimos que los enteros están representados por 𝑐 𝑙𝑔 𝑛
6 bits para alguna constante 𝑐 ≥ 1.
Requerimos 𝑐 ≥ 1 para que cada palabra pueda contener el valor de “n”, lo que nos
permite indexar los elementos de entrada individuales, y restringimos “c” para que sea
una constante para que el tamaño de la palabra no crezca arbitrariamente. (Si el
tamaño de la palabra pudiera crecer arbitrariamente, podríamos almacenar grandes
cantidades de datos en una palabra y operar en todo ello en un tiempo constante,
claramente un escenario poco realista).
Analizar incluso un algoritmo simple en el modelo RAM puede ser un desafío. Las
herramientas matemáticas necesarias pueden incluir combinatoria, teoría de la
probabilidad, destreza algebraica y la capacidad de identificar los términos más
significativos en una fórmula.
el número de veces que se repitió el bucle padre. Y “𝑡𝑗 ” es el número de veces que
se va a repetir este bucle hijo.
7
𝑇(𝑛) = 𝑐1 𝑛 + 𝑐2 (𝑛 − 1) + 0 + 𝑐4 (𝑛 − 1) + 𝑐5 ∑ 𝑡𝑗 + 𝑐6 ∑(𝑡𝑗 − 1)
𝑗=2 𝑗=2
𝑛
+ 𝑐7 ∑(𝑡𝑗 − 1) + 𝑐8 (𝑛 − 1)
𝑗=2
*
𝑛, es la longitud del arreglo
†
𝑡𝑗, determina el número de veces que se ejecuta el bucle mientras y depende del valor de 𝑗
Luis Oyarzun
Futuros análisis
En su mayor parte, los análisis posteriores se centrarán en:
⚫ Tiempo de ejecución en el peor de los casos, por ser el límite superior del tiempo
de ejecución para cualquier entrada.
⚫ El análisis de casos promedios, donde el tiempo de ejecución esperado en todas las
entradas, a menudo se encuentra que, el peor de los casos y el caso promedio
tienen el mismo "orden de crecimiento"
Orden de crecimiento
Este busca simplificar la abstracción del tiempo de ejecución de un algoritmo por: su
tasa (rate) de crecimiento o su orden de crecimiento, el cual permite comparar
algoritmos sin tener la preocupación del rendimiento de la implementación. Por lo
general, solo se toma el término de orden más alto sin coeficiente constante,
utilizando notación “theta” 𝛩.
Para el caso del ordenamiento por inserción, el mejor caso de ordenación por inserción
es 𝛩(𝑛) y el peor caso de ordenación por inserción es 𝛩(𝑛2 )
Análisis y Diseño de Algoritmos
Esta técnica (o paradigma) implica tres etapas que son: división, conquista y combina.
Volviendo al ejemplo del juego de cartas, supongamos ahora, que tenemos dos pilas
de cartas boca arriba en una mesa. Cada pila está ordenada, con las cartas más
pequeñas en la parte superior. Se desea fusionar las dos pilas en una sola pila
ordenada, que debe estar boca abajo sobre la mesa. El paso básico consiste en elegir
la más pequeña de las dos cartas en la parte superior de las pilas boca arriba, sacarla
de su pila (que expone una nueva carta superior) y colocar esta carta boca abajo en la
pila de salida. Repetimos este paso hasta que una pila de entrada esté vacía, En este
momento simplemente se toma la pila de entrada restante y se coloca boca abajo en
la pila de salida. Computacionalmente, cada paso básico toma un tiempo constante,
ya que estamos comparando solo las dos cartas superiores. Dado que se realiza como
máximo 𝑛 pasos básicos, la mezcla lleva 𝛩(𝑛) de tiempo.
El siguiente pseudocódigo implementa la idea anterior, pero con un giro adicional que
evita tener que verificar si alguna pila está vacía en cada paso básico. Se ubica en la
parte inferior de cada pila una tarjeta centinela, que contiene un valor especial que se
usará para simplificar el código. Aquí, se utilizará ∞ como valor centinela, de modo
que siempre que una carta con ∞ esté expuesta, no puede ser la carta más pequeña a
menos que ambas pilas tengan expuestas sus cartas centinela. Pero una vez que eso
sucede, todas las cartas que no son centinelas ya se han colocado en la pila de salida.
Como se sabe de antemano que exactamente 𝑟 − 𝑝 + 1 cartas se colocarán en la pila
de salida, se puede detener una vez que se hayan realizado los pasos básicos.
Mezcla(A,p,q,r)
1 n1 = q – p + 1
2 n2 = r – q
3 Permitir que L[1 … n1 + 1] y R[1 … n2 + 1] sean nuevos arreglos
4 desde i = 1 hasta n1
5 L[i] = A[p + i -1]
6 desde j = 1 hasta n2
7 R[j] = A[q + j]
8 L[n1 + 1] = ∞
9 R[n2 + 1] = ∞
10 i = 1
11 j = 1
12 desde k = p hasta r
13 si L[i] ≤ R[j]
14 A[k] = L[i]
15 i = i + 1
16 caso contrario A[k] = R[j]
17 j = j + 1
Figura 1
La operación de
las líneas 10 a 17
en el llamado al
procedimiento
Merge(A,9,12,16)
Se debe demostrar que este bucle invariante se mantiene antes de la primera iteración
del ciclo desde de las líneas 12 a 17, que cada iteración del ciclo mantiene la invarianza
y que el invariante proporciona una propiedad útil para mostrar la corrección cuando
el ciclo termina.
Inicialización: Antes de la primera iteración del ciclo, se tiene que 𝑘 = 𝑝, de modo que
la subsecuencia 𝐴[𝑝 … 𝑘 − 1] está vacía. Esta subsecuencia vacía contiene los 𝑘 − 𝑝 =
0 elementos más pequeños de 𝐿 y 𝑅, y dado que 𝑖 = 𝑗 = 1, tanto 𝐿[𝑖] como 𝑅[𝑗] son
Luis Oyarzun
los elementos más pequeños de sus secuencias que no se han copiado nuevamente en
12 𝐴.
Mantenimiento: para ver que cada iteración mantiene invariante el ciclo, primero se
supone que 𝐿[𝑖] ≤ 𝑅[𝑗]. Entonces 𝐿[𝑖] es el elemento más pequeño que aún no se ha
copiado de nuevo en 𝐴. Dado que 𝐴[𝑝 … 𝑘 − 1] contiene los 𝑘 − 𝑝 elementos más
pequeños, después de que la línea 14 copia 𝐿[𝑖] en 𝐴[𝑘], la subsecuencia 𝐴[𝑝 … 𝑘]
contendrá el 𝑘 − 𝑝 + 1 elementos más pequeños. Incrementando 𝑘 (en la
actualización del ciclo desde) e 𝑖 (en la línea 15) restablece el bucle invariante para la
siguiente iteración. Si en cambio 𝐿[𝑖] > 𝑅[𝑗], entonces las líneas 16 y 17 realizan la
acción apropiada para mantener el bucle invariante.
Ordena_Mezcla(A,p,r) Ordena_Mezcla(A,1,A.longitud)
1 si p < r // revisa por un caso base
2 𝑞 = ⌊(𝑝+𝑞)/2⌋ // divide
3 Ordena_Mezcla(𝐴, 𝑝, 𝑞) // conquista
4 Ordena_Mezcla(𝐴, q+1, r) // conquista
5 Mezcla(𝐴, 𝑝, 𝑞,𝑟) // combina
Para ordenar una secuencia entera de 𝐴 = 〈𝐴[1], 𝐴[2], … 𝐴[𝑛]〉, se hace un llamado
inicial al procedimiento 𝑂𝑟𝑑𝑒𝑛𝑎_𝑀𝑒𝑧𝑐𝑙𝑎(𝐴, 1, 𝐴. 𝑙𝑜𝑛𝑔𝑖𝑡𝑢𝑑), donde nuevamente
𝐴. 𝑙𝑜𝑛𝑔𝑖𝑡𝑢𝑑 = 𝑛. La siguiente figura (Figura 2) muestra el funcionamiento del
procedimiento de abajo hacia arriba cuando 𝑛 es una potencia de 2. El algoritmo
consiste en fusionar pares de secuencias de 1 elemento para formar secuencias
ordenadas de longitud 2, fusionando pares de secuencias de longitud 2 para formar
secuencias ordenadas de longitud 4, y así sucesivamente, hasta que dos secuencias de
longitud n = 2 se fusionen para formar la secuencia final ordenada de longitud n.
𝛩(1), si 𝑛 ≤ 𝑐
𝑇(𝑛) = {
𝑎𝑇(𝑛/𝑏) + 𝐷(𝑛) + 𝐶(𝑛), otros casos
Cuando se miran los tamaños de entrada lo suficientemente grandes como para hacer
relevante solo el orden de crecimiento del tiempo de ejecución, se está estudiando la
eficiencia asintótica de los algoritmos. Es decir, nos preocupa cómo aumenta el tiempo
de ejecución de un algoritmo con el tamaño de la entrada en el límite, a medida que
el tamaño de la entrada aumenta sin límite. Por lo general, un algoritmo que sea
asintóticamente más eficiente será la mejor opción para todas las entradas excepto las
muy pequeñas.
Notación asintótica
Las notaciones que se usan para describir el tiempo de ejecución asintótico de un
algoritmo se definen en términos de funciones cuyos dominios son el conjunto de
números naturales ℕ = {0, 1, 2, … }. Tales notaciones son convenientes para describir
la función de tiempo de ejecución del peor caso 𝑇(𝑛), que generalmente se define solo
en tamaños de entrada enteros. Sin embargo, a veces resulta conveniente abusar de
la notación asintótica de diversas formas. Por ejemplo, se podría extender la notación
al dominio de los números reales o, alternativamente, restringirla a un subconjunto de
los números naturales. Sin embargo, se debe asegurar de comprender el significado
preciso de la notación para que cuando se abuse, no hacerlo mal. Esta sección define
las notaciones asintóticas básicas y también presenta algunos abusos comunes.
En la Figura 3, se ofrece una imagen intuitiva de las funciones 𝑓(𝑛) y 𝑔(𝑛), donde
𝑓(𝑛) ∈ 𝛩(𝑔(𝑛)). Para todos los valores de 𝑛 en y a la derecha de 𝑛0 , el valor de 𝑓(𝑛)
se encuentra por encima de𝑐1 𝑔(𝑛) y por debajo de 𝑐2 𝑔(𝑛). En otras palabras, para
todo 𝑛 ≥ 𝑛0 , la función 𝑓(𝑛) es igual a 𝑔(𝑛) dentro de un factor constante. Y se
concluye que 𝑔(𝑛) es una cota asintóticamente ajustada para 𝑓(𝑛).
‡
Dentro de la notación una coma significa “tales que”
Luis Oyarzun
en cuenta que estas constantes dependen de la función 1/2 𝑛^2 − 3𝑛; una función
16 diferente perteneciente a 𝛩(𝑛2 ), normalmente requeriría diferentes constantes.
Se escribe 𝑓(𝑛) = 𝑂(𝑔(𝑛)) para indicar que una función 𝑓(𝑛) es miembro del
conjunto 𝑂(𝑔(𝑛)). Tenga en cuenta que 𝑓(𝑛) = 𝛩(𝑔(𝑛)) implica 𝑓(𝑛) = 𝑂(𝑔(𝑛)),
ya que la notación 𝛩‚ es una noción más fuerte que la notación O. Escrito en notación
para teoría de conjuntos, se tiene que Θ(𝑔(𝑛)) ⊆ O(𝑔(𝑛)). Por tanto, la prueba de
que cualquier función cuadrática 𝑎𝑛2 + 𝑏𝑛 + 𝑐, donde 𝑎 > 0, está en 𝛩(𝑛2 ) también
muestra que dicha función cuadrática está en 𝑂(𝑛2 ). Lo que puede ser más
sorprendente es que cuando 𝑎 > 0, cualquier función lineal 𝑎𝑛 + 𝑏 está en 𝑂(𝑛2 ), lo
𝑏
que se verifica fácilmente tomando 𝑐 = 𝑎 + |𝑏| y 𝑛0 = 𝑚á𝑥 (1, − ).
𝑎
Si ha visto la notación O antes, puede resultarle extraño que se deba escribir, por
ejemplo, 𝑛 = 𝑂(𝑛2 ). En la literatura, a veces se encuentra la notación O que describe
informalmente límites asintóticamente estrechos, es decir, lo que hemos definido
usando la notación 𝛩.
ciclo interno se ejecuta como máximo una vez para cada uno de los 𝑛2 pares de valores
para 𝑖 y 𝑗.
17
Dado que la notación O describe un límite superior, cuando se la usa para limitar el
peor tiempo de ejecución de un algoritmo, se tiene un límite en el tiempo de ejecución
del algoritmo en cada entrada, la declaración general que se revisó anteriormente. Por
lo tanto, el límite de 𝑂(𝑛2 ) en el peor de los casos de tiempo de ejecución del
ordenamiento por inserción también se aplica a su tiempo de ejecución en cada
entrada. Sin embargo, el límite 𝛩(𝑛2 ) en el peor tiempo de ejecución del
ordenamiento por inserción no implica un límite‚ 𝛩(𝑛2 ) en el tiempo de ejecución del
ordenamiento por inserción en cada entrada. Por ejemplo, se revisó que cuando la
entrada ya está ordenada, el ordenamiento por inserción se ejecuta en un tiempo
𝛩(𝑛).
A partir de las definiciones de las notaciones asintóticas que se han estudiado hasta
ahora, es fácil probar el siguiente teorema.
Teorema
Para dos funciones cualesquiera 𝑓(𝑛) y 𝑔(𝑛), tenemos 𝑓(𝑛) = 𝛩(𝑔(𝑛)) si y solo si
𝑓(𝑛) = 𝑂(𝑔(𝑛)) y 𝑓(𝑛) = 𝛺(𝑔(𝑛)).
§
Dentro de la notación una coma significa “tales que”
Luis Oyarzun
Algunos autores utilizan este límite como definición de la notación “o”; la definición
en este documento también restringe las funciones anónimas para que sean
asintóticamente no negativas.
𝑓(𝑛)
lim =∞
𝑛→∞ 𝑔(𝑛)
si existe el límite. Es decir, 𝑓(𝑛) se vuelve arbitrariamente grande en relación con 𝑔(𝑛)
cuando n se aproxima al infinito.
Comparando funciones
Muchas de las propiedades relacionales de los números reales se aplican también a las
comparaciones asintóticas. Para lo siguiente, suponga que 𝑓(𝑛) y 𝑔(𝑛) son
asintóticamente positivos.
Transitividad:
𝑓(𝑛) = 𝛩(𝑔(𝑛)) y 𝑔(𝑛) = 𝛩(ℎ(𝑛)) implica 𝑓(𝑛) = 𝛩(ℎ(𝑛))
Reflexividad
𝑓(𝑛) = 𝛩(𝑓(𝑛))
Análisis y Diseño de Algoritmos
𝑓(𝑛) = 𝑂(𝑓(𝑛))
19
𝑓(𝑛) = 𝛺(𝑓(𝑛))
𝑓(𝑛) = 𝑜(𝑓(𝑛))
𝑓(𝑛) = 𝜔(𝑓(𝑛))
Simetría
𝑓(𝑛) = 𝛩(𝑔(𝑛)) si y solo si 𝑔(𝑛) = 𝛩(𝑓(𝑛))
Simetría transpuesta
𝑓(𝑛) = 𝑂(𝑔(𝑛)) si y solo si 𝑔(𝑛) = 𝛺(𝑓(𝑛))
Debido a que estas propiedades son válidas para las notaciones asintóticas, se puede
establecer una analogía entre la comparación asintótica de dos funciones 𝑓 y 𝑔 y la
comparación de dos números reales 𝑎 y 𝑏:
Se define que 𝑓(𝑛) es asintóticamente más pequeño que 𝑔(𝑛) si 𝑓(𝑛) = 𝑜(𝑔(𝑛)), y
𝑓(𝑛) es asintóticamente más grande que 𝑓(𝑛) = 𝜔(𝑔(𝑛)).
Aunque se pueden comparar dos números reales cualesquiera, no todas las funciones
son asintóticamente comparables. Es decir, para dos funciones 𝑓(𝑛) y 𝑔(𝑛), puede
darse el caso de que ni 𝑓(𝑛) = 𝑂(𝑔(𝑛)) ni 𝑓(𝑛) = 𝛺(𝑔(𝑛)) se cumplan. Por ejemplo,
no se pueden comparar las funciones 𝑛 y 𝑛1+sin 𝑛 usando notación asintótica, ya que
el valor del exponente en 𝑛1+sin 𝑛 oscila entre 0 y 2, tomando todos los valores
intermedios.
Unidad 2: Técnicas de diseño de algoritmos.
Tipos de algoritmos
Los algoritmos se pueden clasificar básicamente en 6 tipos:
• Recursivo
• Divide y Conquista
• Programación dinámica
• Voraz (Greedy)
• Fuerza bruta
• Backtracking recursivo
Algoritmos Recursivos
Son los algoritmos, donde una función se vuelve a llamar hasta que se cumple una
condición base.
Ejemplo:
𝑖=1
𝑚𝑢𝑙𝑡𝑖𝑝𝑙𝑖𝑐𝑎𝑐𝑖𝑜𝑛(𝑥, 𝑦) = ∑ 𝑥𝑖
𝑖=𝑦
Ejemplo:
En este algoritmo, encontramos una solución óptima localmente (sin tener en cuenta
ninguna consecuencia en el futuro) y esperamos encontrar la solución óptima a nivel
global.
Ejemplo:
El algoritmo de Dijkstra, busca el camino más corto entre los nodos de un grafo,
analizando cada posible caso que tenga.
Un algoritmo de fuerza bruta itera ciegamente todas las soluciones posibles para
buscar una o más de una solución que pueda resolver una función. 23
Piense en la fuerza bruta como si utilizara todas las combinaciones posibles de
números para abrir una caja fuerte.
Ejemplo:
1 Algoritmo B_Busqueda(A[0..n], X)
2 A[n] ← X
3 i ← 0
4 mientras que A[i] ≠ X haga
5 i ← i + 1
6 si i < n
7 devuelve i
8 sino
9 devuelve -1
Algoritmo de Retroceso (Backtracking)
El bactracking, retroceso o vuelta atrás, es una técnica para encontrar una solución a
un problema en un enfoque incremental.
Ejemplo:
Algoritmo de ordenamiento
Ordenamiento Rápido (Quicksort)
Arboles de búsqueda