04 Grokking Algorithms Illustrated Programmers Curious ESP
04 Grokking Algorithms Illustrated Programmers Curious ESP
04 Grokking Algorithms Illustrated Programmers Curious ESP
asimilando
algoritmos
Machine Translated by Google
Machine Translated by Google
asimilando
algoritmos
Una guía ilustrada
para programadores y otros curiosos
Aditya Y. Bhargava
MANEJO
Isla Refugio
Machine Translated by Google
Para obtener información en línea y solicitar este y otros libros de Manning, visite
www.manning.com. La editorial ofrece descuentos en este libro cuando se pide en cantidad. Para
obtener más información, póngase en contacto
Muchas de las designaciones utilizadas por los fabricantes y vendedores para distinguir sus
productos se reclaman como marcas comerciales. Donde esas designaciones aparecen en el libro, y
Manning Publications estaba al tanto de un reclamo de marca registrada, las designaciones se han
impreso en mayúsculas iniciales o en mayúsculas.
ISBN: 9781617292231
Impreso en los Estados Unidos de América
1 2 3 4 5 6 7 8 9 10 – MBE – 21 20 19 18 17 16
Machine Translated by Google
viii
contenido
prefacio XIII
agradecimientos xiv
Introducción 1
Búsqueda binaria 3
Tiempo de ejecución 10
Notación O grande 10
El vendedor ambulante 17
Resumen 19
2 Clasificación de selección 21
arreglos 26
Terminología 27
viii contenido
Clasificación de selección 32
Resumen 36
3 recursividad 37
recursividad 38
Caso base y caso recursivo 40
La pila 42
La pila de llamadas 43
La pila de llamadas con recursividad 45
Resumen 50
4 Clasificación rápida 51
Divide y vencerás 52
Ordenación rápida 60
Notación Big O revisada 66
Recapitulación 72
5 tablas hash 73
Funciones hash 76
casos de uso 79
Resumen 86
Colisiones 86
Rendimiento 88
Factor de carga 90
6 Búsqueda en amplitud 95
contenido ix
Colas 103
Implementando el gráfico 105
Implementando el algoritmo 107
Tiempo de ejecución 111
Resumen 114
X contenido
Árboles 203
Índices invertidos 206
La transformada de Fourier 207
Algoritmos paralelos 208
Mapa reducido 209
¿Por qué son útiles los algoritmos distribuidos? 209
La función de mapa 209
La función de reducción 210
Filtros Bloom y HyperLogLog 211
Filtros de floración 212
Machine Translated by Google
contenido xi
HyperLogLog 213
Los algoritmos SHA 213
Comparando archivos 214
Comprobación de contraseñas 215
Hashing sensible a la localidad 216
Intercambio de claves Diffie-Hellman 217
Programación lineal 218
Epílogo 219
respuestas a ejercicios 221
índice 235
Machine Translated by Google
Machine Translated by Google
prefacio
Primero me metí en la programación como un hobby. Visual Basic 6 for Dummies me
enseñó los conceptos básicos y seguí leyendo libros para aprender más. Pero el tema
de los algoritmos era impenetrable para mí. Recuerdo saborear la tabla de contenido
de mi primer libro de algoritmos, pensando "¡Por fin voy a entender estos temas!" Pero
era algo denso, y me di por vencido después de unas semanas. No fue hasta que tuve
mi primer buen profesor de algoritmos que me di cuenta de lo simples y elegantes que
eran estas ideas.
Hace unos años, escribí mi primera entrada de blog ilustrada. Soy un aprendiz
visual y me gustó mucho el estilo ilustrado. Desde entonces, he escrito algunas
publicaciones ilustradas sobre programación funcional, Git, aprendizaje automático y
concurrencia. Por cierto: yo era un escritor mediocre cuando empecé. Explicar
conceptos técnicos es difícil. Proponer buenos ejemplos lleva tiempo, y explicar un
concepto difícil lleva tiempo.
Así que es más fácil pasar por alto las cosas difíciles. Pensé que estaba haciendo un
buen trabajo, hasta que una de mis publicaciones se hizo popular, un compañero de
trabajo se me acercó y me dijo: "Leí tu publicación y todavía no entiendo esto". Todavía
tenía mucho que aprender acerca de la escritura.
XIII
Machine Translated by Google
expresiones de gratitud
Felicitaciones a Manning por darme la oportunidad de escribir este libro
y permitirme tener mucha libertad creativa con él. Gracias a la editora
Marjan Bace, Mike Stephens por ayudarme, a Bert Bates por enseñarme
a escribir y a Jennifer Stout por ser una editora increíblemente receptiva
y útil. Gracias también a la gente del equipo de producción de Manning:
Kevin Sullivan, Mary Piergies, Tiffany Taylor, Leslie Haimes y todos los
demás detrás de escena. Además, quiero agradecer a las muchas
personas que leyeron el manuscrito y ofrecieron sugerencias: Karen
Bensdon, Rob Green, Michael Hamrah, Ozren Harlovic, Colin Hastie,
Christopher Haupt, Chuck Henderson, Pawel Kozlowski, Amit Lamba, Jean-
François Morin, Robert Morrison, Sankar Ramanathan, Sander Rossel,
Doug Sparling y Damien White.
Gracias a las personas que me ayudaron a llegar a este punto: la gente
del tablero de Flaskhit, por enseñarme a codificar; los muchos amigos que
me ayudaron revisando capítulos, dándome consejos y permitiéndome
probar diferentes explicaciones, incluidos Ben Vinegar, Karl Puzon, Alex
Manning, Esther Chan, Anish Bhatt, Michael Glass, Nikrad Mahdi, Charles
Lee, Jared Friedman, Hema Manickavasagam , Hari Raja, Murali Gudipati,
Srinivas Varadan y otros; y Gerry Brady, por enseñarme algoritmos. Otro
gran agradecimiento a los académicos de algoritmos como CLRS, Knuth y
Strang. Realmente estoy de pie sobre los hombros de gigantes.
Papá, mamá, Priyanka y el resto de la familia: gracias por su constante
apoyo. Y muchas gracias a mi esposa Maggie. Tenemos muchas
aventuras por delante, y algunas de ellas no implican quedarse en casa
un viernes por la noche reescribiendo párrafos.
Finalmente, muchas gracias a todos los lectores que se arriesgaron con
este libro y a los lectores que me dieron su opinión en el foro del libro.
Realmente ayudaste a mejorar este libro.
xiv
Machine Translated by Google
Mapa vial
Los tres primeros capítulos de este libro sientan las bases:
XV
Machine Translated by Google
• Capítulo 3: aprenderá sobre la recursividad, una técnica útil utilizada por muchos
algoritmos (como el ordenamiento rápido, que se trata en el capítulo 4).
• Tablas hash : cubiertas en el capítulo 5. Una tabla hash es una estructura de datos
muy útil. Contiene conjuntos de pares de clave y valor, como el nombre de una
persona y su dirección de correo electrónico, o un nombre de usuario y la contraseña asociada.
Es difícil exagerar la utilidad de las tablas hash. Cuando quiero resolver un
problema, los dos planes de ataque con los que empiezo son "¿Puedo usar una
tabla hash?" y "¿Puedo modelar esto como un gráfico?"
• Algoritmos de gráficos: se tratan en los capítulos 6 y 7. Los gráficos son una forma de
modelar una red: una red social, una red de caminos, neuronas o cualquier otro
conjunto de conexiones. La búsqueda primero en amplitud (capítulo 6) y el algoritmo
de Dijkstra (capítulo 7) son formas de encontrar la distancia más corta entre dos puntos
en una red: puede usar este enfoque para calcular los grados de separación entre dos
personas o la ruta más corta a un destino .
• Próximos pasos: el Capítulo 11 repasa 10 algoritmos que serían una buena lectura
adicional.
Machine Translated by Google
Recomiendo encarecidamente ejecutar el código de los ejemplos usted mismo. No puedo enfatizar esta
parte lo suficiente. Simplemente escriba mis ejemplos de código palabra por palabra (o descárguelos
de www.manning.com/books/grokking algoritmos o https://github.com/egonschiele/grokking_algorithms)
y ejecútelos. Retendrá mucho más si lo hace.
También recomiendo hacer los ejercicios de este libro. Los ejercicios son breves, generalmente de
uno o dos minutos, a veces de 5 a 10 minutos. Le ayudarán a revisar su forma de pensar, para que
sepa cuándo se está desviando antes de que haya ido demasiado lejos.
• Codificadores aficionados
Puede descargar el código de los ejemplos del libro desde el sitio web de la editorial en
www.manning.com/books/grokking-algorithms o desde https://github.com/egonschiele/grokking_algorithms.
Creo que aprendes mejor cuando realmente disfrutas aprendiendo, ¡así que diviértete y ejecuta los
ejemplos de código!
Machine Translated by Google
Sobre el Autor
Aditya Bhargava es ingeniero de software en Etsy, un mercado en línea
para productos hechos a mano. Tiene una maestría en informática de la
Universidad de Chicago. También dirige un popular blog tecnológico
ilustrado en adit.io.
Autor en línea
La compra de Grokking Algorithms incluye acceso gratuito a un foro web
privado administrado por Manning Publications donde puede hacer
comentarios sobre el libro, hacer preguntas técnicas y recibir ayuda del
autor y de otros usuarios. Para acceder al foro y suscribirse, apunte su
navegador web a www.manning.com/books/grokking algoritmos. Esta
página brinda información sobre cómo ingresar al foro una vez que está
registrado, qué tipo de ayuda está disponible y las reglas de conducta en
el foro.
introducción
a los algoritmos 1
En este capítulo
• Obtienes una base para el resto del libro.
Introducción
Un algoritmo es un conjunto de instrucciones para realizar una tarea. Cada
pieza de código podría llamarse algoritmo, pero este libro cubre las partes
más interesantes. Elegí los algoritmos de este libro para incluirlos porque son
rápidos o resuelven problemas interesantes, o ambas cosas. Aquí hay algunos
aspectos destacados:
1
Machine Translated by Google
En términos más generales, al final de este libro, conocerá algunos de los algoritmos
más ampliamente aplicables. Luego puede usar su nuevo conocimiento para aprender
sobre algoritmos más específicos para IA, bases de datos, etc. O puede asumir desafíos
más grandes en el trabajo.
Machine Translated by Google
Búsqueda binaria 3
Búsqueda binaria
Supongamos que está buscando a una persona en la guía telefónica (¡qué frase tan
anticuada!). Su nombre comienza con K. Puede comenzar desde el principio y seguir
pasando las páginas hasta llegar a las K. Pero es más probable que comience en una
página en el medio, porque sabe que las K estarán cerca del medio de la guía telefónica.
Ahora suponga que inicia sesión en Facebook. Cuando lo haga, Facebook tiene
que verificar que tiene una cuenta en el sitio. Por lo tanto, necesita buscar su
nombre de usuario en su base de datos. Supongamos que su nombre de usuario es
karlmageddon. Facebook podría comenzar desde As y buscar su nombre, pero tiene
más sentido que comience en algún punto intermedio.
Por ejemplo:
Buscando empresas en
una guía telefónica con
búsqueda binaria
Tienes que intentar adivinar mi número en el menor número de intentos posible. Con
cada suposición, te diré si tu suposición es demasiado baja, demasiado alta o correcta.
Búsqueda binaria 5
Un mal enfoque
para adivinar números
Esta es una búsqueda simple (tal vez la búsqueda estúpida sería un mejor término). Con
cada suposición, estás eliminando solo un número. Si mi número fuera 99, ¡podría tomar 99
intentos para llegar allí!
¡Demasiado bajo, pero acabas de eliminar la mitad de los números! Ahora sabes que 1–50
son demasiado bajos. Próxima suposición: 75.
Machine Translated by Google
Eliminar la mitad de la
números cada vez con
búsqueda binaria.
La búsqueda simple podría tomar 240,000 pasos si la palabra que está buscando
es la última del libro. Con cada paso de la búsqueda binaria, reduce el número de
palabras a la mitad hasta que solo le queda una palabra.
Machine Translated by Google
Búsqueda binaria 7
Entonces, la búsqueda binaria tomará 18 pasos, ¡una gran diferencia! En general, para cualquier
lista de n, la búsqueda binaria tomará log2 n pasos para ejecutarse en el peor de los casos,
mientras que la búsqueda simple tomará n pasos.
logaritmos
Puede que no recuerdes qué son los logaritmos, pero probablemente sepas qué son los
exponenciales. log10 100 es como preguntar: "¿Cuántos 10 multiplicamos para obtener
100?" La respuesta es 2: 10 × 10. Así que log10 100 = 2. Los logaritmos son la inversión de
exponenciales.
En este libro, cuando hablo sobre el tiempo de ejecución en notación Big O (explicado un
poco más adelante), log siempre significa log2 . Cuando busca un elemento mediante la
búsqueda simple, en el peor de los casos, es posible que tenga que mirar cada uno de los
elementos. Entonces, para una lista de 8 números, tendrías que marcar 8 números como máximo.
Para la búsqueda binaria, debe verificar los elementos log n en el peor de los casos. Para
una lista de 8 elementos, log 8 == 3, porque 23 == 8. Entonces, para una lista de 8 números,
tendrías que marcar 3 números como máximo. Para una lista de 1024 elementos, log 1024
= 10, porque 210 == 1024. Entonces, para una lista de 1024 números, tendrías que marcar
10 números como máximo.
Nota
Hablaré mucho sobre el tiempo de registro en este libro, por lo que debe
comprender el concepto de logaritmos. Si no es así, Khan Academy
(khanacademy.org) tiene un buen video que lo deja claro.
Machine Translated by Google
Nota
La búsqueda binaria solo funciona cuando su lista está ordenada. Por ejemplo, los
nombres de una guía telefónica se ordenan alfabéticamente, por lo que puede
utilizar la búsqueda binaria para buscar un nombre. ¿Qué pasaría si los nombres no
estuvieran ordenados?
Veamos cómo escribir una búsqueda binaria en Python. El ejemplo de código aquí usa
matrices. Si no sabe cómo funcionan las matrices, no se preocupe; se tratan en el próximo
capítulo. Solo necesita saber que puede almacenar una secuencia de elementos en una
fila de cubos consecutivos llamada matriz.
Los cubos están numerados comenzando con 0: el primer cubo está en la posición #0, el
segundo es el #1, el tercero es el #2 y así sucesivamente.
bajo = 0
alto = len(lista) - 1
Búsqueda binaria 9
mi_lista = [1, 3, 5, 7, 9]
EJERCICIOS
1.1 Suponga que tiene una lista ordenada de 128 nombres y la está buscando
utilizando una búsqueda binaria. ¿Cuál es el número máximo de pasos que
daría?
Tiempo de ejecución
Cada vez que hablo de un algoritmo, hablaré de su tiempo de ejecución.
Por lo general, desea elegir el algoritmo más eficiente:
ya sea que esté tratando de optimizar por tiempo o espacio.
La búsqueda binaria es diferente. Si la lista tiene 100 elementos, se necesitan como máximo 7
intentos. Si la lista es de 4 mil millones de elementos, se necesitan como máximo 32 conjeturas.
Potente, ¿eh? La búsqueda binaria se ejecuta en tiempo logarítmico (o tiempo de registro, como
lo llaman los nativos). Aquí hay una tabla que resume nuestros hallazgos de hoy.
Tiempos de ejecución
Notación O grande
La notación Big O es una notación especial que te dice qué tan rápido es un algoritmo.
¿A quien le importa? Bueno, resulta que usarás los algoritmos de otras personas con frecuencia,
y cuando lo haces, es bueno entender cuán rápidos o lentos son. En esta sección, explicaré qué
es la notación Big O y le daré una lista de los tiempos de ejecución más comunes para los algoritmos
que la usan.
Machine Translated by Google
Notación O grande 11
Tiempo de ejecución
para búsqueda simple
frente a búsqueda
binaria, con una lista de 100
elementos
Bob ejecuta una búsqueda binaria con mil millones de elementos y tarda 30 ms
(log2 1,000,000,000 es aproximadamente 30). “¡32 ms!” él piensa. “La búsqueda binaria
es aproximadamente 15 veces más rápida que la búsqueda simple, porque la búsqueda
simple tomó 100 ms con 100 elementos y la búsqueda binaria tomó 7 ms. Entonces, la
búsqueda simple tomará 30 × 15 = 450 ms, ¿verdad? Muy por debajo de mi umbral de 10
segundos”. Bob decide ir con la búsqueda simple. ¿Es esa la elección correcta?
Machine Translated by Google
No. Resulta que Bob está equivocado. Completamente equivocado. El tiempo de ejecución para
una búsqueda simple con 1000 millones de elementos será de 1000 millones de ms, ¡lo que
equivale a 11 días! El problema es que los tiempos de ejecución de la búsqueda binaria y la
búsqueda simple no crecen al mismo ritmo.
Es decir, a medida que aumenta el número de elementos, la búsqueda binaria tarda un poco más
en ejecutarse. Pero la búsqueda simple requiere mucho más tiempo para ejecutarse. Entonces, a
medida que la lista de números crece, la búsqueda binaria de repente se vuelve mucho más rápida
que la búsqueda simple. Bob pensó que la búsqueda binaria era 15 veces más rápida que la
búsqueda simple, pero eso no es correcto. Si la lista tiene mil millones de elementos, es más como
33 millones de veces más rápido. Es por eso que no es suficiente saber cuánto tarda en ejecutarse
un algoritmo: debe saber cómo aumenta el tiempo de ejecución a medida que aumenta el tamaño de
la lista. Ahí es donde Big O
entra la notación.
La notación Big O te dice qué tan rápido es un algoritmo. Por ejemplo, suponga que tiene una lista de
tamaño n. La búsqueda simple necesita verificar cada elemento, por lo que tomará n operaciones. El
tiempo de ejecución en notación Big O es O(n). ¿Dónde están los segundos? No hay ninguno: Big O no
te dice la velocidad en segundos. La notación Big O le permite comparar el número de operaciones. Te
dice qué tan rápido crece el algoritmo.
Machine Translated by Google
Notación O grande 13
Aquí hay otro ejemplo. La búsqueda binaria necesita operaciones de registro n para verificar
una lista de tamaño n. ¿Cuál es el tiempo de ejecución en notación Big O? Es O (registro n).
En general, la notación Big O se escribe de la siguiente manera.
Cómo se ve la
notación Big O
Esto le indica el número de operaciones que realizará un algoritmo. Se llama notación Big
O porque pones una "gran O" delante del número de operaciones (suena como una broma, ¡pero
es verdad!).
Ahora veamos algunos ejemplos. Vea si puede calcular el tiempo de ejecución de estos
algoritmos.
Dibujar una
cuadrícula un cuadro a la vez
Algoritmo 2
Pruebe este algoritmo en su lugar. Dobla el papel.
En este ejemplo, doblar el papel una vez es una operación. ¡Acabas de hacer dos
cajas con esa operación!
Puede "dibujar" el doble de cajas con cada pliegue, por lo que puede dibujar 16
cajas en 4 pasos. ¿Cuál es el tiempo de ejecución de este algoritmo? Proponga
tiempos de ejecución para ambos algoritmos antes de continuar.
Notación O grande 15
Suponga que está utilizando la búsqueda simple para buscar a una persona en la guía
telefónica. Sabe que la búsqueda simple tarda O(n) tiempo en ejecutarse, lo que significa que,
en el peor de los casos, tendrá que revisar todas las entradas de su directorio telefónico. En este
caso, estás buscando a Adit. Este tipo es la primera entrada en tu guía telefónica. Por lo tanto,
no tuvo que mirar todas las entradas: las encontró en el primer intento. ¿Este algoritmo tomó
O(n) tiempo?
¿O tomó O (1) tiempo porque encontró a la persona en el primer intento?
La búsqueda simple aún requiere tiempo O(n). En este caso, encontraste lo que buscabas al
instante. Ese es el mejor de los casos. Pero la notación Big O se trata del peor de los casos.
Así que puedes decir que, en el peor de los casos, tendrás que mirar cada entrada en la guía
telefónica una vez.
Eso es O(n) tiempo. Es una garantía: sabe que la búsqueda simple nunca será más lenta que
el tiempo O(n).
Nota
Junto con el tiempo de ejecución del peor de los casos, también es importante observar el
tiempo de ejecución del caso promedio. El peor de los casos versus el caso promedio se analiza
en el capítulo 4.
Aquí hay cinco tiempos de ejecución de Big O que encontrará mucho, ordenados del más
rápido al más lento:
• O(log n), también conocido como tiempo de registro. Ejemplo: búsqueda binaria.
Suponga que está dibujando una cuadrícula de 16 cuadros nuevamente y puede elegir entre
5 algoritmos diferentes para hacerlo. Si usa el primer algoritmo, le llevará O (log n) tiempo
dibujar la cuadrícula. Puedes hacer 10 operaciones.
Machine Translated by Google
dieciséis
capitulo 1 yo Introducción a los algoritmos
por segundo. Con el tiempo O (log n), le tomará 4 operaciones dibujar una cuadrícula de
16 cajas (log 16 es 4). Por lo tanto, le llevará 0,4 segundos dibujar la cuadrícula. ¿Qué
pasa si tienes que dibujar 1.024 cajas? Le tomará registrar 1024 = 10 operaciones, o 1
segundo para dibujar una cuadrícula de 1024 cajas.
Estos números están usando el primer algoritmo.
Este es el tiempo que llevaría dibujar una cuadrícula para el resto de los
algoritmos, del más rápido al más lento:
También hay otros tiempos de ejecución, pero estos son los cinco más comunes.
• O(log n) es más rápido que O(n), pero se vuelve mucho más rápido a medida que la lista de elementos
estás buscando crece.
Machine Translated by Google
Notación O grande 17
EJERCICIOS
Proporcione el tiempo de ejecución para cada uno de estos escenarios en términos de Big O.
El vendedor ambulante
Es posible que haya leído la última sección y haya pensado: "No hay forma de que
me encuentre con un algoritmo que tome O (n!) tiempo". Bueno, ¡déjame intentar
demostrarte que estás equivocado! Aquí hay un ejemplo de un algoritmo con un
tiempo de ejecución realmente malo. Este es un problema famoso en informática,
porque su crecimiento es terrible y algunas personas muy inteligentes piensan que
no se puede mejorar. Se llama el problema del viajante de comercio.
Tienes un vendedor.
Machine Translated by Google
Este vendedor, a quien llamaré Opus, quiere llegar a las cinco ciudades
recorriendo la distancia mínima. He aquí una forma de hacerlo: mire todos los
órdenes posibles en los que podría viajar a las ciudades.
El número de
operaciones
aumenta drásticamente.
Machine Translated by Google
Resumen 19
¡Este es un algoritmo terrible! Opus debería usar uno diferente, ¿verdad? Pero no puede. Este es
uno de los problemas no resueltos en informática.
No existe un algoritmo rápido conocido para ello, y las personas inteligentes piensan que es
imposible tener un algoritmo inteligente para este problema. Lo mejor que podemos hacer es llegar
a una solución aproximada; consulte el capítulo 10 para obtener más información.
Una nota final: si eres un lector avanzado, ¡echa un vistazo a los árboles de búsqueda binarios!
Hay una breve descripción de ellos en el último capítulo.
Resumen
• O(log n) es más rápido que O(n), pero se vuelve mucho más rápido una vez que la lista de
elementos que está buscando a través de crece.
clasificación de
selección
2
En este capítulo
• Aprende sobre arreglos y listas enlazadas, dos de las estructuras
de datos más básicas. Se utilizan absolutamente en todas
partes. Ya usó arreglos en el capítulo 1 y los usará en casi
todos los capítulos de este libro. Las matrices son un tema
crucial, ¡así que presta atención!
Pero a veces es mejor usar una lista enlazada en lugar de una
matriz. Este capítulo explica los pros y los contras de ambos
para que pueda decidir cuál es el adecuado para su algoritmo.
21
Machine Translated by Google
/
fe0ffeeb es la dirección de una ranura en la memoria.
¿Deberías usar una matriz o una lista enlazada? Primero almacenemos todos en una matriz,
porque es más fácil de entender. El uso de una matriz significa que todas sus tareas se
almacenan de forma contigua (una al lado de la otra) en la memoria.
Ahora suponga que desea agregar una cuarta tarea. ¡Pero el siguiente cajón está ocupado
por las cosas de otra persona!
Si viene otro amigo, se quedarán sin espacio otra vez, ¡y tendrán que mudarse por segunda vez! Que dolor.
Del mismo modo, agregar nuevos elementos a una matriz puede ser un gran dolor. Si no tiene espacio y
necesita moverse a un nuevo lugar en la memoria cada vez, agregar un nuevo elemento será muy lento.
Una solución fácil es "reservar asientos": incluso si solo tiene 3 elementos en su lista de tareas, puede pedirle
a la computadora 10 espacios, por si acaso. Luego, puede agregar 10 elementos a su lista de tareas sin tener
que moverse. Esta es una buena solución, pero debe tener en cuenta un par de inconvenientes:
• Es posible que no necesite las ranuras adicionales que solicitó, y entonces esa memoria se
Así que es una buena solución, pero no es una solución perfecta. Las listas enlazadas resuelven este
listas enlazadas
Con las listas vinculadas, sus elementos pueden estar en cualquier lugar de la memoria.
Cada elemento almacena la dirección del siguiente elemento de la lista. Un montón de direcciones de
Direcciones de
memoria enlazadas
Es como una búsqueda del tesoro. Vas a la primera dirección y dice: "El siguiente elemento se puede
encontrar en la dirección 123". Así que vas a la dirección 123 y dice: "El siguiente elemento se puede encontrar
en la dirección 847", y así sucesivamente. Agregar un elemento a una lista vinculada es fácil: lo coloca en
Con las listas vinculadas, nunca tendrá que mover sus elementos. También te evitas otro problema.
Digamos que vas a una película popular con cinco de tus amigos. Los seis están tratando de encontrar un
lugar para sentarse, pero el teatro está lleno. No hay seis asientos juntos. Bueno, a veces esto sucede con
las matrices. Digamos que está tratando de encontrar 10 000 ranuras para una matriz. Su memoria tiene 10
000 ranuras, pero no tiene 10 000 ranuras juntas. ¡No puedes conseguir espacio para tu matriz! Una lista
enlazada es como decir: "Separémonos y veamos la película". Si hay espacio en la memoria, tiene espacio
Si las listas enlazadas son mucho mejores en las inserciones, ¿para qué sirven las matrices?
arreglos
Los sitios web con listas de los 10 principales utilizan una táctica sucia para obtener más visitas a la página.
En lugar de mostrarle la lista en una página, colocan un elemento en cada página y le hacen hacer clic en
Siguiente para pasar al siguiente elemento de la lista. Por ejemplo, Top 10 Best TV Villains no le mostrará
la lista completa en una sola página. En cambio, comienza en el n. ° 10 (Newman) y debe hacer clic en
Siguiente en cada página para llegar al n. ° 1 (Gustavo Fring). Esta técnica le da a los sitios web 10 páginas
completas en las que mostrar anuncios, pero es aburrido hacer clic en Siguiente 9 veces para llegar al n.° 1.
Sería mucho mejor si la lista completa estuviera en una página y pudiera hacer clic en el nombre de cada
Las listas enlazadas tienen un problema similar. Suponga que desea leer el último elemento de una lista
enlazada. No puedes simplemente leerlo, porque no sabes en qué dirección está. En su lugar, debe ir al
artículo #2. Luego, debe ir al elemento n. ° 2 para obtener la dirección del elemento n. ° 3.
Y así sucesivamente, hasta llegar al último elemento. Las listas vinculadas son excelentes
si va a leer todos los elementos de uno en uno: puede leer un elemento, seguir la dirección
hasta el siguiente elemento, etc. Pero si vas a seguir dando vueltas, las listas enlazadas son
terribles.
Las matemáticas simples te dicen: es 04. Las matrices son geniales si quieres leer
elementos aleatorios, porque puedes buscar cualquier elemento en tu matriz al instante.
Con una lista enlazada, los elementos no están uno al lado del otro, por lo que no puede
calcular instantáneamente la posición del quinto elemento en la memoria: debe ir al primer
elemento para obtener la dirección del segundo elemento, luego ir al segundo elemento
para obtener la dirección del tercer elemento, y así sucesivamente hasta llegar al quinto
elemento.
Terminología
Los elementos de una matriz están numerados. Esta numeración comienza desde 0, no
desde 1. Por ejemplo, en esta matriz, 20 está en la posición 1.
Y el 10 está en la posición 0. Esto suele hacer que los programadores nuevos se den
una vuelta. Comenzar en 0 hace que todo tipo de código basado en matrices sea más fácil
de escribir, por lo que los programadores se han quedado con él. Casi todos los lenguajes
de programación que utilice enumerarán los elementos de la matriz a partir de 0. Pronto se
acostumbrará.
Machine Translated by Google
La posición de un elemento se llama su índice. Entonces, en lugar de decir "20 está en la posición
1", la terminología correcta es "20 está en el índice 1". Usaré índice para indicar posición a lo largo
de este libro.
Pregunta: ¿Por qué lleva O(n) tiempo insertar un elemento en una matriz? Suponga que
desea insertar un elemento al comienzo de una matriz. ¿Como lo harias? ¿Cuanto tiempo
tardaría? ¡Encuentre las respuestas a estas preguntas en la siguiente sección!
EJERCICIO
2.1 Suponga que está creando una aplicación para realizar un seguimiento de sus finanzas.
Todos los días, escribes todo en lo que gastaste dinero. Al final del mes, revisas tus gastos
y sumas cuánto gastaste. Entonces, tienes muchas inserciones y algunas lecturas. ¿Deberías
usar una matriz o una lista?
Machine Translated by Google
desordenado Ordenado
¿Qué es mejor si desea insertar elementos en el medio: matrices o listas? Con las
listas es tan fácil como cambiar a qué apunta el elemento anterior.
Pero para las matrices, debe desplazar el resto de los elementos hacia abajo.
Y si no hay espacio, es posible que deba copiar todo en una nueva ubicación. Las listas
son mejores si desea insertar elementos en el medio.
Machine Translated by Google
Eliminaciones
¿Qué sucede si desea eliminar un elemento? Nuevamente, las listas son mejores, porque
solo necesita cambiar lo que apunta el elemento anterior. Con las matrices, todo debe
moverse hacia arriba cuando elimina un elemento.
Vale la pena mencionar que las inserciones y eliminaciones son O (1) tiempo solo si puede
acceder instantáneamente al elemento que se eliminará. Es una práctica común realizar un
seguimiento del primer y último elemento de una lista vinculada, por lo que solo se necesitaría
O (1) tiempo para eliminarlos.
¿Cuáles se usan más: arrays o listas? Obviamente, depende del caso de uso. Pero las
matrices tienen mucho uso porque permiten el acceso aleatorio. Hay dos tipos diferentes de
acceso: acceso aleatorio y acceso secuencial.
El acceso secuencial significa leer los elementos uno por uno, comenzando por el
primer elemento. Las listas enlazadas solo pueden hacer acceso secuencial. Si desea
leer el décimo elemento de una lista enlazada, debe leer los primeros 9 elementos y seguir
los enlaces hasta el décimo elemento. El acceso aleatorio significa que puede saltar
directamente al décimo elemento. Con frecuencia me escuchará decir que las matrices son
más rápidas en las lecturas. Esto se debe a que proporcionan acceso aleatorio. Muchos
casos de uso requieren acceso aleatorio, por lo que las matrices se usan mucho. Las matrices
y las listas también se utilizan para implementar otras estructuras de datos (más adelante en
el libro).
Machine Translated by Google
EJERCICIOS
2.2 Suponga que está creando una aplicación para que los restaurantes lleven a los clientes
pedidos. Su aplicación necesita almacenar una lista de pedidos. Los meseros
continúan agregando pedidos a esta lista, y los chefs quitan pedidos de la lista y los preparan.
Es una cola de pedidos: los servidores agregan pedidos al final de la cola y el chef
toma el primer pedido de la cola y lo cocina.
¿Usaría una matriz o una lista enlazada para implementar esta cola?
(Sugerencia: las listas vinculadas son buenas para inserciones/eliminaciones, y las
matrices son buenas para el acceso aleatorio. ¿Cuál va a hacer aquí?)
2.3 Hagamos un experimento mental. Supongamos que Facebook mantiene una lista de
nombres de usuario. Cuando alguien intenta iniciar sesión en Facebook, se realiza una
búsqueda de su nombre de usuario. Si su nombre está en la lista de nombres de usuario,
pueden iniciar sesión. Las personas inician sesión en Facebook con bastante frecuencia,
por lo que hay muchas búsquedas a través de esta lista de nombres de usuario.
Supongamos que Facebook usa la búsqueda binaria para buscar en la lista. La búsqueda
binaria necesita acceso aleatorio: debe poder llegar al medio de la lista de nombres de
usuario al instante. Sabiendo esto, ¿implementaría la lista como una matriz o como una
lista enlazada?
2.4 Las personas también se registran en Facebook con bastante frecuencia. Suponga que decide
usar una matriz para almacenar la lista de usuarios. ¿Cuáles son las desventajas de una
matriz para inserciones? En particular, suponga que está utilizando la búsqueda binaria para
buscar inicios de sesión. ¿Qué sucede cuando agrega nuevos usuarios a una matriz?
2.5 En realidad, Facebook no utiliza ni una matriz ni una lista enlazada para almacenar la
información del usuario. Consideremos una estructura de datos híbrida: una matriz de
listas enlazadas. Tienes una matriz con 26 ranuras. Cada ranura apunta a una lista
enlazada. Por ejemplo, el primer espacio en la matriz apunta a una lista vinculada que
contiene todos los nombres de usuario que comienzan con a. El segundo espacio apunta a
una lista enlazada que contiene todos los nombres de usuario que comienzan con b, y así
sucesivamente.
Machine Translated by Google
Compare esta estructura de datos híbrida con arreglos y listas enlazadas. ¿Es más lento
o más rápido que cada uno para buscar e insertar? No tiene que dar tiempos de ejecución
de Big O, solo si la nueva estructura de datos sería más rápida o más lenta.
Clasificación de selección
Desea ordenar esta lista de más a menos reproducidos, de modo que pueda clasificar a sus artistas
favoritos. ¿Cómo puedes hacerlo?
Machine Translated by Google
Clasificación de selección 33
Una forma es revisar la lista y encontrar el artista más reproducido. Agregue ese artista a una nueva
lista.
Para encontrar al artista con el mayor número de reproducciones, debe verificar cada
elemento de la lista. Esto toma O(n) tiempo, como acabas de ver. Así que tienes una
operación que toma O(n) tiempo, y tienes que hacer eso n veces:
• Fechas de viaje
Clasificación de selección 35
Tiene razón en que no tiene que verificar una lista de n elementos cada vez.
Verifica n elementos, luego n – 1, n - 2 … 2, 1. En promedio, verifica una lista que tiene
1/2 × n elementos. El tiempo de ejecución es O(n × 1/2 × n). Pero las constantes como
1/2 se ignoran en la notación Big O (nuevamente, vea el capítulo 4 para la discusión
completa), así que simplemente escriba O(n × n) u O(n2 ).
Ahora puede usar esta función para escribir el ordenamiento por selección:
Resumen
• Cuando desee almacenar varios elementos, utilice una matriz o una lista.
• Con una matriz, todos sus elementos se almacenan uno al lado del otro.
• Con una lista, los elementos están esparcidos por todas partes y un elemento
almacena la dirección del siguiente.
• Todos los elementos de la matriz deben ser del mismo tipo (todos enteros,
todos dobles, etc.).
Machine Translated by Google
recursión 3
En este capítulo
• Aprendes sobre la recursividad. La recursividad es una codificación.
técnica utilizada en muchos algoritmos. Es un elemento básico
para comprender los capítulos posteriores de este libro.
• Este capítulo tiene muchos ejemplos de código. Ejecute el código usted mismo para
ver cómo funciona.
37
Machine Translated by Google
38 Capítulo 3 I Recurrencia
recursividad
Suponga que está excavando en el ático de su abuela y se encuentra con una misteriosa maleta
cerrada con llave.
La abuela te dice que la llave de la maleta probablemente esté en esta otra caja.
Esta caja contiene más cajas, con más cajas dentro de esas cajas. La llave está en una caja
en alguna parte. ¿Cuál es tu algoritmo para buscar la clave?
Piense en un algoritmo antes de seguir leyendo.
Machine Translated by Google
recursividad 39
5. Repita.
40 Capítulo 3 I Recurrencia
def buscar_clave(caja_principal):
pila = main_box.make_a_pile_to_look_through()
mientras que la pila no está vacía:
caja = pila.grab_a_box()
para artículo en caja:
si item.is_a_box():
pile.append(elemento)
elif elemento.es_una_clave():
imprimir "¡encontré la llave!"
La segunda forma utiliza la recursividad. La recursividad es donde una función se llama a sí misma.
Aquí está la segunda forma en pseudocódigo:
def buscar_clave(caja):
para artículo en caja:
si item.is_a_box():
buscar_clave(elemento) elif ¡Recursión!
elemento.es_una_clave():
imprimir "¡encontré la llave!"
Ambos enfoques logran lo mismo, pero el segundo enfoque es más claro para mí. La recursividad
se usa cuando aclara la solución.
Debido a que una función recursiva se llama a sí misma, es fácil escribir una
función incorrectamente que termine en un bucle infinito. Por ejemplo, suponga
que desea escribir una función que imprima una cuenta regresiva, como esta:
> 3...2...1
1 http://stackoverflow.com/a/72694/139117.
Machine Translated by Google
Bucle infinito
> 3...2...1...0...-1...-2...
Cuando escribes una función recursiva, tienes que decirle cuándo dejar de
recurrir. Es por eso que cada función recursiva tiene dos partes: el caso base
y el caso recursivo. El caso recursivo es cuando la función se llama a sí misma.
El caso base es cuando la función no se vuelve a llamar a sí misma... por lo que
no entra en un bucle infinito.
Agreguemos un caso base a la función de cuenta regresiva:
42 Capítulo 3 I Recurrencia
La pila
Supongamos que estás organizando una barbacoa. Mantienes una lista de cosas por
hacer para la barbacoa, en forma de una pila de notas adhesivas.
Esta estructura de datos se llama pila. La pila es una estructura de datos simple.
¡Has estado usando una pila todo este tiempo sin darte cuenta!
Machine Translated by Google
La pila 43
La pila de llamadas
def saludar(nombre):
imprimir "hola", saludar2 + nombre + “!”
(nombre)
print "preparándome para decir adiós..."
adiós()
Esta función lo saluda y luego llama a otras dos funciones. Aquí están esas dos
funciones:
def saludo2(nombre):
“ + nombre + “?”
escribe “¿cómo estás,
definitivamente adios():
Nota
print es una función en Python, pero para facilitar las cosas en este ejemplo,
pretendemos que no lo es. Solo sigue el juego.
Supongamos que llamas saludo ("maggie"). Primero, su computadora asigna una caja
de memoria para esa llamada de función.
44 Capítulo 3 I Recurrencia
Cada vez que realiza una llamada de función, su computadora guarda los valores de
todas las variables para esa llamada en la memoria de esta manera. A continuación, imprime
hola, maggie! Luego llamas a greeting2 ("maggie"). Nuevamente, su
computadora asigna una caja de memoria para esta llamada de función.
Su computadora está usando una pila para estas cajas. El segundo cuadro se agrega encima
del primero. Imprimes ¿cómo estás, maggie? Luego regresa de la llamada a la función.
Cuando esto sucede, la caja en la parte superior de la pila se abre.
Ahora, el cuadro superior de la pila es para la función de saludo , lo que significa que
regresó a la función de saludo . Cuando llamó a la función de saludo2 , la función de saludo
se completó parcialmente. Esta es la gran idea detrás de esta sección: cuando llama a una
función desde otra función, la función que llama se detiene en un estado parcialmente
completado. Todos los valores de las variables para esa función aún se almacenan en la
memoria.
Ahora que ha terminado con la función saludar2 , ha vuelto a la función saludar y
continúa donde la dejó. Primero imprimes preparándote para decir adiós…. Llamas a
la función bye .
Machine Translated by Google
La pila 45
Se agrega un cuadro para esa función en la parte superior de la pila. Entonces imprimes ok bye!
y regresa de la llamada a la función.
Y vuelves a la función de saludo . No hay nada más que hacer, por lo que también regresa de la
función de saludo . Esta pila, que se usa para guardar las variables para varias funciones, se
denomina pila de llamadas.
EJERCICIO
3.1 Suponga que le muestro una pila de llamadas como esta.
manera similar, factorial(3) es 3 * 2 * 1. Aquí hay una función recursiva para calcular el factorial
de un número:
def hecho(x):
si x == 1:
volver 1
demás:
volver x * hecho(x-1)
Ahora llamas fact(3). Repasemos esta llamada línea por línea y veamos cómo cambia la pila.
Recuerde, el cuadro superior de la pila le indica en qué llamada al hecho se encuentra
actualmente.
Machine Translated by Google
46 Capítulo 3 I Recurrencia
Machine Translated by Google
La pila 47
Observe que cada llamada al hecho tiene su propia copia de x. No puede acceder a
la copia de una función diferente de x.
De esta manera, crea una pila de cajas para buscar, para que siempre sepa qué
cajas aún necesita buscar.
Machine Translated by Google
48 Capítulo 3 I Recurrencia
La pila 49
Usar la pila es conveniente, pero tiene un costo: guardar toda esa información puede
consumir mucha memoria. Cada una de esas llamadas de función ocupa algo de
memoria, y cuando su pila es demasiado alta, eso significa que su computadora está
guardando información para muchas llamadas de función. En ese momento, tienes
dos opciones:
EJERCICIO
3.2 Suponga que accidentalmente escribe una función recursiva que se
ejecuta para siempre. Como vio, su computadora asigna memoria en
la pila para cada llamada de función. ¿Qué sucede con la pila cuando su
función recursiva se ejecuta para siempre?
Machine Translated by Google
50 Capítulo 3 I Recurrencia
Resumen
• Toda función recursiva tiene dos casos: el caso base y el caso recursivo.
• La pila de llamadas puede volverse muy grande, lo que consume mucha memoria.
Machine Translated by Google
ordenación rápida 4
En este capítulo
• Aprendes sobre divide y vencerás. A veces te encontrarás con
un problema que no se puede resolver con ningún algoritmo
que hayas aprendido. Cuando un buen algoritmo se encuentra
con un problema así, no se da por vencido. Tienen una caja
de herramientas llena de técnicas que utilizan en el problema,
tratando de encontrar una solución. Divide y vencerás es la
primera técnica general que aprendes.
51
Machine Translated by Google
Al final del capítulo, aprenderá su primer algoritmo importante de D&C: clasificación rápida. Quicksort
es un algoritmo de clasificación, mucho más rápido que la clasificación por selección (que aprendiste
en el capítulo 2). Es un buen ejemplo de código elegante.
Divide y vencerás
D&C puede tomar algún tiempo para comprender. Entonces, haremos
tres ejemplos. Primero les mostraré un ejemplo visual. Luego haré un
ejemplo de código que es menos bonito pero quizás más fácil. Finalmente,
revisaremos Quicksort, un algoritmo de clasificación que usa D&C.
Desea dividir esta granja de manera uniforme en parcelas cuadradas. Quieres que las parcelas sean
lo más grandes posible. Así que ninguno de estos funcionará.
Machine Translated by Google
Divide y vencerás 53
¿Cómo calculas el tamaño cuadrado más grande que puedes usar para un terreno? ¡Usa
la estrategia D&C! Los algoritmos D&C son algoritmos recursivos.
Para resolver un problema usando D&C, hay dos pasos:
1. Averigüe el caso base. Este debería ser el caso más simple posible.
Usemos D&C para encontrar la solución a este problema. ¿Cuál es el tamaño de cuadrado
más grande que puedes usar?
Primero, averigüe el caso base. El caso más fácil sería si un lado fuera múltiplo del otro lado.
Supongamos que un lado mide 25 metros (m) y el otro mide 50 m. Entonces, la caja más
grande que puedes usar es de 25 m × 25 m. Necesitas dos de esas cajas para dividir la
tierra.
Ahora necesitas averiguar el caso recursivo. Aquí es donde entra D&C. De acuerdo
con D&C, con cada llamada recursiva, debe reducir su problema. ¿Cómo se reduce el
problema aquí? Comencemos marcando las cajas más grandes que puedes usar.
Machine Translated by Google
Puede colocar dos cajas de 640 × 640 allí, y aún queda algo de tierra por dividir.
Ahora aquí viene el "¡Ajá!" momento. Queda un segmento de granja por dividir.
¿Por qué no aplicas el mismo algoritmo a este segmento?
Entonces, comenzó con una granja de 1680 × 640 que necesitaba dividirse.
Pero ahora necesita dividir un segmento más pequeño, 640 × 400. Si encuentra el
cuadro más grande que funcionará para este tamaño, ese será el cuadro más grande
que funcionará para toda la granja. ¡Acaba de reducir el problema de una granja de
1680 × 640 a una granja de 640 × 400!
Algoritmo de Euclides
“Si encuentra la caja más grande que funcionará para este tamaño, esa
será la caja más grande que funcionará para toda la granja”. Si no le resulta
obvio por qué esta afirmación es cierta, no se preocupe. No es obvio.
Desafortunadamente, la prueba de por qué funciona es demasiado larga para
incluirla en este libro, por lo que tendrá que creerme que funciona. Si quieres
entender la prueba, busca el algoritmo de Euclides. La academia Khan tiene
una buena explicación aquí: https://www.khanacademy.org/computing/computer-science/
criptografía/modaritmética/a/el-algoritmo-euclidiano.
Divide y vencerás 55
Y puedes dibujar un cuadro en eso para obtener un segmento aún más pequeño,
240 × 160 m.
Y luego dibujas un cuadro sobre eso para obtener un segmento aún más pequeño.
Oye, estás en el caso base: 80 es un factor de 160. Si divides este segmento usando
cajas, ¡no te sobra nada!
Machine Translated by Google
Entonces, para la granja original, el tamaño de parcela más grande que puede usar es de 80 × 80 m.
Tienes que sumar todos los números y devolver el total. Es bastante fácil hacer esto
con un bucle:
def suma(arr):
totales = 0
para x en arr:
total += x
devolución total
Paso 1: Averiguar el caso base. ¿Cuál es la matriz más simple que podría
obtener? Piense en el caso más simple y luego siga leyendo. Si obtiene una
matriz con 0 o 1 elemento, es bastante fácil de resumir.
Machine Translated by Google
Divide y vencerás 57
Paso 2: debe acercarse a una matriz vacía con cada llamada recursiva.
¿Cómo reduce el tamaño de su problema? Aquí hay una forma.
Propina
Cuando está escribiendo una función recursiva que involucra una matriz, el caso base suele
ser una matriz vacía o una matriz con un elemento. Si estás atascado, prueba eso primero.
Machine Translated by Google
Divide y vencerás 59
Observe que parece que tiene dos definiciones para la función. La primera definición
se ejecuta cuando llega al caso base. La segunda definición se ejecuta en el caso
recursivo. También puede escribir esta función en Haskell usando una declaración if:
Pero la primera definición es más fácil de leer. Debido a que Haskell hace un uso
intensivo de la recursividad, incluye todo tipo de sutilezas como esta para facilitar la
recursividad. Si le gusta la recursividad o está interesado en aprender un nuevo
idioma, consulte Haskell.
EJERCICIOS
4.1 Escriba el código de la función de suma anterior .
4.2 Escriba una función recursiva para contar el número de elementos en una lista.
Ordenación rápida
Usemos quicksort para ordenar una matriz. ¿Cuál es la matriz más simple que puede
manejar un algoritmo de clasificación (recuerda mi consejo de la sección anterior)?
Bueno, algunas matrices no necesitan ordenarse en absoluto.
Las matrices vacías y las matrices con un solo elemento serán el caso base. Puede devolver
esas matrices tal como están; no hay nada que ordenar:
Veamos arreglos más grandes. Una matriz con dos elementos es bastante fácil de
ordenar, también.
Recuerde, está usando D&C. Entonces, desea desglosar esta matriz hasta que esté en
el caso base. Así es como funciona Quicksort. Primero, elija un elemento de la matriz.
Este elemento se llama pivote.
Hablaremos sobre cómo elegir un buen pivote más adelante. Por ahora,
digamos que el primer elemento de la matriz es el pivote.
Machine Translated by Google
Ordenación rápida 61
Ahora encuentre los elementos más pequeños que el pivote y los elementos más
grandes que el pivote.
• El pivote
Los dos subarreglos no están ordenados. Solo están particionados. Pero si estuvieran
ordenados, ordenar toda la matriz sería bastante fácil.
Si los subconjuntos están ordenados, puede combinar todo de esta manera: conjunto
izquierdo + pivote + conjunto derecho, y obtendrá un conjunto ordenado. En este caso,
es [10, 15] + [33] + [] = [10, 15, 33], que es una matriz ordenada.
Esto funcionará con cualquier pivote. Suponga que elige 15 como pivote.
Ambos subconjuntos tienen un solo elemento, y usted sabe cómo ordenarlos. Así
que ahora sabes cómo ordenar una matriz de tres elementos. Aquí están los pasos:
1. Elija un pivote.
La matriz de la izquierda tiene tres elementos. Ya sabe cómo ordenar una matriz de tres
elementos: llame a Quicksort recursivamente.
Machine Translated by Google
Ordenación rápida 63
Entonces puede ordenar una matriz de cuatro elementos. Y si puede ordenar una matriz
de cuatro elementos, puede ordenar una matriz de cinco elementos. ¿Porqué es eso?
Suponga que tiene esta matriz de cinco elementos.
Estas son todas las formas en que puede particionar esta matriz, según el pivote que
elija.
Por ejemplo, suponga que elige 3 como pivote. Llamas a quicksort en los sub-arreglos.
Las sub-matrices se ordenan y luego se combina todo para obtener una matriz ordenada. Esto
funciona incluso si elige 5 como pivote.
Esto funciona con cualquier elemento como pivote. Entonces puede ordenar una matriz
de cinco elementos. Usando la misma lógica, puede ordenar una matriz de seis
elementos, y así sucesivamente.
Machine Translated by Google
Pruebas inductivas
¡Acabas de obtener un adelanto de las pruebas inductivas! Las pruebas inductivas
son una forma de probar que su algoritmo funciona. Cada prueba inductiva tiene dos
pasos: el caso base y el caso inductivo. ¿Suena familiar? Por ejemplo, supongamos
que quiero demostrar que puedo subir a lo alto de una escalera. En el caso inductivo,
si mis piernas están en un peldaño, puedo poner mis piernas en el siguiente peldaño.
Entonces, si estoy en el peldaño 2, puedo subir al peldaño 3. Ese es el caso inductivo.
Para el caso base, diré que mis piernas están en el peldaño 1. Por lo tanto, puedo
subir toda la escalera, subiendo un peldaño a la vez.
Utiliza un razonamiento similar para quicksort. En el caso base, mostré que el algoritmo
funciona para el caso base: matrices de tamaño 0 y 1. En el caso inductivo, mostré
que si Quicksort funciona para una matriz de tamaño 1, funcionará para una matriz de
tamaño 2 Y si funciona para arreglos de tamaño 2, funcionará para arreglos de tamaño
3, y así sucesivamente. Entonces puedo decir que Quicksort funcionará para todas las
matrices de cualquier tamaño. No profundizaré en las pruebas inductivas aquí, pero
son divertidas y van de la mano con D&C.
Antes de hablar sobre ordenación rápida, echemos un vistazo a los tiempos de ejecución más comunes de
Big O nuevamente.
Estimaciones basadas en una computadora lenta que realiza 10 operaciones por segundo
Los tiempos de ejemplo en este gráfico son estimaciones si realiza 10 operaciones por segundo.
Estos gráficos no son precisos, solo están ahí para darle una idea de cuán diferentes son estos tiempos
Cada tiempo de ejecución también tiene adjunto un algoritmo de ejemplo. Revisa el ordenamiento
por selección, que aprendiste en el capítulo 2. Es O(n2 ). Ese es un algoritmo bastante lento.
Hay otro algoritmo de clasificación llamado merge sort, que es O(n log n). ¡Mucho mas
rápido! Quicksort es un caso complicado. En el peor de los casos, quicksort toma O(n2 ) tiempo.
¡Es tan lento como el tipo de selección! Pero ese es el peor de los casos. En el caso promedio, quicksort
• Si la ordenación rápida es O(n log n) en promedio, pero la ordenación por fusión es O(n log n)
siempre, ¿por qué no usar la ordenación por fusión? ¿No es más rápido?
Machine Translated by Google
Supongamos que tiene esta función simple para imprimir todos los elementos de una lista:
def imprimir_elementos(lista):
para el artículo en la lista:
elemento de impresión
Antes de imprimir un artículo, hará una pausa de 1 segundo. Suponga que imprime una
lista de cinco elementos utilizando ambas funciones.
Ambas funciones recorren la lista una vez, por lo que ambas son tiempo O(n).
¿Cuál crees que será más rápido en la práctica? Creo que print_items será mucho más rápido
porque no hace una pausa de 1 segundo antes de imprimir un elemento. Entonces, aunque ambas
funciones tienen la misma velocidad en notación Big O, print_items es más rápido en la práctica.
Cuando escribes la notación Big O como O(n), realmente significa esto.
c es una cantidad fija de tiempo que toma su algoritmo. Se llama la constante. Por ejemplo, podría
ser 10 milisegundos * n para print_
*
elementos versus 1 segundo n para imprimir_elementos2.
Machine Translated by Google
Por lo general, ignora esa constante, porque si dos algoritmos tienen diferentes
tiempos de Big O, la constante no importa. Tome la búsqueda binaria y la búsqueda simple,
por ejemplo. Supongamos que ambos algoritmos tuvieran estas constantes.
Podrías decir: “¡Guau! La búsqueda simple tiene una constante de 10 milisegundos, pero la
búsqueda binaria tiene una constante de 1 segundo. ¡La búsqueda simple es mucho más
rápida!” Ahora suponga que está buscando en una lista de 4 mil millones de elementos. Aquí
están los tiempos.
Como puede ver, la búsqueda binaria sigue siendo mucho más rápida. Esa constante no
hizo una diferencia en absoluto.
Pero a veces la constante puede marcar la diferencia. Quicksort versus merge sort es un
ejemplo. Quicksort tiene una constante más pequeña que merge sort. Entonces, si ambos
tienen el tiempo O (n log n), la ordenación rápida es más rápida. Y quicksort es más rápido
en la práctica porque golpea el caso promedio con más frecuencia que el peor de los casos.
Así que ahora te estás preguntando: ¿cuál es el caso promedio versus el peor de los casos?
Observe cómo no está dividiendo la matriz en dos mitades. En cambio, uno de los
subconjuntos siempre está vacío. Entonces, la pila de llamadas es realmente larga.
Ahora, en cambio, suponga que siempre eligió el elemento central como pivote.
Mire la pila de llamadas ahora.
¡Es tan corto! Debido a que divide la matriz por la mitad cada vez, no necesita
realizar tantas llamadas recursivas. Alcanza el caso base antes y la pila de llamadas
es mucho más corta.
Machine Translated by Google
Incluso si divide la matriz de manera diferente, todavía está tocando elementos O (n) cada
vez.
En este ejemplo, hay niveles O(log n) (la forma técnica de decirlo es: “La altura de la pila
de llamadas es O(log n)”). Y cada nivel toma O(n) tiempo. Todo el algoritmo tomará el tiempo
O(n) * O(log n) = O(n log n). Este es el mejor de los casos.
En el peor de los casos, hay niveles O(n), por lo que el algoritmo tomará el tiempo O(n) *
O(n) = O(n2 ).
¿Bien adivina que? Estoy aquí para decirles que el mejor caso es también el caso
promedio. Si siempre elige un elemento aleatorio en la matriz como pivote, la ordenación rápida
se completará en un tiempo promedio de O (n log n). Quicksort es uno de los algoritmos de
clasificación más rápidos que existen y es un muy buen ejemplo de D&C.
Machine Translated by Google
EJERCICIOS
¿Cuánto tiempo tomaría cada una de estas operaciones en notación Big O?
4.8 Crear una tabla de multiplicar con todos los elementos del arreglo. Entonces, si
tu arreglo es [2, 3, 7, 8, 10], primero multiplicas cada elemento por 2, luego
multiplicas cada elemento por 3, luego por 7, y así sucesivamente.
Resumen
• D&C funciona dividiendo un problema en partes cada vez más pequeñas. Si usa
D&C en una lista, el caso base probablemente sea una matriz vacía o una
matriz con un elemento.
tablas hash 5
En este capítulo
73
Machine Translated by Google
Como recordatorio, ¡hay una gran diferencia entre el tiempo O(n) y O(log n)!
Suponga que puede mirar 10 líneas del libro por segundo. Esto es lo que
tardaría la búsqueda simple y la búsqueda binaria.
tablas hash 75
Tu amiga Maggie puede darte el precio en O(1) tiempo para cualquier artículo, sin
importar qué tan grande sea el libro. Es incluso más rápida que la búsqueda binaria.
Funciones hash
Una función hash es una función en la que ingresas una cadena1 y obtienes un
número.
En terminología técnica, diríamos que una función hash "asigna cadenas a números".
Puede pensar que no hay un patrón discernible sobre el número que obtiene cuando
ingresa una cadena. Pero hay algunos requisitos para una función hash:
• Tiene que ser consistente. Por ejemplo, suponga que ingresa "manzana" y obtiene "4".
Cada vez que ingrese "manzana", debe obtener "4" de regreso.
Sin esto, su tabla hash no funcionará.
Entonces, una función hash asigna cadenas a números. ¿Para qué sirve eso?
Bueno, ¡puedes usarlo para hacer tu "Maggie"!
Almacenará todos sus precios en esta matriz. Agreguemos el precio de una manzana.
Introduzca "manzana" en la función hash.
1
Cadena aquí significa cualquier tipo de datos, una secuencia de bytes.
Machine Translated by Google
Funciones hash 77
La función hash genera "3". Así que almacenemos el precio de una manzana
en el índice 3 de la matriz.
La función hash le dice exactamente dónde se almacena el precio, ¡así que no tiene
que buscar en absoluto! Esto funciona porque
• La función hash asigna consistentemente un nombre al mismo índice. Cada vez que
ingrese "aguacate", obtendrá el mismo número. Entonces puede usarlo la primera vez
para encontrar dónde almacenar el precio de un aguacate, y luego puede usarlo para
encontrar dónde almacenó ese precio.
• La función hash sabe qué tan grande es su matriz y solo devuelve índices válidos.
Entonces, si su matriz tiene 5 elementos, la función hash no devuelve 100... ese no sería
un índice válido en la matriz.
¡Acabas de construir una "Maggie"! Ponga una función hash y una matriz juntas, y
obtendrá una estructura de datos llamada tabla hash. Una tabla hash es la primera
estructura de datos que aprenderá que tiene algo de lógica adicional detrás. Las matrices
y las listas se asignan directamente a la memoria, pero las tablas hash son más inteligentes.
Utilizan una función hash para averiguar de forma inteligente dónde almacenar elementos.
Las tablas hash son probablemente la estructura de datos compleja más útil que
aprenderá. También se conocen como mapas hash, mapas, diccionarios y matrices
asociativas. ¡Y las tablas hash son rápidas! ¿Recuerda nuestra discusión sobre arreglos
y listas enlazadas en el capítulo 2? Puede obtener un elemento de una matriz al instante. Y
las tablas hash usan una matriz para almacenar los datos, por lo que son igualmente rápidas.
Probablemente nunca tendrá que implementar tablas hash usted mismo. Cualquier buen
lenguaje tendrá una implementación para tablas hash. Python tiene tablas hash; se llaman
diccionarios. Puede crear una nueva tabla hash usando la función dict :
book es una nueva tabla hash. Agreguemos algunos precios para reservar:
casos de uso 79
Una tabla hash tiene claves y valores. En el libro hash, los nombres de los
productos son las claves y sus precios son los valores. Una tabla hash asigna claves a
valores.
En la siguiente sección, verá algunos ejemplos en los que las tablas hash son
realmente útiles.
EJERCICIOS
Es importante que las funciones hash devuelvan constantemente la misma salida para
la misma entrada. ¡Si no lo hacen, no podrás encontrar tu artículo después de ponerlo
en la tabla hash!
¿Cuáles de estas funciones hash son consistentes?
casos de uso
Las tablas hash se utilizan en todas partes. Esta sección le mostrará algunos
casos de uso.
Suponga que desea crear una guía telefónica como esta. Está asignando nombres
de personas a números de teléfono. Su directorio telefónico debe tener esta funcionalidad:
¡Este es un caso de uso perfecto para tablas hash! Las tablas hash son
geniales cuando quieres
• Busca algo
Crear una guía telefónica es bastante fácil. Primero, crea una nueva tabla hash:
Por cierto, Python tiene un atajo para crear una nueva tabla hash. Puedes usar dos llaves:
Las tablas hash se utilizan para búsquedas a una escala mucho mayor. Por ejemplo,
suponga que va a un sitio web como http://adit.io. Su computadora tiene que traducir
adit.io a una dirección IP.
Machine Translated by Google
casos de uso 81
Para cualquier sitio web al que vaya, la dirección debe traducirse a una dirección
IP.
Guau, ¿asignar una dirección web a una dirección IP? ¡Suena como un caso de uso
perfecto para tablas hash! Este proceso se denomina resolución de DNS. Las tablas
hash son una forma de proporcionar esta funcionalidad.
Cada vez que alguien nuevo entra a votar, debe escanear esta lista gigante
para ver si ya votó. Pero hay una mejor manera: ¡usa un hash!
Primero, haga un hash para realizar un seguimiento de las personas que han votado:
>>> votado = {}
votado = {}
def check_voter(nombre):
si votado.obtener(nombre):
imprimir "¡échalos!"
demás:
votado[nombre] = Verdadero
escribe “¡que voten!”
La primera vez que Tom entre, se imprimirá "¡déjalos votar!" Entonces Mike
entra y se imprime, "¡déjalos votar!" Luego, Mike intenta ir por segunda vez, y
se imprime, "¡échalos!"
Machine Translated by Google
casos de uso 83
Recuerde, si estuviera almacenando estos nombres en una lista de personas que votaron, esta
función eventualmente se volvería muy lenta, ya que tendría que realizar una búsqueda simple en
toda la lista. Pero en su lugar, está almacenando sus nombres en una tabla hash, y una tabla hash
le dice instantáneamente si el nombre de esta persona está en la tabla hash o no. La comprobación
de duplicados es muy rápida con una tabla hash.
Por ejemplo, en Facebook, el servidor puede recopilar toda la actividad de sus amigos para
mostrársela. Tarda un par de segundos en recopilar toda esa actividad y te la muestra. Ese par
de segundos puede parecer mucho tiempo como usuario. Podrías pensar: "¿Por qué Facebook
es tan lento?"
Por otro lado, los servidores de Facebook tienen que atender a millones de personas, y ese par de
segundos les suma. Los servidores de Facebook realmente están trabajando duro para servir a
todos esos sitios web. ¿Hay alguna manera de hacer que Facebook sea más rápido y que sus
servidores trabajen menos al mismo tiempo?
Suponga que tiene una sobrina que no deja de preguntarle acerca de los planetas. “¿A qué distancia
está Marte de la Tierra?” “¿Qué tan lejos está la Luna?” "¿A qué distancia está Júpiter?" Cada vez,
tienes que hacer una búsqueda en Google y darle una respuesta. Se necesita
Machine Translated by Google
un par de minutos. Ahora, supongamos que ella siempre preguntaba: "¿Qué tan lejos está la
Luna?" Muy pronto, memorizarías que la Luna está a 238,900 millas de distancia. No tendrías
que buscarlo en Google… simplemente recordarías y responderías. Así es como funciona el
almacenamiento en caché: los sitios web recuerdan los datos en lugar de volver a calcularlos.
Si ha iniciado sesión en Facebook, todo el contenido que ve está diseñado solo para usted.
Cada vez que ingresa a facebook.com, sus servidores tienen que pensar en qué contenido le
interesa. Pero si no ha iniciado sesión en Facebook, verá la página de inicio de sesión. Todos
ven la misma página de inicio de sesión.
A Facebook se le pregunta lo mismo una y otra vez: "Dame la página de inicio cuando esté
desconectado". Por lo tanto, deja de hacer que el servidor funcione para descubrir cómo se ve
la página de inicio. En su lugar, memoriza cómo se ve la página de inicio y te la envía.
• Accedes a la página web mucho más rápido, como cuando memorizaste la distancia de la Tierra
a la Luna. La próxima vez que tu sobrina te pregunte, no tendrás que buscarlo en Google.
Puedes responder al instante.
El almacenamiento en caché es una forma común de hacer las cosas más rápido. Todos los grandes sitios web utilizan el
casos de uso 85
Cuando visitas una página en Facebook, primero verifica si la página está almacenada
en el hash.
caché = {}
def get_page(url):
si cache.get(url):
devolver caché [url] más: Devuelve datos almacenados en caché
datos = get_data_from_server(url)
cache[url] = datos de Primero guarda estos datos en su caché
retorno de datos
Resumen
• Filtrado de duplicados
Colisiones
Como dije antes, la mayoría de los idiomas tienen tablas hash. No es necesario
que sepa escribir el suyo propio. Por lo tanto, no hablaré demasiado sobre los aspectos
internos de las tablas hash. ¡Pero todavía te preocupas por el rendimiento! Para
comprender el rendimiento de las tablas hash, primero debe comprender qué son las
colisiones. Las siguientes dos secciones cubren las colisiones y el rendimiento.
Primero, te he estado diciendo una mentira piadosa. Te dije que una función hash
siempre asigna diferentes claves a diferentes ranuras en la matriz.
En realidad, es casi imposible escribir una función hash que haga esto.
Tomemos un ejemplo simple. Suponga que su matriz contiene 26 ranuras.
Colisiones 87
¡Todo va tan bien! Pero ahora quieres poner el precio de los aguacates en
tu hachís. Te asignan el primer espacio nuevamente.
¡Oh, no! ¡Las manzanas ya tienen esa ranura! ¿Qué hacer? A esto se le
llama colisión: a dos llaves se les ha asignado la misma ranura. Esto es un problema.
Si almacena el precio de los aguacates en esa ranura, sobrescribirá el precio de
las manzanas. Entonces, la próxima vez que alguien pregunte por el precio de las
manzanas, ¡obtendrá el precio de los aguacates! Las colisiones son malas y debe
evitarlas. Hay muchas maneras diferentes de lidiar con las colisiones. La más
simple es esta: si varias claves se asignan a la misma ranura, comience una lista
vinculada en esa ranura.
Machine Translated by Google
¡Oye, espera un minuto! Toda la tabla hash está totalmente vacía a excepción de una
ranura. ¡Y esa tragamonedas tiene una lista enlazada gigante! Cada elemento de esta
tabla hash está en la lista enlazada. Para empezar, eso es tan malo como poner todo en
una lista enlazada. Va a ralentizar tu tabla hash.
Hay dos lecciones aquí:
• Su función hash es realmente importante. Su función hash asignó todas las claves a
una sola ranura. Idealmente, su función hash asignaría claves uniformemente en
todo el hash.
• Si esas listas enlazadas se hacen largas, se ralentiza mucho la tabla hash. ¡Pero
no durarán mucho si usas una buena función hash!
Las funciones hash son importantes. Una buena función hash le dará muy pocas
colisiones. Entonces, ¿cómo eliges una buena función hash? ¡Eso viene en la siguiente
sección!
Rendimiento
Comenzaste este capítulo en el supermercado. Quería construir algo que le diera
los precios de los productos al instante. Bueno, las tablas hash son realmente rápidas.
En el caso promedio, las tablas hash toman O(1) para todo. O(1) se llama tiempo
constante. No has visto el tiempo constante antes. no significa
Machine Translated by Google
Rendimiento 89
¿Ves cómo es una línea plana? Eso significa que no importa si su tabla hash tiene 1
elemento o mil millones de elementos: sacar algo de una tabla hash llevará la misma
cantidad de tiempo. En realidad, has visto el tiempo constante antes. Obtener un
elemento de una matriz lleva un tiempo constante. No importa cuán grande sea su
matriz; se necesita la misma cantidad de tiempo para obtener un elemento. En el caso
promedio, las tablas hash son realmente rápidas.
Machine Translated by Google
En el peor de los casos, una tabla hash toma O(n) —tiempo lineal— para todo, lo cual es
realmente lento. Comparemos tablas hash con arreglos y listas.
Mire el caso promedio de las tablas hash. Las tablas hash son tan rápidas como las matrices
en la búsqueda (obtener un valor en un índice). Y son tan rápidos como las listas enlazadas
en las inserciones y eliminaciones. ¡Es lo mejor de ambos mundos! Pero en el peor de los
casos, las tablas hash son lentas en todo eso. Por lo tanto, es importante que no alcance el
peor rendimiento con tablas hash. Y para hacer eso, necesitas evitar colisiones. Para evitar
colisiones, necesita
Nota
Antes de comenzar con la siguiente sección, sepa que no es una lectura
obligatoria. Voy a hablar sobre cómo implementar una tabla hash, pero nunca
tendrá que hacerlo usted mismo. Cualquiera que sea el lenguaje de programación
que use, tendrá una implementación de tablas hash incorporadas. Puede usar la
tabla hash incorporada y asumir que tendrá un buen rendimiento. La siguiente
sección le da un vistazo debajo del capó.
Factor de carga
El factor de carga de una tabla hash
es fácil de calcular.
Las tablas hash usan una matriz para el almacenamiento, por lo que cuenta la
cantidad de ranuras ocupadas en una matriz. Por ejemplo, esta tabla hash tiene un factor de
2 de
carga
/5, o 0,4.
Machine Translated by Google
Rendimiento 91
1
Si dijiste /3, tienes razón. El factor de carga mide cuántos espacios vacíos
permanecer en tu tabla hash.
Suponga que necesita almacenar el precio de 100 productos en su tabla hash, y su tabla
hash tiene 100 ranuras. En el mejor de los casos, cada elemento tendrá su propio
espacio.
Esta tabla hash tiene un factor de carga de 1. ¿Qué sucede si su tabla hash tiene solo
50 ranuras? Entonces tiene un factor de carga de 2. No hay forma de que cada elemento
tenga su propio espacio, ¡porque no hay suficientes espacios! Tener un factor de carga
superior a 1 significa que tiene más elementos que ranuras en su matriz.
Una vez que el factor de carga comienza a crecer, debe agregar más espacios a su tabla
hash. Esto se llama cambiar el tamaño. Por ejemplo, suponga que tiene esta tabla hash
que se está llenando bastante.
Necesita cambiar el tamaño de esta tabla hash. Primero creas una nueva matriz que
es más grande. La regla general es hacer una matriz que tenga el doble de tamaño.
Machine Translated by Google
Ahora debe volver a insertar todos esos elementos en esta nueva tabla hash usando la
función hash :
Esta nueva tabla tiene un factor de carga de 3/8. ¡Mucho mejor! Con una carga menor
factor, tendrá menos colisiones y su tabla funcionará mejor. Una buena regla general es cambiar
el tamaño cuando el factor de carga sea superior a 0,7.
Tal vez estés pensando: “¡Este negocio de redimensionamiento lleva mucho tiempo!” Y tienes
razón. Cambiar el tamaño es costoso y no desea cambiar el tamaño con demasiada frecuencia.
Pero en promedio, las tablas hash toman O (1) incluso con el cambio de tamaño.
¿Qué es una buena función hash? Eso es algo de lo que nunca tendrás que preocuparte:
los hombres (y mujeres) mayores con grandes barbas se sientan en cuartos oscuros y se
preocupan por eso. Si tiene mucha curiosidad, busque la función SHA (hay una breve descripción
en el último capítulo). Podrías usar eso como tu función hash.
Machine Translated by Google
Resumen 93
EJERCICIOS
Es importante que las funciones hash tengan una buena distribución. Deben mapear los
elementos de la manera más amplia posible. El peor de los casos es una función hash
que asigna todos los elementos al mismo espacio en la tabla hash.
Suponga que tiene estas cuatro funciones hash que funcionan con cadenas:
C. Utilice el primer carácter de la cadena como índice. Entonces, todas las cadenas
que comienzan con a se codifican juntas, y así sucesivamente.
Para cada uno de estos ejemplos, ¿qué funciones hash proporcionarían una buena distribución?
Suponga un tamaño de tabla hash de 10 ranuras.
5.5 Una agenda donde las claves son nombres y los valores son números de
teléfono. Los nombres son los siguientes: Esther, Ben, Bob y Dan.
5.6 Un mapeo del tamaño de la batería a la potencia. Los tamaños son A, AA, AAA,
y AAAA.
5.7 Un mapeo de títulos de libros a autores. Los títulos son Maus, Fun
Hogar y Vigilantes.
Resumen
Casi nunca tendrá que implementar una tabla hash usted mismo. El lenguaje de
programación que utilice debería proporcionarle una implementación. Puede usar las tablas
hash de Python y asumir que obtendrá el rendimiento promedio del caso: tiempo constante.
Las tablas hash son una estructura de datos poderosa porque son muy rápidas y le
permiten modelar los datos de una manera diferente. Es posible que pronto descubras que
los estás usando todo el tiempo:
Machine Translated by Google
• Puede crear una tabla hash combinando una función hash con una matriz.
• Las tablas hash tienen funciones de búsqueda, inserción y eliminación realmente rápidas.
• Una vez que su factor de carga sea mayor a .07, es hora de cambiar el tamaño
de su tabla hash.
• Las tablas hash se utilizan para almacenar datos en caché (por ejemplo,
con un servidor web).
primero en anchura
búsqueda 6
En este capítulo
Este capítulo presenta los gráficos. Primero, hablaré sobre qué son los gráficos (no
involucran un eje X o Y). Luego te mostraré tu primer algoritmo gráfico. Se llama búsqueda
en amplitud (BFS).
• Escriba una IA de damas que calcule la menor cantidad de movimientos hacia la victoria
95
Machine Translated by Google
Los algoritmos gráficos son algunos de los algoritmos más útiles que conozco. Asegúrese
de leer detenidamente los siguientes capítulos: estos son algoritmos que podrá aplicar una
y otra vez.
Supongamos que estás en San Francisco y quieres ir de Twin Peaks al puente Golden
Gate. Quieres llegar en autobús, con el mínimo número de transbordos. Aquí están sus
opciones.
Machine Translated by Google
Bueno, ¿puedes llegar allí en un solo paso? Aquí están todos los lugares a los que puedes
llegar en un solo paso.
El puente no está resaltado; no se puede llegar en un solo paso. ¿Puedes llegar en dos pasos?
Nuevamente, el puente no está ahí, así que no puedes llegar al puente en dos pasos.
¿Qué hay de tres pasos?
Machine Translated by Google
¡Ajá! Ahora aparece el puente Golden Gate. Entonces se necesitan tres pasos para llegar desde
Twin Peaks al puente usando esta ruta.
Hay otras rutas que también te llevarán al puente, pero son más largas (cuatro pasos). El
algoritmo encontró que la ruta más corta al puente es de tres pasos. Este tipo de problema se
llama problema del camino más corto. Siempre estás tratando de encontrar algo más corto. Podría
ser la ruta más corta a la casa de tu amigo. Podría ser el número más pequeño de movimientos
para dar jaque mate en un juego de ajedrez. El algoritmo para resolver un problema de ruta más
corta se llama búsqueda primero en anchura.
Para averiguar cómo llegar desde Twin Peaks al puente Golden Gate, hay dos pasos:
A continuación, cubriré lo que son los gráficos. Luego entraré en la búsqueda primero en amplitud
con más detalle.
¿Qué es un gráfico?
Un gráfico modela un conjunto de conexiones. Por
ejemplo, suponga que usted y sus amigos están jugando al
póquer y desea modelar quién le debe dinero a quién. Así es como
podrías decir: “Alex le debe dinero a Rama”.
Machine Translated by Google
Búsqueda en amplitud 99
Alex le debe dinero a Rama, Tom le debe dinero a Adit, y así sucesivamente. Cada
gráfico se compone de nodos y aristas.
¡Eso es todo al respecto! Los gráficos están formados por nodos y aristas. Un nodo se
puede conectar directamente a muchos otros nodos. Esos nodos se llaman sus vecinos.
En este gráfico, Rama es la vecina de Alex. Adit no es vecino de Alex, porque no están
conectados directamente. Pero Adit es vecino de Rama y Tom.
Los gráficos son una forma de modelar cómo las diferentes cosas están conectadas
entre sí. Ahora veamos la búsqueda primero en amplitud en acción.
Búsqueda en amplitud
Ya vio la búsqueda primero en anchura una vez, cuando calculó la ruta más corta
desde Twin Peaks hasta el puente Golden Gate. Esa era una pregunta de tipo 2: "¿Cuál
es el camino más corto?" Ahora veamos el algoritmo con más detalle. Hará una pregunta
de tipo 1: "¿Hay un camino?"
Cada vez que busque a alguien de la lista, agregue todos sus amigos a la lista.
Machine Translated by Google
De esta manera, no solo busca a sus amigos, sino que también busca a sus amigos. Recuerde, el
objetivo es encontrar un vendedor de mango en su red.
Entonces, si Alice no vende mangos, también agrega a sus amigos a la lista. Eso significa que
eventualmente buscará a sus amigos, y luego a sus amigos, y así sucesivamente. Con este algoritmo,
buscará en toda su red hasta que se encuentre con un vendedor de mango. Este algoritmo es una
búsqueda primero en amplitud.
• Pregunta tipo 1: ¿Existe un camino desde el nodo A hasta el nodo B? (¿Hay algún vendedor de
mango en su red?)
Preferiría una conexión de primer grado a una conexión de segundo grado, y una conexión
de segundo grado a una conexión de tercer grado, y así sucesivamente. Por lo tanto, no
debe buscar conexiones de segundo grado antes de asegurarse de que no tiene una
conexión de primer grado que sea un vendedor de mango. Bueno, ¡la búsqueda en amplitud
ya hace esto! La forma en que funciona la búsqueda primero en amplitud, la búsqueda se
irradia desde el punto de partida. Por lo tanto, verificará las conexiones de primer grado antes
que las conexiones de segundo grado. Examen sorpresa: ¿quién será revisado primero,
Claire o Anuj? Respuesta: Claire es una conexión de primer grado y Anuj es una conexión
de segundo grado. Entonces Claire será revisada antes que Anuj.
Tenga en cuenta que esto solo funciona si busca personas en el mismo orden en que se
agregan. Es decir, si Claire se agregó a la lista antes que Anuj, Claire debe buscarse antes
que Anuj. ¿Qué pasa si buscas a Anuj antes que a Claire y ambos son vendedores de
mangos? Bueno, Anuj es un contacto de segundo grado y Claire es un contacto de primer
grado. Termina con un vendedor de mango que no es el más cercano a usted en su red. Por
lo tanto, debe buscar personas en el orden en que se agregan. Hay una estructura de datos
para esto: se llama cola.
Colas
Una cola funciona exactamente igual que en la
vida real. Suponga que usted y su amigo están
haciendo fila en la parada del autobús. Si estás
antes que él en la cola, te subes primero al autobús.
Una cola funciona de la misma manera.
Las colas son similares a las pilas. No puede
acceder a elementos aleatorios en la cola.
En su lugar, hay dos únicas operaciones, enqueue
y dequeue.
Machine Translated by Google
Si pone en cola dos elementos en la lista, el primer elemento que agregó se quitará de la cola
antes que el segundo elemento. ¡Puedes usar esto para tu lista de búsqueda!
Las personas que se agreguen primero a la lista serán eliminadas y buscadas primero.
La cola se denomina estructura de datos FIFO: primero en entrar, primero en salir. Por el
contrario, una pila es una estructura de datos LIFO: último en entrar, primero en salir.
Ahora que sabe cómo funciona una cola, ¡implementemos la búsqueda en amplitud!
EJERCICIOS
Ejecute el algoritmo de búsqueda primero en amplitud en cada uno de estos gráficos para encontrar
la solución.
Implementando el gráfico
Primero, debe implementar el gráfico en el código. Un gráfico consta
de varios nodos.
gráfico = {}
gráfico[“tú”] = [“alice”, “bob”, “claire”]
Observe que "usted" está asignado a una matriz. Entonces graph[“you”] le dará una
matriz de todos los vecinos de “usted”.
Un gráfico es solo un montón de nodos y bordes, por lo que esto es todo lo que
necesita para tener un gráfico en Python. ¿Qué tal un gráfico más grande, como este?
Machine Translated by Google
en vez de
gráfico[“anuj”] = []
gráfico[“claire”] = [“thom”, “jonny”]
Implementando el algoritmo
En resumen, así es como funcionará la implementación.
Nota
Cuando actualizo las colas,
utilizo los términos enqueue y
dequeue. También encontrará
los términos empujar y hacer estallar.
Push es casi siempre lo mismo
que poner en cola, y pop casi
siempre es lo mismo que quitar
de la cola.
Veamos el resto:
Una última cosa: aún necesita una función person_is_seller para saber cuándo alguien
es vendedor de mango. Aquí hay uno:
def persona_es_vendedor(nombre):
devolver nombre[-1] == 'm'
Alice y Bob comparten una amiga: Peggy. Por lo tanto, Peggy se agregará a
la cola dos veces: una cuando agregue a los amigos de Alice y otra vez cuando
agregue a los amigos de Bob. Terminarás con dos Peggys en la cola de búsqueda.
Pero solo necesita verificar a Peggy una vez para ver si vende mango. Si la
revisa dos veces, está haciendo un trabajo extra innecesario. Entonces, una vez
que busque a una persona, debe marcar a esa persona como buscada y no buscarla
nuevamente.
Ahora revisa a Peggy. Ella no vende mangos, por lo que agrega a todos sus
vecinos a la cola de búsqueda.
Machine Translated by Google
Y así. Este será un bucle infinito, porque la cola de búsqueda seguirá yendo de
ti a Peggy.
Aquí está el código final para la búsqueda en amplitud, teniendo eso en cuenta:
def buscar(nombre):
cola_busqueda = deque()
search_queue += gráfico[nombre] buscado =
[] while search_queue: Esta matriz es la forma en que realiza un
seguimiento de las personas que ha buscado antes.
buscar ("tú")
Machine Translated by Google
Intente ejecutar este código usted mismo. Tal vez intente cambiar la persona_es_
función de vendedor a algo más significativo, y vea si imprime lo que espera.
Tiempo de ejecución
Si busca en toda su red un vendedor de mango, eso significa que seguirá cada borde
(recuerde, un borde es la flecha o la conexión de una persona a otra). Entonces, el
tiempo de ejecución es al menos O (número de aristas).
También mantiene una cola de cada persona para buscar. Añadir una persona a la cola
lleva un tiempo constante: O(1). Hacer esto para cada persona tomará O (número de
personas) en total. La búsqueda en amplitud toma O (número de personas + número de
bordes), y se escribe más comúnmente como O (V + E)
(V para número de vértices, E para número de aristas).
EJERCICIO
Aquí hay un pequeño gráfico de mi rutina matutina.
Te dice que no puedo desayunar hasta que me haya cepillado los dientes. Así que
“desayunar” depende de “cepillarse los dientes”.
Por otro lado, ducharme no depende de cepillarme los dientes, porque puedo
ducharme antes de cepillarme los dientes. A partir de este gráfico, puedes hacer una
lista del orden en el que debo hacer mi rutina matutina:
1. Despierta.
2. Ducha.
4. Desayuna.
Machine Translated by Google
Tenga en cuenta que "ducha" se puede mover, por lo que esta lista también es válida:
1. Despierta.
2. Cepíllese los dientes.
3. Ducha.
4. Desayuna.
6.3 Para estas tres listas, marque si cada una es válida o no.
6.4 Aquí hay un gráfico más grande. Haz una lista válida para este gráfico.
Se podría decir que esta lista está ordenada, en cierto modo. Si la tarea A depende
de la tarea B, la tarea A aparece más adelante en la lista. Esto se denomina clasificación
topológica y es una forma de hacer una lista ordenada a partir de un gráfico. Suponga
que está planeando una boda y tiene un gran gráfico lleno de tareas por hacer, y no
está seguro por dónde empezar. Podría ordenar topológicamente el gráfico y obtener
una lista de tareas por hacer, en orden.
Machine Translated by Google
Esto se llama un árbol. Un árbol es un tipo especial de gráfico, donde ningún borde
apunta hacia atrás.
Resumen
• Si hay una ruta, la búsqueda primero en anchura encontrará la ruta más corta.
7 de Dijkstra
algoritmo
En este capítulo
115
Machine Translated by Google
Pero ese camino toma 7 minutos. ¡Veamos si puedes encontrar un camino que lleve
menos tiempo! Hay cuatro pasos para el algoritmo de Dijkstra:
1. Encuentra el nodo "más barato". Este es el nodo al que menos puedes llegar
cantidad de tiempo.
2. Actualizar los costos de los vecinos de este nodo. voy a explicar lo que yo
quiero decir con esto en breve.
3. Repita hasta que haya hecho esto para cada nodo en el gráfico.
Paso 1: Encuentra el nodo más barato. Está parado al principio, preguntándose si debe
ir al nodo A o al nodo B. ¿Cuánto tiempo se tarda en llegar a cada nodo?
Paso 2: calcule cuánto tiempo lleva llegar a todos los vecinos del nodo B siguiendo un borde
desde B.
¡Oye, acabas de encontrar una ruta más corta al nodo A! Solía tardar 6 minutos en
llegar al nodo A.
Pero si pasa por el nodo B, ¡hay un camino que solo toma 5 minutos!
Cuando encuentre una ruta más corta para un vecino de B, actualice su costo. En este
caso, usted encontró
Paso 3: ¡Repite!
Paso 2 nuevamente: actualice los costos para los vecinos del nodo A.
Ha ejecutado el algoritmo de Dijkstra para cada nodo (no necesita ejecutarlo para el nodo
final). En este punto, ya sabes
Guardaré el último paso, calcular la ruta final, para la siguiente sección. Por ahora, solo te
mostraré cuál es el camino final.
En el último capítulo, utilizó la búsqueda primero en anchura para encontrar el camino más
corto entre dos puntos. En aquel entonces, "camino más corto" significaba el camino con la
menor cantidad de segmentos. Pero en el algoritmo de Dijkstra, asignas un número o peso a
cada segmento. Luego, el algoritmo de Dijkstra encuentra el camino con el peso total más
pequeño.
1. Encuentra el nodo más barato. Este es el nodo al que menos puedes llegar
cantidad de tiempo.
2. Verifique si hay una ruta más barata a los vecinos de este nodo.
De ser así, actualice sus costos.
3. Repita hasta que haya hecho esto para cada nodo en el gráfico.
Terminología
Quiero mostrarles algunos ejemplos más del algoritmo de Dijkstra en acción. Pero primero
permítanme aclarar algo de terminología.
Cuando trabaja con el algoritmo de Dijkstra, cada borde del gráfico tiene un número asociado.
Estos se llaman pesos.
Un gráfico con pesos se llama gráfico ponderado. Un gráfico sin pesos se llama gráfico
no ponderado.
Machine Translated by Google
Terminología 121
¿Tendría sentido seguir el ciclo? Bueno, puedes usar el camino que evita el ciclo.
Terminas en el nodo A de cualquier manera, pero el ciclo agrega más peso. Incluso podrías
seguir el ciclo dos veces si quisieras.
Pero cada vez que sigues el ciclo, solo sumas 8 al peso total. Así que seguir el ciclo
nunca te dará el camino más corto.
Un gráfico no dirigido significa que ambos nodos apuntan entre sí. ¡Eso es un ciclo!
“Te daré este póster para tu libro”, dice Alex. “Es un póster de mi banda
favorita, Destroyer. O te daré este LP raro de Rick Astley para tu libro y $5
más”. “Ooh, he oído que LP tiene una canción realmente genial”, dice Amy.
“Te cambio mi guitarra o batería por el poster o el LP.”
En este gráfico, los nodos son todos los artículos que Rama puede
intercambiar. Los pesos en los bordes son la cantidad de dinero que tendría
que pagar para realizar el intercambio. Entonces puede cambiar el poster por la
guitarra por $30, o cambiar el LP por la guitarra por $15. ¿Cómo va a encontrar
Rama el camino del libro al piano donde gasta menos dinero?
¡El algoritmo de Dijkstra al rescate! Recuerde, el algoritmo de Dijkstra tiene
cuatro pasos. En este ejemplo, realizará los cuatro pasos, por lo que también
calculará la ruta final al final.
Seguirá actualizando esta tabla a medida que avance el algoritmo. Para calcular la
ruta final, también necesita una columna principal en esta tabla.
Esta es la idea clave detrás del algoritmo de Dijkstra: mira el nodo más barato en tu
gráfico. ¡No hay forma más económica de llegar a este nodo!
Paso 2: Calcule cuánto tiempo se tarda en llegar a sus vecinos (el costo).
Ok, finalmente tienes un precio para el piano, cambiando la guitarra por el piano. Así
que estableces la guitarra como padre. Finalmente, el último nodo, la batería.
Rama puede conseguir el piano aún más barato cambiando la batería por el piano.
Entonces, el conjunto de intercambios más barato le costará a Rama $ 35.
Ahora, como te prometí, necesitas descubrir el camino. Hasta ahora, sabes que el
camino más corto cuesta $35, pero ¿cómo calculas el camino? Para empezar, mira el
padre para piano.
El piano tiene la batería como padre. Eso significa que Rama cambia la batería por el
piano. Entonces sigues este borde.
Machine Translated by Google
Veamos cómo seguirías los bordes. El piano tiene la batería como padre.
Así que Rama cambiará el LP por la batería. Y por supuesto, cambiará el libro por el
LP. Al seguir a los padres hacia atrás, ahora tiene el camino completo.
Hasta ahora, he estado usando el término ruta más corta literalmente: calcular la
ruta más corta entre dos ubicaciones o entre dos personas. Espero que este ejemplo
te haya mostrado que el camino más corto no tiene que estar relacionado con la
distancia física. Puede tratarse de minimizar algo. En este caso, Rama quería
minimizar la cantidad de dinero que gastaba.
¡Gracias, Dijkstra!
¡El borde del LP al cartel tiene un peso negativo! Rama recupera $7 si hace
ese intercambio. Ahora Rama tiene dos formas de llegar al cartel.
Machine Translated by Google
Así que tiene sentido hacer el segundo intercambio: ¡Rama recibe $2 de vuelta de esa manera!
Ahora, si recuerdas, Rama puede cambiar el cartel por los tambores. Hay dos caminos que
podría tomar.
El segundo camino le cuesta $2 menos, así que debería tomar ese camino, ¿verdad?
¿Bien adivina que? Si ejecuta el algoritmo de Dijkstra en este gráfico, Rama tomará el camino
equivocado. Él tomará el camino más largo. No puede usar el algoritmo de Dijkstra si tiene
bordes de peso negativo. Los bordes de peso negativo rompen el algoritmo. Veamos qué
sucede cuando ejecutas el algoritmo de Dijkstra en esto. Primero, haz la tabla de costos.
A continuación, busque el nodo de menor costo y actualice los costos de sus vecinos.
En este caso, el cartel es el nodo de menor costo. Entonces, de acuerdo con el
algoritmo de Dijkstra, no hay una forma más barata de llegar al cartel que pagando $0
(¡sabes que eso está mal!). De todos modos, actualicemos los costos para sus vecinos.
Ya procesó el nodo del póster, pero está actualizando el costo. Esta es una gran
bandera roja. Una vez que procesa un nodo, significa que no hay una forma más
económica de llegar a ese nodo. ¡Pero acabas de encontrar una forma más
barata de llegar al cartel! Drums no tiene vecinos, así que ese es el final del
algoritmo. Aquí están los costos finales.
Cuesta $35 llegar a la batería. Sabes que hay un camino que cuesta solo $33,
pero el algoritmo de Dijkstra no lo encontró. El algoritmo de Dijkstra asumió que
debido a que estaba procesando el nodo del póster, no había una forma más
rápida de llegar a ese nodo. Esa suposición solo funciona si no tiene bordes de
peso negativo. Por lo tanto, no puede usar bordes de peso negativo con el
algoritmo de Dijkstra. Si desea encontrar la ruta más corta en un gráfico que tiene
bordes de peso negativo, ¡hay un algoritmo para eso! Se llama el algoritmo de
Bellman-Ford. Bellman-Ford está fuera del alcance de este libro, pero puede
encontrar excelentes explicaciones en línea.
Machine Translated by Google
Implementación 131
Implementación
Veamos cómo implementar el algoritmo de Dijkstra en el código. Aquí está
el gráfico que usaré para el ejemplo.
Actualizará los costes y las tablas hash principales a medida que avance
el algoritmo. Primero, necesitas implementar el gráfico. Usará una tabla hash
como lo hizo en el capítulo 6:
gráfico = {}
Pero esta vez, debe almacenar a los vecinos y el costo de llegar a ese vecino. Por
ejemplo, Start tiene dos vecinos, A y B.
Machine Translated by Google
gráfico[“inicio”] = {}
gráfico[“inicio”][“a”] = 6
gráfico[“inicio”][“b”] = 2
Entonces , el gráfico [“inicio”] es una tabla hash. Puede obtener todos los vecinos para el
inicio de esta manera:
gráfico[“b”] = {}
gráfico[“b”][“a”] = 3 gráfico[“b”]
[“aleta”] = 5
Implementación 133
A continuación, necesita una tabla hash para almacenar los costos de cada nodo.
infinito = float(“inf”)
infinito = float(“inf”)
costos = {}
costos[“a”] = 6
costos[“b”] = 2
costos[“fin”] = infinito
Aquí está el código para hacer la tabla hash para los padres:
padres = {}
padres[“a”] = “inicio”
padres[“b”] = “inicio”
padres[“fin”] = Ninguno
procesado = []
Implementación 135
La nueva ruta pasa por el nodo B, así que establezca B como el nuevo padre.
Ok, estás de vuelta en la parte superior del ciclo. El siguiente vecino es el nodo
Finalizar.
Implementación 137
Ok, actualizó los costos para todos los vecinos del nodo B. Márquelo como procesado.
Implementación 139
Una vez que haya procesado todos los nodos, el algoritmo habrá terminado.
Espero que el tutorial te haya ayudado a comprender un poco mejor el algoritmo.
Encontrar el nodo de menor costo es bastante fácil con find_lowest_
función cost_node . Aquí está en código:
def find_lowest_cost_node(costos):
coste_mínimo = float(“inf”)
low_cost_node = Ninguno
para nodo en costes: coste = Ir a través de cada nodo. Si es el costo más bajo
costes[nodo] hasta ahora y no ha sido
si el costo <costo_más bajo y el nodo no está procesado: procesado todavía…
costo_bajo = costo … configurarlo como el nuevo nodo de menor costo.
costo_bajo_nodo = nodo
devolver el nodo_de_coste_más_bajo
EJERCICIO
7.1 En cada uno de estos gráficos, ¿cuál es el peso del camino más corto
de principio a fin?
Machine Translated by Google
Resumen
8
algoritmos codiciosos
En este capítulo
• Aprendes a hacer frente a lo imposible:
problemas que no tienen solución algorítmica rápida
(problemas NP-completos).
141
Machine Translated by Google
Desea realizar tantas clases como sea posible en este salón de clases. ¿Cómo
eliges qué conjunto de clases realizar, de modo que obtengas el mayor conjunto de
clases posible?
Suena como un problema difícil, ¿verdad? En realidad, el algoritmo es tan fácil que
podría sorprenderte. Así es como funciona:
1. Elige la clase que termine antes. Esta es la primera clase que tendrá en este salón
de clases.
2. Ahora, debe elegir una clase que comience después de la primera clase.
Nuevamente, elija la clase que termine antes. Esta es la segunda clase
que tendrá.
Machine Translated by Google
¡Sigue haciendo esto y terminarás con la respuesta! Probémoslo. Arte termina lo antes posible, a las 10:00 am,
Ahora necesita la próxima clase que comienza después de las 10:00 a. m. y termina lo antes posible.
El inglés está descartado porque entra en conflicto con el arte, pero las matemáticas funcionan.
Así que estas son las tres clases que tendrá en este salón de clases.
Machine Translated by Google
Mucha gente me dice que este algoritmo parece fácil. Es demasiado obvio, así que
debe estar mal. Pero esa es la belleza de los algoritmos codiciosos: ¡son fáciles! Un
algoritmo codicioso es simple: en cada paso, elija el movimiento óptimo.
En este caso, cada vez que eliges una clase, eliges la clase que termina antes. En
términos técnicos: en cada paso eliges la solución óptima localmente y al final te
quedas con la solución óptima globalmente.
Lo crea o no, ¡este simple algoritmo encuentra la solución óptima a este problema de
programación!
El problema de la mochila
Supongamos que eres un ladrón codicioso. Estás en una tienda
con una mochila y hay todos estos artículos que puedes robar.
Pero solo puedes llevar lo que cabe en tu mochila.
La mochila puede contener 35 libras.
Excepto que esta vez, ¡no funciona! Por ejemplo, suponga que hay tres elementos
que puede robar.
Machine Translated by Google
EJERCICIOS
8.1 Trabajas para una empresa de muebles y tienes que enviar muebles a
todo el país. Necesitas empacar tu camión con cajas. Todas las cajas
son de diferentes tamaños y estás tratando de maximizar el espacio que
usas en cada camión. ¿Cómo escogerías las cajas para maximizar el
espacio? Piensa en una estrategia codiciosa. ¿Eso le dará la solución
óptima?
8.2 Te vas a Europa y tienes siete días para ver todo lo que puedas. Asignas
un valor en puntos a cada artículo (cuánto quieres
Machine Translated by Google
para verlo) y estimar cuánto tiempo lleva. ¿Cómo puede maximizar el total de
puntos (ver todas las cosas que realmente quiere ver) durante su estadía? Piensa
en una estrategia codiciosa. ¿Eso le dará la solución óptima?
2. De estos, elija el conjunto con el menor número de estaciones que cubra los
50 estados.
¡No hay ningún algoritmo que lo resuelva lo suficientemente rápido! ¿Qué puedes hacer?
Algoritmos de aproximación
¡Algoritmos codiciosos al rescate! Aquí hay un algoritmo codicioso que se acerca
bastante:
1. Elija la estación que cubra la mayor cantidad de estados que aún no han sido
cubiertos. Está bien si la estación cubre algunos estados que ya han sido
cubiertos.
Los algoritmos codiciosos son una buena opción porque no solo son fáciles de
crear, sino que esa simplicidad significa que generalmente también se ejecutan rápido.
En este caso, el algoritmo codicioso se ejecuta en tiempo O(n^2), donde n es el
número de estaciones de radio.
Machine Translated by Google
Para este ejemplo, usaré un subconjunto de estados y estaciones para simplificar las cosas.
Usé un conjunto para esto. Un conjunto es como una lista, excepto que cada elemento puede aparecer
solo una vez en un conjunto. Los conjuntos no pueden tener duplicados. Por ejemplo, suponga que
tiene esta lista:
Y lo convertiste en un conjunto:
>>> establecer(arr)
conjunto ([1, 2, 3])
También necesita la lista de estaciones que está eligiendo. Elegí usar un hash para esto:
estaciones = {}
estaciones[“kone”] = conjunto([“id”, “nv”, “ut”])
estaciones[“kdos”] = conjunto([“wa”, “id”, “mt”])
estaciones[“ktres”] = conjunto([“o”, “nv”, “ca”])
estaciones[“kcuatro”] = conjunto([“nv”, “ut”])
estaciones[“kcinco”] = conjunto([“ca”, “az”])
Las claves son los nombres de las estaciones y los valores son los estados que cubren.
Entonces, en este ejemplo, la estación kone cubre Idaho, Nevada y Utah.
Todos los valores también son conjuntos. Hacer que todo sea un conjunto te hará la vida más fácil,
como verás pronto.
Finalmente, necesita algo para contener el conjunto final de estaciones que usará:
estaciones_finales = establecer()
Machine Translated by Google
Calculando la respuesta
Ahora necesitas calcular qué estaciones usarás. Mire la imagen de la derecha
y vea si puede predecir qué estaciones debe usar.
Puede haber más de una solución correcta. Debe pasar por todas las
estaciones y elegir la que cubra la mayor cantidad de estados descubiertos.
Llamaré a esta best_station:
mejor_estacion = Ninguno
estados_cubiertos = set()
para estación, estados_para_estación en estaciones.elementos():
estados_cubiertos = cubiertos
Conjuntos
Cuando tienes dos conjuntos, puedes hacer algunas cosas divertidas con ellos.
Machine Translated by Google
• Una intersección de conjuntos significa “buscar los elementos que aparecen en ambos conjuntos” (en
• Una diferencia de conjuntos significa "restar los elementos de un conjunto de los elementos
en el otro conjunto.”
Por ejemplo:
verduras([“tomate”])
>>> conjunto frutas – Esta es una diferencia establecida.
verduras([“aguacate”, “banana”])
>>> verduras – frutas ¿Qué crees que hará esto?
Machine Translated by Google
Recordar:
• Los conjuntos son como listas, excepto que los conjuntos no pueden tener duplicados.
volver al código
estaciones_finales.add(mejor_estación)
Y recorres hasta que estados_necesitados esté vacío. Aquí está el código completo
para el ciclo:
mientras estados_necesarios:
mejor_estación = Ninguno
estados_cubiertos = set() para
estación, estados en estaciones.elementos(): cubierto =
estados_necesarios & estados si len(cubierto) >
len(estados_cubiertos): mejor_estación = estación
estados_cubiertos = cubierto
estados_necesarios -= estados_cubiertos
estaciones_finales.add(mejor_estación)
Machine Translated by Google
¿Es eso lo que esperabas? En lugar de las estaciones 1, 2, 3 y 5, podría haber elegido las
estaciones 2, 3, 4 y 5. Comparemos el tiempo de ejecución del algoritmo voraz con el algoritmo
exacto.
EJERCICIOS
Para cada uno de estos algoritmos, diga si es un algoritmo codicioso o no.
Problemas NP-completos
Para resolver el problema de cobertura de conjuntos, tenía que calcular todos los
conjuntos posibles.
Machine Translated by Google
Tal vez te acordaste del problema del vendedor ambulante del capítulo 1.
En este problema, un vendedor tiene que visitar cinco ciudades diferentes.
Y está tratando de encontrar la ruta más corta que lo lleve a las cinco
ciudades. Para encontrar la ruta más corta, primero debe calcular todas las
rutas posibles.
Usted puede pensar que esta debería ser la misma ruta. Después de todo, ¿no es SF > Marin la
misma distancia que Marin > SF? No necesariamente. Algunas ciudades (como San Francisco)
tienen muchas calles de sentido único, por lo que no puedes volver por donde viniste. También es
posible que tenga que desviarse 1 o 2 millas para encontrar una rampa de acceso a una autopista.
Así que estas dos rutas no son necesariamente las mismas.
Quizás se esté preguntando: "En el problema del vendedor ambulante, ¿hay una
ciudad específica desde la que deba comenzar?" Por ejemplo, digamos que soy el
vendedor ambulante. Vivo en San Francisco y necesito ir a otras cuatro ciudades. San
Francisco sería mi ciudad de inicio.
3 ciudades
Ahora suponga que agrega una ciudad más. ¿Cuántas rutas posibles hay?
Hay seis rutas en total, dos por cada ciudad en la que puede comenzar.
4 ciudades
Hay seis rutas posibles a partir de Fremont. ¡Y oye! Se parecen mucho a las
seis rutas que calculó anteriormente, cuando solo tenía tres ciudades. ¡Excepto
que ahora todas las rutas tienen una ciudad adicional, Fremont!
Hay un patrón aquí. Suponga que tiene cuatro ciudades y elige una ciudad de inicio,
Fremont. Quedan tres ciudades. Y sabes que si hay tres ciudades, hay seis rutas
diferentes para llegar entre esas ciudades.
Si comienza en Fremont, hay seis rutas posibles. También puede comenzar en una
de las otras ciudades.
Cuatro ciudades de inicio posibles, con seis rutas posibles para cada ciudad de
inicio = 4 * 6 = 24 rutas posibles.
¿Ves un patrón? Cada vez que agrega una nueva ciudad, aumenta la cantidad de
rutas que tiene que calcular.
¿Cuántas rutas posibles hay para seis ciudades? Si adivinó 720, tiene razón.
5.040 para 7 ciudades, 40.320 para 8 ciudades.
¡Las rutas se vuelven grandes muy rápido! Esta es la razón por la que es imposible
calcular la solución "correcta" para el problema del vendedor ambulante si tiene una
gran cantidad de ciudades.
aproximando
¿Cuál es un buen algoritmo de aproximación para el vendedor ambulante?
Algo simple que encuentra un camino corto. Vea si puede encontrar una respuesta antes de
seguir leyendo.
Así es como lo haría: elegir arbitrariamente una ciudad de inicio. Luego, cada vez que el
vendedor tiene que elegir la siguiente ciudad para visitar, elige la ciudad no visitada más
cercana. Supongamos que comienzan en Marin.
Distancia total: 71 millas. Tal vez no sea el camino más corto, pero sigue siendo bastante
corto.
Jonah necesita un equipo que cumpla con todas sus habilidades y el tamaño del
equipo es limitado. “Espera un segundo”, se da cuenta Jonah. “¡Este es un problema
de cobertura del set!”
1. Encuentra el jugador que cumple más habilidades que aún no se han cumplido.
2. Repita hasta que el equipo cumpla con todas las habilidades (o se quede sin espacio en
el equipo).
Pero si quieres encontrar el camino más corto que conecta varios puntos, ese es el problema
del vendedor ambulante, que es NP-completo. La respuesta corta: no hay una manera fácil de
saber si el problema en el que está trabajando es NP-completo. Aquí hay algunos obsequios:
• ¿Tiene que calcular “todas las versiones posibles” de X porque no puede dividirlo en
subproblemas más pequeños? Podría ser NP-completo.
• Si su problema involucra una secuencia (como una secuencia de ciudades, como un vendedor
ambulante) y es difícil de resolver, podría ser NP-completo.
EJERCICIOS
8.6 Un cartero necesita hacer entregas en 20 hogares. Necesita encontrar la ruta
más corta que vaya a las 20 casas. ¿Es este un problema NP-completo?
8.8 Estás haciendo un mapa de los EE. UU. y necesitas colorear los adyacentes
estados con diferentes colores. Tienes que encontrar el número mínimo de colores que
necesitas para que no haya dos estados adyacentes del mismo color.
¿Es este un problema NP-completo?
Machine Translated by Google
Resumen
• Los algoritmos codiciosos se optimizan localmente, con la esperanza de terminar con un global
óptimo.
• Los algoritmos codiciosos son fáciles de escribir y rápidos de ejecutar, por lo que son
buenos algoritmos de aproximación.
Machine Translated by Google
dinámica
programación 9
En este capítulo
• Aprendes programación dinámica, un
técnica para resolver un problema difícil
dividiéndolo en subproblemas y resolviendo esos
subproblemas primero.
El problema de la mochila
Repasemos el problema de la mochila del capítulo 8.
Eres un ladrón con una mochila que puede llevar 4 libras de
mercancías.
161
Machine Translated by Google
¿Qué artículos debe robar para robar el máximo valor de dinero en bienes?
La solución simple El
algoritmo más simple es este: prueba todos los conjuntos posibles de
bienes y encuentra el conjunto que le da el mayor valor.
Programación dinámica
Respuesta: ¡Con programación dinámica! Veamos cómo funciona aquí el
algoritmo de programación dinámica. La programación dinámica comienza
resolviendo subproblemas y avanza hasta resolver el gran problema.
Cada algoritmo de programación dinámica comienza con una cuadrícula. Aquí hay una
cuadrícula para el problema de la mochila.
Las filas de la cuadrícula son los artículos y las columnas son los pesos de las mochilas
de 1 lb a 4 lb. Necesitas todas esas columnas porque te ayudarán a calcular los valores de
las submochilas.
la fila de la guitarra
Te mostraré la fórmula exacta para calcular esta cuadrícula más adelante. Primero hagamos
un recorrido. Comience con la primera fila.
Esta es la fila de guitarras, lo que significa que estás tratando de meter la guitarra en la
mochila. En cada celda, hay una simple decisión: ¿robar la guitarra o no? Recuerde, está
tratando de encontrar el conjunto de artículos para robar que le darán el mayor valor.
Así, cada celda de la cuadrícula contendrá una lista de todos los elementos que caben en
la mochila en ese punto.
Lo mismo para el resto de las celdas de esta fila. Recuerde, esta es la primera fila, por lo
que solo tiene la guitarra para elegir. Estás fingiendo que los otros dos elementos no están
disponibles para robar en este momento.
En este punto, probablemente estés confundido. ¿Por qué haces esto para mochilas
con una capacidad de 1 lb, 2 lb, etc., cuando el problema habla de una mochila de 4 lb?
¿Recuerdas que te dije que la programación dinámica comienza con un pequeño
problema y se desarrolla hasta llegar al gran problema? Estás resolviendo subproblemas
aquí que te ayudarán a resolver el gran problema. Siga leyendo y las cosas se aclararán.
Machine Translated by Google
La fila estéreo
Hagamos la siguiente fila. Este es para el estéreo. Ahora que estás en la segunda
fila, puedes robar el estéreo o la guitarra. En cada fila, puede robar el elemento de
esa fila o los elementos de las filas superiores. Así que no puedes elegir robar la
computadora portátil en este momento, pero puedes robar el estéreo y/o la guitarra.
Comencemos con la primera celda, una mochila de 1 libra de capacidad. El valor
máximo actual que puede caber en una mochila de 1 libra es de $1500.
Machine Translated by Google
Lo mismo para las próximas dos celdas. Estas mochilas tienen una capacidad de 2 lb
y 3 lb. El valor máximo anterior para ambas era de $1,500.
El estéreo aún no encaja, por lo que sus conjeturas permanecen sin cambios.
¿Qué pasa si tienes una mochila de 4 lb de capacidad? Ajá: ¡el estéreo finalmente encaja!
El valor máximo anterior era de $1500, pero si colocas el estéreo allí, ¡el valor es de
$3000! Tomemos el estéreo.
Machine Translated by Google
La fila de portátiles
¡Hagamos lo mismo con la computadora portátil! La computadora portátil pesa 3 lb, por
lo que no cabe en una mochila de 1 lb o 2 lb. El presupuesto para las dos primeras celdas se
mantiene en $1,500.
A 3 lb, el cálculo anterior era de $1500. Pero puede elegir la computadora portátil en
su lugar, y eso vale $ 2,000. ¡Así que la nueva estimación máxima es de $2,000!
Con 4 lb, las cosas se ponen realmente interesantes. Esta es una parte importante.
La estimación actual es de $3,000. Puedes poner la computadora portátil en la mochila,
pero solo vale $2,000.
Machine Translated by Google
¿Cuál es el valor máximo que puede caber en 1 libra de espacio? Bueno, lo has
estado calculando todo el tiempo.
Es posible que se haya preguntado por qué estaba calculando valores máximos
para mochilas más pequeñas. ¡Espero que ahora tenga sentido! Cuando te quede
espacio, puedes usar las respuestas a esos subproblemas para averiguar qué cabrá
en ese espacio. Es mejor llevar la laptop + la guitarra por $3,500.
Machine Translated by Google
Puede usar esta fórmula con cada celda de esta cuadrícula y debería
terminar con la misma cuadrícula que yo. ¿Recuerdas que hablé de resolver
subproblemas? Combinaste las soluciones de dos subproblemas para
resolver el problema mayor.
Machine Translated by Google
Tal vez esto todavía se siente como magia. Esta sección responde algunas
preguntas comunes.
¿Tiene que volver a calcular todo para dar cuenta de este nuevo artículo?
No. Recuerde, la programación dinámica se basa progresivamente en su
estimación. Hasta ahora, estos son los valores máximos.
Eso significa que por una mochila de 4 libras, puedes robar $3,500 en bienes.
Pensaste que ese era el valor máximo final. Pero agreguemos una fila para el
iPhone.
Machine Translated by Google
¡Resulta que tienes un nuevo valor máximo! Intente completar esta nueva fila antes de
seguir leyendo.
Para el celular 3, nada mejor que tomar el iPhone y la guitarra nuevamente, así que
déjalo como está.
Para la última celda, las cosas se ponen interesantes. El máximo actual es de $3,500.
En su lugar, puede robar el iPhone y le sobran 3 libras de espacio.
Machine Translated by Google
¡Esas 3 libras valen $2,000! $2000 del iPhone + $2000 del antiguo subproblema: eso es
$4000. ¡Un nuevo máximo!
EJERCICIO
9.1 Suponga que puede robar otro artículo: un reproductor de MP3. Pesa
1 libra y vale $1,000. ¿Deberías robarlo?
Machine Translated by Google
Debido al collar, debe tener en cuenta una granularidad más fina, por lo que la
cuadrícula debe cambiar.
Machine Translated by Google
La quinua es más cara por libra que cualquier otra cosa. ¡Entonces, toma toda
la quinua que puedas llevar! Si eso llena tu mochila, es lo mejor que puedes
hacer.
Para cada cosa que desea ver, escribe cuánto tiempo llevará y califica cuánto desea
verla. ¿Puedes averiguar lo que deberías ver, según esta lista?
¡Otra vez el problema de la mochila! En lugar de una mochila, tienes una cantidad
limitada de tiempo. Y en lugar de estéreos y computadoras portátiles, tiene una lista de
lugares a los que desea ir. Dibuje la cuadrícula de programación dinámica para esta lista
antes de continuar.
Estos lugares toman mucho tiempo, porque primero hay que viajar de Londres
a París. Eso lleva medio día. Si desea hacer los tres elementos, tomará cuatro
días y medio.
Espera, eso no está bien. No tienes que viajar a París para cada artículo.
Una vez que estés en París, cada artículo solo debería tomar un día. Entonces
debería ser un día por artículo + medio día de viaje = 3,5 días, no 4,5 días.
Este es un gran diamante: pesa 3,5 libras. Vale un millón de dólares, mucho más
que cualquier otra cosa. ¡Definitivamente deberías robarlo!
Pero queda media libra de espacio, y nada cabrá en ese espacio.
EJERCICIO
9.2 Supongamos que vas a acampar. Tiene una mochila con capacidad para
6 lb y puede llevar los siguientes artículos. Cada uno tiene un valor, y
cuanto mayor sea el valor, más importante es el elemento:
• Agua, 3 libras, 10
• Libro, 1 libra, 3
• Alimentos, 2 libras, 9
• Chaqueta, 2 libras, 5
• Cámara, 1 libra, 6
Puede ser difícil encontrar una solución de programación dinámica. Eso es en lo que nos
centraremos en esta sección. A continuación se dan algunos consejos generales:
• Los valores de las celdas suelen ser los que intenta optimizar.
Para el problema de la mochila, los valores eran el valor de los bienes.
Pero si alguien escribe mal una palabra, querrá poder adivinar qué palabra
quiso decir. Alex está buscando pescado, pero accidentalmente metió hish.
Esa no es una palabra en su diccionario, pero tiene una lista de palabras que
son similares.
(Este es un ejemplo de juguete, por lo que limitará su lista a dos palabras. En realidad,
esta lista probablemente tendría miles de palabras).
Alex tecleó hish. ¿Qué palabra quiso escribir Alex: pez o vista?
haciendo la grilla
¿Cómo se ve la cuadrícula de este problema? Necesitas responder estas preguntas:
En la programación dinámica, estás tratando de maximizar algo. En este caso, está tratando
de encontrar la subcadena más larga que tienen dos palabras en común. ¿Qué subcadena
tienen en común hish y fish? ¿Qué hay de hish y vista? Eso es lo que quieres calcular.
Machine Translated by Google
Recuerde, los valores de las celdas suelen ser los que intenta optimizar. En
este caso, los valores probablemente serán un número: la longitud de la subcadena
más larga que las dos cadenas tienen en común.
Llenando la grilla
Ahora tiene una buena idea de cómo debería verse la cuadrícula. ¿Cuál es la
fórmula para llenar cada celda de la cuadrícula? Puede hacer un poco de trampa,
porque ya sabe cuál debería ser la solución: hish y fish tienen una subcadena de
longitud 3 en común: ish.
Pero eso todavía no te dice la fórmula a usar. Los informáticos a veces bromean
sobre el uso del algoritmo de Feynman. El algoritmo de Feynman lleva el
nombre del famoso físico Richard Feynman y funciona así:
1. Escriba el problema.
2. Piensa mucho.
3. Escriba la solución.
Machine Translated by Google
Intente encontrar una solución a este problema usted mismo. Te daré una pista:
parte de la cuadrícula se ve así.
¿Cuáles son los otros valores? Recuerda que cada celda es el valor de un
subproblema. ¿Por qué la celda (3, 3) tiene un valor de 2? ¿Por qué la celda (3,
4) tiene un valor de 0?
Siga leyendo después de que haya tratado de encontrar una fórmula usted
mismo. Incluso si no lo haces bien, mi explicación tendrá mucho más sentido.
Machine Translated by Google
La solución
Aquí está la grilla final.
Una cosa a tener en cuenta: para este problema, ¡la solución final puede no
estar en la última celda! Para el problema de la mochila, esta última celda
siempre tenía la solución final. Pero para la subcadena común más larga, la solución
es el número más grande de la cuadrícula, y puede que no sea la última celda.
Ambos son iguales: ¡dos letras! Pero el fosh está más cerca del pescado.
¡Uf, lo hiciste! Este es definitivamente uno de los capítulos más difíciles del libro.
Entonces, ¿realmente se usa alguna vez la programación dinámica? Sí:
• Los biólogos usan la subsecuencia común más larga para encontrar similitudes en
las cadenas de ADN. Pueden usar esto para decir qué tan similares son dos animales
o dos enfermedades. La subsecuencia común más larga se está utilizando para
encontrar una cura para la esclerosis múltiple.
• ¿Ha usado alguna vez diff (como git diff)? Diff le dice las diferencias entre dos archivos
y utiliza programación dinámica para hacerlo.
• ¿Ha usado alguna vez una aplicación que ajuste el ajuste de texto, como Microsoft Word?
¿Cómo determina dónde envolver para que la longitud de la línea se mantenga
constante? ¡Programación dinámica!
EJERCICIO
9.3 Dibuje y complete la cuadrícula para calcular la subcadena común más larga
entre azul y pistas.
Resumen
• La programación dinámica es útil cuando intenta optimizar algo dada una restricción.
• Los valores de las celdas suelen ser los que intenta optimizar.
• Cada celda es un subproblema, así que piensa en cómo puedes dividir tu problema
en subproblemas.
k- vecinos
más cercanos 10
En este capítulo
• Aprendes a construir un sistema de clasificación usando el
algoritmo de k-vecinos más cercanos.
187
Machine Translated by Google
¿Cómo clasificarías esta fruta? Una forma es mirar a los vecinos de este lugar. Echa
un vistazo a los tres vecinos más cercanos de este lugar.
Machine Translated by Google
Más vecinos son naranjas que pomelos. Así que esta fruta es probablemente
una naranja. Felicitaciones: ¡Acaba de usar el algoritmo de k-vecinos más
cercanos (KNN) para la clasificación! Todo el algoritmo es bastante simple.
¡El algoritmo KNN es simple pero útil! Si está tratando de clasificar algo,
es posible que desee probar KNN primero. Veamos un ejemplo más real.
Estos usuarios se grafican por similitud, por lo que los usuarios con gustos similares se
grafican más juntos. Suponga que desea recomendar películas para Priyanka. Encuentra a
los cinco usuarios más cercanos a ella.
Justin, JC, Joey, Lance y Chris tienen gustos similares en películas. Entonces, ¡cualquiera
que sea la película que les guste, a Priyanka probablemente también le gustará!
Una vez que tenga este gráfico, construir un sistema de recomendaciones es fácil.
Si a Justin le gusta una película, recomiéndasela a Priyanka.
Machine Translated by Google
Pero todavía falta una gran pieza. Graficaste los usuarios por similitud.
¿Cómo averiguas qué tan similares son dos usuarios?
Extracción de características
A partir de la gráfica, puedes decir visualmente que las frutas A y B son similares.
Medimos qué tan cerca están. Para encontrar la distancia entre dos puntos, usas
la fórmula de Pitágoras.
Machine Translated by Google
Una vez que pueda graficar a los usuarios, puede medir la distancia entre ellos.
distancia. Tal vez te estés preguntando, "¿Qué significa la distancia cuando tienes cinco
números?" La distancia te dice qué tan similares son esos conjuntos de números.
Priyanka y Justin son bastante similares. ¿Cuál es la diferencia entre Priyanka y Morfeo?
Calcula la distancia antes de continuar.
¡Estupendo! Ahora recomendar películas a Priyanka es fácil: si a Justin le gusta una película,
recomiéndasela a Priyanka y viceversa. ¡Acabas de crear un sistema de recomendaciones de
películas!
Si eres un usuario de Netflix, Netflix seguirá diciéndote: “Califica más películas. Cuantas más
películas califiques, mejores serán tus recomendaciones”. Ahora sabes por qué. Cuantas más
películas califiques, Netflix podrá ver con mayor precisión a qué otros usuarios te pareces.
Machine Translated by Google
EJERCICIOS
10.1 En el ejemplo de Netflix, calculó la distancia entre dos usuarios diferentes
utilizando la fórmula de distancia. Pero no todos los usuarios califican las
películas de la misma manera. Suponga que tiene dos usuarios, Yogi y Pinky,
que tienen el mismo gusto por las películas. Pero Yogi califica cualquier película
que le gusta con un 5, mientras que Pinky es más selectiva y reserva los 5 solo
para las mejores. Están bien emparejados, pero según el algoritmo de distancia,
no son vecinos. ¿Cómo consideraría sus diferentes estrategias de calificación?
Regresión
Supongamos que quiere hacer algo más que recomendar una película: quiere adivinar
cómo calificará Priyanka esta película. Toma a las cinco personas más cercanas a ella.
Por cierto, sigo hablando de las cinco personas más cercanas. No hay nada
Suponga que está tratando de adivinar una calificación para Pitch Perfect. Bueno,
¿cómo lo calificaron Justin, JC, Joey, Lance y Chris?
Machine Translated by Google
La regresión es muy útil. Suponga que tiene una pequeña panadería en Berkeley y
hace pan fresco todos los días. Estás tratando de predecir cuántos panes hacer para
hoy. Tienes un conjunto de características:
Hoy es un día de fin de semana con buen tiempo. Según los datos que acabas
de ver, ¿cuántos panes venderás? Usemos KNN, donde K = 4. Primero,
determine los cuatro vecinos más cercanos para este punto.
Semejanza de coseno
O suponga que le pide a los usuarios que califiquen películas para poder
darles recomendaciones, pero solo les pide que califiquen Toy Story, Toy Story
2 y Toy Story 3. ¡Esto no le dirá mucho sobre los gustos cinematográficos de los usuarios!
Cuando trabaja con KNN, es muy importante elegir las características correctas para
compararlas. Escoger las características correctas significa
• Funciones que no tienen sesgo (por ejemplo, si les pide a los usuarios que solo
califiquen películas de comedia, eso no le indicará si les gustan las películas de
acción)
¿Crees que las calificaciones son una buena forma de recomendar películas? Tal vez
califiqué The Wire más alto que House Hunters, pero en realidad paso más tiempo
viendo House Hunters. ¿Cómo mejorarías este sistema de recomendaciones de Netflix?
No hay una respuesta correcta cuando se trata de elegir buenas funciones. Tienes que
pensar en todas las cosas diferentes que necesitas considerar.
EJERCICIO
10.3 Netflix tiene millones de usuarios. El ejemplo anterior analizó a los cinco vecinos más
cercanos para construir el sistema de recomendaciones. ¿Es esto demasiado bajo?
¿Demasiado alto?
Machine Translated by Google
LOC
OCR significa reconocimiento óptico de caracteres. Significa que puede tomar una foto
de una página de texto y su computadora leerá automáticamente el texto por usted. Google
usa OCR para digitalizar libros. ¿Cómo funciona OCR?
Por ejemplo, considere este número.
¿Cómo averiguarías automáticamente qué número es este? Puedes usar KNN para esto:
2. Cuando obtenga una nueva imagen, extraiga las características de esa imagen y
¡Mira cuáles son sus vecinos más cercanos!
Es el mismo problema que las naranjas contra las toronjas. En términos generales, los
algoritmos de OCR miden líneas, puntos y curvas.
Luego, cuando obtienes un nuevo personaje, puedes extraer las mismas características de
él.
Machine Translated by Google
Suponga que recibe un correo electrónico con el asunto "¡Recoja su millón de dólares
ahora!" ¿Es correo no deseado? Puedes dividir esta oración en palabras. Luego, para
cada palabra, vea cuál es la probabilidad de que esa palabra aparezca en un correo
electrónico no deseado. Por ejemplo, en este modelo muy simple, la palabra millón
solo aparece en los correos electrónicos no deseados. Naive Bayes calcula la
probabilidad de que algo sea spam. Tiene aplicaciones similares a KNN.
Machine Translated by Google
Por ejemplo, podría usar Naive Bayes para categorizar la fruta: tiene una fruta
que es grande y roja. ¿Cuál es la probabilidad de que sea una toronja?
Es otro algoritmo simple que es bastante efectivo. ¡Nos encantan esos
algoritmos!
Resumen
¡Espero que esto le dé una idea de todas las cosas diferentes que puede hacer
con KNN y con el aprendizaje automático! El aprendizaje automático es un campo
interesante en el que puede profundizar bastante si decide:
a donde
ir al siguiente 11
En este capítulo
• Obtiene una breve descripción de 10 algoritmos
que no se trataron en este libro y por qué son útiles.
Árboles
203
Machine Translated by Google
Para cada nodo, los nodos a su izquierda tienen un valor menor y los nodos a la derecha
tienen un valor mayor.
Árboles 205
¡Encontraste a Maggie! ¡Es casi como ejecutar una búsqueda binaria! La búsqueda de un
elemento en un árbol de búsqueda binaria requiere un tiempo O(log n) en promedio y un
tiempo O(n) en el peor de los casos. La búsqueda de una matriz ordenada lleva tiempo O (log
n) en el peor de los casos, por lo que podría pensar que una matriz ordenada es mejor. Pero
un árbol de búsqueda binario es mucho más rápido para inserciones y eliminaciones en promedio.
Los árboles de búsqueda binarios también tienen algunas desventajas: por un lado,
no obtiene acceso aleatorio. No puedes decir: “Dame el quinto elemento de este árbol”.
Esos tiempos de rendimiento también son promedio y dependen de que el árbol esté
equilibrado. Suponga que tiene un árbol desequilibrado como el que se muestra a
continuación.
Machine Translated by Google
¿Ves cómo se inclina hacia la derecha? Este árbol no tiene muy buen rendimiento, porque no
está equilibrado. Hay árboles de búsqueda binarios especiales que se equilibran. Un ejemplo es el
árbol rojo-negro.
Entonces, ¿cuándo se usan los árboles de búsqueda binarios? Los árboles B, un tipo especial de
árbol binario, se usan comúnmente para almacenar datos en bases de datos.
Si está interesado en bases de datos o estructuras de datos más avanzadas, consulte estos:
• árboles B
• Árboles rojo-negros
• Montones
• árboles de juego
Índices invertidos
Aquí hay una versión muy simplificada de cómo funciona un motor de búsqueda. Suponga que tiene
tres páginas web con este contenido simple.
Machine Translated by Google
Las claves de la tabla hash son las palabras, y los valores le indican
en qué páginas aparece cada palabra. Ahora supongamos que un
usuario busca hola. Veamos en qué páginas aparece hi.
La transformada de Fourier
La transformada de Fourier es uno de esos raros algoritmos: brillante,
elegante y con un millón de casos de uso. La mejor analogía para la transformada
de Fourier proviene de Better Explained (un gran sitio web que explica las
matemáticas de manera simple): dado un batido, la transformada de Fourier le
dirá los ingredientes del batido.1 O, para decirlo de otra manera, dada una canción,
la transformada puede separarlo en frecuencias individuales.
Resulta que esta simple idea tiene muchos casos de uso. Por ejemplo, si puede
separar una canción en frecuencias, puede aumentar las que le interesan.
Podrías potenciar los graves y ocultar los agudos. La transformada de Fourier
es excelente para procesar señales. También puedes usarlo para comprimir
música. Primero, divide un archivo de audio en sus notas de ingredientes. La
transformada de Fourier te dice exactamente cuánto contribuye cada nota a la
canción en general. Así que puedes deshacerte de las notas que no son
importantes. ¡Así es como funciona el formato MP3!
1
Kalid, "Una guía interactiva de la transformada de Fourier", mejor explicado, http://mng.bx/874X.
Machine Translated by Google
Puede usarlo para crear una aplicación como Shazam, que adivina qué canción se está reproduciendo.
La transformada de Fourier tiene muchos usos. ¡Hay muchas posibilidades de que te encuentres con
él!
Algoritmos paralelos
Los siguientes tres temas tratan sobre la escalabilidad y el trabajo con una gran cantidad de datos.
En el pasado, las computadoras eran cada vez más rápidas. Si quisiera hacer que su algoritmo
fuera más rápido, podría esperar unos meses y las computadoras mismas se volverían más
rápidas. Pero estamos cerca del final de ese período. En cambio, las computadoras portátiles y las
computadoras se envían con múltiples núcleos. Para que sus algoritmos sean más rápidos, debe
cambiarlos para que se ejecuten en paralelo en todos los núcleos a la vez.
Aquí hay un ejemplo simple. Lo mejor que puede hacer con un algoritmo de clasificación es
aproximadamente O (n log n). Es bien sabido que no puede ordenar una matriz en tiempo O(n), ¡a menos
que use un algoritmo paralelo! Hay una versión paralela de ordenación rápida que ordenará una matriz
en tiempo O(n).
Los algoritmos paralelos son difíciles de diseñar. Y también es difícil asegurarse de que funcionen
correctamente y averiguar qué tipo de aumento de velocidad verá. Una cosa es segura: las ganancias
de tiempo no son lineales. Entonces, si tiene dos núcleos en su computadora portátil en lugar de
uno, eso casi nunca significa que su algoritmo se ejecutará mágicamente el doble de rápido. Hay un
par de razones para esto:
• Equilibrio de carga: suponga que tiene 10 tareas para realizar, por lo que asigna 5 tareas principales
a cada una. Pero el núcleo A realiza todas las tareas fáciles, por lo que se realiza en 10 segundos,
mientras que el núcleo B realiza todas las tareas difíciles, por lo que demora un minuto.
¡Eso significa que el núcleo A estuvo inactivo durante 50 segundos mientras que el núcleo B
estaba haciendo todo el trabajo! ¿Cómo distribuye el trabajo de manera uniforme para que
ambos núcleos trabajen igual de duro?
Si está interesado en el lado teórico del rendimiento y la escalabilidad, ¡los algoritmos paralelos podrían
ser para usted!
Machine Translated by Google
Mapa reducido
Hay un tipo especial de algoritmo paralelo que se está volviendo cada vez más popular:
el algoritmo distribuido. Está bien ejecutar un algoritmo paralelo en su computadora
portátil si necesita de dos a cuatro núcleos, pero ¿qué sucede si necesita cientos de
núcleos? Luego puede escribir su algoritmo para que se ejecute en varias máquinas. El
algoritmo MapReduce es un algoritmo distribuido popular. Puede usarlo a través de la
popular herramienta de código abierto Apache Hadoop.
O suponga que tiene que procesar una larga lista de trabajos. Cada trabajo tarda
10 segundos en procesarse y necesita procesar 1 millón de trabajos como este. Si
haces esto en una máquina, ¡te llevará meses! Si pudiera ejecutarlo en 100 máquinas,
podría terminar en unos pocos días.
Los algoritmos distribuidos son geniales cuando tienes mucho trabajo por hacer y
quieres acelerar el tiempo necesario para hacerlo. MapReduce en particular se
construye a partir de dos ideas simples: la función map y la función reduce .
La función de mapa
La función map es simple: toma una matriz y aplica la misma función a cada
elemento de la matriz. Por ejemplo, aquí estamos duplicando todos los elementos
de la matriz:
Aquí tiene una lista de URL y desea descargar cada página y almacenar el
contenido en arr2. Esto podría tomar un par de segundos para cada URL. Si
tuviera 1,000 URL, ¡esto podría llevar un par de horas!
La función de reducción
La función de reducción confunde a la gente a veces. La idea es que “reduzcas”
una lista completa de elementos a un solo elemento. Con el mapa, pasa de una
matriz a otra.
MapReduce utiliza estos dos conceptos simples para ejecutar consultas sobre datos
en varias máquinas. Cuando tiene un gran conjunto de datos (miles de millones de
filas), MapReduce puede brindarle una respuesta en minutos, mientras que una base
de datos tradicional podría demorar horas.
O suponga que es Google y está rastreando páginas web. Solo desea rastrear una
página web si aún no la ha rastreado. Por lo tanto, debe averiguar si esta página se ha
rastreado antes.
O suponga que está ejecutando bit.ly, que es un acortador de URL. No desea redirigir
a los usuarios a sitios web maliciosos. Tiene un conjunto de direcciones URL que se
consideran maliciosas. Ahora debe averiguar si está redirigiendo al usuario a una URL en
ese conjunto.
Todos estos ejemplos tienen el mismo problema. Tienes un conjunto muy grande.
Machine Translated by Google
Ahora tiene un nuevo elemento y desea ver si pertenece a ese conjunto. Podrías
hacer esto rápidamente con un hash. Por ejemplo, supongamos que Google tiene
un gran hash en el que las claves son todas las páginas que ha rastreado.
Filtros de floración
Los filtros Bloom ofrecen una solución. Los filtros Bloom son estructuras de
datos probabilísticos. Te dan una respuesta que podría estar equivocada pero
probablemente sea correcta. En lugar de un hash, puede preguntarle a su filtro de
floración si ha rastreado esta URL antes. Una tabla hash le daría una respuesta
precisa. Un filtro de floración le dará una respuesta que probablemente sea correcta:
• Los falsos positivos son posibles. Google podría decir: "Ya ha rastreado este sitio",
aunque no lo haya hecho.
• Los falsos negativos no son posibles. Si el filtro de floración dice: "No ha rastreado
este sitio", entonces definitivamente no ha rastreado este sitio.
Los filtros Bloom son geniales porque ocupan muy poco espacio. Una tabla hash
tendría que almacenar cada URL rastreada por Google, pero un filtro de floración
no tiene que hacer eso. Son geniales cuando no necesitas una respuesta exacta,
como en todos estos ejemplos. Está bien que bit.ly diga: "Creemos que este sitio
puede ser malicioso, así que ten mucho cuidado".
Machine Translated by Google
HyperLogLog
En la misma línea hay otro algoritmo llamado HyperLogLog.
Supongamos que Google quiere contar el número de búsquedas
únicas realizadas por sus usuarios. O supongamos que Amazon quiere contar
la cantidad de artículos únicos que los usuarios miraron hoy. ¡Responder a
estas preguntas requiere mucho espacio! Con Google, tendría que mantener
un registro de todas las búsquedas únicas. Cuando un usuario busca algo,
debe ver si ya está en el registro. De lo contrario, debe agregarlo al registro.
¡Incluso por un solo día, este registro sería enorme!
HyperLogLog aproxima el número de elementos únicos en un conjunto.
Al igual que los filtros de floración, no le dará una respuesta exacta, pero
se acerca mucho y usa solo una fracción de la memoria que tomaría una tarea
como esta.
Utiliza una función hash para decirle en qué ranura colocar el valor.
En este caso, desea que la función hash le proporcione una buena distribución.
Entonces, una función hash toma una cadena y le devuelve el número de ranura para
esa cadena.
Comparando archivos
Otra función hash es una función de algoritmo hash seguro (SHA).
Dada una cadena, SHA le da un hash para esa cadena.
La terminología puede ser un poco confusa aquí. SHA es una función hash.
Genera un hash, que es solo una cadena corta. La función hash para las tablas
hash pasó de una cadena a un índice de matriz, mientras que SHA va de una
cadena a otra.
Nota
Los hash SHA son largos. Han sido truncados aquí.
Puede usar SHA para saber si dos archivos son iguales. Esto es útil cuando tiene
archivos muy grandes. Suponga que tiene un archivo de 4 GB. Desea verificar si su
amigo tiene el mismo archivo grande. No tiene que tratar de enviarles por correo
electrónico su archivo grande. En su lugar, puede calcular el hash SHA y compararlo.
Machine Translated by Google
Comprobación de contraseñas
SHA también es útil cuando desea comparar cadenas sin revelar cuál era la cadena
original. Por ejemplo, suponga que Gmail es pirateado y el atacante roba todas las
contraseñas. ¿Está su contraseña a la vista? No, no lo es. ¡Google no almacena la
contraseña original, solo el hash SHA de la contraseña! Cuando ingresa su contraseña,
Google la codifica y la compara con el hash en su base de datos.
Entonces, solo está comparando hashes, ¡no tiene que almacenar su contraseña!
SHA se usa muy comúnmente para codificar contraseñas como esta. Es un hash
unidireccional. Puede obtener el hash de una cadena.
Machine Translated by Google
Eso significa que si un atacante obtiene los hash SHA de Gmail, ¡no podrá
volver a convertir esos hash en las contraseñas originales! Puede convertir
una contraseña en un hash, pero no al revés.
SHA es en realidad una familia de algoritmos: SHA-0, SHA-1, SHA-2 y
SHA-3. Al escribir estas líneas, SHA-0 y SHA-1 tienen algunas debilidades.
Si usa un algoritmo SHA para el hash de contraseña, use SHA-2 o SHA-3.
Actualmente, el estándar de oro para las funciones de hashing de contraseñas
es bcrypt (aunque nada es infalible).
• Scribd permite a los usuarios cargar documentos o libros para compartir con
otros. ¡Pero Scribd no quiere que los usuarios carguen contenido con derechos de autor!
El sitio podría usar Simhash para verificar si una carga es similar a un libro de Harry Potter y,
de ser así, rechazarla automáticamente.
Incluso si logramos cambiarlo todos los días, un cifrado simple como este es fácil de descifrar
con un ataque de fuerza bruta. Supongamos que veo el mensaje "9,6,13,13,16 24,16,19,13,5".
Supongo que esto usa a = 1, b = 2 y
pronto.
¡Eso funciono! Un cifrado simple como este es fácil de descifrar. Los alemanes usaron
un cifrado mucho más complicado en la Segunda Guerra Mundial, pero aún estaba descifrado.
Diffie-Hellman resuelve ambos problemas:
• Ambas partes no necesitan saber el cifrado. Así que no tenemos que encontrarnos
y de acuerdo con lo que debería ser el cifrado.
Diffie-Hellman tiene dos claves: una clave pública y una clave privada. La clave pública es
exactamente eso: pública. Puedes publicarlo en tu sitio web, enviarlo por correo electrónico
a tus amigos o hacer lo que quieras con él. No tienes que ocultarlo.
Cuando alguien quiere enviarte un mensaje, lo encripta usando la clave pública. Un
mensaje cifrado solo se puede descifrar con la clave privada. Mientras seas la única
persona con la clave privada, ¡solo tú podrás descifrar este mensaje!
Programación lineal
Guardé lo mejor para el final. La programación lineal es una de las mejores cosas
que conozco.
La programación lineal se usa para maximizar algo dadas algunas restricciones. Por
ejemplo, suponga que su empresa fabrica dos productos, camisas y bolsos. Las camisas
necesitan 1 metro de tela y 5 botones. Los totes necesitan 2 metros de tela y 2 botones.
Tienes 11 metros de tela y 20 botones. Usted gana $2 por camisa y $3 por bolso. ¿Cuántas
Aquí está tratando de maximizar las ganancias y está limitado por la cantidad de
materiales que tiene.
Otro ejemplo: eres un político y quieres maximizar la cantidad de votos que obtienes. Su
investigación ha demostrado que se necesita un promedio de una hora de trabajo
(marketing, investigación, etc.) por cada voto de un residente de San Francisco o 1,5
horas/voto de un residente de Chicago. Necesita al menos 500 habitantes de San
Francisco y al menos 300 habitantes de Chicago. Tienes
Machine Translated by Google
Epílogo 219
Aquí está tratando de maximizar los votos y está limitado por el tiempo y el dinero.
Es posible que esté pensando: “Ha hablado sobre muchos temas de optimización en
este libro. ¿Cómo se relacionan con la programación lineal?” Todos los algoritmos de
gráficos se pueden hacer a través de la programación lineal en su lugar.
La programación lineal es un marco mucho más general, y los problemas de
gráficos son un subconjunto de eso. ¡Espero que tu mente esté alucinada!
Epílogo
Espero que este recorrido rápido por 10 algoritmos le haya mostrado cuánto queda
por descubrir. Creo que la mejor manera de aprender es encontrar algo que te interese
y sumergirte. Este libro te dio una base sólida para hacer precisamente eso.
Machine Translated by Google
Machine Translated by Google
respuestas
a ejercicios
CAPÍTULO 1
1.1 Suponga que tiene una lista ordenada de 128 nombres y la está
buscando utilizando una búsqueda binaria. ¿Cuál es el número
máximo de pasos que daría?
Respuesta: 7.
221
Machine Translated by Google
O(n + 26), O(n - 26), O(n * 26), O(n / 26). ¡Son todos iguales a O(n)! ¿Por qué? Si
tiene curiosidad, vaya a "Revisión de la notación Big O", en el capítulo 4, y lea sobre
las constantes en la notación Big O (una constante es solo un número; 26 fue la
constante en esta pregunta).
CAPITULO 2
2.1 Suponga que está creando una aplicación para realizar un seguimiento de sus finanzas.
Todos los días, escribes todo en lo que gastaste dinero. Al final del mes, revisas
tus gastos y sumas cuánto gastaste. Entonces, tienes muchas inserciones y
algunas lecturas.
¿Deberías usar una matriz o una lista?
Respuesta: En este caso, está agregando gastos a la lista todos los días y leyendo
todos los gastos una vez al mes. Las matrices tienen lecturas rápidas e inserciones
lentas. Las listas enlazadas tienen lecturas lentas e inserciones rápidas.
Debido a que insertará más a menudo que leerá, tiene sentido usar una lista enlazada.
Además, las listas vinculadas tienen lecturas lentas solo si accede a elementos
aleatorios en la lista. porque estas leyendo
cada elemento de la lista, las listas vinculadas también funcionarán bien en las lecturas .
Entonces, una lista enlazada es una buena solución a este problema.
2.2 Suponga que está creando una aplicación para que los restaurantes lleven a los clientes
pedidos. Su aplicación necesita almacenar una lista de pedidos. Los meseros
continúan agregando pedidos a esta lista, y los chefs quitan pedidos de la lista y los preparan.
Es una cola de pedidos: los servidores agregan pedidos al final de la cola y el
chef toma el primer pedido de la cola y lo cocina.
Machine Translated by Google
¿Usaría una matriz o una lista enlazada para implementar esta cola?
(Pista: las listas enlazadas son buenas para inserciones/eliminaciones, y las matrices
son buenas para el acceso aleatorio. ¿Cuál vas a hacer aquí?)
2.3 Hagamos un experimento mental. Supongamos que Facebook mantiene una lista de nombres de
usuario. Cuando alguien intenta iniciar sesión en Facebook, se realiza una búsqueda de su
nombre de usuario. Si su nombre está en la lista de nombres de usuario, pueden iniciar sesión.
Las personas inician sesión en Facebook con bastante frecuencia, por lo que hay muchas
búsquedas a través de esta lista de nombres de usuario. Supongamos que Facebook usa la
búsqueda binaria para buscar en la lista. La búsqueda binaria necesita acceso aleatorio: debe
poder llegar al medio de la lista de nombres de usuario al instante. Sabiendo esto, ¿implementaría
la lista como una matriz o como una lista enlazada?
Respuesta: Una matriz ordenada. Las matrices le brindan acceso aleatorio: puede
obtener un elemento del medio de la matriz al instante. No puedes hacer eso con
listas enlazadas. Para llegar al elemento central en una lista enlazada, debe comenzar
en el primer elemento y seguir todos los enlaces hasta el elemento central.
2.4 Las personas también se registran en Facebook con bastante frecuencia. Supongamos que
decidió usar una matriz para almacenar la lista de usuarios. ¿Cuáles son las
desventajas de una matriz para inserciones? En particular, suponga que está
utilizando la búsqueda binaria para buscar inicios de sesión. ¿Qué sucede cuando
agrega nuevos usuarios a una matriz?
2.5 En realidad, Facebook no utiliza ni una matriz ni una lista enlazada para almacenar la
información del usuario. Consideremos una estructura de datos híbrida: una matriz de
listas enlazadas. Tienes una matriz con 26 ranuras. Cada ranura apunta a una lista
enlazada. Por ejemplo, el primer espacio en la matriz apunta a una lista vinculada que
contiene todos los nombres de usuario que comienzan con a. El segundo espacio apunta
a una lista enlazada que contiene todos los nombres de usuario que comienzan con b, y
así sucesivamente.
Suponga que Adit B se registra en Facebook y desea agregarlos a la lista. Vaya a la ranura
1 en la matriz, vaya a la lista vinculada para la ranura 1 y agregue Adit B al final. Ahora,
suponga que desea buscar a Zakhir H. Vaya al espacio 26, que apunta a una lista vinculada
de todos los nombres de Z. Luego busca en esa lista para encontrar a Zakhir H.
Compare esta estructura de datos híbrida con arreglos y listas enlazadas. ¿Es más lento o
más rápido que cada uno para buscar e insertar? No tiene que dar tiempos de ejecución de
Big O, solo si la nueva estructura de datos sería más rápida o más lenta.
Respuesta: Búsqueda: más lenta que las matrices, más rápida que las listas enlazadas.
Inserción: más rápido que las matrices, la misma cantidad de tiempo que las listas vinculadas.
Por lo tanto, es más lento para buscar que una matriz, pero más rápido o igual que las
listas vinculadas para todo. Hablaremos de otra estructura de datos híbrida llamada tabla
hash más adelante en el libro. Esto debería darle una idea de cómo puede construir
hash, árboles B y otros. Las matrices y las listas vinculadas son los componentes básicos
de estas estructuras de datos más complejas.
Machine Translated by Google
CAPÍTULO 3
3.1 Suponga que le muestro una pila de llamadas como esta.
3.2 Suponga que accidentalmente escribe una función recursiva que se ejecuta para
siempre. Como vio, su computadora asigna memoria en la pila para cada llamada
de función. ¿Qué sucede con la pila cuando su función recursiva se ejecuta para
siempre?
Respuesta: La pila crece para siempre. Cada programa tiene una cantidad
limitada de espacio en la pila de llamadas. Cuando su programa se quede sin
espacio (que eventualmente lo hará), saldrá con un error de desbordamiento de
pila.
Machine Translated by Google
CAPÍTULO 4
4.1 Escriba el código de la función de suma anterior .
Respuesta:
def suma(lista):
si lista == []:
volver 0
devolver lista[0] + suma(lista[1:])
4.2 Escriba una función recursiva para contar el número de elementos en una lista.
Respuesta:
def cuenta(lista):
si lista == []:
volver 0
devuelve 1 + cuenta (lista [1:])
Respuesta:
def max(lista):
si len(lista) == 2:
volver lista[0] si lista[0] > lista[1] sino lista[1]
sub_max = max(lista[1:])
volver lista[0] si lista[0] > sub_max else sub_max
Respuesta: El caso base para la búsqueda binaria es una matriz con un elemento.
Si el elemento que está buscando coincide con el elemento de la matriz, ¡lo ha encontrado!
De lo contrario, no está en la matriz.
En el caso recursivo de la búsqueda binaria, divide la matriz por la mitad, desecha una
mitad y llama a la búsqueda binaria en la otra mitad.
4.8 Crear una tabla de multiplicar con todos los elementos del arreglo.
Entonces, si tu arreglo es [2, 3, 7, 8, 10], primero multiplicas cada elemento por 2, luego
multiplicas cada elemento por 3, luego por 7, y así sucesivamente.
Respuesta: O(n2 )
CAPÍTULO 5
¿Cuáles de estas funciones hash son consistentes?
Respuesta: consistente
Respuesta: no consistente
Respuesta: no consistente
Respuesta: consistente
Suponga que tiene estas cuatro funciones hash que funcionan con cadenas:
C. Utilice el primer carácter de la cadena como índice. Entonces, todas las cadenas
que comienzan con a se codifican juntas, y así sucesivamente.
Para cada uno de los siguientes ejemplos, ¿qué funciones hash proporcionarían una
buena distribución? Suponga un tamaño de tabla hash de 10 ranuras.
5.5 Una agenda donde las claves son nombres y los valores son números
de teléfono. Los nombres son los siguientes: Esther, Ben, Bob y Dan.
5.6 Un mapeo del tamaño de la batería a la potencia. Los tamaños son A, AA, AAA y AAAA.
5.7 Un mapeo de títulos de libros a autores. Los títulos son Maus, Fun Home y Watchmen.
CAPÍTULO 6
Ejecute el algoritmo de búsqueda primero en amplitud en cada uno de estos gráficos
para encontrar la solución.
6.2 Encuentre la longitud del camino más corto desde "taxi" hasta "murciélago".
6.4 Aquí hay un gráfico más grande. Haz una lista válida para este gráfico.
CAPÍTULO 7
7.1 En cada una de estas gráficas, ¿cuál es el peso del camino más corto
de principio a fin?
CAPÍTULO 8
8.1 Trabajas para una empresa de muebles y tienes que enviar muebles a todo el
país. Necesitas empacar tu camión con cajas. Todas las cajas son de
diferentes tamaños y estás tratando de maximizar el espacio que usas en
cada camión. ¿Cómo escogerías las cajas para maximizar el espacio?
Piensa en una estrategia codiciosa. ¿Eso le dará la solución óptima?
Respuesta: Una estrategia codiciosa sería elegir la caja más grande que
quepa en el espacio restante y repetir hasta que no puedas empacar más cajas.
No, esto no le dará la solución óptima.
8.2 Te vas a Europa y tienes siete días para ver todo lo que puedas. Asignas un
valor en puntos a cada artículo (cuánto quieres verlo) y estimas cuánto
tiempo lleva. ¿Cómo puede maximizar el total de puntos (ver todas las cosas
que realmente quiere ver) durante su estadía? Piensa en una estrategia
codiciosa. ¿Eso le dará la solución óptima?
Respuesta: Siga eligiendo la actividad con el valor de puntos más alto que todavía
puede hacer en el tiempo que le queda. Detente cuando no puedas hacer nada
más. No, esto no le dará la solución óptima.
respuesta: no
Respuesta: Sí.
8.8 Estás haciendo un mapa de los EE. UU. y necesitas colorear los estados
adyacentes con diferentes colores. Tienes que encontrar el número mínimo
de colores que necesitas para que no haya dos estados adyacentes del
mismo color. ¿Es este un problema NP-completo?
Respuesta: Sí.
CAPÍTULO 9
9.1 Suponga que puede robar otro artículo: un reproductor de MP3. Pesa 1 libra y
vale $1,000. ¿Deberías robarlo?
Respuesta: Sí. Entonces podrías robar el reproductor de MP3, el iPhone y la guitarra, por un valor
total de $4,500.
9.2 Supongamos que vas a acampar. Tienes una mochila que aguanta
6 lb, y puede llevar los siguientes artículos. Cada uno tiene un valor, y cuanto mayor sea el valor,
más importante es el artículo:
• Agua, 3 libras, 10
• Libro, 1 libra, 3
• Alimentos, 2 libras, 9
• Chaqueta, 2 libras, 5
• Cámara, 1 libra, 6
9.3 Dibuja y completa la cuadrícula para calcular la subcadena común más larga entre el azul
y las pistas.
Respuesta:
Machine Translated by Google
CAPÍTULO 10
10.1 En el ejemplo de Netflix, calculó la distancia entre dos
diferentes usuarios utilizando la fórmula de la distancia. Pero no todos los
usuarios califican las películas de la misma manera. Suponga que tiene dos
usuarios, Yogi y Pinky, que tienen el mismo gusto por las películas. Pero Yogi
califica cualquier película que le gusta con un 5, mientras que Pinky es más
selectiva y reserva los 5 solo para las mejores. Están bien emparejados, pero
según el algoritmo de distancia, no son vecinos. ¿Cómo consideraría sus
diferentes estrategias de calificación?
Índice
235
Machine Translated by Google
236 índice
descripción general
requiere más de dos submochilas 177
42–45 con recursividad 45–
git diff 185
fila de portátiles 168–170 optimización
50 nodo más barato 117, 125 gráficos
del itinerario de viaje 175–177
clasificación 189 problema de búsqueda en amplitud y 99–104
índice 237
ejercicios 145–146
Khan Academy 7, 54 aprendizaje automático 199–201
problema de la mochila 144-145
problema de mochila Algoritmo MapReduce
Problemas NP-completos 152–158
cambiando el orden de las filas 174 función de mapa 209–210
Problema de cobertura de conjuntos 146– función de reducción 210–211
Preguntas
151 Algoritmos de aproximación 147–
frecuentes 171–173 llenar la cuadrícula memoria 22–23
150
en columnas 174 fila de guitarras 164– clasificación por fusión frente a clasificación
volver al código 151–152
167 si la solución no llena la mochila por rápida 67–68 formato MP3 207
ejercicio 152
completo 178 si la solución requiere
resumen 146
más de dos mochilas secundarias 177 norte
saludar2 función 44
fila de computadoras portátiles 168–
saludar función 43–45
170 optimización del itinerario de viaje Clasificador Naive Bayes 200
variable de nombre 43 vecinos 99
H 175–177
n! (n factorial) operaciones 19
descripción general 144–145, nodos 99, 105 n operaciones 12
tablas hash 73–88
161 solución simple 162–163 problemas NP-completos 152–158
colisiones 86–88
robar fracciones de un artículo 175 fila
funciones hash 76–78
estéreo 166–168
rendimiento 88–91 ejercicios
algoritmo de los k-vecinos más cercanos
93
construcción de un sistema de O
buena función hash 90–91 factor
recomendaciones 189–194
de carga 90–91
clasificación de naranjas frente a pomelos OCR (reconocimiento óptico
casos de uso 79–86
187–189
de caracteres) 199–201
prevención de entradas duplicadas ejercicios 195–198
81–83
aprendizaje automático 199–201 PAGS
238 índice
ordenación por fusión frente a ordenación rápida 67–68 implementando 105–106 ejercicio 45, 49–50 resumen
recomendaciones de acceso aleatorio, edificio 189– de cobertura de conjuntos 146–151 algoritmos mercado de valores, predicción 201
194 de aproximación calcular respuesta 149 cadenas, asignación a números 76 función de
recursividad 37–49 caso código para configuración 147–148 suma 57, 59
base y caso recursivo conjuntos 149–150
Pila de T
llamadas 40–41 con 45–50 ejercicio 152
resumen 37–39 resumen 146
conexión de tercer grado 103 clasificación
establecer diferencia 150
regresión 196 cambio topológica 112 entrenamiento 200 árboles
establecer intersección 150
de tamaño 91 tiempo de 203–206
ejecución juegos 148
tiempos de ejecución comunes 15–16 establecer unión 150