[go: up one dir, main page]

0% encontró este documento útil (0 votos)
32 vistas33 páginas

Algoritmos de Búsqueda y Recursividad

Este documento describe diferentes algoritmos de búsqueda como la búsqueda lineal y la búsqueda binaria, y explica su complejidad computacional. También introduce el concepto de recursividad y cómo puede aplicarse a problemas como el ordenamiento por mezcla.

Cargado por

JRoiz
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
32 vistas33 páginas

Algoritmos de Búsqueda y Recursividad

Este documento describe diferentes algoritmos de búsqueda como la búsqueda lineal y la búsqueda binaria, y explica su complejidad computacional. También introduce el concepto de recursividad y cómo puede aplicarse a problemas como el ordenamiento por mezcla.

Cargado por

JRoiz
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 33

Lenguaje C

Semana 7

Clase 12
Agenda

1 Búsqueda 2 Recursividad
Búsqueda
★ La búsqueda es la manera en la que resolvemos el problema de
buscar información. Un problema simple tiene una entrada de
ciertos valores, y una salida de un bool, dependiendo si un valor se
encontró o no.
★ A esto le conoceremos como algoritmos de búsqueda y para
comparar su eficiencia, vamos a considerar su tiempo de ejecución,
o cuánto tiempo toma un algoritmo en ejecutarse dada cierta
entrada; así que antes de continuar, recordemos un poco de
complejidad computacional.
★ Los científicos computacionales suelen describir el tiempo de
ejecución con la notación gran O, lo podemos pensar cómo “en
orden de” algo, queremos dar la idea de tiempo de ejecución y no
un número exacto de milisegundos o de pasos.
★ Recordemos el ejemplo visto en la primera semana, en donde buscábamos
un número en una agenda telefónica.

Recuerda que la línea roja


representa la búsqueda lineal, una
página a la vez, la línea amarilla
representa buscar dos páginas a la
vez, y la línea verde es dividir y
conquistar, empezamos en el
medio y dividimos el problema a
la mitad cada vez.
Usamos la gran notación O, para describir tanto la línea roja cómo la amarilla,
estas terminan siendo muy similares cada vez que “n” se hace más y más largo.

Pero, la línea verde, es


fundamentalmente distinta en su
forma, incluso si n se vuelve muy
largo, va tomar “Gran O de log n”
pasos.
★ Los científicos computacionales también suelen usar big 𝛀,
notación gran Omega, la cual describe la menor cantidad de pasos
que toma el algoritmo en el mejor de los casos. En gran notación O,
cuando decimos “en orden de”, contamos la mayor cantidad de
pasos que un algoritmo va tomar en el peor de los casos.

★ Finalmente, hay otra notación, Θ (theta), la cual usaremos para


describir el tiempo de ejecución de un algoritmo si la cantidad de
pasos que toma es igual.
Ahora, volvamos al tema principal…
★ Supongamos que tenemos siete casilleros con sus puertas cerradas,
con números escondidos detrás de ellas. Debido a que las
computadoras sólo pueden ver un elemento a la vez, solo podemos
abrir una puerta a la vez.

★ Si queremos buscar el número cero, vamos a tener que abrir una puerta
a la vez, y si no supiéramos nada acerca de los números detrás de las
puertas, el algoritmo más sencillo sería empezar a revisar de izquierda
a derecha.
Este algoritmo, “búsqueda lineal”, sería correcto pero no muy eficiente. El
pseudocódigo se vería así:

Por cada puerta de izquierda a derecha


Si el número está detrás de la puerta
Return true
Return false

El return false está fuera del ciclo, debido a que queremos hacer
eso solo si ya revisamos todas las puertas.
Podemos escribir nuestro pseudocódigo para que sea más cercano a C:

For i from 0 to n-1


If número detrás puertas[i]
Return true
Return false

-> El tiempo de ejecución gran O para este algoritmo va ser O(n).

-> La menor cantidad de pasos, gran Omega, sería 𝛀(1), debido a que
podemos encontrar el número que estamos buscando enseguida.
★ Si sabemos que los números detrás de las puertas están ordenados,
entonces, podemos empezar en el medio, y encontrar nuestro valor
de manera más eficaz, debido a que sabemos que podemos ir
derecha o izquierda, dividiendo el problema a la mitad cada vez. A
esto le conoceremos como búsqueda binaria.
Para la “búsqueda binaria”, el pseudocódigo para nuestro algoritmo se
vería así:

Si no hay puertas
Return false
Si número detrás la puerta del medio
Return true
Else if numero < puerta del medio
Busca la parte izquierda
Else if numero > puerta del medio
Busca la parte derecha
Podemos escribir este pseudocódigo para que sea más cómo C:

If no puertas
Return false
If número detrás puertas[medio]
Return true
Else if número < puertas[medio]
Busca puertas[0] hasta puertas[medio - 1]
Else if número > puertas[medio]
Busca puertas[medio + 1] hasta puertas[n - 1]
★ El peor de los casos para búsqueda es O(log n), debido a que es
posible que tengamos que dividir el número de puertas entre dos
hasta que ya no haya más puertas. El mejor de los casos 𝛀(1) es que
el número que estemos buscando se encuentre directamente en el
medio.

★ Aunque la búsqueda binaria es mucho más rápida que la búsqueda


lineal, nuestro arreglo tiene que estar ordenado primero.
Si queremos verificar strings, recordemos que debemos usar strcmp para
comparar cadenas.

argv.c

for (int i = 0; i < 7; i++) {


if (strcmp(names[i], "David") == 0) {
printf("Found %s\n", numbers[i]);
return 0;
}
}
printf("Not found\n");
return 1;
Recursividad
★ La recursión es la habilidad que tiene una función para llamarse a sí
misma. No hemos visto esto en código todavía, pero vimos el
pseudocódigo para la búsqueda binaria:
Si no hay puertas
Return false
Si el número está en la puerta del medio
Return true
Else if número < puerta del medio
Busca en el lado izquierdo
Else if número > puerta del medio
Busca en el lado derecho
★ Usamos el mismo algoritmo de “Buscar” para cada mitad. Esto
parece un proceso cíclico que nunca va terminar, pero realmente le
estamos cambiando la entrada a la función y dividiendo el
problema a la mitad cada vez, parando una vez ya no haya más
puertas que abrir.
Veamos el tema de una forma más sencilla, antes, implementamos una
“pirámide” de bloques con la siguiente forma:

argv.c

void draw(int n)
{
#
for (int i = 0; i < n; i++) { ##
for (int j = 0; j < i + 1; j++) {
printf("#");
###
} ####
printf("\n");
}
}
★ Debido a que una pirámide es una estructura recursiva, podemos
escribir una función recursiva para dibujar la imagen, una función
que se llama a sí misma para dibujar una versión más pequeña de la
imagen antes de añadir otra fila.
argv.c

void draw(int n)
{
if (n <= 0) {
return;
}
draw(n - 1);
for (int i = 0; i < n; i++) {
printf("#");
}
printf("\n");
}
★ Podemos reescribir nuestra función draw para que se use a sí
misma.
★ Si n es 0 o negativo pararemos sin imprimir nada. Tenemos que
asegurarnos que va parar para tener nuestro caso base, sino la
función se va a llamar a sí misma una y otra vez.
★ Si la entrada es válida, llamaremos a draw de nuevo, para imprimir
una pirámide de tamaño n - 1.
★ Luego, imprimimos la fila de bloques que necesitamos para nuestra
pirámide de tamaño n.
Veamos otro ejemplo:

factorial.c

long factorial(int n)
{
if (n == 0) // Caso base
{
return 1;
}
else
{
return n * factorial(n - 1);
}
}
Supongamos que queremos calcular 5!, el cual se puede subdividir de la
siguiente manera:

factorial(1) 1

factorial(2) 2 * factorial(1)

factorial(3) 3 * factorial(2)

factorial(4) 4 * factorial(3)

factorial(5) 5 * factorial(4)

Donde cada llamada a la función retornará un valor a la


llamada anterior.
★ Ahora, podemos tomar la idea de recursión y usarla para ordenar,
usaremos otro algoritmo llamado ordenamiento por mezcla. El
pseudocódigo se ve:

Si solo hay un número


Salir
Else
Ordena el lado izquierdo del número
Ordena el lado derecho del número
Mezcla ambas mitades
★ Nuestra estrategia será la de ordenar cada mitad, y luego mezclarlas
juntas. Empecemos con dos mitades ordenadas que se miran así:

2 4 5 7 0 1 3 6

0 2 4 5 7 1 3 6
0 1 2 4 5 7 3 6

0 1 2 4 5 7 3 6

0 1 2 3 4 5 7 6
0 1 2 3 4 5 7 6

0 1 2 3 4 5 7 6

0 1 2 3 4 5 6 7
★ Cada vez que mezclamos dos mitades, solo necesitamos ver a cada
número una sola vez. Dividimos nuestra lista de ocho números tres
veces, o log(n) veces. Necesitamos más memoria para mezclar
nuestras nuevas listas, pero, el tiempo de ejecución en el peor de
los casos para este ordenamiento es de O(n log n).

★ El tiempo de ejecución en el mejor de los casos sigue siendo 𝛀(n


log n), ya que debemos hacer todo el trabajo incluso si la lista está
ordenada. Entonces el ordenamiento por mezcla también (n log n).
¿Preguntas?

También podría gustarte