[MÚSICA DE FONDO]
DAVID: Muy bien.
Esto es CS50 y vamos a iniciar la lección 3.
Hoy, la meta será mucho más algorítmica que enfocada en códigos
y en cierto modo volveremos al inicio, a la lección cero,
cuando hablamos sobre pensamiento computacional y algoritmos.
Ahora ya tienen un par de semanas de experiencia
y cuentan con nuevas herramientas y habilidades bajo la manga,
aun cuando es probable que todavía estén tratando
de sentirse cómodos con algunas de esas habilidades.
Para comenzar, echemos un vistazo a este dispositivo de hardware
del que ya hablamos un par de veces.
¿Y este es un ejemplo de qué?
PÚBLICO: De memoria.
DAVID: Sí, de memoria o RAM, Random Access Memory.
Y al parecer se ve bastante grande en la pantalla,
pero de hecho se vería realmente pequeña si tuvieran
que encontrarla en su Mac, PC, laptop, o cualquier otro dispositivo.
Y esto es solo memoria.
Y la memoria son sitios donde pueden almacenar información, números, caracteres,
cadenas y cosas aún más grandes.
Y recuerden que si desean usar esta memoria, deben direccionarla
y colocar cosas en ubicaciones específicas.
Y hoy comenzaremos a hacerlo. Entonces, en particular,
tenemos aquí por lo menos cinco...
cuatro chips negros, que es donde se almacenan los ceros y los unos
en el tablero de circuitos verde.
Y los pequeños conectores dorados que también se ven en la pantalla,
permiten que todos esos chips negros
se comuniquen, básicamente, y que los ceros y los unos se muevan.
Pero si hacemos un acercamiento a uno de estos chips de memoria,
arbitrariamente, y luego hacemos un acercamiento acá, podríamos pensar
en un rectángulo.
Podría ser cualquier forma, sin duda, pero si lo pensamos como un rectángulo,
podemos dividir este chip de memoria en una cuadrícula como esta.
El modo en que lo he dibujado es completamente arbitrario, pero el hecho
es que tengo muchos recuadros pequeños,
y pueden pensar en esto como si fuera un byte, y después
otro byte y después otro byte.
Y casualmente puse ocho bytes, pero podría ser cualquier número.
Realmente no importa.
Entonces, pueden imaginar que estos bytes
son como ubicaciones específicas, números o direcciones,
a la par que empezamos a nombrarlos.
Entonces, si hago zoom en la parte superior de esto, podrían
pensar que en la parte superior izquierda estaría el byte número 0,
porque los especialistas en sistemas por lo general comienzan
a contar desde cero, y luego el byte 1, y el 2, y el 3, y el 4, etcétera, etcétera.
Entonces, a modo de repaso, en una Mac o PC típica en estos días,
por lo general, ¿cuánta RAM o memoria, tenemos?
PÚBLICO: Cuatro
DAVID: ¿Cuatro qué?
PÚBLICO: Cuatro gigabytes.
DAVID: Cuatro gigabytes.
Y giga significa mil millones, entonces serían 4 mil millones de bytes.
Tal vez sería un poco menos, o quizás tendrían un poco más,
pero hablamos en términos de miles de millones de bytes.
Solo estamos explorando superficialmente la cantidad de almacenamiento que tenemos
y cuántos números y caracteres podemos almacenar.
El punto es que podríamos direccionarlos de alguna forma... cero, uno, dos,
y así sucesivamente.
Así es como almacenamos el nombre de Stelios en la memoria.
Recuerden que este fue un ejemplo de la vez pasada, en donde
teníamos que poner los caracteres de este nombre en algún sitio.
Y entonces pusimos la S y luego la T,
y así sucesivamente, desde la ubicación más a la izquierda y hacia adelante.
Pero también pusimos algo más.
No es suficiente con solo poner las letras del nombre en la memoria.
¿Qué más tuvimos que hacer?
PÚBLICO: ¿Poner un cero ahí?
DAVID: Sí, el comando cero, o más específicamente, el byte cero,
también conocido como el byte nulo.
N-U-L-O, e cual se representa con la diagonal inversa y el 0.
Porque si solamente escribieran un cero normal,
técnicamente se referiría a un carácter del teclado.
Parece un número, pero técnicamente hablando es un carácter.
Entonces, la diagonal inversa más cero significa 8-0 bits en la ubicación de ese byte.
Bien.
Entonces, ahora que tenemos esta habilidad para representar a Stelios
con una secuencia de caracteres que al final termina en nulo,
realmente ya no necesitamos preocuparnos por el hardware.
¿Correcto?
Podemos abstraernos de ello tal como lo hicimos en la semana cero,
y no preocuparnos más por cómo se almacenan los ceros y los unos.
Podemos tan solo confiar en que están allí, de alguna manera,
y entonces, podemos abstraernos y solo empezar
a pensar en ello como una secuencia, o una cuadrícula, de letras como esta.
Pero esa diagonal inversa más cero es importante, porque
es la única forma en que este lenguaje que estamos utilizando actualmente,
el C, sabe donde terminan las cadenas.
Si no fuera así, entonces printf, por ejemplo,
podría seguir imprimiendo todo el contenido, los cuatro gigabytes de memoria
que la computadora tiene, sin importar lo que fueran esos caracteres.
Y entonces no podríamos distinguir entre los nombres de Stelios y María,
o en general, el de alguien más que se encuentre en la memoria.
A continuación abriré el IDE, solo para demostrar
lo que pueden hacer con este tipo de conocimiento.
Supongan que deseo escribir un programa que permita
que un usuario escriba su nombre,
y que luego solo quiero imprimir sus iniciales.
Por simplicidad, voy a suponer que las iniciales de la persona,
o cualesquiera que sean las letras en mayúscula, se escriban en la entrada.
Entonces, seré un poco quisquilloso, y escribiré mi nombre correctamente.
Pero una vez dicho esto, seguiré adelante y voy a improvisar.
Entonces, voy a guardar esto como initials.c.
Solo por costumbre, voy a incluir la biblioteca CS50,
porque quiero obtener una cadena del usuario.
Después voy a incluir el estándar io.h,
para que pueda imprimir algo en la pantalla
y posteriormente decidiremos si necesitamos algo más.
Para este programa no necesito ningún argumento de línea de comandos
y luego volveré a nuestra antigua versión principal, donde solo
especifico void, no argc ni argv.
Y entonces, aquí, voy a declarar una cadena llamada s,
y obtendré un nombre de usuario, con "name: " y get_string,
Y luego, ¿qué quiero hacer después de esto?
Quiero iterar sobre los caracteres en el nombre del usuario,
e imprimirlos solo si están en mayúsculas.
Pero, ¿saben qué?, en vez de simplemente imprimirlos,
¿saben lo que quiero hacer?
Quiero crear una nueva cadena que contenga las iniciales del usuario.
Por lo tanto, no solo imprimir, con %c,
un carácter tras otro, más bien
deseo almacenar las iniciales del usuario en una nueva cadena,
entonces tengo una cadena para su nombre,
y una cadena para sus iniciales.
Y, damas y caballeros, Ian.
IAN: Perdón por las fallas de video.
DAVID: Gracias.
Bien.
Entonces, para ser más claro, déjenme renombrar
esto a "Name: ", y luego quiero tener una cadena para sus iniciales.
Pero sabemos que una cadena es, tal como lo vimos la vez pasada,
solo una secuencia de caracteres.
Y una secuencia, en realidad, tiene otro nombre en programación.
¿Cuál es otro sinónimo que hemos usado para nombrar una secuencia de algo?
PÚBLICO: [INAUDIBLE]
DAVID: Un arreglo.
Y un arreglo es esa estructura de datos que teníamos cuando
comenzamos a utilizar la notación de corchetes.
Entonces, si retrocedo y rompo esta abstracción,
si también lo hacen ustedes, no piensen en esto como una cadena.
¿Qué es una cadena?
Es una secuencia de caracteres.
Técnicamente podría poner char, y luego podría poner initials,
y luego podría especificar tantas letras
como yo quiera para las iniciales de un nombre.
Suponiendo que tenemos un primer nombre, tal vez un segundo nombre y un apellido,
tres caracteres deberían bastar.
Tres caracteres.
Por lo tanto, para David J. Malan, los tres caracteres serían DJM.
¿Son suficientes?
PÚBLICO: [INAUDIBLE]
DAVID: Lo dudo, porque no...
PÚBLICO: [INAUDIBLE]
DAVID: No es así, pero ¿por qué?
PÚBLICO: Se necesita el carácter nulo.
DAVID: Sí.
Entonces, si queremos completar mis iniciales, lo cual técnicamente no es
una palabra, aunque sin duda alguna es una cadena,
una secuencia de caracteres, necesitamos un cuarto carácter,
o necesitamos anticipar un cuarto carácter,
de modo que lo que pongamos en la memoria de la computadora sea,
en mi caso, algo como, D-J-M diagonal inversa 0.
Necesitamos ese cuarto byte.
De otro modo, no tendremos espacio para completar la cadena.
Entonces, aunque esto no parece una cadena,
en la medida en que no digo la palabra cadena, en realidad lo es.
Es una secuencia de caracteres de tamaño cuatro en la que puedo poner caracteres.
Ahora, de modo predeterminado, ¿cuáles son los caracteres en este arreglo?
Cuando declaramos una variable de algún tamaño, ¿qué valores tiene?
PÚBLICO: [INAUDIBLE]
DAVID: A veces ceros, pero en general,
¿cuál es la regla de oro?
PÚBLICO: No se sabe.
DAVID: Así es, no se sabe.
Son los llamados valores basura.
Nada. En general no deberíamos confiar en el valor de una variable,
a menos que nosotros mismos pusiéramos el valor allí, como si lo almacenáramos,
con el operador de asignación, o escribiéndolo manualmente nosotros mismos.
Entonces, solo para ser claro, si quisiera que este programa
fuera inútil para cualquiera, excepto para mí mismo, podría hacer esto,
podría escribir
initials [0] = 'D'; initials [1] = 'J'; initials [2] = 'J';
y finalmente, initials [2] = 'M';
Y por último, y este es el que se me olvida a veces,
initials [3] = '\0'; necesito poner diagonal inversa 0.
Por supuesto que esto no es en absoluto dinámico,
pero ahora, en estas líneas de código,
creé una nueva cadena llamada iniciales.
Es de longitud... es de longitud humana tres, DJM,
pero la computadora en realidad está usando 4 bytes para almacenarlo.
Pero este no es el punto del ejercicio,
porque ya le preguntamos al usuario por su nombre.
Ahora necesito imaginar qué es eso.
Entonces, lógicamente, o algorítmicamente, si quieren verlo así,
qué proceso podríamos usar, dado un nombre como David J. Malan, o Brian Yu,
o cualquier otro nombre,
¿Cómo podríamos ver esa entrada y averiguar
cuáles son las iniciales del usuario?
¿Cuál es el proceso de pensamiento?
Vamos a regresarnos un poco.
Entonces, para David J. Malan, o cualquier otro nombre,
¿cuál es el proceso?
¿Qué opinan?
PÚBLICO: [INAUDIBLE]
DAVID: ¡OK, bien!
Iterar con un bucle for las letras en el nombre,
ya han hecho eso antes, o el proceso de hacer eso,
para algo como [INAUDIBLE] o [INAUDIBLE] muy probablemente.
Entonces pueden usar algo como "isupper",
o incluso pueden usar asciimath, como lo hicimos antes, para determinar
si está en el rango de A a Z mayúsculas en ambos extremos.
Entonces tenemos un par de opciones.
Voy a tratar de convertir eso en código.
Dejen me deshago del nombre hardcoded que
es un poco absurdo, pero demuestra cómo
podemos almacenar caracteres arbitrarios,
y luego haré lo siguiente.
for (int i = 0; < strlen(name); i++)
y luego pondré algo como esto.
Si el i-ésimo carácter del nombre es una letra mayúscula: if (isupper(name[i]))
y voy a usar una función que es posible que no hayan utilizado,
pero les recuerdo que existe,
isupper volverá verdadero o falso, esta es una letra mayúscula,
entonces, si esto es mayúscula, ¿qué haré después?
Bueno, la historia aún no termina.
No basta solamente con iterar sobre los nombres y
las letras en el nombre,
debemos decidir dónde colocar la primera letra en mayúscula que encontremos.
Obviamente va a ir en el arreglo de las iniciales,
pero, ¿qué más necesito añadir a mi código para saber en qué parte
de este bloque de cuatro caracteres irá el primer carácter D,
en el caso de mi nombre?
Sí.
PÚBLICO: [INAUDIBLE] initials i
DAVID: Initials i.
OK, entonces si hago eso...
buena sugerencia.
Entonces pongamos initials[i] = name[i];
parece que esto pondría el i-ésimo carácter del nombre
en la i-ésima ubicación en initials, lo cual es perfectamente correcto
porque i es 0.
Y si escribiera David J. Malan, D estaría en la ubicación cero.
Entonces, estamos bien.
Pero va a haber un error aquí.
PÚBLICO: [INAUDIBLE] continúa hasta el nombre, entonces habrá menos espacios.
DAVID: Exacto.
i se moverá hacia adelante, un carácter a la vez,
por todo el nombre del usuario, pero solo queremos indexar,
no queremos hacer lo mismo que en el arreglo de iniciales
porque el número de iniciales es mucho más pequeño.
Así que, aunque se mueva por el nombre del usuario,
queremos dar pequeños pasos, por así decirlo, a través de las iniciales.
Entonces, ¿cómo podemos resolver esto?
Parece que no puedo usar i.
PÚBLICO: Se puede usar una variable que sea como [INAUDIBLE]
DAVID: OK, genial.
Hagamos eso.
Necesitamos otra variable.
Entonces podría poner esto en lugares diferentes,
pero para continuar lo pondré aquí por el momento.
Entonces, int counter = 0;
Solo estoy inicializando mi contador en cero
y tan pronto como no encuentre una letra mayúscula,
¿Creo que haría esto?
¿Ponerlo en cualquiera que sea el valor del contador?
Y luego necesitamos aplicar un paso más.
¿Qué debo hacer una vez que lo ponga en la ubicación del contador?
PÚBLICO: Incrementar el contador en uno.
DAVID: Exacto.
Incrementar el contador en uno.
Y puedo hacerlo de varias formas.
Lo puedo hacer muy literalmente, counter = counter + 1;
Es un poco latoso escribirlo.
Podríamos escribirlo counter += 1; lo cual es un poco más breve,
o usar la forma más bonita, counter++; la cual logra el mismo resultado.
Menos caracteres, mismo resultado exacto, en este caso.
OK, ahora, creo que tenemos un bucle for que hace iteraciones
sobre todas las letras del nombre.
Si es una letra mayúscula, almacena esa letra, y solo esa letra,
en el arreglo de iniciales y luego incrementa
el contador para que la próxima letra quede
en la siguiente ubicación del arreglo de iniciales.
Entonces, si todo parece correcto,
quisiera hacer lo siguiente,
escribiré printf(%s\ n, initials);
Solo quiero imprimir las iniciales del usuario.
Pero hay un paso clave en esta línea en blanco que no debería olvidar.
¿Qué es lo último que necesito hacer?
Sí.
PÚBLICO: Se debe imprimir un carácter nulo [INAUDIBLE]
poner un carácter nulo al final de [INAUDIBLE].
DAVID: Exacto.
Necesito poner un carácter nulo al final del arreglo.
Entonces, ¿cómo lo hago?
Bueno, tengo la sintaxis y creo,
ya saben, quiero decir, initials[end of array]
pero ¿cómo puedo hacerlo?
¿Qué valores debo poner aquí?
Sí.
DAVID: ¿La longitud de la cadena del nombre?
DAVID: Sí, podría poner initials[Strlen(name), bueno, no del nombre.
PÚBLICO: De las iniciales.
DAVID: De las iniciales, pero ahora me pusieron en un argumento circular,
porque estoy tratando de...
es como un catch-22 ahora.
Estoy tratando de almacenar una \ n al final de la cadena.
Pero recuerden que en la vez pasada, el modo en que strlen
sabe dónde termina la cadena, es poniendo \0.
Así que aún no está allí.
Entonces no podemos usar strlen.
Pero creo que ya tenemos la solución.
PÚBLICO: ¿No se podría solo poner initials[4]?
[INAUDIBLE]
DAVID: OK.
Entonces, ciertamente podríamos hacer eso, o casi eso.
No es exactamente cuatro.
Con un ligero cambio, ¿sí?
PÚBLICO: [INAUDIBLE]
DAVID: Sí, allá atrás.
PÚBLICO: [INAUDIBLE]
DAVID: OK, bien.
Entonces... sí, contador...
es, sí.
Contador será la respuesta.
Pero dejen arreglo este error de aquí.
Cuatro era la intuición correcta, pero recuerden,
si tienen cuatro posibles caracteres, entonces 0, 1, 2, 3 será el último.
Necesitamos fijar esto en 3.
Pero aún más general sería poner un contador, porque el valor del contador
es la longitud de la cadena que acabamos de leer en las iniciales.
Y entonces, si queremos terminar esa cadena,
ya sabemos cuántos caracteres hemos contado.
Y, de hecho, sería técnicamente incorrecto poner a ciegas \0
solamente al final de estos cuatro caracteres.
¿Por qué?
¿En cuál situación sería lógicamente incorrecto?
¿Sí?
PÚBLICO: si alguien tuviera más de tres iniciales.
DAVID: Si tuviera más de tres iniciales
realmente tendríamos un problema, porque no tenemos espacio para nada
más allá de tres letras y un terminador nulo.
Y hay otro problema potencial.
¿Sí?
PÚBLICO: ¿Si no tienen un segundo nombre?
DAVID: Así es, si no tienen un segundo nombre,
habrá tal vez solo dos letras, nombre y apellido.
Y entonces se pondrá la diagonal inversa cero
al final del arreglo, lo cual es bueno.
Pero ¿qué va a ser ese tercer valor, el penúltimo valor,
en el arreglo?
PÚBLICO: Un valor basura.
Es solo basura, por así decirlo.
Y entonces podría ser algún carácter raro
que podría generar símbolos extraños en la pantalla, no lo sabemos
simplemente porque es incorrecto.
La diagonal inversa cero tiene que ir al final de la cadena.
Ahora, déjenme ver si no cometí ningún error de sintaxis aquí,
guardaré esto, y pondré make initials
OK, entonces, hmm.
"Implicitly declaring library function strlen with type 'unsigned' long"
Entonces, hay muchas palabras y solo reconozco algunas de inmediato.
Podría ejecutarlo mediante help50, lo cual debería ser nuestro primer instinto,
por su cuenta o en horas extras.
Pero veamos si no podemos separar lo que realmente es el error.
¿Qué se me olvidó?
Sí.
PÚBLICO: Bueno, [INAUDIBLE]
DAVID: Sí, necesitaba la biblioteca de cadenas, la cual
se está volviendo un hábito.
Cada vez que uso strlen debo recordar usar una cadena,
de lo contrario aparecerá ese mensaje de error una y otra vez,
Tal vez hay más errores, pero no estoy seguro,
A continuación volveré a compilar porque
podríamos tener más errores en pantalla de los que vemos en este momento.
De hecho hay uno más,
es un error similar pero en una función diferente.
"Implicit declaration of function is upper".
Y ahí está de nuevo el mismo error.
Olvidé algo.
¿Alguien recuerda?
Este es un poco
menos común, diría yo.
Sí.
PÚBLICO: Se necesita la biblioteca de tipos de caracteres.
DAVID: Sí, la biblioteca tipo carácter, o tipo C.
Entonces, solo sabríamos esto al revisar la página
principal, por así decirlo, o reference.cs50.net,
o revisar sus notas, o Google, o lo que sea.
Y entonces queda bien.
Parece que está en Ctype.h.
Ahora voy a recompilar.
Parece que funciona, entonces escribo initials y luego David J. Malan,
Enter, parece que funciona.
Ahora probare un caso aparte, Rob Bowden, sin su segundo nombre.
RB.
Y parece que funciona.
Este no es el proceso para una prueba rigurosa, pero confiemos que utilizamos
al menos las bases correctas.
Pero la clave aquí es que rompimos esta abstracción, por así decirlo,
de lo que es una cadena.
Porque si entendemos que una cadena es solo
una secuencia de caracteres y que una cadena debe terminar con \0,
podemos hacerlo nosotros mismos.
Y esto es lo que podemos hacer en C, podemos poner caracteres
o números en cualquier lugar que deseemos en la memoria
Y esto finalmente te da mucho poder y flexibilidad,
pero también, como ya veremos, muchas posibilidades para cometer errores
con este poder.
Muy bien, ¿tienen alguna duda sobre este ejemplo en particular?
Sí.
PÚBLICO: ¿Puedes explicar el contador de iniciales?
DAVID: Claro.
PÚBLICO: [INAUDIBLE]
DAVID: Claro que sí.
Vamos a explicar el contador de iniciales que se usa aquí
y que se declara acá.
Entonces, básicamente queremos hacer un seguimiento de dos ubicaciones.
Si mi nombre es de esta longitud, comenzaré en el primer
carácter, D, y con i itero desde D-A-V-I-D, espacio, etc.
Entonces, en cierto modo, avanza un paso a la vez.
El arreglo de iniciales, mientras tanto, es otra porción de memoria.
Es de tamaño cuatro, en alguna parte de ese chip de silicio verde,
con todas esas fichas negras.
Y queremos avanzar más lentamente, porque solo
queremos movernos a la siguiente ubicación en el arreglo de iniciales
una vez que hayamos puesto una letra mayúscula allí.
Y es posible que eso ocurra con menor frecuencia
a menos que el usuario haya escrito todo en mayúsculas.
Entonces, para lograr eso, necesitamos una segunda variable
y ustedes propusieron que usemos una variable llamada contador,
pero podríamos haberla llamado j o de algún otro modo.
Entonces, el contador se inicializa en cero
y se incrementa aquí cada vez que encontramos una letra mayúscula.
Entonces, tiene el efecto de contar las letras mayúsculas
en el nombre de alguien: D, J, M, y debe ser 3 al final de esos bucles.
Y eso es perfecto, porque como ya vimos anteriormente,
3 es la ubicación correcta para poner la diagonal inversa 0, incluso
cuando quiera ir en la cuarta ubicación, la dirección de la misma,
o su ubicación sea 3.
Entonces, de alguna manera resolvemos varios problemas a la vez.
Buena pregunta.
¿Otras preguntas?
¿Alguna otra?
Muy bien.
Entonces.
Dicho esto, no nos preocupemos tanto por ese tipo de implementación
de bajo nivel y pensemos qué más podemos hacer con estas cosas
llamadas arreglos.
Si comenzamos a deshacernos de las direcciones y solo sabemos
tenemos una secuencia de caracteres, o cualquier otra cosa en la memoria,
y a final de cuentas tenemos la habilidad
para establecer cosas contiguamente en la memoria
de manera consecutiva, como esta.
Entonces, aquí hay ocho cajas, dentro de las cuales podemos poner cualquier cosa,
pero como humanos hemos estado haciendo trampa por algún tiempo.
Cuando teníamos el nombre de Stelios en la pantalla, todos los de esta sala
solo echábamos un vistazo y asimilábamos su nombre de golpe.
Pero las computadoras no son tan intuitivas ni lo ven todo
como nosotros, que podemos ver todo el panorama a la vez.
Una computadora solo puede ver una cosa a la vez.
Y entonces, una metáfora mejor que una caja como esta
para las ubicaciones que hay en la memoria de la computadora
sería como tener ocho casilleros en una escuela,
donde una computadora, para ver el valor en cualquiera de esos cajones
tendría que trabajar un poco y abrir la puerta para ver lo que hay adentro.
y por lo tanto, no podría ver ocho ubicaciones simultáneamente.
Por lo tanto, esto tendría implicaciones muy reales, porque ahora
si queremos comprobar la longitud de una cadena
o contar el número de cosas que hay en un arreglo, o mover cosas
tendremos que hacer una cosa a la vez,
mientras que los humanos podríamos simplemente verlo
y decir, oh, ordena estos números de alguna manera intuitiva.
Y eso es una buena transición a un problema muy distinto
que es el de clasificar valores.
Entonces, para esto tenemos, digamos, el equivalente a siete casilleros,
aquí arriba.
Y detrás de estas puertas, por así decirlo, hay un montón de números.
Así que haremos la transición desde caracteres y cadenas
y simplemente lo generalizaremos a números, porque son
convenientes para trabajar, y tenemos muchos de ellos a nuestra disposición.
Pero me gustaría encontrar un número en particular.
Supongamos que una computadora almacena un arreglo de números,
un montón de enteros consecutivos, consecutivos, consecutivos.
Así es como podrían verse.
Las puertas están cerradas y necesitamos un algoritmo para encontrar un número,
porque una computadora no puede solo ver y decir, ahí está tu número.
Probablemente la computadora tiene que ser más metódica
debe ir de izquierda a derecha, de derecha a izquierda, comenzar en el medio,
abrirlos al azar.
Necesitamos un algoritmo.
¿Alguien quiere ser un valiente voluntario?
OK, ven por favor.
¿Cómo te llamas?
CHRISSY: Chrissy.
DAVID: ¿Kristen?
CHRISSY: Chrissy.
DAVID: Chrissy.
Ven por este lado.
Muy bien, Chrissy, todo lo que sabes es que hay siete puertas,
encantado de conocerte,
aquí en la pantalla.
Con ayuda de tu dedo puedes tocar una puerta para abrirla,
o desbloquearla para ver que hay
y quisiera que encuentres el número 50.
Diablos.
[RISAS]
¡Muy bien!
[APLAUSOS]
Creo que necesitamos un premio mejor que una pelotita para el estrés.
Muy bien hecho.
Quisiera preguntarte si podemos
pasarte el micrófono,
aquí tienes.
Dime, ¿cuál fue tu increíble algoritmo para encontrar el 50?
CHRISSY: Solo le di clic.
DAVID: OK, eso es genial.
CHRISSY: Se ve bien.
DAVID: OK.
Entonces, tú, OK.
Muy bueno.
Maravillosamente efectivo.
Deja revelo... hagamos esto.
Estos son todos los sitios donde podías haberte equivocado.
Y deja le pregunto al público antes de probar con otro ejemplo.
¿Qué les sorprende de estos números?
¿Nada en particular?
PÚBLICO: Están desordenados.
DAVID: ¿Están en orden?
PÚBLICO: Desordenados.
DAVID: Desordenados.
Son aleatorios.
Aunque alguien astuto podría notar algo,
o alguien que vea demasiada televisión.
Sí.
PÚBLICO: Son los números de Lost.
DAVID: Sí, gracias.
Los dos vemos mucho Lost.
Además, teníamos un séptimo número y le agregamos el nuestro,
entonces quedaron en orden aleatorio.
porque los revolví arbitrariamente.
No están ordenados de mayor a menor,
ni de mayor a menor.
No tienen ningún patrón porque simplemente los mezclé.
Y eso es un problema, porque aunque Chrissy tuvo suerte y encontró el 50,
fue muy buena para encontrar el 50,
podría ser difícil replicar ese algoritmo y encontrarlo
cada vez, a menos que sepamos algo
sobre los números que hay detrás de las puertas.
Y entonces, lo sabremos en el siguiente ejemplo.
En el próximo ejemplo también tenemos siete puertas
y los mismos siete números, pero ahora están ordenados.
Sabiendo eso, ¿qué cambia en tu enfoque?
CHRISSY: Uh, bueno, supongo que si están ordenados, como de menor a mayor
entonces podría, porque sabría que está más cerca del final.
Pero si no sé cómo están ordenados, entonces
creo que realmente no lo sabría.
DAVID: OK, bien.
Entonces si digo que están ordenados de menor a mayor
te propongo de nuevo que encuentres el número 50.
Sí, esto no está funcionando muy bien.
Eso estuvo muy bien.
OK, felicidades.
[APLAUSOS]
Entonces, vieron... gracias.
Entonces, vieron qué tan eficiente
fue su segundo algoritmo cuando ella aprovechó esa información.
Pero con toda seriedad, podría hacerse mejor, especialmente
para conjuntos de datos muy grandes si conocen algo sobre los datos.
Si saben que estos números están ordenados,
podrían, tal como Chrissy lo hizo, muy intuitiva y correctamente,
ir al final, sabiendo que el número más grande,
probablemente sea el que está más a la derecha.
Si ella no lo sabía, y solo sabía que los números estaban ordenados,
y no sabía si 50 era un número pequeño, un número medio,
o el número más grande, solo era un número detrás de las puertas.
¿Qué sería una estrategia inteligente dada esa mínima información?
PÚBLICO: [INAUDIBLE] a mitad de camino, y luego si es
mayor, lo mueves hacia la derecha, si es menor, lo mueves hacia la izquierda.
DAVID: Sí, podemos intentar esa misma división
y conquistar el enfoque que tuvimos en la primera clase, con la guía telefónica,
¿correcto?
Donde revisamos casi en medio, porque
sabíamos que MS estaría más o menos en medio.
Y luego, si buscáramos
a Mike Smith, cuyo apellido comienza con S,
sabríamos que estaría a la derecha, y entonces iríamos a la derecha,
y luego dividiríamos el problema
[RISAS] Hoy no nos va tan bien.
Entonces, dividiríamos y venceríamos el problema una y otra vez.
Y podemos hacerlo aquí.
Aunque no es tan visualmente atractivo como una guía telefónica,
podemos ir al centro.
Y si Chrissy hubiera abierto la puerta de en medio y visto el 16,
sabrían, ¿qué?
Y de hecho puedo recrear esto.
Voy a actualizar la pantalla.
Entonces, en este caso, tenemos las mismas puertas.
Sé que 50 está en algún lado, y no sé cómo... dónde está, excepto el 16.
Ahora sé que está a la derecha, como proponen.
Entonces, puedo eliminar todas estas puertas y no preocuparme por ellas.
Y de hecho, si las abro podemos confirmar
que no necesito desperdiciar mi tiempo buscando en ellas.
Ahora tengo tres puertas.
Ahora, ¿qué proponen que hagamos?
PÚBLICO: ¿Ir a la de en medio otra vez?
DAVID: Sí, ir a la de en medio otra vez.
Entonces, el 42 es una buena respuesta, pero no es lo que estamos buscando.
Y de hecho, podemos tirar esta mitad del problema
y buscar en la mitad derecha, que ahora se ha reducido
a uno, y entonces encontraríamos el 50.
Y si pudiéramos reconstruir lo que hubiera sido
una gran historia, en el primer ejemplo qué tan bien lo habría
hecho Chrissy, en teoría, o si hiciéramos este ejercicio una y otra
y otra vez, con el primer ejemplo.
Si no sabemos nada sobre los números,
podemos tener suerte, simplemente eligiendo una puerta y, guau,
ahí está el número.
Pero eso no va a suceder siempre, es lo más probable.
Y entonces quizás tuviéramos que comenzar, desde el principio, no,
no, no.
Tal vez podríamos ser más inteligentes y brincar hacia adelante, no,
no, OK, en algún momento lo encontraremos si está allí.
Pero si no sabemos nada de los números, lo mejor que podemos hacer
es aplicarles la fuerza bruta.
Solo la fuerza bruta a nuestra manera, a través de las posibilidades
hasta encontrar la respuesta.
Pero en el peor de los casos, ¿Cuántas puertas podríamos
abrir hasta encontrar el número 50, si no sabíamos nada?
PÚBLICO: Las siete.
DAVID: Sí, las siete.
¿Correcto?
Si n es el número de puertas, entonces podría tomar n pasos.
En este caso, fue n menos uno, o seis pasos.
Pero en el caso de Chrissy, claramente, hay un límite inferior muy emocionante,
porque si se tiene suerte, puede tomar solo un paso.
Entonces, eso es un rango interesante a considerar.
La solución al problema podría tomar un paso, o n pasos,
o cualquier número intermedio.
Pero en la búsqueda binaria que propusieron en el segundo enfoque,
la de divide y vencerás,
recuerden cuando hicimos eso en el pasado,
teníamos una forma muy linda en la curva que dibujamos, la cual describió
la eficiencia de este algoritmo.
Y volveremos a ello dentro de poco.
Pero, para mayor claridad, formalicemos cuáles son estos dos algoritmos.
Si comienzo desde la izquierda y voy a la derecha o comienzo desde la derecha
y voy a la izquierda, siguiendo una línea, podríamos llamarlo búsqueda lineal.
Y puede escribirse de muchas maneras.
Se me ocurrió este pseudocódigo, pero cualquier persona razonable
podría tener otra alternativa y decir, también es búsqueda lineal.
Estas no son definiciones oficiales.
Y lo escribí de la siguiente manera.
Para cada elemento en el arreglo, si es el elemento que busco,
devuelve verdadero.
Esta es una forma muy concisa de decir, para cada elemento del arreglo,
busca de izquierda a derecha o de derecha a izquierda.
Si es el que estamos buscando, devuelve verdadero.
Encontré el número 50, o lo que realmente sea.
De lo contrario, devuelve falso al final.
Y fíjense en la sangría.
La eliminé porque solo como último paso lo devuelvo falso
si ninguna de mis iteraciones a través del bucle regresa verdadera.
Entonces, eso sería búsqueda lineal.
En la búsqueda binaria, se debe ser un poco más detallado para explicarlo.
Y hay muchas formas diferentes de escribirlo,
pero es muy similar a algo
que vimos antes, con Mike Smith y la guía telefónica.
Entonces, si vemos en el medio del arreglo ordenado,
tal como lo propusieron, y si el elemento que buscamos está ahí,
entonces devuelve verdadero.
Encontré el 50.
Tuve suerte, estaba en el centro muerto en medio de mi arreglo,
en una ejecución particular de este programa.
Si no, si el elemento está a la izquierda, busca en la mitad izquierda del arreglo.
Si no, si está a la derecha, busca en la mitad derecha del arreglo.
De lo contrario, devuelve falso, porque posiblemente no está allí.
Entonces, esta sería una búsqueda binaria.
Y a pesar de que son más líneas de código, o pseudocódigo,
podríamos decir que debería ser un poco más rápido, ¿verdad?
Debido a eso de divide y vencerás, y tirando la mitad de la mitad de la mitad
de la mitad, de la mitad se acaba el problema, este
se vuelve más pequeño y mucho, mucho más rápido.
Todo bien.
Dicho lo anterior, parece ser algo muy bueno
que tener las cosas ordenadas sea un ingrediente poderoso para un problema.
¿Correcto?
Cuesta más trabajo, a Chrissy debería haberle costado más trabajo,
en general nos costaría a todos más trabajo
encontrar un número usando la búsqueda lineal en vez de la búsqueda binaria.
Me hubiera llevado una eternidad encontrar a Mike Smith buscando en una
página a la vez versus usando divide y vencerás,
pues lo encontré mucho más rápido dividiendo el problema a la mitad
y a la mitad, una y otra vez.
Entonces, eso lleva a la pregunta, ¿cómo se ordena algo?
Bueno, para continuar recogeré estas cosas
que no usaremos en este curso, sino en otro sitio en el campus,
es posible que tengan estos libros azules.
y que en el momento del examen escriban su nombre, el curso,
y todo eso en ellos.
Y lo hemos simplificado, entonces, esta es una persona cuyo apellido comienza
con A, el apellido
de una persona que comienza con B, C, y así sucesivamente hasta la Z.
Pero supongamos que las personas acaban en diferentes momentos
durante el examen, y por supuesto, cuando se está en el centro de ciencias
o donde sea, todos simplemente comienzan a entregarlos,
y vienen de esta manera y luego
los asistentes que están en el frente de la habitación, o el profesor,
tienen que ordenar estos valores.
Bueno, ¿cuál es el algoritmo que utilizamos?
Si tienen un montón de libros de examen como este,
todos tienen una letra asociada, ¿qué haríamos para ordenarlos?
¿Qué debo hacer?
PÚBLICO: Comparar dos a la vez.
DAVID: ¿Comparar dos a la vez?
OK, entonces tomaré un par de aquí.
Y dado que soy el único que puede verlos,
deberían poder verlos en el proyector, gracias a Ian y Scully.
Tenemos P y H, entonces alfabéticamente H va antes de P.
Muy bien, ¿y ahora qué hago?
¿Tomo otros dos?
PÚBLICO: Toma uno.
DAVID: OK, entonces tomo solo uno,
y tomé uno de N, entonces ese va entre H y P.
así que puedo deslizarlo.
Ahora tomé el de G, que va antes que H, luego
tomé otro con E, ese va antes que G, entonces,
de hecho se vuelve cada vez más fácil.
Oh, oh, uno con U, este va al final, después de P.
Y puedo seguir tomando uno a la vez, F en este caso,
y luego ponerlo en la ubicación apropiada.
Y no completaremos todo porque sería muy tedioso
y al final me avergonzaré si saco una letra
equivocada.
Pero eso es un algoritmo, ¿verdad?
Para cada cuaderno que hay en la pila, tómalo, compáralo
con todos los elementos que ya estás sosteniendo
e insertarlo en la ubicación apropiada.
Y podemos llamarlo de algún modo y a eso
se le llama ordenamiento por inserción, en tanto que énfasis del algoritmo es
gracias, insertar letras, en este caso,
en la ubicación apropiada.
Dicho lo anterior, ¿hay otras formas distintas?
Para continuar, y tenemos suficientes pelotitas para el estrés como para
aplicar una demostración humana, más allá de estos libros
tenemos aquí estos números.
Supongamos que queremos ordenarlos.
Tengo ocho pelotitas para el estrés y ocho pedazos de papel.
¿Podríamos tener ocho voluntarios?
Ok, sí, derecha y atrás, dos tres, vayamos más atrás... cuatro.
¿Puedo ir más atrás?
Cinco, seis, siete y ocho.
Vengan.
Ah, para la próxima.
Para la próxima.
Muy bien, vengan.
OK.
¿Cómo te llamas?
JERRY: Jerry.
DAVID: Jerry.
OK, si gustas pasar y pararte frente a uno de los atriles.
ARMAH: Armah
DAVID: Armah, David.
CHRIS: Chris.
DAVID: Chris, David.
Gracias.
KAYLIND: Kaylind.
DAVID: Kaylind.
NOLAN: Nolan.
DAVID: Nolan, David.
JAY: Jay.
DAVID: David.
MATTHEW: Matthew.
DAVID: Matthew, David.
OK.
Entonces, esto es estadísticamente anómalo
ya que estamos muy emocionados porque este semestre en CS50,
por primera vez, y nosotros vemos estos números anualmente,
el 44 % de los alumnos en CS50 son mujeres este año.
Entonces, como pueden ver, ninguna de mis demostraciones
ha funcionado correctamente hoy.
Pero confíen en esa información.
Entonces, si cada uno de ustedes puede pararse detrás de uno de los
atriles, entonces conté exactamente ocho personas.
Frente a ustedes chicos tenemos números.
Así que adelante y volteen las hojas de papel,
las cuales representan los números que tenemos en la pantalla,
si las puse en el orden correcto.
Entonces habrá muchas maneras diferentes
en las que podemos ordenar estos ocho valores.
¿Qué hacemos para lograr esto?
Bueno, similar a lo que propusieron antes, cuando tomamos un par de libros azules
y los comparamos, ¿por qué no intento esa misma intuición?
Cuatro y dos, estos números obviamente están desordenados.
¿Qué haré para continuar?
Sí, proseguiré y cambiaré estos.
Ahora, el problema está resuelto, lo cual es brillante.
Tomé un bocado del problema,
y a continuación.
¿Cuatro y siete?
Esos lucen bien.
¿Siete y cinco?
No, Ok.
Entonces, ¿qué quiero hacer aquí?
PÚBLICO: Cambiarlos.
DAVID: Sí, entonces puedo intercambiar esos, gracias.
Entonces, ¿siete y seis?
También están desordenados,
intercambiemos esos.
¿Siete y ocho?
Están bien.
¿Ocho y tres?
Están mal, si quieren proseguiremos e intercambiaremos esos.
Y, ocho y uno, si quieres seguir adelante e intercambiar esos.
Entonces, ¿cuál fue el efecto neto?
¿He ordenado esta lista de números?
Obviamente no.
Pero lo mejoré.
¿Y cuál es el ejemplo, tal vez, más evidente de la mejora, en frente
de estos?
PÚBLICO: Ocho al final.
DAVID: Ocho está ahora al final.
Entonces, el número más grande, por así decirlo, se reordenó hasta el final,
como si fuera el más grande y emergió del ordenamiento.
Así que eso es bueno, pero todavía hay trabajo por hacer aquí.
Entonces, puedo, de nuevo, intentar arreglar estos problemas localmente.
Solo tomaré un par de problemas e intentaré resolverlos.
Entonces, ¿dos y cuatro?
Vamos bien.
¿Cuatro y cinco?
¿Cinco y seis?
¿Seis y siete?
Oh, ¿siete y tres?
Muchachos, ¿ustedes quieren intercambiar eso?
Maravilloso.
Luego, siete y uno, queremos que lo hagan de nuevo.
Y ahora, ¿necesito molestarme en comparar siete y ocho?
Técnicamente no, porque si sabemos que ocho hizo su trabajo, ahora
podemos iniciar el corte correcto de algunas esquinas
para ahorrar un poco de tiempo fuera del algoritmo,
y hacerlo un poco más eficiente.
Bien.
¿Están ordenados?
No, entonces seguiremos haciendo esto.
Muchachos, ahora en parejas continuaremos para ejecutar esto.
Así que, aquí vamos.
Dos, cuatro.
Cuatro, cinco.
Cinco, seis.
Seis, tres.
Seis, uno.
Podemos detenernos allí, porque sabemos que siete está en orden.
Ahora lo haremos de nuevo.
Dos y cuatro.
Cuatro y cinco.
¿Cinco y tres?
¿Cinco y uno?
Bien mejorado.
Y los que siguen, dos y cuatro, cuatro y tres, cuatro y uno,
y después dos y tres, tres y uno,
y luego dos y uno.
OK, ahora está ordenado.
Sí, muy bien hecho.
Muy bien hecho.
Es un poco tedioso y no
quiero seguir yendo y viniendo, porque afortunadamente tenemos
toda esta fuerza de trabajo, varios CPU, supongo
ahora.
Así que, tenemos todos estos CPU o computadoras
que pueden ayudarme.
Esto todavía es muy lento.
Me refiero a que es una larga historia,
Rebobinemos una vez y tratemos una vez más.
Si pudieran reorganizar sus trozos de papel, chicos, para que
coincidan de nuevo con la pantalla y se restablezca nuestra ubicación original.
Regresemos allá.
Intentemos otro enfoque.
Probé el enfoque de inserción, en el cual asumo solo un problema,
como en el cuaderno azul, los inserto en la ubicación correcta.
Sinceramente, eso fue un poco tedioso, por eso lo aborté,
porque tomaría más y más y más tiempo
encontrar la ubicación correcta entre todos esos libros azules.
Entonces, intentaré algo más intuitivo.
Si quiero ordenar estos números, seleccionaré
el número más pequeño que encuentre.
OK, cuatro, en este momento, es el número más pequeño que encontré.
Así que, por el momento lo tomaré.
De nuevo, estas son casillas.
Aunque nosotros los humanos podemos verlas todas, la computadora
solo puede ver una ubicación a la vez.
Este es el número más pequeño que vi hasta ahora.
Así que lo tomaré,
pero muy rápido, puedo detener esto, porque ahora
encontré un número aún más pequeño,
entonces me quedaré con este en su lugar.
Siete, no me preocuparé por este, cinco, seis, ocho, tres, uno,
encontré un número más pequeño.
Ahora necesito hacer algo con esto.
Quiero tomar el uno, así que aquí pondría el dos.
Ahora, ¿qué quiero hacer con el número uno?
Sí, solo quiero ponerlo aquí.
Puedo hacer esto de diferentes maneras,
pero lo removeré
cualquiera que sea, porque es un número bastante bajo y aleatorio.
Pudo ser un número grande,
así que le haré un espacio y aplicaré eso.
Seleccioné el elemento más pequeño.
¿La lista está ordenada?
Quiero decir, obviamente no.
Pero, ¿es mejor?
Lo es, ¿verdad?
Porque ahora el uno está en la ubicación correcta.
Además, hasta el momento resolví un octavo del problema.
Entonces no está mal.
¿Qué podría hacer después?
Voy a aplicar el mismo algoritmo.
y seleccionaré el número más pequeño, el cual es este,
y este y este,
no, tres es más pequeño, oh, dos es incluso más pequeño, así que finalmente
tomaré este.
¿Saben qué?, en realidad el cuatro no necesita estar allí,
lo removeré otra vez, y colocaré al dos donde pertenece,
y moveré este aquí.
Ahora la lista está aún más ordenada.
Y si prosigo haciendo esto otra vez, y otra vez, si solo queremos
tomar el número más pequeño, ¿tres?
OK, entonces iré directo y removeré el siete porque es,
de cualquier manera, un tipo de número al azar.
Gracias, cuatro,
continuaré y removeré el cinco, a pesar de que siento,
que en promedio, yo mismo estoy haciendo un poco más de trabajo,
eso no importa.
Algunas veces será bueno, algunas será malo.
Continuaré y pondré el cinco aquí.
Ahora seleccionaré el siguiente elemento más pequeño, que será cinco.
Nos recuperamos bastante rápido.
Removeré al seis de aquí.
Ahora buscaré el elemento más pequeño.
De hecho es seis.
Removeré al ocho, colocaré este aquí,
y ahora siete está bien, ocho está bien,
terminé.
Es una especie de larga historia, ¿cierto?
Estuve yendo y viniendo, buscando estos números.
A esto le llamaríamos ordenamiento por selección.
Muchas gracias, si gustan, guarden sus pedazos de papel,
se los pueden quedar.
Chicos, también les daré una bola de estrés.
Y si pudiéramos, una ronda de aplausos para ustedes muchachos.
Si quieren entréguenlos.
Así como antes, veremos si podemos aplicar,
veremos si podemos aplicar algún pseudocódigo a este algoritmo,
porque una cosa es hablar sobre eso, y otra cosa es ordenar por una razón
a través de la intuición, pero al final del día,
si queremos programar esto, tenemos que ser más precisos,
y consideraremos el menor número de operaciones que la computadora
ejecutará.
Entonces, así es como podríamos implementar el ordenamiento burbuja.
Repetir hasta que no haya cambios.
Para i, de 0 hasta n 2 y n es solo
el tamaño del problema, el número de puertas, el número de humanos,
la cantidad de números, cualquiera que sea la entrada al verdadero problema.
Así que, desde 0 hasta n-2, si los elementos i-ésimo y el i-ésimo más 1
están desordenados, se intercambian.
Esto es como un trabalenguas.
Pero si lo piensan, solo estoy aplicando un tipo de vocabulario
que tenemos de C, y un tipo de orden desde cero,
hacia otro tipo de experiencia humana muy orgánica,
pero uso un lenguaje más metódico en vez de solo usar la improvisación,
cuando lo hicimos con humanos.
Porque, ¿qué significa esto?
Para i desde 0 hasta n-2.
Eso significa que se inicia en la ubicación 0,
y si aquí hay n elementos, este es 0,
y esta, al final, es la ubicación,
que será n-1.
¿Correcto?
Si comienzan a contar en 0, tendrán que reajustar toda su vida
para restar 1 del final de la cola de este intervalo de números.
¿Correcto?
0, si eso fuera 1, esto sería n.
Pero si eso fuera 0, esto es ahora n-1.
Así que dice, para 0,
para i desde 0 hasta n-2, lo cual es técnicamente esto.
Entonces uso un tipo de lenguaje para que la iteración inicie aquí,
e implemento algo como esto, hasta el penúltimo
elemento, lo cual no hicimos antes.
Parece que casi no tiene errores.
Pero si leen más adelante en el pseudocódigo, ¿por qué hice eso?
¿Solamente iterar hasta el penúltimo elemento de i?
¿Qué salta a la vista?
Sí.
PÚBLICO: ¿Porque intercambiarás el i en los i + 1 elementos?
DAVID: Bien.
PÚBLICO: Cuando llegas a n-2, lo cambiarás por n - 1.
DAVID: Exacto.
En el ordenamiento burbuja todo era intercambiar o potencialmente intercambiar
pare de elementos vecinos, por así decirlo.
Debemos asegurarnos de que si iteramos a través de todos estos números,
nos detendremos un poco antes del final del arreglo, por lo que i + 1
se refiere a un elemento que realmente está en nuestra lista,
y no nos conduzca hasta aquí, el cual
sería algún valor basura que no se debe tocar.
Entonces, si esos elementos están desordenados, los intercambiamos,
y tengo un gran bucle exterior que solo dice,
haz esto, una y otra vez, y otra vez, hasta que no haya intercambios.
En ese momento podemos inferir que terminamos.
Ya que cada vez que iba y venía en la lista,
y los muchachos ayudaron intercambiando sus números según correspondió,
Y lo hice una y otra vez, si había margen para mejorarlo.
E intuitivamente, ¿por qué es absoluta y lógicamente seguro
detener todo ese proceso una vez que no se ha hecho ningún intercambio
a través de la lista?
¿Por qué es esa una conclusión segura?
Así que, si reviso la lista,
no, estos son buenos, estos son buenos, estos son buenos,
OK, ¿no querías tu número?
estos son buenos, estos son buenos,
¿Cómo sé que no necesito hacer eso de nuevo?
PÚBLICO: Ya está ordenado.
DAVID: Ya está ordenado, ¿cierto?
Y sería un poco irracional,
si hemos recorrido la lista, buscando todo en pareja,
si no hay nada para intercambiar, incluso molestándonos en hacer esto otra vez,
¿por qué esperaríamos resultados diferentes, si los mismos números
no se movieron y nosotros mismos no los movimos?
Entonces podemos detenernos.
Pero esto todavía nos invita a la pregunta, ¿qué tan costoso fue eso?
¿Cuántos intercambios o comparaciones hicimos?
Regresaremos a esto dentro de poco.
El ordenamiento por selección puede expresarse incluso de forma resumida.
Y fue el segundo algoritmo que hicimos con nuestros ocho voluntarios
aquí, para i desde 0 hasta n-1.
Esta vez, en todo el camino hasta el final de la lista,
se encuentra el elemento más pequeño entre el i-ésimo y n-1 i-ésimo.
Entonces, entre esos dos intervalos, el inicio
de la lista y el final, intercambiamos el menor con el elemento i-ésimo.
Entonces, ¿qué significa esto?
Así que de nuevo, para i desde 0 hasta n-1.
Esto es un pseudocódigo que dice, inicia una variable i en la ubicación 0,
y haz esto hasta que iguale a n-1.
Hicimos esto hasta que recorrimos todo el camino a través de la lista.
¿Qué me está diciendo que haga?
Encuentra el elemento más pequeño entre el i-ésimo elemento y el final de la lista.
n-1 nunca cambia,
siempre se sitúa al final de la lista, entonces
¿Por qué caminé en la lista buscando, en última instancia, el número 1?
Y, ¿qué hice con el número 1?
Intercambiar el elemento más pequeño con el i-ésimo elemento.
Y pude haber dado un mal paso cuando realicé un intercambio pequeño,
pero lo arreglamos después.
Al final, removí al que ocupó la ubicación i-ésima
e hice espacio para el elemento que yo sabía que pertenecía allí.
Pude mezclarlos y hacer espacio para esos elementos,
que matemáticamente, es igual de bueno
se remueven y se trasladan toda la lista hasta el final, como lo hicimos.
Una vez que recorrí toda la lista,
no hubo un elemento más pequeño que encontrar.
Y como vimos, la lista está ordenada.
Tal vez esto es más rápido, tal vez sea más lento,
no es obvio inmediatamente.
Y tipo de ordenamiento por inserción, que surgió a través de los libros azules
en el piso, podría ser descrito como esto.
Para i, desde 1 hasta n-1, llama al 0-ésimo por el i-1 i-ésimo elemento
del lado ordenado,
eso es un trabalenguas, consideramos el lado izquierdo de nuestra lista
como el lado ordenado de la lista.
E inicialmente, no hay nada allí.
Tenemos cero elementos ordenados a nuestra izquierda,
y ocho elementos sin ordenar a nuestra derecha.
Ese tipo de ordenamiento describe la historia de cuando
tuvimos voluntarios y números aquí.
No había elementos ordenados, todo a mi derecha no estaba ordenado.
Eso es todo lo que dice.
Remueve el elemento i-ésimo.
Eso fue como elegir este cuaderno azul si
utilizáramos los libros azules en este ejemplo.
Entonces, ¿qué quiero hacer?
Insertarlos en orden en el lado ordenado.
Entonces, si este es el lado ordenado y este es el lado sin ordenar,
este es el equivalente que dice, toma ese cuaderno azul
y ponlo en la primera ubicación.
Podemos hacer un tipo de brecha visual para eso.
Ahora, este es el lado ordenado y este es el lado desordenado.
O, de manera equivalente, cuando estaba aquí abajo con los libros azules,
los libros en mis manos fueron el lado ordenado, los que estaban en el escenario
fueron el lado desordenado.
La misma idea.
¿Qué ocurre después?
Entonces itero una ubicación siguiente, y remuevo el siguiente elemento,
y cualquier número que sea, ¿va a la izquierda
o va a la derecha?
Fue lo mismo que pasó en el escenario,
parece como si recogiera un tercer libro azul y decidiera,
¿va entre estos libros?
¿va debajo, va arriba?
Lo inserté en su ubicación apropiada.
En este ordenamiento por inserción tomamos cada número
según lo encontramos, y lidiamos con eso aquí y allá.
Tomamos el número y lidiamos con eso, entonces,
sabemos que este tiene que ir aquí, si pretendemos qué
los números se parezcan por un momento.
Así que eso sería insertarlo en la ubicación correcta,
como lo hice con los libros azules.
Tal vez este, oh, tal vez este uno es un número realmente pequeño,
y entonces lo inserto aquí.
Así que trato de lidiar con cada problema cuando lo encuentro,
pero se vuelve costoso, o muy molesto,
tener que mover todo esto fuera del camino
para abrirle paso a esos elementos.
Por eso que me aburrí con el ejemplo del libro azul,
porque se volvió muy tedioso
y estaba harto de buscar la ubicación correcta de todos los libros azules.
En resumen, estos tres algoritmos,
se expresaron de manera muy distinta, aunque todos ellos son
algo instintivos hasta que intentamos describir lo que nuestra intuición
ha hecho durante los últimos años
cuando ordenamos cosas en el mundo real,
y todo puede describirse en términos de estos algoritmos.
Así que estos algoritmos, e iniciamos esta conversación en la primera
lección,
todos tienen, en última instancia, algún tipo de tiempo de
ejecución asociado con ellos, parecido a cuánto tiempo toma hacer algo.
Y hablamos acerca del proceso de encontrar a Mike Smith en términos
de este gráfico bastante genérico.
No era muy matemático, ni muy sofisticado, solo
quería tener una idea de las relaciones, o compromisos, de espacio
y tiempo, por así decirlo.
Y así, en el eje x u horizontal, tuvimos el tamaño del problema,
como un número de páginas en la guía telefónica,
o número de personas en la guía telefónica,
y en el eje y o vertical, tuvimos la cantidad de tiempo
para resolver el problema.
Cuántos segundos, o cuántos cambios de página,
podríamos contar al usar cualquier unidad de medida que nos guste.
En el primer algoritmo para Mike Smith, cuando inicié con la primera página
y cambié, y cambié, y cambié, fue una línea recta, una relación lineal.
Una página más, un paso más.
Por lo tanto, es una línea recta.
En el siguiente algoritmo se buscó por pares, evocando a la primera lección.
Dos, cuatro, seis, ocho.
Eso aún es una línea recta, porque hay
una relación predecible entre número de páginas y número de segundos,
o cambios de página.
Son dos a uno en lugar de uno a uno, así que aún es una línea recta,
pero es inferior en la gráfica.
Es mejor.
Pero el mejor, por mucho, fue el enfoque de divide y vencerás.
¿Correcto?
Y sin duda se sintió más rápido, es grandioso, porque fue intuitivo.
No fue tan fácil expresarlo en un pseudocódigo,
ya que fue uno de los más largos de hoy,
pero al menos nos dio una la respuesta rápidamente.
Este es logarítmico, en el sentido de que el logaritmo,
técnicamente es de base 2, si recordamos algunas de nuestras matemáticas,
es porque dividimos el problema a la mitad, y a la mitad, y a la mitad.
Y está bien si están incómodos con eso,
ni siquiera recuerdo lo qué es un logaritmo.
Por ahora, asumamos que el tiempo logarítmico tiene esta forma diferente,
que crece mucho más lento.
Podemos elegir log n/n, que al elegir entre dos algoritmos,
elegir el log n, o algo similar a eso,
porque será más pequeño, como lo podemos visualizar aquí.
Entonces, lo consideraremos similar al ordenamiento burbuja.
Existe un par de maneras en las que podemos ver esto.
Y de nuevo, el objetivo aquí no es ser muy matemático,
no comenzaremos a hacer pruebas, pero echaremos
un vistazo a algunos de los pasos en este algoritmo,
para tener una idea general de qué tan lento o tan rápido es un algoritmo.
Y de hecho, ese es el objetivo.
Existe una notación elegante que veremos y se denomina notación asintótica,
con caracteres griegos especiales.
Pero al final del día, solo intentamos
tener una idea general de que tan bueno o malo es un algoritmo, parecido
a lo que intentamos hacer con esta imagen.
Pero ahora lo haremos un poco más convencional,
como lo haría un especialista en sistemas.
Recordemos que en el ordenamiento burbuja comparamos a cada par de humanos
y se hizo un intercambio cuando no estaban ordenados,
y después lo repetimos,
y lo repetimos, y lo repetimos.
Y seguimos repasando la lista.
Eso puede ser cuantificado, como un tipo de análisis
de un número total de pasos.
Entonces, si tenemos n humanos frente a la habitación,
y queremos compararlos en parejas de izquierda a derecha,
¿Cuántos pares posibles están ahí mientras camino
por primera vez a través de la lista?
Si existen n elementos, y los puedo regresar a dónde estaban.
¿Cuántos pares hubo, ya que caminé de izquierda a derecha?
Comparé estos dos, estos dos, estos dos.
Sí, hay n menos uno,
específicamente siete.
Incluso si no estamos del todo seguros de a dónde vamos con esto,
si hay ocho asientos, este es uno, dos, tres, cuatro, cinco, seis,
siete,
y hay ocho en total.
Así que, eso es n-1.
Esa es la cantidad de comparaciones que pudimos hacer
la primera vez a través del ordenamiento burbuja.
Pero la primera vez que fui a través de la lista en tipo burbuja
¿la lista se ordenó por completo?
No, tuvimos que hacerlo otra vez.
Sabíamos que 8 se reordenó hasta al final, así que fue bueno.
8 estaba en el lugar correcto.
Pero tuve que hacerlo de nuevo, y arreglar más problemas en el camino.
Pero la segunda vez, no tuve que recorrer toda la lista.
Para ser claro, ¿a quién no necesité ver?
A la última ubicación, u 8, en nuestro caso particular.
Así que la segunda vez a través de la lista de humanos,
solo tuve que hacer n-2 comparaciones.
¿Correcto?
Porque puedo ignorar al número 8, al último humano, y solo lidiar
con los otros siete humanos que están mal ordenados.
Entonces, si quisiera ser quisquilloso, escribir esto, y contar
cuántos pasos, o cuántas comparaciones, hice,
podríamos generalizarlo así.
¿Correcto?
Será (n - 1) + (n - 2) + (n - 3) + ...
O, más específicamente, 7 + 6 + 5 + 4, esto
es solo la fórmula elegante que dice lo mismo.
Ahora, no recuerdo todas las fórmulas del final de mi libro de matemáticas
bien, pero recuerdo esto.
En sus libros de física y de matemáticas, frecuentemente en la tapa dura,
vemos pequeñas anotaciones en las que las series de números
sirven para sumar o multiplicar.
Y resulta que esa suma puede
expresarse de manera resumida como n (n -1) todo dividido entre 2.
Eso es lo mismo, matemáticamente, como las largas series
que tenían punto, punto, punto.
Si multiplico esto al aplicar algo de álgebra,
como (n^2 - n) / 2.
Es un tipo de multiplicación como n^2/2 ,
-n/2.
Entonces, si yo quisiera ser preciso, este
es el tiempo de ejecución del ordenamiento burbuja
que toma el número de pasos para ordenar a n personas.
¿Por qué?
Porque solo conté la cantidad de comparaciones que hice.
Eso es, cuántas comparaciones necesita el ordenamiento burbuja.
Pero, esto se está volviendo tedioso,
y mis ojos ya comenzaron a ponerse vidriosos.
No quiero recordar estas fórmulas algebraicas.
Así que, intentemos un ejemplo para tener una idea de qué tan lento
o qué tan rápido es esto.
Supongamos que n fuera un millón.
No ocho, sino un millón personas, o un millón de números.
¿Qué tan lento o qué tan rápido será el ordenamiento burbuja?
Bueno, si introducimos un millón, es como decir que n es un millón.
Es un millón al cuadrado, dividido entre 2, menos un millón dividido entre 2.
Porque en resumen, eso es todo.
Entonces, si hago esto, es un número realmente grande.
500 mil millones menos 500 mil.
Y en cualquier otro contexto, 500 mil es un número bastante grande.
Pero no es tanto cuando se resta de 500 mil millones,
porque todavía se obtienen 499,999,500,000 después de la resta.
Lo que quiere decir, que de esos dos términos, el de la izquierda contra el de
la derecha, ¿cuál es el más grande?
¿El factor más dominante en la expresión matemática?
Es el de la izquierda, ¿verdad?
Ese es enormemente más grande.
Y de manera más general, n al cuadrado dividido entre 2
se siente como un resultado más grande que n dividido entre 2.
Y lo hemos visto con pruebas, por ejemplo, lo que no es una prueba,
pero al menos nos da una sensación del tamaño del programa,
o el número de comparaciones que hacemos.
Saben qué, si ese es el caso, si el factor dominante
es el de la izquierda, en nuestro caso, es el que tiene el cuadrado,
en concreto, es mucho más influyente
debido al número de comparaciones que haremos,
agitaremos nuestras manos hacia los términos de orden inferior,
los dividiremos entre 2 y solo diremos
algo así como, uf, este algoritmo parece algo como n al cuadrado.
Ahora quitaré el denominador,
le quitaré lo que estoy restando,
quitaré cualquier cosa que no sea el factor dominante,
qué es el término que contribuye a la mayor cantidad total de pasos
efectuados.
Generalmente, esto es lo que un especialista en sistemas describiría
como el tiempo de ejecución de este algoritmo.
Está en el orden de n al cuadrado.
No es n al cuadrado, pero está en el orden de n cuadrado.
Está bastante cerca, y es bastante bueno para una conversación
con otra persona razonable que quiera debatir si su algoritmo
es quizás mejor o peor que el nuestro.
A esto se le denominaría una notación de gran O.
La gran O se usa para el límite en el tiempo de ejecución de un algoritmo.
El significado del límite superior que consideraremos, en el peor de los casos,
¿Cuánto tiempo tomará este algoritmo?
Bueno, tomará en el orden de n pasos al cuadrado,
Porque si la lista de los números no están ordenados inicialmente,
tenemos que hacer mucho trabajo para ordenarla.
Hay otros términos que podríamos poner en esos paréntesis.
Algunos algoritmos no están en el orden de n al cuadrado.
Algunos algoritmos son del orden de n log n,
algunos algoritmos están en el orden mismo de n, o log n, o incluso de 1,
donde 1 se refiere al tiempo constante.
Y de hecho, los resalté aquí,
hasta ahora hemos visto ejemplos sobre la marcha de todos estos.
Por ejemplo, ¿en qué algoritmo vimos
que su tiempo de ejecución es del orden de n?
¿n pasos?
PÚBLICO: Búsqueda lineal.
DAVID: Búsqueda lineal.
Si tuviéramos que pensar en la búsqueda lineal,
o desde la primera lección, cuando busqué a Mike,
una página de agenda telefónica a la vez, es un algoritmo lineal.
Si existen n páginas, o n humanos, podría tomarme el orden de n pasos,
porque Mike Smith,
si S está hacia el final del abecedario, él podría estar por allí,
o al final de la agenda telefónica, o Dios no lo quiera,
su nombre inicia con Z, entonces
proseguiré todo el camino en la agenda telefónica.
Eso está en el orden de n pasos,
así que, esto podría ser lineal.
También vimos otro algoritmo, en amarillo,
la gran O de log n.
Lo vimos hace un momento.
¿Cuál de nuestros algoritmos estaba en el orden de log n tiempo de ejecución?
Sí, la búsqueda binaria.
Divide y vencerás.
No lo llamamos,
no describimos la fórmula de esto en la primera lección,
pero así es como describiríamos el tiempo de ejecución.
No solo con una bonita imagen, pero con una expresión
así, que todos los humanos--
al menos especialistas en sistemas, pueden estar de acuerdo.
Y en tiempo constante.
Lo gracioso aquí es porque desperdiciamos
términos que realmente no importaron, O de 1 no
significa un algoritmo que solo toma un paso.
Eso sería un número muy limitado de opciones para nuestros algoritmos,
pero significa, simbólicamente, un número constante de pasos.
Una cantidad constante de pasos.
¿Qué es algo que podríamos tomar como una cantidad constante de pasos,
en un algoritmo?
Tal vez, como en la primera lección que tuvimos, el tonto ejemplo de la crema
de cacahuate y la mermelada.
¿Cuál de los pasos de ese día pudo tomar la gran O
de 1 paso, un número constante de pasos?
Recuerdo uno, ¿inserte un cuchillo en el tarro?
Eso es como un paso.
Tal vez son dos, porque tendría que levantar el cuchillo
e insertarlo en el tarro.
Un paso, dos pasos, pero es un número constante.
Para nada nos informaron del número de pasos por el mismo algoritmo.
Solo ocurren.
Lo hacen en un solo paso.
Si hay alguna cantidad de otros algoritmos,
y dejamos en blanco uno al que regresamos, pero
consideremos lo contrario a esto, si quieren.
Si la gran O es nuestro límite superior en el tiempo de ejecución, resulta que
hay un vocabulario que discute el límite inferior del tiempo de ejecución de un
algoritmo.
Para nuestros propósitos, lo valoramos en el contexto del mejor de los casos.
En el mejor de los casos, ¿cuánto tiempo puede tomar un algoritmo?
Bien, esta es una omega mayúscula y se usa exactamente de la misma manera
para decir, omega de n al cuadrado, u omega de n, u omega de log n.
Así que, es la misma simbología, solo que se refiere a un límite inferior.
Se necesitan estos pocos pasos, o estos muchos pasos,
gran O, gran omega.
Por lo tanto, ¿qué algoritmo está, por así decirlo, en omega de 1?
¿Qué algoritmo, en el mejor de los casos, podría tomar solo un paso?
Y quién dio hoy la mejor respuesta en la sala.
¿Qué algoritmo podría tomar un paso?
Sí.
PÚBLICO: [INAUDIBLE]
DAVID: Sí, la búsqueda lineal podría tomar omega de un paso.
Porque en el mejor caso, está justo allí.
O en el caso de Chrissy, incluso si nuestro algoritmo
se ordena para elegir al azar, en el mejor de los casos,
está justo allí, el número 50.
Incluso su algoritmo, o nuestro algoritmo de búsqueda lineal,
y para el caso, incluso nuestro algoritmo de búsqueda binaria,
están en omega de 1, en al menos en el mejor de los casos.
Porque si tenemos suerte, terminamos.
Un paso.
Por el contrario, ¿qué es un algoritmo? que toma al menos n pasos?
¿Cómo omega de n?
PÚBLICO: [INAUDIBLE]
DAVID: Esa es una buena idea.
Escuché que dijeron ordenamiento burbuja.
PÚBLICO: Sí.
DAVID: ¿Por qué?
PÚBLICO: Porque si ya están ordenados,
solo revisa cada comparación, y no hace ningún intercambio.
DAVID: Bien.
En el caso del ordenamiento burbuja, donde generalmente
tuvimos mucho más trabajo por hacer que encontrar algo fácilmente
con un algoritmo de búsqueda, el ordenamiento burbuja es un evento omega
que necesita de al menos el orden de n pasos, tal vez es n-1,
o n-2, pero está en el orden de n.
¿Por qué?
Porque revisa a través de la lista al menos una vez,
ustedes saben qué, ¿para ser claro?
PÚBLICO: Que todos están en orden.
DAVID: Que están en orden.
Saben que como un efecto secundario de no intercambiar,
solo pueden determinar que la lista está ordenada en primer lugar
gastando al menos n pasos en ese proceso.
Excelente.
Hay otro uno, y este es el último,
por lo que si tienen un algoritmo o un escenario
donde el límite superior y el inferior es el mismo
también hay un símbolo para eso, pueden describir el algoritmo
en términos de notación theta.
Significa que theta de n, theta de log n, sean lo que sea,
significa que el límite superior y el inferior uno y el mismo.
Existen más formalidades para esto, y pueden bucear en la profundidad
de este curso de teoría.
Para nuestros propósitos, gran O y omega lo utilizarán de manera útil
para describir cuál es el tiempo de ejecución de un algoritmo
en realidad.
La gran O de n al cuadrado es lo más rápido que vimos hasta ahora.
Desafortunadamente, tienden a correr bastante lento.
Como lo vimos con un ejemplo de 500 mil millones de pasos solo
ordenan un millón de elementos.
Podemos hacer algo mucho mejor que eso,
como en la primera lección, cuando locamente propuse,
Supuse que su agenda telefónica tenía cerca de cuatro mil millones de páginas,
solo necesitamos 32 pasos al usar la búsqueda binaria,
en lugar de cuatro mil millones pasos al usar la búsqueda lineal.
Sería agradable si, después de toda de esta discusión de algoritmos
si introdujéramos estas formalidades, podríamos hacerlo mejor.
Nosotros podemos hacerlo mejor, pero
hicimos mucho para que estuviera listo, así que continuemos
y tomemos cinco minutos de descanso.
Regresaremos, cuando terminen de ejecutarse los tres
algoritmos que acabamos de ver.
Muy bien.
Echemos un vistazo rápido y veamos qué parecen estos algoritmos,
para que los comparemos contra algo
que yo afirmo que terminará por ser mejor.
OK.
Aquí tenemos un arreglo de números representado como barras verticales.
Una barra pequeña es un número pequeño, la barra alta es un gran número.
Es una manera acertada de visualizar lo que serían solo números
bastante pequeños.
Continuaré y haré la animación bastante rápido,
iré directo y elegiré, por ejemplo, ordenamiento burbuja.
Reduciré la velocidad un poco,
en interés de la discusión.
Entonces, veremos este algoritmo ordenamiento burbuja.
Que hace varios intercambios a través de la lista, tal como lo hice,
destacando en rosa dos vecinos a la vez,
y decide si realizar o no el intercambio,
al igual que nosotros, con nuestros ocho voluntarios, hicimos exactamente lo mismo.
Desde luego, con esta visualización, se puede hacer más rápido,
y podemos acelerar esto hasta el punto donde
se comienza a sentir el efecto de la reordenación, por lo que el número
más grande se reordena en la cima, que se sitúa a la derecha, como lo hizo el 8
cuando lo hicimos en papel.
Esto es el ordenamiento burbuja.
Podríamos ver esto por bastante tiempo, y en cierto sentido
es un poco fascinante.
Pero en otro sentido, es bastante decepcionante,
porque al final de la día, todo lo que obtendremos
es un grupo de barras ordenadas, que van de las barras cortas a las grandes.
Tal vez la conclusión es que este algoritmo
se detiene por una buena cantidad de tiempo,
a pesar de que se ejecuta esto a la velocidad más rápida, porque solo
arregla, en el mejor de los casos, un número a la vez.
Quizás algunos otros mejoren, pero solo nos movemos todo el camino
hasta el final de un número a la vez.
Y tenemos que regresar, regresar, regresar, y hacer más trabajo.
Será muy poco satisfactorio detenerlo, pero regresemos al azar.
Ahora, si elegimos, por ejemplo, ordenamiento por selección,
veremos que el algoritmo funciona un poco diferente.
Lo detendré un poco.
Lo que ahora hace, es un poco menos obvio,
busca a través de la lista el siguiente elemento más pequeño,
y lo coloca al inicio de la lista.
todo el camino a la izquierda.
Así que busca, busca, y busca,
y resalta en rojo el descubrimiento más reciente
el elemento más pequeño.
Tan pronto como se sitúa hasta el final de la lista,
el elemento más pequeño se moverá todo el camino a la izquierda.
Esto es algo contrario al ordenamiento burbuja,
mueve todos los elementos más pequeños a la izquierda.
Sin embargo, esto es arbitrario.
Podríamos reordenar los elementos pequeños solamente al revertir
el orden de nuestras operaciones, podríamos ordenar desde el más grande
al más pequeño, eso es irrelevante.
Solo por la convención humana tendemos a clasificar de menor a mayor, al menos
en ejemplos como este.
Podemos acelerarlo, pero no hace exactamente el efecto de la misma
comparación, y todo lo que hacemos es una redada buscando dentro de la lista
el más pequeño, buscando el más pequeño, buscando el más pequeño.
Y de esta manera, construirá del corto al alto.
Proseguiré y haré más tiempo, esta vez con el ordenamiento por inserción,
y lo frenaré.
Lo que hacemos aquí es lo siguiente.
Identificamos el siguiente elemento, y después lo insertamos en su ubicación
que pertenece a la "mitad ordenada" de la lista.
Recuerden que en general describo a las cosas de la izquierda como ordenadas,
las de la derecha como desordenadas, y la implicación
de que a pesar de que los números de la izquierda
están ordenados, cuando encuentro un número del área desordenada,
debería trasladar las cosas de un sitio a otro y mezclarlas.
Y a diferencia de la copia que hicimos en persona,
antes de tomar la música del estante y moverla aquí,
no es realmente legítimo.
¿Correcto?
Este es el valor de estos disparates.
No debería tener acceso a esta memoria.
Entonces, ¿qué fue más legítimo con nuestro ocho humanos?,
¿El hecho de que nuestros voluntarios hicieron el trabajo físico
de mover esos números?
También ese fue el trabajo de nivel bajo que la computadora hace.
Aquí lo vemos aún más, ya sea lento o rápido.
Se insertaron en la ubicación apropiada.
Así que, ¿el caso puntual de estos elementos pequeños?
Tenemos que hacer una gran cantidad de trabajo para encontrar su ubicación,
hasta que finalmente, la encontramos y hacemos lo mismo.
Todo esto tiene algunos puntos a favor y en contra.
Pero con el ordenamiento por mezcla, podemos hacerlo aún mejor.
Un algoritmo con el nombre de ordenamiento por mezcla.
Pero para hacerlo mejor, necesitamos un nuevo ingrediente, formalmente
definido, como un tipo de ordenamiento por ventaja, pero no por nombre.
Para hacer esto, tomaré un poco del código, en CS50 IDE,
un programa llamado sigma-0.c.
Y veremos la interconexión en un momento.
Notemos en este programa lo siguiente.
Tenemos la función main, cuyo propósito en la vida
es obtener como resultado un número entero positivo del usuario,
y molestarlo una y otra vez
y otra vez, hasta que coopere y proporcione un número entero positivo.
Eso en lo que hace el bucle mientras es frecuentemente útil.
Y luego, ¿qué hacemos con eso?
Simplemente, pasamos ese valor n, que el humano escribió,
por medio de get int, a una función llamada sigma.
Sigma es como el carácter griego, o es parecida a la letra mayúscula E.
en general significa, en matemáticas, una suma de muchos números.
La suma de un grupo de números.
Esta es función denominada sigma, cuyo propósito en la vida
es sumar todos los números del 0 hasta n,
o de manera equivalente, de 1 hasta n.
Así, 1 más 2 más 3 más 4 más, ta-ta-ta, n, lo que sea que n
resulte ser.
Y luego lo imprimimos por medio de printf.
Ahora, ejecutaré este programa para dejar muy en claro lo que está pasando.
Y puedo hacer esto, por supuesto, en mi directorio fuente tres de hoy,
make sigma0, Enter, ./sigma0, Positive integer, será 2.
Según esa definición, debería sumar 0 más 1 más 2, entonces 1
más 2, debería ser 3,
así que debería ver 3
y de hecho, veo 3.
Hagamos uno más, si añado tres números,
esto debería ser 1 más 2 más 3, entonces, eso es 1, son 6 en total
y así sucesivamente.
Y aumentan rápidamente.
Si pongo 50, entonces llegaremos a los miles.
Eso es todo lo que hace.
Pero ¿cómo lo hace?
Bueno, existen muchas maneras,
pero si aprovechamos las técnicas que conocemos,
podríamos hacerlo mediante un ciclo for.
De todas las herramientas en nuestro kit, esa es una de las que más hemos usado.
¿y cómo la uso aquí?
Primero declaramos una variable llamada sum y la iniciamos en 0,
porque aún no he hecho ningún trabajo.
Tengo un bucle for donde i es igual a 1 hasta m, for(i = 1; i <= m)
¿Por qué m?
Bueno, porque
recordemos que cuando hacemos nuestra propia función, ya sea en Scratch o en C,
podemos decidir cómo le llamaremos a sus entradas,
es decir, a sus argumentos o parámetros, como se les conoce.
Únicamente para que esto quede claro, lo llamé m, incluso
cuando, por lo general, hemos usado n.
Lo podría haber llamado de cualquier manera,
solamente quería dejar muy en claro que es una variable diferente.
Más o menos en una semana veremos más al respecto.
Solo estoy contando de uno hasta m y sumando cualquiera que sea el valor de i.
Ahora, como una revisión rápida ¿por qué no
uso sum ++, como lo haría en este tipo de casos?
PÚBLICO: Porque no lo estás incrementando por [INAUDIBLE]...
DAVID: Exacto.
No solo lo incremento por 1, lo incremento por 1, luego por 2,
por 3, por 4, y así sucesivamente.
De modo que para contar necesito este bucle y
también necesito incluir i en la suma, no solo un ++, en este caso
y posteriormente regreso al sum.
Así, este es un ejemplo de una abstracción.
Como, ahora tengo un función llamada sigma,
al igual que en matemáticas, representamos el símbolo sigma como
una gran letra mayúscula que indica, suma todos estos números juntos,
tengo una función en C que hace lo mismo.
Ahora, puedo llamar esa función desde la parte superior de mi código
e imprimirá la respuesta.
Pero sucede que esta sencilla función se presta
para un buen ejemplo de otra técnica de programación, algo llamado recursión.
En CS50 no tendremos demasiadas oportunidades
para aplicar esta técnica, pero lo haremos hacia el final del semestre,
aunque la usarán todo el tiempo si toman una clase como CS51.
Si utilizan otro tipo de lenguaje de programación,
utilizarán esta técnica muy frecuentemente.
Se llama recursión y así es como se ve.
Permítanme continuar y abrir otro archivo que se encuentra
disponible en el sitio web del curso y se llama sigma 1.
Observen que la función main es la misma.
La función main es la misma.
Y de hecho, sigue llamando a la función que conocemos como sigma,
luego utiliza a printf e imprime la respuesta.
En eso no hay ninguna diferencia.
Pero en el resto del programa, lo que es diferente es el código para sigma.
Entonces ¿qué ocurre aquí?
Todavía toma como entrada a un entero llamado m.
Lo que es bueno, porque necesito saber cuál es el valor de sum.
Al igual que antes, nos devuelve un entero.
Y sorprendentemente tiene algo como
¿qué? ¿cuatro líneas de código más algunas llaves?
e incluso esas líneas de código son súper cortas,
tampoco hay variables adicionales y hay una lógica absurda y extraña aquí.
Pero antes que nada, veamos qué es lo que está haciendo.
En la línea 23 digo, si m es menor o igual a 0, devuelve 0.
Ahora, ¿por qué esto tiene sentido?
Bueno, solo quiero mantener a los números positivos, o a los números no negativos,
que van de 0 hasta m.
De modo que solo necesito verificar si hay algún error allí ¿cierto?
Si una persona, de alguna manera, pone en esta función un -50 o algo así,
no quiero que la función se vuelva loca y tenga un comportamiento impredecible,
solo quiero que devuelva un 0, en caso de error
o cuando el número sea muy pequeño, o incluso menor como para ser 0.
A esto le voy a llamar un planteamiento básico.
Es solo, como este examen médico, como
no dejes que las matemáticas vayan más allá de este punto, de 0 o menos.
Lo más sorprendente si nos acercamos al código, es que el programa completo
en realidad se resume en una línea.
Pero ¿qué está pasando aquí?
Obtengo una respuesta a partir sigma.
Pero curiosamente, mi respuesta se determina en relación a sí misma,
lo cual, por lo general, es una mala idea.
¿Cierto?
Es como si en inglés, tratáramos de definir
una palabra utilizando esa misma palabra en la definición,
usualmente alguien nos regañará por eso, porque no es
del todo útil usar una palabra en la definición de esa misma palabra.
A primera vista, la recursión se trata de la misma idea.
Estamos utilizando la misma función para resolver un problema que
en primer lugar se supone, debe resolverse por esa misma función.
Pero, ¿qué quiero decir con esto?
Main está llamando a sigma y ​​eso significa que el código
que hemos estado mirando, aquí abajo, se ejecuta.
Luego, vamos a suponer que llegamos a esta línea del código,
lo que la recursión nos permite hacer en este caso
es tomar un bocado del problema, y luego dárselo a alguien más
para que resuelva el resto del problema.
¿qué queremos decir con eso?
Bueno, sigma, de nuevo, es este proceso de sumar todos los números entre 0
y algún número m.
Así, 1 más 2 más 3 más ta-ta-ta.
Pero ¿saben algo?
No quiero hacer todo ese trabajo, ya que lo hice en la versión 0 con mi bucle for,
haré solo una parte de ese trabajo.
¿Y cómo hago eso?
Bueno, saben algo, cuando preguntaron ¿cuál es la suma de 0 hasta m?
Seré algo redundante al respecto y sería algo como, bueno,
es la respuesta de,
la respuesta es m, el mayor número que me dieron, más la suma de todo
lo que está abajo.
¿Cierto?
Por lo que, si pasamos el número 10, esto es como decir, bueno, sigma de 10
es 10 más sigma de nueve y una especie de déjenme en paz,
no quiero hacer el resto de las matemáticas.
Pero, como llamamos de nuevo a la misma función, eso está bien.
Una función puede llamarse a sí misma, porque si
pensamos hacia dónde va la historia, ahora en esta historia, sigma se llama
con sigma de 9.
¿Qué es lo que hace el código?
Bueno, sigma de nueve nos devuelve a 9 más cualquiera que sea sigma de 8.
Entonces, no resolvemos todo el problema.
Estamos devolviendo un 10, más un 9
y si continuamos así, más un 8, más un 7, más un 6.
Pero no vamos a hacer esto para siempre.
Aunque estoy usando sigma en la implementación de mi respuesta, ¿en cuáles
circunstancias no estaré llamando a sigma?
PÚBLICO: Si m es igual a 0.
DAVID MALAN: Si m es igual, o es menor que 0, lo cual no debería suceder,
pero me aseguraré de que no se pueda con el menor o igual que,
En algún momento me preguntarán, ¿cuánto es sigma de 0?
Y no voy a complicarme al respecto, lo único que diré es 0.
De ese modo no seguiré "pasando la pelota" para esa misma función,
a pesar de que toma un tiempo llegar a este punto en la historia,
debido a que decimos 10 más sigma de 9, sigma de 9
es 9 más sigma de 8, el cual es sigma de 8 más sigma de 7,
como si continuaran saliendo y saliendo y saliendo.
Pero es una especie de regulación mental, por así decirlo,
es como en un video en nuestro navegador, todos esos números
que nos devuelven, uno a la vez,
los cuales técnicamente se suman juntos
para nosotros por el programa con el operador +, el último número que
nos van a devolver es el cero y en este punto, todos los signos más
pueden simplemente entrar y devolvernos
cualquier número que estemos buscando.
Entonces, la recursión es la acción de una función que se llama a sí misma,
lo cual es muy, muy, muy malo, a menos que tengamos un planteamiento que asegure que
mientras tomamos un bocado del problema,
lo atenderemos con un caso especial, por así decirlo,
un planteamiento básico, una pequeña pieza del rompecabezas y que solo
devolverá una respuesta hardcoded, para asegurarnos de que esto
no ocurrirá muchas veces, de manera infinita.
¿Alguna pregunta sobre este principio, de una función capaz de llamarse a sí misma?
Sí.
PÚBLICO: ¿El planteamiento básico aquí fue cuando m es igual a 0?
DAVID: Cuando m es igual o menor que 0, solo para estar seguros.
Pero sí, cuando m es igual a cero.
En efecto.
Veamos.
Si están cómodos, al menos con el hecho de, ¡oh! y ahora
aquí tenemos un poco de buen humor nerd,
si van a Google.com y supongamos que se preguntan,
se están preguntando qué es la recursión, en especial algunas horas después de esto.
Bueno, pueden buscarlo en Google y los especialistas en sistemas de Google,
ahí lo tienen.
OK si se están riendo, lo entienden, lo cual es genial.
Entonces esto es la recursión,
es algo que te devuelve una respuesta en términos de sí mismo.
Pero, ¿por qué esto es útil?
Bueno, ahora podemos aprovechar esto
para resolver un problema, si sabemos que podemos convertirlo en un código.
Nos enfocaremos menos en la implementación y más sobre la idea,
pero veamos si podemos envolver nuestras mentes alrededor del problema
para resolverlo con este código.
Este es el ordenamiento por mezcla, en pseudocódigo.
Y de nuevo, como todos los pseudocódigos que hemos escrito,
podríamos escribir este de muchísimas maneras diferentes.
Esta es una de ellas.
Observen la primera parte: on input of n elements, n números, n libros azules,
n cualquier cosa, continúa y haz lo siguiente,
If n < 2, return.
Esta es un poco diferente de la condición que tuve hace un momento,
pero el contexto aquí es para ordenar, no para sumar.
¿Por qué es lógicamente correcto decir, si n es menor que 2, solo regresa?
Sí, porque se trata de sí mismo.
Si esto es menor que 2, significa que solo hay un libro azul, o tal vez incluso
cero, en cualquier caso, no hay trabajo por hacer,
Solo regresar.
La lista está ordenada, por corta que sea.
Pero si es más largo que eso, probablemente tengamos que trabajar,
y hacer algún ordenamiento.
¿qué ocurre después?
Entonces, else, ¿saben algo?
Sort the left half of the elements y luego sort
the right half of the elements, y luego, OK, mézclenlos juntos.
Es el mismo tipo de actitud indiferente, donde
¡ah! si me piden que ordene algo, solo voy a decirles,
bueno, ordenen los de la izquierda, luego los de la derecha
y simplemente se mezclan los resultados para que vuelvan a estar juntos.
Y esto es cíclico en el sentido de que, ¿cómo
ordenaremos la mitad izquierda de algo?
Necesitamos un algoritmo de ordenamiento.
Pero este es el algoritmo de ordenamiento.
Es como decir, use el ordenamiento por mezcla para ordenar la mitad izquierda
use el ordenamiento por mezcla en la mitad derecha, y los mezclamos juntos.
La mezcla no necesita ser un algoritmo elegante,
mezclarlos, es como si aquí tuvieran una pila de números
que están ordenados, una pila de números que están ordenados acá,
podemos simplemente mirarlos y tomar el que sea adecuado para
intercalarlos en el orden correcto.
Eso es a lo que nos referimos con mezclarlos.
¿Cómo es esto correcto en el mundo?
Porque de esta manera, aparentemente no hicimos ningún trabajo.
Parece que no hay bucles, tampoco hay comparaciones,
está totalmente definido para que sea recursivo, por así decirlo.
Bueno, veamos qué significa esto.
Esta es una secuencia de visualizaciones que
potencialmente podrían caerse de la historia.
Trataré de ir despacio, pero no tan lentamente que el ejemplo por sí mismo
sea aburrido.
Pasaremos esto una vez y luego
una vez más, las diapositivas están en línea, por si desean
pasar a verla posteriormente.
Aquí hay una lista de 8 números, los mismos 8 números,
que vimos antes.
Los dibujé de manera contigua, como si estuvieran en un arreglo.
Esta lista tiene actualmente un tamaño de 8.
Por lo que el comienzo de esta historia es una entrada de 8 elementos.
¿Cuál fue el primer paso en nuestro algoritmo?
Bueno, vamos a verificar, if n is less than 2, return.
Lo cual es irrelevante, porque n es 8, no menor que 2.
Entonces ese es un punto cuestionable.
Para mí, las primeras tres cosas que debo hacer para ordenar esta lista
son: to sort the left half, luego sort the right half
y después mezclen las mitades ordenadas.
OK, veamos cómo llegamos allí.
De modo que aquí está la lista, aquí está la mitad izquierda
y al parecer necesito ordenar la mitad izquierda
¿Cómo puedo hacerlo?
Bueno, ¿cómo ordenarían una lista de cuatro elementos?
PÚBLICO: ¿Y si los separamos una vez más?
DAVID: Los separamos una vez más.
Ordenamos la mitad izquierda, luego la derecha y mezclamos esas dos mitades.
Así que déjenme hacer eso.
Dibujaré un cuadro alrededor de los elementos con los que estamos
trabajando en este momento,
a continuación revisaré la mitad izquierda
OK, ahora necesito ordenar esta lista.
¿Cómo ordeno una lista de tamaño 2?
En realidad es 2, no es menor de 2,
tengo que hacer algún trabajo.
Entonces, ¿cómo ordenamos una lista de tamaño 2?
Es un poco extraño decirlo, pero--
ordenamos la mitad izquierda, luego la mitad derecha y mezclamos ambas.
En este punto de la historia, podríamos muy bien
perdernos, porque seguimos diciendo lo mismo,
sin que hagamos algún trabajo.
Pero pensemos como si reguláramos estas instrucciones,
como lo he dicho, para ordenar la mitad izquierda, luego la mitad derecha
por ahora únicamente nos enfocaremos en la mitad izquierda.
Pero desafortunadamente nos distrajimos un poco,
porque para ordenar la mitad izquierda, tenemos que ordenar la mitad izquierda,
por lo que tenemos que hacer un poco más de trabajo.
Si dejamos que esta especie de carga mental se acumule,
tendremos que recordar cómo se hace, para volver a hacerlo.
Pero en realidad aún no hacemos el verdadero trabajo.
Estamos a punto de hacerlo.
Pues ahora que me dijeron, en una lista de tamaño 2, ordena la mitad izquierda,
aquí es donde tocamos fondo con el planteamiento básico
y comenzamos a tener algunos progresos.
Entonces, aquí están el 4 y el 2, una lista de tamaño 2,
vamos a ordenar la mitad izquierda.
¿Cómo se ordena una lista de tamaño 1?
No podemos, ¿cierto?
Porque n es 1 y 1 por supuesto, es menor a 2
y ¿cuál era la única instrucción que había en la parte superior de esta función?
ordenamiento por mezcla?
Regresa.
algo así como no hagas nada.
OK todo el mundo,
ahora he ordenado el número 4 para ustedes.
Es cierto, es una declaración tonta,
pero la magia llegará cuando combinamos los resultados.
Veamos a dónde va la historia,
he ordenado la mitad izquierda, listo.
Regresa.
¿qué se supone que debo hacer ahora?
Tengo que ordenar la mitad derecha en esa lista de tamaño 2.
OK listo.
En este punto de la historia ¿cuál es el tercer paso?
Mezclarlos.
Miremos de nuevo esta lista de tamaño 2, cada una de sus mitades se ordenó
de acuerdo con la absurda lógica que usamos aquí,
pero ahora sucede algo interesante.
Tengo a la izquierda el número 4 y a la derecha
el número 2, y cada una de estas listas son de tamaño 1.
Y si los cambiamos ya sea con su imaginación o visualmente con mis dedos,
piensen que su mano izquierda apunta hacia la primera lista
y su mano derecha apunta hacia la segunda, el proceso de mezclar los números
simplemente es comparar lo que apuntan tus dedos y decidir
cuál de ellos está primero.
Obviamente el 2 estará primero, así que en un momento,
veremos que este 2 debería moverse aquí y luego
no queda nada para mi mano derecha.
Está hecho.
Obviamente, el 4 irá aquí
y este proceso de reunir el 2 seguido del 4, es lo que entendemos como mezclarlos.
Es más o menos lo que hice con el ordenamiento por inserción,
pero aquí solo lo hacemos con cada uno de los elementos a la vez, como
si entrelazáramos o comprimiéramos las cosas,
del mismo modo que con un cierre, si lo pensamos de esa
manera.
Ahora, déjenme tomar el 2 y ponerlo aquí,
permítanme tomar el 4 y ponerlo aquí.
OK.
Así que, ordené la mitad izquierda, la mitad derecha, también los mezclé.
¿cuál es el desenlace de la historia?
¿En qué punto la dejamos?
PÚBLICO: en ordenar la mitad derecha.
DAVID: Ahora tenemos que ordenar la mitad derecha que era, como,
hace un minuto en la historia el cual, para resaltarlo esto, son el 7 y el 5.
A continuación tengo que hacer lo mismo.
Estoy ordenando una lista de tamaño 2, que de la derecha pasa a la izquierda.
Luego ordeno la mitad izquierda, listo
y ordeno la mitad derecha, listo.
Después debo mezclarlas juntas.
Ahora mis manos tienen algo de trabajo por hacer, pero lo haré por aquí,
bajamos el 5 y luego bajamos el 7.
Este punto de la historia, hemos ordenado la mitad de la mitad izquierda,
y también la mitad de la mitad izquierda.
¿En qué punto de la historia nos encontramos ahora?
Es cierto.
Estamos, ahora tenemos, bueno, acabamos de ordenar la mitad derecha.
Ahora tenemos que mezclar las dos mitades.
Si hacemos esto en casa y deseamos recordar todos nuestros pasos,
solo deberemos hacer una lista de pendientes, desde arriba hasta abajo
en una hoja de papel.
Y conforme hagamos alguno de los pasos lo tachamos y regresamos
al resto de las cosas en la lista, de este modo veríamos o sentiríamos
aún más, lo pesada que era esa carga mental
que acumulábamos y de la cual debemos ocuparnos.
Pero ahora tengo dos listas de tamaño 2, hagamos la cosa de los dedos otra vez.
Comienzo por señalar en lista izquierda y hago lo mismo en la de la derecha.
El primer número por mezclar por lo visto será el 2.
¿cuál sigue después de ese?
Actualizaré mi dedo izquierdo, ahora una vez
que mi mano izquierda apunta hacia el 4, en este punto, mi mano derecha todavía
apunta hacia el 5, ¿cuál es el siguiente?
El 4.
Ya que no hay más trabajo para mi mano izquierda, es probable
que esto será bastante trivial,
el 5 y el 7.
Pero necesito mezclarlos.
Parece que ya están mezclados, pero tenemos que hacerlo.
Y al igual que antes, lo haré en un espacio nuevo.
Entonces, 2, 4, 5 y 7.
Y ahora podemos verlo por primera vez.
La mitad izquierda de la lista original finalmente está ordenada.
Desafortunadamente, comenzamos la historia hace como tres minutos.
Y ahora relajemos nuestra mente, para volver a la mitad derecha original.
Si lo pensamos ahora, aunque he dicho muchas palabras,
técnicamente este es el segundo paso en nuestro algoritmo.
O al menos la primera invocación del mismo.
Muy bien, haremos la misma idea pero un poco más rápido.
Ordenemos la mitad izquierda.
¿Cómo puedo hacerlo?
Ordenemos la mitad izquierda, luego la derecha lo cual resulta demasiado fácil,
pero ahora tengo que mezclar al 6 y al 8.
En este caso mezclarlos no tuvo mucho efecto,
pero teníamos que hacerlo para asegurarnos.
A continuación, vamos a ordenar la mitad de la mitad derecha.
Ahora voy a ordenar ambas,
el paso clave es mezclarlas.
Ahora es cuando hacemos un verdadero trabajo,
y que tenemos trabajo pendiente,
también debemos ordenar las mitades izquierda y derecha, de la mitad derecha
original.
Entonces es 1, luego 3, luego 6, luego 8.
Por fin, casi terminamos.
¿Ahora qué hacemos con estos?
Ya que tenemos las dos mitades, la izquierda y la derecha del original,
pensemos en que los dedos hagan el trabajo de nuevo.
El 1 irá aquí, el 2 irá aquí, el 3 aquí, luego el 4,
y constantemente comparo hacia dónde apuntan mis dedos,
pero mis dedos se mueven todo el tiempo y de izquierda a derecha.
Tan pronto como me ocupo de un número, avanzo al siguiente
de la lista.
Obviamente ahora tenemos, 1, 2, 3, 4, 5, 6.
Pero si se imaginan mis dedos mientras hacen este trabajo,
todo el tiempo se mueven desde la derecha hasta el final de la lista
y tan pronto como tocan los extremos de esas listas, debo terminar de mezclarlas.
Y voila,
ordenamos los elementos.
Existe una enorme cantidad de palabras y sería una pesadilla
hacerlo con personas, porque suceden muchas cosas
y tendríamos que recordar, o separar muchos de los pasos.
Pero al final, hicimos algo y lo capturamos incluso
por esta imagen.
Entonces, el ordenamiento por mezcla, aunque suena como una larga historia,
es en esencia más rápido y es más rápido porque estamos
dividiendo el problema a la mitad, como lo hicimos con la búsqueda binaria,
en el ejemplo de la agenda telefónica hace algunos días.
Si miramos en la pantalla, podremos ver los residuos del trabajo
que hemos hecho.
¿Cuántas veces movimos los elementos de una fila a otra?
Comenzaron aquí, luego llegaron hasta acá
y luego acá y acá.
Esos son uno, dos, tres movimientos de las letras, o de los números
en la memoria, si lo prefieren.
Entonces, si se imaginan cada una de estas filas como un fragmento diferente
de memoria y de RAM, únicamente muevo las cosas en la memoria.
De modo que el tres solo es un número.
Pero, si esto lo aplicáramos a otros casos,
resulta que el logaritmo base 2 de n, donde n es 8
y 8 es el número de elementos con los que empezamos, el log base 2 de 8 es 3.
De hecho, si tenemos un poco de fe,
para que no tengamos que hacer un ejemplo más grande,
para demostrarlo,
el número de veces que movemos los números será igual,
al log base 2 de n.
Que en este caso es 3.
Esto nos lleva a la siguiente pregunta, en cada
una de las filas, cada vez que movemos los números a nueva ubicación en la memoria,
¿Cuántas veces tocamos ése número mientras está en esa posición?
O, ¿cuántas veces, de manera equivalente, lo buscamos
para hacer algo con él?
¿Qué quiero decir con esto?
Bueno, el movimiento de arriba hacia abajo ocurrió cada vez
que los mezclamos.
¿Cierto?
Movimos los números de aquí hasta aquí.
Pero tan pronto como lo hicimos, tuvimos que hacer algún trabajo, con el puntero izquierdo
y el derecho.
Necesité mezclarlos juntos.
Y como lo enfaticé antes, cada vez que comparo los números
mi mano izquierda y derecha avanzan
constantemente desde la izquierda y la derecha.
Nunca retrocedo.
Como constantemente regresaba con el ordenamiento burbuja, el de inserción,
ordenamiento por selección, ocurrieron muchas comparaciones,
se sentía como un montón de trabajo, y físicamente lo fue.
Pero aquí, ya saben, los mezclo, y muevo estas cosas de lugar,
y mis manos se mueven constantemente hacia adelante, buscando, en cada fila,
n números en total.
Mi mano izquierda o derecha apuntan a cada uno de los números una vez.
Nunca retroceden.
Así que, nunca fue n +1 o 2 n, solo fue n.
Entonces, tenemos movimientos log n de los números, en la memoria.
Cada vez que hacemos eso, en efecto, los mezclamos de izquierda a derecha,
tocando cada número a la vez.
Hacemos n cosas log n veces.
Entonces, eso sería matemáticamente igual a n log n.
De nuevo, si no están muy cómodos con los logaritmos,
sabemos que, de nuestra imagen, con las líneas rectas y la línea curva,
¿cuál es más chica?
¿Log de n, o n en términos generales?
PÚBLICO: Log de n.
DAVID: Log de n es más pequeño, ¿cierto?
Es por eso que la línea verde fue menor, y también fue curva.
Estaba debajo de la línea lineal n.
En general, cuanto más grande es n, más lentamente crece log n.
Y de nuevo, si solo se tiene fe en que esto
es una expresión matemática que comunica el tiempo requerido
para hacer algo, en menos.
¿Cuál, por lo tanto, es más pequeño?
¿n al cuadrado, que desde luego es n veces n?
O, ¿n log n?
PÚBLICO: n log n.
DAVID: n log n.
Ahora encontramos un algoritmo que a diferencia de todos los vistos,
y a pesar de que tomó un tiempo para explicar, y aunque
es posible que tenga que escudriñar de nuevo para enfocar nuestra mente
a nuestro alrededor, me llevó tiempo,
también es fundamentalmente más rápido.
Para intentar de nuevo esto, nos mostrará otra perspectiva.
Al menos si estamos más matemáticamente cómodos después de hoy,
si nos preocupa que esto sean más matemáticas de las que esperaban,
nos damos cuenta de que rápidamente nos abstraemos lejos de estos detalles,
y comenzamos a agitar nuestras manos al usar gran 0 y gran omega.
Consideremos cómo podríamos buscar en esta otra manera.
Si la imagen no estuviera trabajando para nosotros,
veríamos si podemos, anotar cuántos pasos da cada uno
de estas líneas de código.
No hay muchas líneas de código aquí, por lo que
no debería ser una expresión muy larga.
Entonces, ¿cuánto tiempo lleva decidir si n < 2?
Y si es así, ¿regresar?
Bueno, ya pasaron muchos números, así que
lo llamaré tiempo constante.
¿Sabemos cuántos números se nos entregaron?
No, si es no son menores a 2, o si, lo son.
Nosotros respondemos sí o no.
Tiempo constante.
Gran O de uno.
¿Correcto?
Así que, describiré como es esto.
Esta es la manera formal de decirlo.
T (n), es cuánto tiempo toma un problema dado de tamaño n,
es una manera elegante de decir eso.
Está en el orden de un paso.
Tal vez sean dos, tal vez sean tres, porque
buscamos tener algo.
Pero es un número fijo de pasos para decidir,
que hay menos de n elementos en frente de mí.
No nos llevará mucho tiempo.
Así que, esa pieza de rompecabezas toma una gran O de un paso.
Ahora, tenemos otras tres preguntas para responder.
Eso es algo así como un desechable.
Eso es realmente rápido, si son solo uno, dos o tres pasos.
Entonces, ¿estas cosas son costosas?
Bien, veamos.
Ordena la mitad izquierda de los elementos.
Bueno, puedo ser inteligente y proponer lo siguiente.
¿Saben qué?
La cantidad de tiempo requerido para ordenar n elementos
es técnicamente igual a la cantidad de tiempo
toma la mitad del tiempo ordenar del tiempo, más la cantidad de tiempo requerido
para ordenar la otra mitad de esos elementos, más
algo de tiempo en mezclarlos.
Y es esencialmente n, pero voy a generalizarlo como una gran O de n,
porque tuve que mover mis manos.
Pero, la clave fue que mis manos se movieron constantemente a la derecha.
No hubo bucle una y otra vez, y de nuevo, como con los otros algoritmos.
Así que, son como n pasos para mezclarlos.
Si tengo 4 números aquí, y 4 números aquí,
tengo que tocar un total de 8 elementos.
8 es n, así que se siente, sí, por orden de n pasos para mezclarlos.
Lamentablemente, esto es una respuesta repetitiva
a la pregunta de cuán eficiente es el ordenamiento por mezcla.
Pero es un tipo de consistencia con el hecho de que
ese ordenamiento por mezcla se implementó por repeticiones en este código.
Y resulta que también aquí, si tenemos
uno de esos libros de texto antiguos eso tiene anotaciones al frente
o la parte posterior de nuestro libro de física o matemáticas,
esta es una serie que podemos, que los matemáticos saben
como sumarle a algo conocido, lo cual es n log n.
Y no nos adentraremos en la maleza del por qué matemáticamente es así,
pero si tomamos un problema de tamaño n y le sumamos el tiempo
de ejecución para la primera mitad, la segunda mitad y le añadimos una n,
esto es lo que matemáticamente termina siendo.
Así que, si se sienten más cómodos con eso,
noten que esto proviene de contar todos esos pasos.
Y en última instancia, es mucho mejor que eso.
De hecho, podemos sentirlo aquí.
Pero podremos percibirlo mejor con otro conjuntos de datos,
pero sigamos adelante y lo volveré a cargar aquí, continuaré a la misma velocidad
que tuvimos antes, y elijo el ordenamiento por mezcla.
Lo ajustaré a la pantalla de una vez.
Deberíamos acelerarlo como el original.
Pero, ¿qué está haciendo?
Está usando un poco de memoria extra, del mismo modo que en la pantalla,
utilizamos un poco de espacio adicional.
Pero noten, que conforme funciona, mueve las cosas de un lado a otro.
Y en realidad está ahorrando espacio.
A pesar de que utilicé una cantidad de memoria log n para seguir moviéndolo,
eso fue tonto.
No era necesario que siguiera usando más memoria, más memoria, más
memoria, porque ya no utilizaba las cosas de más arriba.
Con el ordenamiento por mezcla, necesito el doble de memoria
como con esos otros algoritmos, porque es la primera vez que necesitamos moverlos,
moverlos aquí.
Aun cuando, de manera deliberada, lo hice visible
para moverlo a otra ubicación, sigue moviendo las cosas de un
lado a otro según sea necesario.
Y es lo mismo que pasa con el algoritmo de aquí.
No es tan fácil de ver con esta visualización, así que
abriré este otro.
¡Ahora, vamos!
Y veremos el ordenamiento por mezcla todo el camino a la derecha,
listo.
Muy bien, el ordenamiento por inserción tiene algo de suerte aquí,
pero solo porque el orden de los elementos y el tamaño del conjunto de datos
no es tan grande, por eso quería mostrarles el otro.
Pero si lo repetimos, podremos ver de nuevo que ese ordenamiento por mezcla
es mucho más rápido.
Podemos ver que hace las cosas en mitades.
El ordenamiento por selección, y el burbuja, también hacen lo suyo.
Si lo hiciéramos usando, como, ¿qué es eso
10, en total 20 barras, pero con 100 barras?
¿O un millón de barras?
En verdad, en verdad sentiríamos la diferencia,
tal como lo hicimos con el ejemplo de la agenda telefónica.
¿Tienen alguna pregunta sobre esto?
No revisaremos el código, pero si les
interesa ver cómo algunas de estas ideas esquematizan un código en C,
encontrarán en la aplicación de CS50, en el código fuente de hoy,
un par de archivos, binary0.c y binary one in linear.c, los cuales
implementan la búsqueda binaria y la lineal de dos maneras diferentes,
si desean ver como las esquematizan.
Con el tiempo que nos queda de hoy, lo que podríamos hacer
es competir en alguno de los siguientes desafíos algorítmicos.
En las ciencias de la computación existen magníficas oportunidades
para interactuar con otros ámbitos, entre los cuales
están las artes y más específicamente, la música.
En la música, independientemente de si son audiófilos o incluso
teóricos musicales, sabrán que hay relaciones
entre los sonidos y la velocidad en la que escuchamos esas notas.
Lo cual quiere decir, que siguen patrones.
Y estos patrones pueden producirse mediante computadoras,
pueden ser generados por ellas y lo que haremos
por último, en la serie de problemas número tres,
será introducirnos un poco en el mundo musical,
ya sea que tengamos alguna experiencia previa o no.
Y Brian, si no te importa puedes venir para ayudarnos con este ejercicio.
Aquí tenemos algunas teclas de un teclado
y también hay 88 teclas de un piano real.
Brian nos ayudará en un momento, con algunas de estas definiciones.
Pero como pueden ver hay ocho teclas, una, dos, tres, cuatro, cinco,
seis, siete teclas blancas y cinco teclas negras en el tablero.
Parece ser que los humanos,
hace algunos años decidieron, al menos en la música occidental,
estandarizar la manera en que describimos estas teclas,
de modo que les asignamos letras.
Probablemente hayan escuchado del Do medio, incluso si nunca han
tocado un piano antes y es la tecla que se encuentra más a la izquierda
ahí.
También tenemos Re, Mi, Fa, Sol y luego La, Si. Por supuesto, en un piano real
hay teclas a la izquierda a la derecha.
¿Quieres tocar lo que sonaría como Do aquí?
[MÚSICA DE PIANO]
Ese es el Do. Y luego, si quieres, ¡muy bien hecho!
[APLAUSOS]
¿Quieres tocar todas las notas de la escala de Si?
[MÚSICA DE PIANO]
Eso también es algo sin resolver, por lo que debió venir a continuación
[MÚSICA DE PIANO]
Eso sería otro Do. Entonces lo que Brian tocó para nosotros es
una octava completa, así, hace referencia al ocho,
en este caso del Do al Do.
Los que estemos familiarizados con la música,
o con escuchar cierto tipo de música, podremos
notar que ciertas cosas suenan bien.
Pero de hecho, hay razones matemáticas o algorítmicas y fórmulas
para que algunos de estos sonidos suenen tan bien.
Pero, ¿qué hay acerca de estas teclas negras?
De hecho, se pueden definir de dos maneras diferentes.
Seguro alguna vez escucharon de bemoles o de agudos,
Brian, ¿quieres explicarnos cuál es la relación que hay
entre las teclas blancas y las negras, y por qué suenan diferente?
BRIAN: Sí, claro.
Veamos un poco de terminología primero.
Un semitono es la distancia desde una nota hasta la nota
que le sigue inmediatamente después, e incluye ambas notas blancas y negras.
Y todo lo que signifique para algo ser agudo,
se representa por el hashtag o el signo de libra como vemos allí, es tomar una nota
y moverla hacia arriba en un semitono.
Entonces, si comenzamos con Do y hacemos que esa nota sea aguda, a Do agudo,
movemos un semitono a la nota de inmediato
después de eso, la cual es esa nota negra entre Do y Re, ése es el Do agudo.
E igualmente, si añadimos el Mi agudo, eso es un semitono, o la nota inmediatamente
después, Mi, que en este caso, es lo mismo que Fa. Entonces,
Fa y Mi agudos son la misma nota.
Y mientras tanto, un bemol es lo opuesto a eso.
Si un agudo significa subir un semitono, un bemol significa bajar un semitono.
Si tengo un Mi, el Mi bemol es un semitono moviéndose a la izquierda del teclado.
DAVID: Aunque el teclado de un piano convencional no estaría
etiquetado como tal, esto sigue un patrón,
y lo repite, también de izquierda a derecha.
Y a medida que aprendan a tocar el piano, conocerán cómo suenan estas notas,
sabrán donde se localizan las teclas y también
aprenderán cómo leer las notas musicales, las cuales podrían verse así.
Esta es una canción familiar, que ahora oficialmente es del dominio público.
Verán que aquí tenemos estas pequeñas formas llamadas notas,
o pequeños círculos, ocurre que se encuentran en líneas específicas.
Si una nota está en una línea,
podría representar la nota La, pero si está en una línea diferente,
más arriba o abajo, podría representar a Si, Do, Re, Mi, Fa o Sol.
Si hay un símbolo agudo o un bemol delante de ella,
eso podría cambiarlo ligeramente, así estarán
tocando, en muchos casos, también una de las teclas negras.
Lo que quiere decir que una vez que tenemos el vocabulario,
y que conocemos el alfabeto, al cual tenemos acceso,
podemos comenzar a escribirlo, igual a como escribimos programas de computadora.
Pero esto es lo que un músico vería en realidad.
Y para darnos una muestra de lo que pueden
hacer cuando toman en cuenta los diferentes sonidos de notas,
y los diferentes ritmos en los que tocamos las notas,
¿Puedes darnos un poco más que solo una escala?
BRIAN: Claro.
[MÚSICA DE PIANO]
[APLAUSOS]
DAVID: Si están un poco preocupados porque
nos adentramos, no solo en las ciencias de la computación y de la programación,
sino también en la música,
definitivamente estoy entre los menos cómodos con esto
y esa es la razón por la cual Brian, amablemente se unió a nosotros hoy.
Pero será divertido, esperamos, en definitiva, poder explorar
estas relaciones y también la interacción de un ámbito con el otro.
Y para unir estos temas juntos, nos pareció
que podríamos concluir con una visualización corta
se trata de aproximadamente un minuto de sonidos generados por computadora, eso
nos da, no solo una sensación visual de algunos de los algoritmos
y de otras cosas que vimos hoy, sino también de los sonidos asociados
con las operaciones de movimiento e intercambio de cosas
y finalmente tocar números más grandes o más pequeños digitalmente.
Aquí tenemos primero, el ordenamiento por inserción.
[SONIDOS DE COMPUTADORA]
De nuevo, está insertando el número en el lugar correcto.
Esto, ahora, es el ordenamiento burbuja.
En tanto, podemos ver ambos y sentir todos los intercambios
que está haciendo.
Sentiremos la satisfacción esta vez.
Esto ahora, es el ordenamiento por selección, mediante el cual
seleccionamos a través de la lista, el siguiente elemento más pequeño
y lo ponemos en su lugar.
Y las barras se vuelven cada vez más y más altas, al igual que las notas,
o las frecuencias.
Este es el ordenamiento por mezcla.
Observen las mitades que se están desarrollando.
Este es por mucho el sonido más gratificante, al final de esta.
Este es el ordenamiento del gnomo, el cual no vimos,
pero es muy evidente que también tiene una forma diferente,
no es tan ordenado como los demás.
Entonces, esos son los algoritmos.
Y Brian tocó hoy para nosotros.
Por lo pronto, nos vemos la próxima semana.