Data Structures and Algorithms With Java-2
Data Structures and Algorithms With Java-2
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
Michael McMillan
[Link][Link]
Machine Translated by Google
Publicado por O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
Los libros de O'Reilly se pueden adquirir para fines educativos, comerciales o promocionales. También hay ediciones en línea disponibles
para la mayoría de los títulos ([Link] Para obtener más información, comuníquese con nuestro departamento de
ventas corporativas/institucionales: 8009989938 o corporate@[Link].
Nutshell Handbook, el logotipo de Nutshell Handbook y el logotipo de O'Reilly son marcas comerciales registradas de O'Reilly Media, Inc.
Data Structures and Algorithms with JavaScript, la imagen de un erizo amur y la imagen comercial relacionada son marcas comerciales de
O'Reilly Media, Inc.
Muchas de las designaciones que utilizan los fabricantes y vendedores para distinguir sus productos se consideran marcas comerciales.
Cuando dichas designaciones aparecen en este libro y O'Reilly Media, Inc. tenía conocimiento de que se trataba de una marca comercial, las
designaciones se han impreso en mayúsculas o con las iniciales en mayúsculas.
Aunque se han tomado todas las precauciones en la preparación de este libro, el editor y los autores no asumen ninguna
responsabilidad por errores u omisiones, ni por daños resultantes del uso de la información aquí contenida.
ISBN: 9781449364939
[LSI]
[Link][Link]
Machine Translated by Google
Tabla de contenido
Prefacio. ... ix
Construcciones de repetición 6
Funciones 7
Alcance variable 8
Recursión 10
Resumen 12
2. Matrices. ...
Matrices de JavaScript definidas 13
mediante 13
matrices Creación 14
de cadenas Operaciones de 16
agregación de matrices Funciones de acceso 17
En busca de un valor 17
iii
[Link][Link]
Machine Translated by Google
Matrices irregulares 30
Matrices de objetos 30
Matrices en objetos 31
Ceremonias 33
3. Listas. ...
Una lista ADT 35
4. Pilas. ...
Operaciones de pila 49
Demostración de la recursión 56
Ceremonias 57
5. Colas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Operaciones de cola 59
iv | Tabla de contenidos
[Link][Link]
Machine Translated by Google
Colas de prioridad 70
Ceremonias 72
7. Diccionarios. ...
La clase del diccionario 89
8. Hash. ...
Descripción general del hash Una 97
clase de tabla hash 98
9. Conjuntos. ...
Definiciones, operaciones y propiedades de conjuntos fundamentales 113
Definiciones de conjuntos 113
Índice de contenidos | v
[Link][Link]
Machine Translated by Google
Construcción de un 143
vi | Tabla de contenidos
[Link][Link]
Machine Translated by Google
Conteo de 196
ocurrencias 200
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
Prefacio
En los últimos años, JavaScript se ha utilizado cada vez más como lenguaje de programación informática
del lado del servidor gracias a plataformas como [Link] y SpiderMonkey.
Ahora que la programación en JavaScript está dejando de estar disponible en los navegadores, los
programadores descubrirán que necesitan utilizar muchas de las herramientas que ofrecen los lenguajes
más convencionales, como C++ y Java. Entre estas herramientas se encuentran las estructuras de datos
clásicas, como listas enlazadas, pilas, colas y gráficos, así como los algoritmos clásicos para ordenar y
buscar datos. Este libro analiza cómo implementar estas estructuras de datos y algoritmos para la
programación en JavaScript del lado del servidor.
Los programadores de JavaScript encontrarán este libro útil porque analiza cómo implementar estructuras
de datos y algoritmos dentro de las restricciones que JavaScript les impone, como matrices que son en
realidad objetos, variables demasiado globales y un sistema de objetos basado en prototipos. JavaScript
tiene una reputación injusta como un lenguaje de programación "malo", pero este libro demuestra cómo
se puede utilizar JavaScript para desarrollar estructuras de datos y algoritmos eficientes y efectivos
utilizando las "partes buenas" del lenguaje.
El científico informático Nicklaus Wirth escribió un libro de texto de programación informática titulado
Algoritmos + Estructuras de datos = Programas (PrenticeHall). Ese título es la esencia de la programación
informática. Cualquier programa informático que vaya más allá del trivial “¡Hola, mundo!” normalmente
requerirá algún tipo de estructura para gestionar los datos que el programa está escrito para manipular,
junto con uno o más algoritmos para traducir los datos de su forma de entrada a su forma de salida.
ix
[Link][Link]
Machine Translated by Google
Para muchos programadores que no estudiaron informática en la escuela, la única estructura de datos con
la que están familiarizados es la matriz. Las matrices son excelentes para algunos problemas, pero para
muchos problemas complejos, simplemente no son lo suficientemente sofisticadas. La mayoría de los
programadores experimentados admitirán que para muchos problemas de programación, una vez que se
les ocurre la estructura de datos adecuada, los algoritmos necesarios para resolver el problema son más
fáciles de diseñar e implementar.
Un ejemplo de una estructura de datos que conduce a algoritmos eficientes es el árbol binario de búsqueda
(BST). Un árbol binario de búsqueda está diseñado para que sea fácil encontrar los valores mínimos y
máximos de un conjunto de datos, lo que produce un algoritmo que es más eficiente que los mejores
algoritmos de búsqueda disponibles. Los programadores que no estén familiarizados con los BST
probablemente utilizarán una estructura de datos más simple que termine siendo menos eficiente.
El estudio de algoritmos es importante porque siempre hay más de un algoritmo que se puede utilizar para
resolver un problema, y saber cuáles son los más eficientes es importante para el programador productivo.
Por ejemplo, hay al menos seis o siete formas de ordenar una lista de datos, pero saber que el algoritmo
Quicksort es más eficiente que el algoritmo de ordenación por selección conducirá a un proceso de
ordenación mucho más eficiente. O que es bastante fácil implementar un algoritmo de búsqueda secuencial
o lineal para una lista de datos, pero saber que el algoritmo de ordenación binaria a veces puede ser el
doble de eficiente que la búsqueda secuencial conducirá a un mejor programa.
El estudio exhaustivo de las estructuras de datos y los algoritmos le enseñará no solo qué estructuras de
datos y qué algoritmos son los más eficientes, sino que también aprenderá a decidir qué estructuras de
datos y qué algoritmos son los más apropiados para el problema en cuestión. A menudo habrá que hacer
concesiones cuando se escriba un programa, especialmente en el entorno de JavaScript, y conocer los
entresijos de las distintas estructuras de datos y algoritmos que se tratan en este libro le ayudará a tomar la
decisión adecuada para cualquier problema de programación particular que esté tratando de resolver.
x | Prefacio
[Link][Link]
Machine Translated by Google
• El capítulo 2 analiza la estructura de datos más común en la programación informática: la matriz, que es nativa de
JavaScript.
cubre la estructura de datos de pila. Las pilas se utilizan en todo el sistema informático.
Ciencia tanto en implementaciones de compiladores como de sistemas operativos.
• El capítulo 5 analiza las estructuras de datos de colas. Las colas son una abstracción de las colas que se forman
en un banco o en una tienda de comestibles. Las colas se utilizan ampliamente en software de simulación donde
los datos deben alinearse antes de ser procesados. • El capítulo 6 trata sobre las
listas enlazadas. Una lista enlazada es una modificación de la estructura de datos de lista, donde cada elemento es
un objeto separado vinculado a los objetos a cada lado de la misma.
Las listas enlazadas son eficientes cuando necesitas realizar múltiples inserciones y eliminaciones en tu programa.
• El capítulo 7 demuestra cómo construir y utilizar diccionarios, que son estructuras de datos
que almacenan datos como pares clavevalor.
• Una forma de implementar un diccionario es utilizar una tabla hash, y el Capítulo 8 analiza cómo
construir tablas hash y los algoritmos hash que se utilizan para almacenar datos en la tabla.
• El capítulo 9 trata sobre la estructura de datos de los conjuntos. Los conjuntos no suelen estar incluidos en los libros
sobre estructuras de datos, pero pueden resultar útiles para almacenar datos que no deben tener duplicados en
el conjunto de datos.
• Los árboles binarios y los árboles binarios de búsqueda son el tema del Capítulo 10. Como se mencionó
anteriormente, los árboles binarios de búsqueda son útiles para almacenar datos que deben almacenarse
originalmente en forma ordenada.
• El capítulo 11 cubre los gráficos y los algoritmos gráficos. Los gráficos se utilizan para representar datos.
como los nodos de una red informática o las ciudades en un mapa.
• El capítulo 12 pasa de las estructuras de datos a los algoritmos y analiza varios algoritmos para ordenar datos,
incluidos algoritmos de ordenación simples que son fáciles de implementar pero no son eficientes para grandes
conjuntos de datos, y algoritmos más complejos que son apropiados para conjuntos de datos más grandes. • El
capítulo 13 también cubre algoritmos, esta vez
algoritmos de búsqueda como la búsqueda secuencial y la búsqueda binaria. • El último capítulo del libro, el capítulo
14, analiza un par de algoritmos
más avanzados para trabajar con datos: programación dinámica y algoritmos voraces.
Prefacio | xi
[Link][Link]
Machine Translated by Google
Estos algoritmos son útiles para resolver problemas difíciles en los que un algoritmo más tradicional
es demasiado lento o demasiado difícil de implementar. En este capítulo, examinamos algunos
problemas clásicos tanto de programación dinámica como de algoritmos voraces.
Ancho constante Se
utiliza para listados de programas, así como dentro de párrafos para hacer referencia a elementos del
programa, como nombres de variables o funciones, bases de datos, tipos de datos, variables de
entorno, declaraciones y palabras clave.
Muestra texto que debe reemplazarse con valores proporcionados por el usuario o por valores
determinados por el contexto.
Este libro está aquí para ayudarle a realizar su trabajo. En general, si se ofrece un código de ejemplo
con este libro, puede usarlo en sus programas y documentación. No necesita ponerse en contacto con
nosotros para solicitar permiso a menos que vaya a reproducir una parte importante del código.
Por ejemplo, escribir un programa que utilice varios fragmentos de código de este libro no requiere
permiso. Vender o distribuir un CDROM con ejemplos de los libros de O'Reilly sí requiere permiso.
Responder a una pregunta citando este libro y citando el código de ejemplo no requiere permiso.
Incorporar una cantidad significativa de código de ejemplo de este libro en la documentación de su
producto sí requiere permiso.
Apreciamos, pero no exigimos, la atribución. Una atribución generalmente incluye el título, el autor, el
editor y el ISBN. Por ejemplo: “Estructuras de datos y algoritmos con Java Script de Michael McMillian
(O'Reilly). Copyright 2014 Michael McMillan, 9781449364939”.
Si considera que su uso de ejemplos de código no se ajusta al uso justo o al permiso otorgado
anteriormente, no dude en contactarnos a permissions@[Link].
xii | Prefacio
[Link][Link]
Machine Translated by Google
Safari Books Online ofrece una gama de combinaciones de productos y programas de precios para
organizaciones, agencias gubernamentales y particulares. Los suscriptores tienen acceso a miles de
libros, vídeos de formación y manuscritos en fase de prepublicación en una base de datos totalmente
consultable de editoriales como O'Reilly Media, Prentice Hall Professional, AddisonWesley
Professional, Microsoft Press, Sams, Que, Peachpit Press, Focal Press, Cisco Press, John Wiley &
Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning,
New Riders, McGrawHill, Jones & Bartlett, Course Technology y muchas más. Para obtener más
información sobre Safari Books Online, visítenos en línea.
Cómo contactarnos
Por favor, dirija sus comentarios y preguntas sobre este libro al editor:
Contamos con una página web para este libro, donde enumeramos erratas, ejemplos y cualquier
información adicional. Puede acceder a esta página en [Link]
Para comentar o hacer preguntas técnicas sobre este libro, envíe un correo electrónico a
bookquestions@[Link].
Para obtener más información sobre nuestros libros, cursos, conferencias y noticias, visite nuestro sitio
web en [Link]
Prefacio | xiii
[Link][Link]
Machine Translated by Google
Expresiones de gratitud
Siempre hay mucha gente a la que agradecer cuando terminas de escribir un libro. Me
gustaría agradecer a mi editor de adquisiciones, Simon St. Laurent, por creer en este libro y
ayudarme a empezar a escribirlo. Meghan Blanchette trabajó duro para que cumpliera con el
cronograma y, si me salí del cronograma, definitivamente no fue culpa suya. Brian MacDonald
trabajó muy duro para hacer que este libro fuera lo más comprensible posible y ayudó a que
varias partes del texto fueran mucho más claras de lo que yo las había escrito originalmente.
También quiero agradecer a mis revisores técnicos por leer todo el texto y el código, y por
señalar los lugares en los que tanto mi prosa como mi código necesitaban ser más claros. Mi
colega e ilustradora, Cynthia Fehrenbach, hizo un trabajo excepcional al traducir mis
garabatos en ilustraciones nítidas y claras, y se merece un elogio especial por su disposición
a volver a dibujar varias ilustraciones en el último minuto. Por último, me gustaría agradecer
a todas las personas de Mozilla por diseñar un excelente motor y shell de JavaScript y por
escribir una excelente documentación para usar tanto el lenguaje como el shell.
xiv | Prefacio
[Link][Link]
Machine Translated by Google
CAPÍTULO 1
El entorno de JavaScript
Históricamente, JavaScript ha sido un lenguaje de programación que se ejecutaba únicamente
dentro de un navegador web. Sin embargo, en los últimos años se han desarrollado entornos de
programación de JavaScript que se pueden ejecutar desde el escritorio o, de manera similar, desde
un servidor. En este libro, utilizamos uno de esos entornos: el shell de JavaScript que forma parte
del completo entorno de JavaScript de Mozilla conocido como SpiderMonkey.
Para descargar el shell de JavaScript, diríjase a la página web de Nightly Build. Desplácese hasta
la parte inferior de la página y seleccione la descarga que coincida con su sistema informático.
Una vez que hayas descargado el programa, tienes dos opciones para usar el shell. Puedes usarlo
en modo interactivo o para interpretar programas JavaScript almacenados en un archivo. Para usar
el shell en modo interactivo, escribe el comando js en el símbolo del sistema.
Aparecerá el indicador de shell, js>, y estará listo para comenzar a ingresar expresiones y
declaraciones de JavaScript.
js> 1 1
js> 1+2 3
[Link][Link]
Machine Translated by Google
}
1
3
4
5
es>
Puede introducir expresiones aritméticas y el shell las evaluará inmediatamente. Puede escribir cualquier
declaración de JavaScript válida y el shell también la evaluará inmediatamente. El shell interactivo es
ideal para explorar las declaraciones de JavaScript y descubrir cómo funcionan. Para salir del shell
cuando haya terminado, escriba el comando quit().
La otra forma de utilizar el shell es que interprete programas JavaScript completos. Así es como
utilizaremos el shell en el resto del libro.
Para utilizar el shell para interpretar programas, primero hay que crear un archivo que contenga un
programa JavaScript. Se puede utilizar cualquier editor de texto, asegurándose de guardar el archivo
como texto sin formato. El único requisito es que el archivo debe tener una extensión .js . El shell debe
ver esta extensión para saber que el archivo es un programa JavaScript.
Una vez que hayas guardado el archivo, puedes interpretarlo escribiendo el comando js seguido del
nombre completo del archivo de tu programa. Por ejemplo, si guardaste el fragmento de código del bucle
for que se muestra anteriormente en un archivo llamado [Link], deberás escribir lo siguiente:
c:\js>js [Link]
Lo que produciría el siguiente resultado:
1
2
3
4
5
analizamos cómo usamos JavaScript. Sabemos que los programadores tienen diferentes estilos y
prácticas a la hora de escribir programas, y queremos describir los nuestros aquí, al principio del libro,
para que puedas entender el código más complejo que presentamos en el resto del libro. Este no es un
tutorial sobre el uso de JavaScript, sino una guía sobre cómo usamos las construcciones fundamentales
del lenguaje.
[Link][Link]
Machine Translated by Google
Para declarar una variable en JavaScript, utilice la palabra clave var seguida de un nombre de variable y,
opcionalmente, una expresión de asignación. A continuación, se muestran algunos ejemplos:
var numero;
nombre de la
variable ; tasa var = 1,2;
var saludo = "¡Hola mundo!"; bandera var = falso;
• + (suma) •
(resta)
• * (multiplicación) • /
(división)
• % (módulo)
JavaScript también tiene una biblioteca matemática que puedes usar para funciones avanzadas
como la raíz cuadrada, el valor absoluto y las funciones trigonométricas. Los operadores aritméticos
siguen el orden estándar de operaciones y se pueden usar paréntesis para modificar ese orden.
[Link][Link]
Machine Translated by Google
4.1
3.3000000000000003
7.789999999999999
3 0,3666666666666667
Si no desea o necesita la precisión que se muestra arriba, puede formatear un número con una
precisión fija:
var z = x
*
print([Link](2)); // y;
muestra 3.30
Construcciones de decisión
Las construcciones de decisión permiten que nuestros programas tomen decisiones sobre qué
sentencias de programación ejecutar en función de una expresión booleana. Las dos construcciones
de decisión que utilizamos en este libro son la sentencia if y la sentencia switch .
• La simple declaración if
• La declaración ifelse
• La declaración ifelse if
[Link][Link]
Machine Translated by Google
de lo
contrario { medio = (actual+alto) / 2;
}
La otra estructura de decisión que utilizamos en este libro es la sentencia switch . Esta sentencia
proporciona una construcción más clara y estructurada cuando hay que tomar varias decisiones
sencillas. El ejemplo 15 demuestra cómo funciona la sentencia switch .
nombreMes = "Mayo";
break;
case "6":
nombreMes = "Junio"; break;
case "7":
[Link][Link]
Machine Translated by Google
¿Es esta la forma más eficiente de resolver este problema? No, pero demuestra muy bien cómo funciona
la sentencia switch .
Una diferencia importante entre la sentencia switch de JavaScript y las sentencias switch de otros
lenguajes de programación es que la expresión que se está probando en la sentencia puede ser de
cualquier tipo de datos, a diferencia de un tipo de datos integral, como lo requieren lenguajes como C++
y Java. De hecho, notarás en el ejemplo anterior que usamos los números de los meses como cadenas,
en lugar de convertirlos en números, ya que podemos comparar cadenas usando la sentencia switch en
JavaScript.
Construcciones de repetición
Muchos de los algoritmos que estudiamos en este libro son de naturaleza repetitiva. En este libro
utilizamos dos construcciones de repetición: el bucle while y el bucle for .
Cuando queremos ejecutar un conjunto de instrucciones mientras una condición es verdadera, utilizamos
un bucle while . El ejemplo 16 demuestra cómo funciona el bucle while .
}
print(suma); // muestra 55
[Link][Link]
Machine Translated by Google
Cuando queremos ejecutar un conjunto de instrucciones una cantidad específica de veces, utilizamos un bucle
for . En el ejemplo 17 se utiliza un bucle for para sumar los números enteros del 1 al 10.
} print(suma); // muestra 55
Los bucles for también se utilizan con frecuencia para acceder a los elementos de una matriz, como se muestra
en el Ejemplo 18.
Funciones
JavaScript proporciona los medios para definir funciones que devuelven valores y funciones que no devuelven
valores (a veces llamadas subprocedimientos o funciones vacías).
El ejemplo 19 demuestra cómo se definen y llaman las funciones que devuelven valores en JavaScript.
} devolver producto;
}
El ejemplo 110 ilustra cómo escribir una función que se utiliza no por su valor de retorno, sino por las operaciones
que realiza.
[Link][Link]
Machine Translated by Google
}
}
Todos los parámetros de función en JavaScript se pasan por valor y no hay parámetros de referencia.
Sin embargo, hay objetos de referencia, como matrices, que se pasan a funciones por referencia, como
se demostró en el Ejemplo 110.
Alcance de la variable
El alcance de una variable se refiere a dónde en un programa se puede acceder al valor de una variable.
El alcance de una variable en JavaScript se define como el alcance de la función. Esto significa que el
valor de una variable es visible dentro de la definición de la función donde se declara y define la variable
y dentro de cualquier función que esté anidada dentro de esa función.
Cuando una variable se define fuera de una función, en el programa principal, se dice que la variable
tiene alcance global , lo que significa que cualquier parte de un programa puede acceder a su valor,
incluidas las funciones. El siguiente programa breve demuestra cómo funciona el alcance global:
función showScope() {
alcance de retorno ;
}
La función showScope() puede acceder a la variable scope porque scope es una variable global. Las
variables globales se pueden declarar en cualquier lugar de un programa, ya sea antes o después de las
definiciones de funciones.
Ahora observe lo que sucede cuando definimos una segunda variable de alcance dentro del programa.
Función Scope() :
[Link][Link]
Machine Translated by Google
La variable de ámbito definida en la función showScope() tiene un ámbito local, mientras que la variable
de ámbito definida en el programa principal es una variable global. Aunque las dos variables tienen el
mismo nombre, sus ámbitos son diferentes y sus valores son diferentes cuando se accede a ellos dentro
del área del programa donde están definidas.
Todo este comportamiento es normal y esperado. Sin embargo, puede cambiar si omite la palabra clave
var en las definiciones de variables. JavaScript le permite definir variables sin usar la palabra clave var ,
pero cuando lo hace, esa variable automáticamente tiene alcance global, incluso si se define dentro de
una función.
El ejemplo 111 demuestra las ramificaciones de omitir la palabra clave var al definir variables.
alcance = "global";
print(alcance); // muestra "global"
print(showScope()); // muestra "local" print(alcance); //
muestra "local"
En el ejemplo 111, debido a que la variable de ámbito dentro de la función no se declara con la palabra
clave var , cuando se asigna la cadena "local" a la variable, en realidad estamos cambiando el valor de
la variable de ámbito en el programa principal. Siempre debe comenzar cada definición de una variable
con la palabra clave var para evitar que sucedan cosas como esta.
Anteriormente, mencionamos que JavaScript tiene un alcance de función. Esto significa que JavaScript
no tiene un alcance de bloque , a diferencia de muchos otros lenguajes de programación modernos.
Con el alcance de bloque, puedes declarar una variable dentro de un bloque de código y la variable no
es accesible fuera de ese bloque, como suele ocurrir con un bucle for de C++ o Java:
Aunque JavaScript no tiene alcance de bloque, pretendemos que lo tiene cuando escribimos bucles for
en este libro:
[Link][Link]
Machine Translated by Google
Recursión
En JavaScript, es posible realizar llamadas a funciones de forma recursiva. La función factorial() definida anteriormente también
se puede escribir de forma recursiva, de la siguiente manera:
} else
{ return numero * factorial(numero1);
}
}
imprimir(factorial(5));
Cuando se llama a una función de forma recursiva, los resultados del cálculo de la función se suspenden temporalmente
mientras la recursión está en curso. Para demostrar cómo funciona esto, aquí hay un diagrama para la función factorial()
5 * factorial(4) 5 * 4 *
factorial(3) 5 * 4 * 3 * factorial(2)
5 * 4 * 3 * 2 * factorial(1) 5 * 4 * 3 * 2 * 1
5*4*3*2
5*4*65*
24 120
Varios de los algoritmos analizados en este libro utilizan la recursión. En su mayor parte, Java Script es capaz
de manejar llamadas recursivas bastante profundas (este es un ejemplo de una llamada recursiva relativamente
superficial); pero en una o dos situaciones, un algoritmo requiere una llamada recursiva más profunda de la que
JavaScript puede manejar y, en su lugar, buscamos una solución iterativa para el algoritmo. Debe tener en
cuenta que cualquier función que utilice la recursión se puede reescribir de manera iterativa.
Los objetos se crean definiendo una función constructora que incluye declaraciones de las propiedades y funciones de un
objeto, seguidas de definiciones de las funciones. A continuación, se muestra la función constructora para un objeto de cuenta
corriente:
[Link][Link]
Machine Translated by Google
La palabra clave this se utiliza para vincular cada función y propiedad a una instancia de objeto.
Ahora veamos las definiciones de funciones para las declaraciones anteriores:
función retirar(cantidad) {
si (cantidad <= [Link]) { [Link] =
cantidad;
}
si (cantidad > [Link]) {
print("Fondos insuficientes");
}
}
Nuevamente, tenemos que usar la palabra clave this con la propiedad balance para que el
intérprete sepa a qué propiedad balance del objeto estamos haciendo referencia.
El ejemplo 112 proporciona la definición completa del objeto de verificación junto con un programa de
prueba.
función retirar(cantidad) {
si (cantidad <= [Link]) { [Link] =
cantidad;
}
si (cantidad > [Link]) {
print("Fondos insuficientes");
}
[Link][Link]
Machine Translated by Google
Resumen
En este capítulo se ofrece una descripción general de la forma en que utilizamos JavaScript
en el resto del libro. Intentamos seguir un estilo de programación que es común a muchos
programadores que están acostumbrados a utilizar lenguajes de estilo C, como C++ y Java.
Por supuesto, JavaScript tiene muchas convenciones que no siguen las reglas de esos
lenguajes, y sin duda las señalamos (como la declaración y el uso de variables) y le mostramos
la forma correcta de utilizar el lenguaje. También seguimos tantas de las buenas prácticas de
programación de JavaScript descritas por autores como John Resig, Douglas Crockford y
otros como podemos. Como programadores responsables, debemos tener en cuenta que es
tan importante que nuestros programas sean legibles para los humanos como que sean
ejecutados correctamente por las computadoras.
[Link][Link]
Machine Translated by Google
CAPÍTULO 2
Matrices
La matriz es la estructura de datos más común en la programación informática. Todos los lenguajes de
programación incluyen algún tipo de matriz. Como las matrices están incorporadas, suelen ser muy eficientes
y se consideran buenas opciones para muchos propósitos de almacenamiento de datos.
En este capítulo exploramos cómo funcionan las matrices en JavaScript y cuándo usarlas.
Una matriz de JavaScript es en realidad un tipo especializado de objeto de JavaScript, en el que los índices
son nombres de propiedades que pueden ser números enteros que se utilizan para representar
desplazamientos. Sin embargo, cuando se utilizan números enteros para los índices, se convierten en
cadenas internamente para cumplir con los requisitos de los objetos de JavaScript. Debido a que las matrices
de JavaScript son simplemente objetos, no son tan eficientes como las matrices de otros lenguajes de programación.
Si bien las matrices de JavaScript son, estrictamente hablando, objetos de JavaScript, son objetos
especializados categorizados internamente como matrices. La matriz es uno de los tipos de objetos de
JavaScript reconocidos y, como tal, existe un conjunto de propiedades y funciones que se pueden usar
con matrices.
Uso de matrices
Las matrices en JavaScript son muy flexibles. Hay varias formas diferentes de crear matrices, acceder a los
elementos de la matriz y realizar tareas como buscar y ordenar los elementos almacenados en una matriz.
JavaScript 1.5 también incluye funciones de matriz que permiten a los programadores
13
[Link][Link]
Machine Translated by Google
Trabajar con matrices mediante técnicas de programación funcional. Demostramos todas estas técnicas en las siguientes
secciones.
Creando matrices
La forma más sencilla de crear una matriz es declarando una variable de matriz utilizando la operación [] .
actor:
Cuando creas una matriz de esta manera, obtienes una matriz con una longitud de 0. Puedes verificar esto llamando a la
print([Link]); // muestra 0
Otra forma de crear una matriz es declarar una variable de matriz con un conjunto de elementos dentro del operador [] :
Puede llamar al constructor Array con un conjunto de elementos como argumentos para el constructor:
Finalmente, puedes crear una matriz llamando al constructor Array con un único argumento que especifique la longitud de
la matriz:
A diferencia de muchos otros lenguajes de programación, pero común para la mayoría de los lenguajes de script,
No es necesario que todos los elementos de una matriz de JavaScript sean del mismo tipo:
Podemos verificar que un objeto es una matriz llamando a la función [Link]() , de la siguiente
manera:
var números = 3;
var arr = [7,4,1776];
print([Link](number)); // muestra falso
print([Link](arr)); // muestra verdadero
Hemos cubierto varias técnicas para crear matrices. En cuanto a qué función es mejor, la mayoría de los expertos en
JavaScript recomiendan usar el operador [] , ya que dicen que es más eficiente que
14 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
llamando al constructor Array (ver JavaScript: La guía definitiva [O'Reilly] y JavaScript: Las partes buenas [O'Reilly]).
asignan a los elementos de una matriz mediante el operador [] en una declaración de asignación.
Por ejemplo, el siguiente bucle asigna los valores del 1 al 100 a una matriz:
Por supuesto, acceder a todos los elementos de una matriz de forma secuencial es mucho más fácil utilizando un
bucle for :
}
print(suma); // muestra 53
Tenga en cuenta que el bucle for se controla mediante la propiedad length en lugar de un literal
entero. Debido a que las matrices de JavaScript son objetos, pueden crecer más allá del tamaño
especificado cuando se crearon. Al usar la propiedad length , que devuelve la cantidad de elementos
que hay actualmente en la matriz, puede garantizar que su bucle procese todos los elementos de la
matriz.
matrices se pueden crear como resultado de llamar a la función split() en una cadena. Esta función divide una cadena
en un delimitador común, como un espacio para cada palabra, y crea una matriz que consta de las partes individuales
de la cadena.
El siguiente programa corto demuestra cómo funciona la función split() en una cadena simple:
var oración = "el rápido zorro marrón saltó sobre el perro perezoso"; var palabras = oració[Link]("
"); for (var i = 0; i < [Link]; ++i)
{ print("palabra " + i + ": " + palabras[i]);
Uso de matrices | 15
[Link][Link]
Machine Translated by Google
palabra 0: el
palabra 1: rápido
palabra 2: marrón
palabra 3: zorro
palabra 4: saltó
palabra 5: encima
palabra 6: la
palabra 7:
perezoso palabra 8: perro
Hay varias operaciones de agregación que se pueden realizar en matrices. En primer lugar, se puede asignar
una matriz a otra matriz:
Sin embargo, cuando se asigna una matriz a otra matriz, se asigna una referencia a la matriz asignada.
Cuando se realiza un cambio en la matriz original, ese cambio también se refleja en la otra matriz. El siguiente
fragmento de código demuestra cómo funciona esto:
Esto se denomina copia superficial. La nueva matriz simplemente apunta a los elementos de la matriz original.
Una mejor alternativa es hacer una copia profunda, de modo que cada uno de los elementos de la matriz
original se copie realmente en los elementos de la nueva matriz. Una forma eficaz de hacerlo es crear una
función que realice la tarea:
16 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
copy(nums, samenums);
nums[0] = 400;
print(sameneums[0]); // muestra 1
Otra operación de agregación que se puede realizar con matrices es mostrar el contenido de una matriz
mediante una función como print(). Por ejemplo:
1,2,3,4,5
Es posible que esta salida no sea particularmente útil, pero puedes usarla para mostrar el contenido de una
matriz cuando todo lo que necesitas es una lista simple.
Funciones de acceso
JavaScript proporciona un conjunto de funciones que puedes usar para acceder a los elementos de una matriz.
Estas funciones, llamadas funciones de acceso , devuelven alguna representación de la matriz de destino
como sus valores de retorno.
funciones de acceso más utilizadas es indexOf(), que busca si el argumento pasado a la función se encuentra
en la matriz. Si el argumento está contenido en la matriz, la función devuelve la posición de índice del
argumento. Si el argumento no se encuentra en la matriz, la función devuelve 1. A continuación, se muestra
un ejemplo:
" "
+ nombre + En posición + posición);
} else
"
{ imprimir(nombre + no encontrado en la matriz.");
}
Funciones de acceso | 17
[Link][Link]
Machine Translated by Google
Si tiene varias apariciones de los mismos datos en una matriz, la función indexOf() siempre
devolverá la posición de la primera aparición. Una función similar, lastIndex Of(), devolverá la
posición de la última aparición del argumento en la matriz o 1 si no se encuentra el argumento.
A continuación, se muestra un ejemplo:
var names = ["David", "Mike", "Cynthia", "Raymond", "Clayton", "Mike", "Jennifer"]; var name = "Mike"; var
firstPos =
[Link](nombre);
print("Primer encontrado " en la posición var lastPos
" "
+ nombre + encontrado "
= [Link](nombre); print("Último + primeraPos);
" "
+ nombre + En posición + últimaPos);
Hay dos funciones que devuelven representaciones de cadenas de una matriz: join() y toString().
Ambas funciones devuelven una cadena que contiene los elementos de la matriz delimitados por
comas. A continuación se muestran algunos ejemplos:
var nombres = ["David", "Cynthia", "Raymond", "Clayton", "Mike", "Jennifer"]; var nombrestr = [Link]();
print(nombrestr); //
David,Cynthia,Raymond,Clayton,Mike,Jennifer nombrestr = [Link](); print(nombrestr); //
David,Cynthia,Raymond,Clayton,Mike,Jennifer
Cuando llama a la función print() con un nombre de matriz, automáticamente llama a la función
toString() para esa matriz:
existentes Hay dos funciones de acceso que le permiten crear nuevas matrices a partir de matrices
existentes: concat() y splice(). La función concat() le permite juntar dos o más matrices para crear
una nueva matriz, y la función splice() le permite crear una nueva matriz a partir de un subconjunto
de una matriz existente.
Veamos primero cómo funciona concat() . La función se llama desde una matriz existente y su
argumento es otra matriz existente. El argumento se concatena al final de la matriz que llama a
concat(). El siguiente programa demuestra cómo funciona concat() :
var cisDept = ["Mike", "Clayton", "Terrill", "Danny", "Jennifer"]; var dmpDept = ["Raymond", "Cynthia",
"Bryan"]; var itDiv = [Link](dmp); imprimir(itDiv);
18 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
itDiv = [Link](cisDept);
imprimir(itDiv);
El programa genera:
La primera línea de salida muestra primero los datos de la matriz cis , y la segunda línea de salida
muestra primero los datos de la matriz dmp .
La función splice() crea una nueva matriz a partir del contenido de una matriz existente. Los
argumentos de la función son la posición inicial para realizar el splice y la cantidad de elementos
que se tomarán de la matriz existente. Así es como funciona el método:
var itDiv = ["Mike", "Clayton", "Terrill", "Raymond", "Cynthia", "Danny", "Jennifer"]; var dmpDept =
[Link](3,3); var cisDept = itDiv;
print(dmpDept); //
Raymond, Cynthia, Danny print(cisDept); // Mike,
Clayton, Terrill, Jennifer
Splice() también tiene otros usos , como modificar una matriz agregando o eliminando elementos.
Consulta el sitio web de Mozilla Developer Network para obtener más información.
Hay dos funciones mutadoras para agregar elementos a una matriz: push() y un shift(). La función
push() agrega un elemento al final de una matriz:
Usar push() es más intuitivo que usar la propiedad de longitud para extender una matriz:
Agregar datos al principio de una matriz es mucho más difícil que agregar datos al final de una
matriz. Para hacerlo sin el beneficio de una función mutadora, cada elemento existente
[Link][Link]
Machine Translated by Google
Es necesario desplazar la matriz una posición hacia arriba antes de agregar los datos nuevos. A continuación, se
incluye un código para ilustrar este escenario:
} nums[0] = nuevonum;
print(nums); // 1,2,3,4,5
Este código se vuelve más ineficiente a medida que aumenta el número de elementos almacenados en la matriz.
arrugas.
La función mutadora para agregar elementos de una matriz al comienzo de una matriz es un shift(). Así es como
funciona la función:
La segunda llamada a unshift() demuestra que puedes agregar múltiples elementos a una matriz con una sola llamada
a la función.
Sin funciones mutadoras, eliminar elementos del comienzo de una matriz requiere desplazar elementos hacia el
comienzo de la matriz, lo que provoca la misma ineficiencia que vemos al agregar elementos al comienzo de una
matriz:
} imprimir(numeros); // 1,2,3,4,5,
Además del hecho de que tenemos que desplazar los elementos hacia abajo para contraer la matriz, también nos
queda un elemento adicional. Lo sabemos por la coma adicional que vemos cuando mostramos el contenido de la
matriz.
20 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
La función mutadora que necesitamos para eliminar un elemento del principio de una matriz es shift(). Así es como
funciona la función:
Notarás que no quedan elementos adicionales al final de la matriz. Tanto pop() como shift() devuelven los valores
que eliminan, por lo que puedes recopilar los valores en una variable:
Agregar y quitar elementos del medio de una matriz Intentar agregar o quitar elementos al final de
una matriz conduce a los mismos problemas que encontramos cuando intentamos agregar o quitar elementos del
principio de una matriz: ambas operaciones requieren desplazar los elementos de la matriz hacia el principio o
hacia el final de la matriz. Sin embargo, hay una función mutadora que podemos usar para realizar ambas
operaciones: splice().
Para agregar elementos a una matriz usando splice(), debe proporcionar los siguientes argumentos:
comentarios:
elementos a eliminar (0 cuando está agregando elementos) • Los elementos que desea agregar
a la matriz
Veamos un ejemplo sencillo. El siguiente programa añade elementos en el medio de una matriz:
Los elementos que se unen en una matriz pueden ser cualquier lista de elementos que se pasen a la función, no
necesariamente una matriz de elementos con nombre. Por ejemplo:
En el ejemplo anterior, los argumentos 4, 5 y 6 representan la lista de elementos que queremos insertar en
nums.
A continuación se muestra un ejemplo del uso de splice() para eliminar elementos de una matriz:
[Link][Link]
Machine Translated by Google
dos últimas funciones mutadoras se utilizan para ordenar los elementos de una matriz de alguna manera. La
primera de ellas, reverse(), invierte el orden de los elementos de una matriz. A continuación, se muestra un
ejemplo de su uso:
A menudo necesitamos ordenar los elementos de una matriz. La función mutadora para esta tarea, sort(),
funciona muy bien con cadenas:
La función sort() ordena los datos lexicográficamente, asumiendo que los elementos de los datos son cadenas,
aunque en el ejemplo anterior los elementos son números. Podemos hacer que la función sort() funcione
correctamente para los números al pasar una función de ordenación como primer argumento a la función, que
sort() utilizará luego para ordenar los elementos de la matriz.
Esta es la función que sort() utilizará al comparar pares de elementos de la matriz para determinar su
orden correcto.
En el caso de los números, la función de ordenación puede simplemente restar un número de otro número. Si
el número devuelto es negativo, el operando izquierdo es menor que el operando derecho; si el número devuelto
es cero, el operando izquierdo es igual al operando derecho; y si el número devuelto es positivo, el operando
izquierdo es mayor que el operando derecho.
Con esto en mente, volvamos a ejecutar el pequeño programa anterior utilizando una función de ordenamiento:
La función sort() utiliza la función compare() para ordenar los elementos de la matriz numéricamente en lugar
de lexicográficamente.
22 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
Funciones iterativas
El último conjunto de funciones de matriz que examinamos son las funciones iteradoras. Estas funciones
aplican una función a cada elemento de una matriz, ya sea devolviendo un valor, un conjunto de valores o
una nueva matriz después de aplicar la función a cada elemento de una matriz.
de funciones iteradoras que analizaremos no generan una nueva matriz; en cambio, realizan una operación
en cada elemento de una matriz o generan un único valor a partir de una matriz.
La primera de estas funciones es forEach(). Esta función toma una función como argumento y aplica
la función llamada a cada elemento de una matriz. A continuación, se muestra un ejemplo de cómo
funciona:
función cuadrado(núm)
{ imprimir(núm, núm * núm);
}
11
24
39
4 16
5 25
6 36
7 49
8 64
9 81
10 100
La siguiente función iteradora, every(), aplica una función booleana a una matriz y devuelve verdadero si la
función puede devolver verdadero para cada elemento de la matriz. A continuación, se muestra un ejemplo:
función esPar(num) {
devuelve num % 2 == 0;
}
} else
{ print("no todos los números son pares");
}
Funciones iterativas | 23
[Link][Link]
Machine Translated by Google
El programa muestra:
Si cambiamos la matriz a:
El programa muestra:
La función some() tomará una función booleana y devolverá verdadero si al menos uno de los
elementos de la matriz cumple con el criterio de la función booleana. Por ejemplo:
} else
{ print("ningún número es par");
} nums = [1,3,5,7,9];
someEven = [Link](isEven); if
(someEven)
{ print("algunos números son pares");
} else
{ print("ningún número es par");
}
La función reduce() aplica una función a un acumulador y a los elementos sucesivos de una matriz
hasta que se alcanza el final de la matriz, lo que genera un único valor. A continuación, se muestra un
ejemplo del uso de reduce() para calcular la suma de los elementos de una matriz:
24 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
La función reduce() , junto con la función add() , funciona de izquierda a derecha, calculando una suma acumulada
de los elementos de la matriz, de la siguiente manera:
suma(1,2) > 3
suma(3,3) > 6
suma(6,4) > 10
suma(10,5) > 15
suma(15,6) > 21
suma(21,7) > 28
suma(28,8) > 36
suma(36,9) > 45
suma(45,10) > 55
var palabras = ["el ", "rápido ", "marrón ", "zorro "]; var
oración = [Link](concat);
print(oración); // muestra "el rápido zorro marrón"
JavaScript también proporciona una función reduceRight() , que funciona de manera similar a reduce(), pero
trabaja desde el lado derecho de la matriz hacia el izquierdo, en lugar de hacerlo desde la izquierda hacia la
derecha. El siguiente programa utiliza reduceRight() para invertir los elementos de una matriz .
formación:
var palabras = ["el ", "rápido ", "marrón ", "zorro "]; var
oración = [Link](concat);
print(oración); // muestra "zorro marrón rápido el"
funciones iterativas que devuelven nuevas matrices: map() y filter(). La función map() funciona como la función
forEach() , aplicando una función a cada elemento de una matriz. La diferencia entre las dos funciones es que
map() devuelve una nueva matriz con los resultados de la aplicación de la función. A continuación se muestra un
ejemplo:
función curva(calificación)
{ devolver calificación += 5;
}
Funciones iterativas | 25
[Link][Link]
Machine Translated by Google
función primera(palabra)
{ devolver palabra[0];
}
Para este último ejemplo, la matriz de acrónimos almacena la primera letra de cada palabra de la matriz
de palabras . Sin embargo, si queremos mostrar los elementos de la matriz como un acrónimo verdadero,
debemos deshacernos de las comas que se mostrarán si solo mostramos los elementos de la matriz
utilizando la función toString() implícita . Logramos esto llamando a la función join() con la cadena vacía
como separador.
La función filter() funciona de manera similar a every(), pero en lugar de devolver verdadero si todos los
elementos de una matriz satisfacen una función booleana, la función devuelve una nueva matriz que
consta de aquellos elementos que satisfacen la función booleana. A continuación, se muestra un ejemplo:
función esPar(num) {
devuelve num % 2 == 0;
}
función isOdd(num) {
devuelve num % 2 != 0;
}
Números pares:
2, 4, 6, 8, 10, 12, 14, 16, 18, 20
Números impares:
1,3,5,7,9,11,13,15,17,19
26 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
Todos los
grados: 39,43,89,19,46,54,48,5,13,31,27,95,62,64,35,75,79,88,73,74
Calificaciones para
aprobar: 89 , 95, 62, 64, 75, 79, 88, 73, 74
Por supuesto, también podemos usar filter() con cadenas. Aquí hay un ejemplo que aplica la regla
ortográfica “i antes de e excepto después de c”:
función afterc(str) { si
([Link]("cie") > 1) { devuelve
verdadero;
} devuelve falso;
}
var palabras = ["recibir", "engañar", "percibir", "engaño", "concebir"]; var palabras mal
escritas = [Link](afterc); print(palabras mal
escritas); // muestra recibir,percibir,concebir
matriz bidimensional está estructurada como una hoja de cálculo con filas y columnas. Para crear
una matriz bidimensional en JavaScript, tenemos que crear una matriz y luego hacer que cada
elemento de la matriz también sea una matriz. Como mínimo, necesitamos saber la cantidad de filas
que queremos que contenga la matriz. Con esa información, podemos crear una matriz bidimensional
con n filas y una columna:
El problema con este enfoque es que cada elemento de la matriz se establece como indefinido.
Una mejor manera de crear una matriz bidimensional es seguir el ejemplo de JavaScript:
[Link][Link]
Machine Translated by Google
The Good Parts (O'Reilly, p. 64). Crockford extiende el objeto de matriz de JavaScript con una función que
establece el número de filas y columnas y establece cada valor en un valor pasado a la función. Esta es su
definición:
} arr[i] = columnas;
} devolver arr;
}
También podemos crear una matriz bidimensional e inicializarla con un conjunto de valores en una línea:
var calificaciones = [[89, 77, 78],[76, 82, 81],[91, 94, 89]]; print(calificaciones[2]
[2]); // muestra 89
Para conjuntos de datos pequeños, esta es la forma más sencilla de crear una matriz bidimensional.
patrones fundamentales que se utilizan para procesar los elementos de una matriz bidimensional. Un patrón
enfatiza el acceso a los elementos de la matriz por columnas, y el otro patrón enfatiza el acceso a los elementos
de la matriz por filas. Usaremos la matriz de calificaciones creada en el fragmento de código anterior para
demostrar cómo funcionan estos patrones.
Para ambos patrones, utilizamos un conjunto de bucles for anidados . Para el procesamiento en columnas, el
bucle externo recorre las filas y el bucle interno procesa las columnas. Para la matriz de calificaciones , piense en
cada fila como un conjunto de calificaciones de un estudiante. Podemos calcular el promedio de las calificaciones
de cada estudiante sumando cada calificación de la fila a una variable total y luego dividiendo total por la cantidad
total de calificaciones de esa fila. Aquí está el código para ese proceso:
var calificaciones = [[89, 77, 78],[76, 82, 81],[91, 94, 89]]; var total = 0; var
promedio = 0.0;
para (var fila = 0; fila <
[Link]; ++fila) {
para (var col = 0; col < calificaciones[fila].length; ++col) {
total += calificaciones[fila][col];
28 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
}
promedio = total / calificaciones[fila].longitud;
" " +
print("Estudiante " + parseInt(fila+1) + promedio:
[Link](2));
total = 0;
promedio = 0.0;
}
Esto funciona porque cada fila contiene una matriz, y esa matriz tiene una propiedad de longitud.
Podemos usarlo para determinar cuántas columnas hay en la fila.
Para realizar un cálculo fila por fila, simplemente tenemos que invertir los bucles for de modo que
El bucle externo controla las columnas y el bucle interno controla las filas. Aquí está la cal
Culación para cada prueba:
total = 0;
promedio = 0.0;
}
[Link][Link]
Machine Translated by Google
Matrices irregulares
Una matriz irregular es una matriz en la que las filas de la matriz pueden tener una cantidad diferente
de elementos. Una fila puede tener tres elementos, mientras que otra fila puede tener cinco
elementos, mientras que otra fila puede tener solo un elemento. Muchos lenguajes de programación
tienen problemas para manejar matrices irregulares, pero JavaScript no, ya que podemos calcular
la longitud de cualquier fila.
Para darle un ejemplo, imagine la matriz de calificaciones donde los estudiantes tienen una cantidad
desigual de calificaciones registradas. Aún podemos calcular el promedio correcto para cada
estudiante sin cambiar el programa en absoluto:
var calificaciones = [[89, 77],[76, 82, 81],[91, 94, 89, 99]]; var total = 0;
var promedio =
0.0; para (var fila = 0;
fila < [Link]; ++fila) {
para (var col = 0; col < calificaciones[fila].length; ++col) {
total += calificaciones[fila][col];
Observe que el primer estudiante solo tiene dos calificaciones, mientras que el segundo tiene tres y
el tercero, cuatro. Como el programa calcula la longitud de la fila en el bucle interno, esta
irregularidad no causa ningún problema. Aquí se muestra el resultado del programa:
Matrices de objetos
Todos los ejemplos de este capítulo han consistido en matrices cuyos elementos han sido tipos de
datos primitivos, como números y cadenas. Las matrices también pueden constar de objetos, y
todas las funciones y propiedades de las matrices funcionan con objetos.
Por ejemplo:
30 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
Punto 1: 1, 2
Punto 2: 3, 5
Punto 3: 2, 8
Punto 4: 4, 4
Después de
empujar: 1, 2 3, 5 2, 8 4, 4 12, 3
Después del
turno:
3, 5
2, 8
4, 4 12, 3
Matrices en objetos
Podemos utilizar matrices para almacenar datos complejos en un objeto. Muchas de las estructuras de datos que
estudiamos en este libro se implementan como objetos de clase con una matriz subyacente que se utiliza para
almacenar datos.
El siguiente ejemplo demuestra muchas de las técnicas que utilizamos a lo largo del libro. En
el ejemplo, creamos un objeto que almacena los valores máximos observados semanalmente.
Matrices en objetos | 31
[Link][Link]
Machine Translated by Google
Temperatura. El objeto tiene funciones para agregar una nueva temperatura y calcular el promedio
de las temperaturas almacenadas en el objeto. Aquí está el código:
función weekTemps()
{ [Link] = [];
[Link] = add;
[Link] = promedio;
}
función agregar(temp)
{ [Link](temp);
}
Notarás que la función add() usa la función push() para agregar elementos a la matriz de datos de
Store , usando el nombre add() en lugar de push(). Usar un nombre más intuitivo para una operación
es una técnica común al definir funciones de objetos. No todos entenderán lo que significa insertar
un elemento de datos, pero todos saben lo que significa agregar un elemento de datos.
32 | Capítulo 2: Matrices
[Link][Link]
Machine Translated by Google
Ceremonias
1. Cree un objeto de calificaciones que almacene un conjunto de calificaciones de los estudiantes en un objeto.
Proporcione una función para agregar una calificación y una función para mostrar el promedio de calificaciones del estudiante.
2. Almacene un conjunto de palabras en una matriz y muestre el contenido tanto hacia adelante como hacia atrás.
3. Modifique el objeto weeklyTemps en el capítulo para que almacene los datos de un mes mediante una matriz
bidimensional. Cree funciones para mostrar el promedio mensual, el promedio de una semana específica y los
promedios de todas las semanas.
4. Cree un objeto que almacene letras individuales en una matriz y tenga una función para mostrar las letras como una
sola palabra.
Ejercicios | 33
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 3
Liza
Las listas son una de las herramientas de organización más comunes que las personas utilizan en su vida diaria.
Tenemos listas de cosas por hacer, listas de compras, listas de las diez cosas más importantes, listas de las diez cosas más importantes y muchos otros tipos.
Nuestros programas informáticos también pueden utilizar listas, en particular si sólo tenemos unos pocos
elementos para almacenar en forma de lista. Las listas son especialmente útiles si no tenemos que realizar
búsquedas en los elementos de la lista ni ordenarlos de algún modo. Cuando necesitamos realizar búsquedas
largas u ordenaciones complejas, las listas resultan menos útiles, especialmente con estructuras de datos más
complejas.
En este capítulo se presenta la creación de una clase de lista simple. Comenzamos con la definición de un tipo
de datos abstractos (ADT) de lista y luego demostramos cómo implementar el ADT. Concluimos el capítulo con
algunos problemas que se resuelven mejor con listas.
Para diseñar un ADT para una lista, debemos proporcionar una definición de la lista, incluidas sus propiedades,
así como las operaciones realizadas en ella y por ella.
Una lista es una secuencia ordenada de datos. Cada elemento de datos almacenado en una lista se denomina
elemento . En JavaScript, los elementos de una lista pueden ser de cualquier tipo de datos. No existe un número
predeterminado de elementos que se puedan almacenar en una lista, aunque el límite práctico será la cantidad
de memoria disponible para el programa que utilice la lista.
Una lista sin elementos es una lista vacía . La cantidad de elementos almacenados en una lista se denomina
longitud de la lista. Internamente, la cantidad de elementos de una lista se guarda en una variable listSize .
Puede agregar un elemento al final de una lista o puede insertar un elemento en una lista después de un
elemento existente o al principio de una lista. Los elementos se eliminan de una lista mediante una operación de
eliminación . También puede borrar una lista para eliminar todos sus elementos actuales.
35
[Link][Link]
Machine Translated by Google
Los elementos de una lista se muestran mediante una operación toString() , que muestra
todos los elementos, o con una operación getElement() , que muestra el valor de la
elemento actual .
Las listas tienen propiedades que describen la ubicación. Existe el principio y el final de una lista.
Puede moverse de un elemento de una lista al siguiente elemento utilizando la operación next() ,
y puedes retroceder a través de una lista usando la operación prev() . También puedes
moverse a una posición numerada en una lista usando la operación moveTo(n) , donde n especifica
La posición a la que se va a mover. La propiedad currPos indica la posición actual en una lista.
El ADT de lista no especifica una función de almacenamiento para una lista, sino para nuestra implementación
utilizará una matriz llamada dataStore.
Una implementación de la clase List se puede tomar directamente del ADT List que acabamos de definir.
Comenzaremos con una definición de una función constructora, aunque no es parte del ADT:
función Lista() {
[Link] = 0;
[Link] = 0;
[Link] = []; // inicializa una matriz vacía para almacenar elementos de la lista
[Link] = claro;
[Link] = encontrar;
36 | Capítulo 3: Listas
[Link][Link]
Machine Translated by Google
[Link] = toString;
[Link] = insertar;
[Link] = añadir;
[Link] = eliminar;
[Link] = frontal;
[Link] = fin;
[Link] = anterior;
[Link] = siguiente ;
[Link] = longitud ;
[Link] = currPos ;
[Link] = moveTo;
[Link] = getElement;
[Link] = longitud;
[Link] = contiene;
}
primera función que implementaremos es la función append() . Esta función agrega un nuevo
elemento a la lista en la siguiente posición disponible, que será igual al valor de la variable listSize :
función append(elemento)
{ [Link][[Link]++] = elemento;
}
función buscar(elemento) {
para (var i = 0; i < [Link]; ++i) { si ([Link][i]
== elemento) { devolver i;
} devuelve 1;
}
[Link][Link]
Machine Translated by Google
función find simplemente recorre el dataStore en busca del elemento especificado. Si se encuentra el elemento, la
función devuelve la posición en la que se encontró el elemento. Si no se encuentra el elemento, la función devuelve
1, que es un valor estándar que se devuelve cuando no se puede encontrar un elemento en una matriz. Podemos
La función remove() utiliza la posición devuelta por find() para unir la matriz dataStore en ese lugar. Después de
modificar la matriz, listSize se reduce en 1 para reflejar el nuevo tamaño de la lista. La función devuelve verdadero si
se elimina un elemento y falso en caso contrario. Este es el código:
{ [Link](foundAt,1); [Link];
devolver verdadero;
} devuelve falso;
}
es un buen momento para crear una función que nos permita ver los elementos de una lista. Aquí está el código para
una función toString() simple:
Estrictamente hablando, esta función devuelve un objeto de matriz y no una cadena, pero su utilidad es proporcionar
una vista del estado actual de un objeto, y simplemente devolver la matriz funciona adecuadamente para este propósito.
Hagamos una pausa en la implementación de nuestra clase List para ver qué tan bien funciona hasta ahora. Aquí hay
un programa de prueba breve que pone en práctica las funciones que hemos creado hasta ahora:
38 | Capítulo 3: Listas
[Link][Link]
Machine Translated by Google
[Link]("Barbara");
print([Link]());
[Link]("Raymond");
print([Link]());
función que analizaremos es insert(). ¿Qué sucede si, después de eliminar a Raymond de la lista anterior,
decidimos que necesitamos volver a ponerlo donde estaba al principio? Una función de inserción necesita
saber dónde insertar un elemento, por lo que por ahora diremos que la inserción ocurre después de un elemento
especificado que ya está en la lista. Con esto en mente, aquí está la definición de la función insert() :
} devuelve falso;
}
insert() utiliza la función auxiliar find() para determinar la posición de inserción correcta para el nuevo elemento
al encontrar el elemento especificado en el argumento after . Una vez que se encuentra esta posición, utilizamos
shift() para insertar el nuevo elemento en la lista. Luego incrementamos listSize en 1 y devolvemos true para
indicar que la inserción fue exitosa.
La función clear() utiliza el operador delete para eliminar la matriz dataStore y la siguiente línea vuelve a crear
la matriz vacía. La última línea establece los valores de listSize y pos en 0 para indicar el inicio de una nueva
lista.
[Link][Link]
Machine Translated by Google
contains() es útil cuando se desea verificar una lista para ver si un valor en particular forma parte de
ella. Aquí está la definición:
} devuelve falso;
}
Este último conjunto de funciones permite moverse a través de una lista, y la última función,
getElement(), muestra el elemento actual en una lista:
función prev() { si
([Link] > 0) { [Link];
}
}
función siguiente() { si
([Link] < [Link]1) { ++[Link];
}
}
Creemos una nueva lista de nombres para demostrar cómo funcionan estas funciones:
40 | Capítulo 3: Listas
[Link][Link]
Machine Translated by Google
[Link]();
print([Link]()); // muestra Clayton
Ahora avanzaremos dos veces y retrocederemos una vez, mostrando el elemento actual para demostrar cómo
funciona la función prev() :
El comportamiento que hemos demostrado en estos últimos fragmentos de código se refleja en el concepto
de iterador. Exploraremos los iteradores en la siguiente sección.
• Proporcionar un medio uniforme para acceder a elementos para diferentes tipos de almacenes de datos.
Se utiliza en la implementación de una clase de lista .
Con estas ventajas en mente, aquí se explica cómo utilizar un iterador para recorrer una lista:
[Link][Link]
Machine Translated by Google
El bucle for comienza estableciendo la posición actual al principio de la lista. El bucle continúa mientras el
valor de currPos sea menor que la longitud de la lista. Cada vez que se repite el bucle, la posición actual se
mueve un elemento hacia adelante mediante el uso de la función next() .
También podemos recorrer una lista hacia atrás utilizando un iterador. Aquí está el código:
El bucle comienza en el último elemento de la lista y se mueve hacia atrás utilizando la función prev() mientras
la posición actual sea mayor o igual a 0.
Los iteradores se utilizan únicamente para moverse a través de una lista y no deben combinarse con ninguna
función para agregar o eliminar elementos de una lista.
Para demostrar cómo utilizar listas, vamos a construir un sistema que se pueda utilizar en la simulación de un
sistema de quiosco de alquiler de vídeos como Redbox.
Para obtener la lista de videos disponibles en el kiosco en nuestro programa, necesitamos poder leer los
datos de un archivo. Primero tenemos que crear un archivo de texto que contenga la lista de videos disponibles
usando un editor de texto. Llamamos al archivo [Link]. Aquí está el contenido de los archivos (estas películas
son las 20 mejores películas según la votación de los usuarios de IMDB al 5 de octubre de 2013):
padrino: Parte II 4.
el feo y el malo 6.
Schindler
8. El caballero oscuro 9.
10. El club de la
42 | Capítulo 3: Listas
[Link][Link]
Machine Translated by Google
13. El Señor de los Anillos: La Comunidad del Anillo 14. Origen 15.
Uno de los
Nuestros
16. La guerra de las galaxias
18. La Matriz
Ciudad de Dios
Ahora necesitamos un fragmento de código para leer el contenido del archivo en nuestro programa:
Esta línea realiza dos tareas. En primer lugar, lee el contenido de nuestro archivo de texto de películas en el
programa, read([Link]); y, en segundo lugar, divide el archivo en líneas individuales utilizando el carácter
de nueva línea como delimitador. Esta salida se almacena luego como una matriz en la variable mov ies .
Esta línea de código funciona hasta cierto punto, pero no es perfecta. Cuando los elementos del archivo de
texto se dividen en la matriz, el carácter de nueva línea se reemplaza por un espacio. Si bien un solo espacio
parece bastante inocuo, tener un espacio adicional en una cadena puede causar estragos cuando se realizan
comparaciones de cadenas. Por lo tanto, debemos agregar un bucle que elimine el espacio de cada elemento
de la matriz utilizando la función trim() . Este código funcionará mejor en una función, así que creemos una
función para leer datos de un archivo y almacenarlos en una matriz:
} devolver arr;
}
siguiente paso es tomar la matriz de películas y almacenar su contenido en una lista. Así es como lo hacemos:
Ahora podemos escribir una función para mostrar la lista de películas disponibles en el quiosco:
[Link][Link]
Machine Translated by Google
función displayList(lista) {
para ([Link](); [Link]() < [Link](); [Link]()) { imprimir([Link]());
La función displayList() funciona bien con tipos nativos, como listas compuestas de cadenas, pero
no funcionará con objetos Customer , que se definen a continuación. Modifiquemos la función para
que, si descubre que la lista está compuesta de objetos Customer , muestre esos objetos en
consecuencia. Esta es la nueva definición de displayList():
función displayList(lista) {
para ([Link](); [Link]() < [Link](); [Link]()) {
si ([Link]() instancia de Cliente)
{ imprimir([Link]()["nombre"] + ", " +
[Link]()["película"]);
} de lo
contrario { imprimir([Link]());
}
}
}
Para cada objeto de la lista, utilizamos el operador instanceof para comprobar si el objeto es un
objeto Customer . Si es así, recuperamos el nombre y la película que el cliente ha visto utilizando
cada una de las dos propiedades como índice para recuperar el valor asociado. Si el objeto no es
un Customer, el código simplemente devuelve el elemento.
Ahora que tenemos nuestra lista de películas resuelta, necesitamos crear una lista para almacenar
los clientes que sacan películas en el quiosco:
Esto contendrá objetos de Cliente , que se componen del nombre del cliente y la película que ha
visto. Esta es la función constructora para el objeto de Cliente :
A continuación, necesitamos una función que permita a un cliente retirar una película. Esta función
toma dos argumentos: el nombre del cliente y la película que desea retirar. Si la película está
disponible, la función elimina la película de la lista de películas del quiosco y la agrega a la lista del
cliente. Usaremos la función contains() de la clase List para esta tarea.
44 | Capítulo 3: Listas
[Link][Link]
Machine Translated by Google
} else
"
{ imprimir(película + no está disponible.");
}
}
La función primero comprueba si la película solicitada está disponible. Si la película está disponible, se crea
un objeto Cliente con el título de la película y el nombre del cliente. El objeto Cliente se agrega a la lista de
clientes y la película se elimina de la lista de películas. Si la película no está disponible, se muestra un
mensaje simple que lo indica.
La salida del programa muestra la lista de películas con "El Padrino" eliminado, seguida de la lista de
clientes que tienen películas prestadas.
Agreguemos algunos títulos a la salida de nuestro programa para que sea más fácil de leer, junto con
alguna entrada interactiva:
Películas disponibles:
[Link][Link]
Machine Translated by Google
Cadena perpetua
El Padrino
El Padrino: Parte II
Ficción popular
El bueno, el malo y el feo
12 hombres enojados
La lista de Schindler
El caballero oscuro
El Señor de los Anillos: El Retorno del Rey
Club de lucha
Star Wars: Episodio V El Imperio Contraataca
Alguien voló sobre el nido del cuco
El Señor de los Anillos: La Comunidad del Anillo
Comienzo
Uno de los nuestros
Películas ya disponibles
Cadena perpetua
El Padrino: Parte II
Ficción popular
El bueno, el malo y el feo
12 hombres enojados
La lista de Schindler
El caballero oscuro
El Señor de los Anillos: El Retorno del Rey
Club de lucha
Star Wars: Episodio V El Imperio Contraataca
Alguien voló sobre el nido del cuco
El Señor de los Anillos: La Comunidad del Anillo
Comienzo
Uno de los nuestros
Podemos agregar otras funciones para que nuestro sistema de quiosco de alquiler de videos sea más sólido.
Podrá explorar algunas de estas funciones adicionales en los ejercicios que se presentan a continuación.
46 | Capítulo 3: Listas
[Link][Link]
Machine Translated by Google
Ceremonias
1. Escriba una función que inserte un elemento en una lista solo si el elemento que se va a insertar es
más grande que cualquiera de los elementos que se encuentran actualmente en la lista. Más grande
puede significar mayor que cuando se trabaja con valores numéricos, o más abajo en el alfabeto,
cuando se trabaja con valores textuales.
2. Escriba una función que inserte un elemento en una lista solo si el elemento a insertar es más pequeño
que cualquiera de los elementos actuales en la lista.
3. Cree una clase Persona que almacene el nombre y el género de una persona. Cree una lista de al
menos 10 objetos Persona . Escriba una función que muestre todas las personas de la lista que
tengan el mismo género.
4. Modificar el programa del quiosco de alquiler de vídeos para que, cuando se retira una película, se añada a
una lista de películas alquiladas. Mostrar esta lista cada vez que un cliente retira una película.
5. Cree una función de registro para el programa de quiosco de alquiler de videos para que las películas
devueltas se eliminen de la lista de películas alquiladas y se vuelvan a agregar a la lista de películas
disponibles.
Ejercicios | 47
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 4
Pilas
Las listas son una forma natural de organizar los datos. Ya hemos visto cómo utilizar la clase List para
organizar los datos en una lista. Cuando el orden de los datos que se almacenan no importa, o cuando
no es necesario buscar los datos almacenados, las listas funcionan de maravilla. Sin embargo, para
otras aplicaciones, las listas simples son demasiado simples y necesitamos una estructura de datos
más compleja, similar a una lista.
Una estructura similar a una lista que se puede utilizar para resolver muchos problemas en informática es la pila.
Las pilas son estructuras de datos eficientes porque los datos se pueden agregar o quitar solo desde la parte superior de una
pila, lo que hace que estos procedimientos sean rápidos y fáciles de implementar. Las pilas se utilizan ampliamente en las
implementaciones de lenguajes de programación para todo, desde la evaluación de expresiones hasta el manejo de llamadas
a funciones.
Operaciones de pila
Una pila es una lista de elementos a los que se puede acceder solo desde un extremo de la lista, que se denomina parte
superior. Un ejemplo común y real de pila es la pila de bandejas de una cafetería. Las bandejas siempre se retiran desde la
parte superior y, cuando se vuelven a colocar en la pila después de lavarlas, se colocan en la parte superior de la pila. La pila
se conoce como estructura de datos LIFO (último en entrar, primero en salir).
Debido a la naturaleza de "último en entrar, primero en salir" de la pila, no se puede acceder a ningún elemento que no se
encuentre en la parte superior de la pila. Para llegar a un elemento que se encuentra en la parte inferior de la pila, primero
hay que deshacerse de todos los elementos que se encuentran por encima de él.
Las dos operaciones principales de una pila son agregar elementos a la pila y quitar elementos de la misma. Los elementos
se agregan a la pila mediante la operación push . Los elementos se quitan de la pila mediante la operación pop . Estas
49
[Link][Link]
Machine Translated by Google
Otra operación común en una pila es ver el elemento que se encuentra en la parte superior de la misma.
La operación pop visita el elemento superior de una pila, pero elimina permanentemente el elemento de
la pila. La operación peek devuelve el valor almacenado en la parte superior de una pila sin eliminarlo de
la pila.
Para realizar un seguimiento de dónde está el elemento superior, así como también de dónde agregar un
nuevo elemento, usamos una variable superior que se incrementa cuando insertamos nuevos elementos
en la pila y se decrementa cuando sacamos elementos de la pila.
Si bien las operaciones principales asociadas con una pila son empujar, sacar y mirar, existen otras
operaciones que debemos realizar y propiedades que debemos examinar. La operación clear elimina
todos los elementos de una pila. La propiedad length contiene la cantidad de elementos que contiene una
pila. También definimos una propiedad empty para saber si una pila no tiene elementos, aunque también
podemos usar la propiedad length para esto.
Comenzamos nuestra implementación de pila definiendo la función constructora para una clase Pila :
función Stack()
{ [Link] = [];
[Link] = 0;
[Link] = push;
[Link] = pop;
[Link] = peek;
}
50 | Capítulo 4: Pilas
[Link][Link]
Machine Translated by Google
La matriz que almacena los elementos de la pila se llama dataStore. El constructor la establece como una
matriz vacía. La variable top lleva un registro de la parte superior de la pila y el constructor la establece
inicialmente en 0, lo que indica que la posición 0 de la matriz es la parte superior de la pila, al menos hasta
que se inserta un elemento en la pila.
La primera función que se debe implementar es la función push() . Cuando insertamos un nuevo elemento
en una pila, tenemos que almacenarlo en la posición superior e incrementar la variable superior de modo
que la nueva posición superior sea la siguiente posición vacía en la matriz. Este es el código:
función push(elemento)
{ [Link][[Link]++] = elemento;
}
Preste especial atención a la ubicación del operador de incremento después de la llamada a [Link].
Colocar el operador allí garantiza que el valor actual de top se use para colocar el nuevo elemento en la
parte superior de la pila antes de que se incremente top .
La función pop() hace lo contrario de la función push() : devuelve el elemento en la posición superior de la
pila y luego decrementa la variable superior :
función pop()
{ devolver [Link][[Link]];
}
La función peek() devuelve el elemento superior de la pila accediendo al elemento en la posición superior 1
de la matriz:
función peek()
{ devolver [Link][[Link]1];
}
Si llamas a la función peek() en una pila vacía, obtendrás undefined como resultado. Esto se debe a que
no hay ningún valor almacenado en la posición superior de la pila, ya que está vacía.
Habrá situaciones en las que necesitará saber cuántos elementos están almacenados en una pila. La
función length() devuelve este valor devolviendo el valor de top:
función longitud()
{ devolver [Link];
}
Finalmente, podemos limpiar una pila simplemente estableciendo la variable superior nuevamente en 0:
función clear()
{ [Link] = 0;
}
función Stack()
{ [Link] = [];
[Link][Link]
Machine Translated by Google
[Link] = 0;
[Link] = push;
[Link] = pop;
[Link] = peek;
[Link] = clear;
[Link] = length;
}
función push(elemento)
{ [Link][[Link]++] = elemento;
}
función peek()
{ devolver [Link][[Link]1];
}
función pop()
{ devolver [Link][[Link]];
}
función longitud()
{ devolver [Link];
}
longitud: 3
Bryan
52 | Capítulo 4: Pilas
[Link][Link]
Machine Translated by Google
Se devuelve el penúltimo valor, indefinido, porque una vez que se borra una pila, no hay ningún valor
en la posición superior y cuando echamos un vistazo a la parte superior de la pila, se devuelve
indefinido .
Se puede utilizar una pila para convertir un número de una base a otra. Dado un número, n, que
queremos convertir a una base, b, aquí está el algoritmo para realizar la conversión.
conversión:
2. Reemplaza n por n/ b.
4. Construya la cadena de números convertida haciendo estallar la pila hasta que esté vacía.
Podemos implementar este algoritmo muy fácilmente usando una pila en JavaScript. Aquí está la
definición de una función para convertir un número a cualquiera de las bases del 2 al 9:
[Link][Link]
Machine Translated by Google
}
devuelve convertido;
}
El ejemplo 43 demuestra cómo utilizar esta función para conversiones de base 2 y base 8.
Palíndromos
Un palíndromo es una palabra, frase o número que se escribe igual de adelante hacia atrás y de atrás hacia adelante.
barrio. Por ejemplo, “papá” es un palíndromo; “coche de carreras” es un palíndromo; “Un hombre, un plan, un
canal: Panamá” es un palíndromo si se quitan los espacios y se ignora la puntuación;
y 1.001 es un palíndromo numérico.
Podemos utilizar una pila para determinar si una cadena dada es o no un palíndromo.
la cadena original y empuja cada carácter hacia una pila, moviéndose de izquierda a derecha. Cuando
Se llega al final de la cadena, la pila contiene la cadena original en orden inverso,
con la última letra en la parte superior de la pila y la primera letra en la parte inferior de la pila,
como se muestra en la Figura 42.
54 | Capítulo 4: Pilas
[Link][Link]
Machine Translated by Google
Figura 42. Uso de una pila para determinar si una palabra es un palíndromo
Una vez que la cadena original completa está en la pila, podemos crear una nueva cadena extrayendo
cada letra de la pila. Este proceso creará la cadena original en orden inverso. Luego, simplemente
comparamos la cadena original con el trabajo inverso y, si son iguales, la cadena es un palíndromo.
El ejemplo 44 presenta un programa, menos el código de la clase Stack , que determina si una cadena
dada es un palíndromo.
} si (palabra == rpalabra)
{ devuelve verdadero;
} demás {
[Link][Link]
Machine Translated by Google
devuelve falso;
}
}
si (isPalindrome(palabra)) {
"
print(palabra + es un palíndromo.");
}
demás {
"
imprimir(palabra + no es un palíndromo.");
}
Hola no es un palíndromo.
coche de carreras es un palíndromo.
Demostración de la recursión
Las pilas se utilizan a menudo en la implementación de lenguajes de programación informática.
El área donde se utilizan las pilas es en la implementación de la recursión. Está más allá del alcance de este artículo.
libro para demostrar exactamente cómo se utilizan las pilas para implementar procedimientos recursivos,
pero podemos usar pilas para simular procesos recursivos. Si estás interesado en aprender
Más sobre la recursión, un buen punto de partida es esta página web que realmente utiliza JavaScript
para describir cómo funciona la recursión.
Para demostrar cómo se implementa la recursión usando una pila, consideremos una función recursiva.
Definición de la función factorial. En primer lugar, aquí se presenta una definición de factorial para el número
5:
5! = 5 * 4 * 3 * 2 * 1 = 120
Aquí hay una función recursiva para calcular el factorial de cualquier número:
función factorial(n) {
si (n === 0) {
devuelve 1;
}
demás {
volver n * factorial(n1);
}
}
56 | Capítulo 4: Pilas
[Link][Link]
Machine Translated by Google
Para simular el cálculo del 5! usando una pila, primero coloque los números del 5 al 1 en una pila.
Luego, dentro de un bucle, extraiga cada número y multiplíquelo por el producto corriente, lo que
dará como resultado la respuesta correcta, 120. El ejemplo 45 contiene el código de la función,
junto con un programa de prueba.
} var producto = 1;
mientras ([Link]() > 0)
{ producto *= [Link]();
} devolver producto;
}
Ceremonias
1. Se puede utilizar una pila para garantizar que una expresión aritmética tenga paréntesis
equilibrados. Escriba una función que tome una expresión aritmética como argumento y
devuelva la posición en la expresión donde falta un paréntesis. Un ejemplo de una expresión
aritmética con paréntesis no equilibrados es 2,3 + 23 / 12 + (3,14159
* .24.
Usando dos pilas, una para los operandos y otra para los operadores, diseñe e implemente una
función de JavaScript que convierta expresiones infijas en expresiones postfijas y luego use las
pilas para evaluar la expresión.
3. Un ejemplo de una pila del mundo real es un dispensador de caramelos Pez. Imagina que tu
dispensador virtual de caramelos Pez está lleno de caramelos de colores rojo, amarillo y blanco
y que no te gustan los amarillos. Escribe un programa que utilice una pila (y quizás más de una)
para eliminar los amarillos sin cambiar el orden de los demás caramelos en el dispensador.
Ejercicios | 57
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 5
Colas
Una cola es un tipo de lista en la que los datos se insertan al final y se eliminan desde el principio. Las colas
se utilizan para almacenar datos en el orden en el que aparecen, a diferencia de una pila, en la que el último
dato introducido es el primer elemento utilizado para el procesamiento.
Piense en una cola como si fuera la cola de su banco, donde la primera persona que entra en la cola es la
primera en ser atendida y, a medida que más clientes entran en la cola, esperan en la parte de atrás hasta
que es su turno para ser atendidos.
Una cola es un ejemplo de una estructura de datos FIFO (primero en entrar, primero en salir). Las colas se
utilizan para ordenar los procesos enviados a un sistema operativo o a un administrador de impresión, y las
aplicaciones de simulación utilizan colas para modelar escenarios como clientes haciendo cola en un banco o
en una tienda de comestibles.
operaciones principales que involucran colas son insertar un nuevo elemento en una cola y eliminar un
elemento de una cola. La operación de inserción se denomina poner en cola y la operación de eliminación se
denomina sacar de la cola. La operación poner en cola inserta un nuevo elemento al final de una cola y la
operación sacar de la cola elimina un elemento del frente de una cola. La Figura 51 ilustra estas operaciones.
59
[Link][Link]
Machine Translated by Google
Otra operación importante de la cola es ver el elemento que se encuentra al principio de la cola.
Esta operación se denomina peek. La operación peek devuelve el elemento almacenado al
principio de una cola sin eliminarlo de la misma. Además de examinar el elemento que se
encuentra al principio, también necesitamos saber cuántos elementos están almacenados en una
cola, lo que podemos satisfacer con la propiedad length ; y necesitamos poder eliminar todos los
elementos de una cola, lo que se realiza con la operación clear .
La función push() coloca su argumento en la primera posición abierta de una matriz, que siempre
será la parte final de la matriz, incluso cuando no haya otros elementos en la matriz.
He aquí un ejemplo:
nombres =
[]; [Link]("Cynthia");
[Link]("Jennifer");
print(nombres); // muestra Cynthia,Jennifer
[Link]();
print(names); // muestra a Jennifer
60 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
Ahora estamos listos para comenzar a implementar la clase Queue definiendo la función constructora:
función Queue()
{ [Link] = [];
[Link] = poner en cola;
[Link] = dequeue;
[Link] = front; [Link]
= back; [Link] =
toString; [Link] = vacío;
función enqueue(elemento) {
[Link](elemento);
}
Podemos examinar los elementos frontales y posteriores de una cola utilizando estas funciones:
función back()
{ devolver [Link][[Link]1];
}
También necesitamos una función toString() para mostrar todos los elementos en una cola:
} devuelve retStr;
}
Por último, necesitamos una función que nos permita saber si una cola está vacía:
función vacía() {
si ([Link] == 0) {
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}
[Link][Link]
Machine Translated by Google
El ejemplo 51 presenta la definición completa de la clase Queue junto con un programa de prueba.
función Queue()
{ [Link] = [];
[Link] = poner en cola;
[Link] = dequeue;
[Link] = front; [Link]
= back; [Link] =
toString; [Link] = vacío;
función enqueue(elemento) {
[Link](elemento);
}
función back()
{ devolver [Link][[Link]1];
}
} devuelve retStr;
}
función vacía() {
si ([Link] == 0) {
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}
// programa de prueba
62 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
print([Link]());
[Link]();
print([Link]()); print("Al
"
frente de la cola: print("Al + [Link]()); +
"
final de la cola: [Link]());
Meredith
Cintia
Jennifer
Cintia
Jennifer
Como mencionamos anteriormente, las colas se utilizan a menudo para simular situaciones en las que las
personas tienen que hacer cola. Un escenario que podemos simular con una cola es un baile de plazas para solteros.
Cuando los hombres y las mujeres llegan a este baile, entran en el salón de baile y se colocan en la fila
correspondiente a su género. A medida que queda espacio libre en la pista de baile, se eligen las parejas de
baile tomando al primer hombre y a la primera mujer de la fila. El siguiente hombre y la siguiente mujer pasan al
frente de sus respectivas filas. A medida que las parejas de baile pasan a la pista de baile, se anuncian sus
nombres. Si una pareja abandona la pista y no hay un hombre y una mujer al frente de cada fila, se anuncia
este hecho.
Esta simulación almacenará los nombres de los hombres y mujeres que participan en el baile en un archivo de
texto. Este es el archivo que utilizaremos para la simulación:
F. Allison McMillan
M. Frank Opitz
M. Mason McMillan
M. Clayton Ruff
F. Cheryl Ferenback
M. Raymond Williams
F. Jennifer Ingram
M. Bryan Frazer
M. David Durr
Sr. Danny Martín
F Aurora Adney
[Link][Link]
Machine Translated by Google
[Link] = sexo;
}
A continuación necesitamos una función para cargar los bailarines del archivo al programa:
} else
{ [Link](new Bailarín(nombre, sexo));
}
}
}
Los nombres se leen del archivo de texto en una matriz. Luego, la función recorta el carácter de nueva
línea de cada línea. El segundo bucle divide cada línea en una matriz de dos elementos, por sexo y por
nombre. Luego, la función examina el elemento de sexo y asigna al bailarín a la cola correspondiente.
La siguiente función empareja a los bailarines masculinos y femeninos y anuncia las parejas:
} imprimir();
}
El ejemplo 52 presenta todas las funciones anteriores, así como un programa de prueba y la clase
Queue .
función Queue()
{ [Link] = [];
[Link] = enqueue;
[Link] = dequeue;
[Link] = front; [Link]
= back; [Link] =
toString;
64 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
[Link]ío = vacío;
}
función enqueue(elemento) {
[Link](elemento);
}
función back()
{ devolver [Link][[Link]1];
}
} devuelve retStr;
}
función vacía() {
si ([Link] == 0) {
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}
[Link][Link]
Machine Translated by Google
} else
{ [Link](new Dancer(nombre, sexo));
}
}
}
} imprimir();
}
// programa de prueba
} if (![Link]())
"
{ print([Link]().name + está esperando para bailar.");
}
[Link] = contar;
66 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
En el ejemplo 53, cambiamos el programa de prueba para utilizar esta nueva función.
Este proceso se lleva a cabo mediante el uso de un conjunto de colas. Esta técnica de ordenación se denomina
ordenación por radix (consulte Estructuras de datos con C++ [Prentice Hall]). No es el algoritmo de ordenación
más rápido, pero sí demuestra un uso interesante de las colas.
La ordenación por base funciona haciendo dos pasadas sobre un conjunto de datos, en este caso el conjunto de
números enteros del 0 al 99. La primera pasada ordena los números según el dígito de las unidades y la segunda
pasada ordena los números según el dígito de las decenas. Cada número se coloca en un contenedor según el
dígito que ocupa cada uno de estos dos lugares. Dados estos números:
El primer paso de la ordenación por radix da como resultado la siguiente configuración de bin:
Contenedor 0:
Contenedor 1: 91, 31
Contenedor 2: 92, 22
Contenedor 3:
Contenedor 4:
[Link][Link]
Machine Translated by Google
Contenedor 6:46
Contenedor 7:
Contenedor 8:
Contenedor 9:
A continuación, los números se ordenan por el dígito de las decenas en los contenedores correspondientes:
Contenedor 0:
Contenedor 1:15
Contenedor 2:22
Contenedor 3: 31, 35
Contenedor 4:46
Contenedor 5:
Contenedor 6:
Contenedor 7:
Contenedor 8:85
Papelera 9:91 , 92
Finalmente, saca los números de los contenedores y colócalos nuevamente en una lista, y obtendrás la
siguiente lista ordenada de números enteros:
Podemos implementar este algoritmo utilizando colas para representar los contenedores. Necesitamos nueve
colas, una para cada dígito. Almacenaremos las colas en una matriz. Utilizamos las operaciones de módulo y
división de números enteros para determinar los dígitos 1 y 10. El resto del algoritmo implica agregar números
a sus colas correspondientes, sacar los números de las colas para reordenarlos en función del dígito 1 y
repetir el proceso para el dígito 10. El resultado es un conjunto ordenado de números enteros.
Primero, aquí está la función que distribuye números por el dígito de lugar (1 o 10):
68 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
nums[i++] = colas[dígito].dequeue();
}
}
}
El ejemplo 54 presenta un programa completo para realizar una ordenación por base, junto con una función
para mostrar el contenido de una matriz.
} de lo
contrario { colas[[Link](nums[i] / 10)].enqueue(nums[i]);
}
}
}
}
}
// programa principal
[Link][Link]
Machine Translated by Google
6 15 54 69 71 76 77 79 84 99
Colas de prioridad
En el curso de las operaciones normales de cola, cuando se elimina un elemento de una cola, ese
elemento siempre es el primer elemento que se insertó en la cola. Sin embargo, existen ciertas
aplicaciones de colas que requieren que los elementos se eliminen en un orden distinto al de primero
en entrar, primero en salir. Cuando necesitamos simular una aplicación de este tipo, necesitamos crear
una estructura de datos denominada cola de prioridad.
Una cola de prioridad es aquella en la que se eliminan elementos de la cola en función de una restricción
de prioridad. Por ejemplo, la sala de espera del departamento de urgencias de un hospital funciona con
una cola de prioridad. Cuando un paciente entra en el departamento de urgencias, es visto por una
enfermera de triaje. El trabajo de esta enfermera es evaluar la gravedad de la condición del paciente y
asignarle un código de prioridad. Los pacientes con un código de prioridad alta son vistos antes que los
pacientes con un código de prioridad más bajo, y los pacientes que tienen el mismo código de prioridad
son vistos por orden de llegada o por orden de entrada.
Comencemos a construir un sistema de cola de prioridad definiendo primero un objeto que almacenará
los elementos de la cola:
El valor del código será un número entero que representa la prioridad o gravedad del paciente.
Ahora necesitamos redefinir la función dequeue() que elimina el elemento de la cola con la prioridad
más alta. Definiremos el elemento con la prioridad más alta como el elemento con el código de prioridad
más bajo. Esta nueva función dequeue() se moverá a través de la matriz subyacente de la cola y
encontrará el elemento con el código más bajo. Luego, la función usa la función splice() para eliminar
el elemento con la prioridad más alta. Aquí está la nueva definición de dequeue():
70 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
función dequeue() {
var prioridad = [Link][0].code; para (var i = 1; i <
[Link]; ++i) { si ([Link][i].code < prioridad) { prioridad
= i;
} devuelve [Link](prioridad,1);
}
La función dequeue() utiliza una búsqueda secuencial simple para encontrar el elemento con el
código de mayor prioridad (el número más bajo; 1 tiene mayor prioridad que 5). La función devuelve
una matriz de un elemento: el que se eliminó de la cola.
Por último, agregamos una función toString() modificada para manejar objetos Paciente :
} devuelve retStr;
}
+ visto[0].nombre);
Colas prioritarias | 71
[Link][Link]
Machine Translated by Google
Código Smith: 5
Código de Jones: 4
Código de Fehrenbach: 6
Código marrón: 1
Código Ingram: 1
Ceremonias
1. Modifique la clase Queue para crear una clase Deque . Una deque es una estructura similar a una cola
que permite agregar y quitar elementos tanto del principio como del final de la lista. Pruebe su clase en
un programa.
2. Utilice la clase Deque que creó en el Ejemplo 51 para determinar si una palabra dada es una
palíndromo.
3. Modifique el ejemplo de cola de prioridad del Ejemplo 55 para que los elementos de mayor prioridad
tengan números más altos en lugar de números más bajos. Pruebe su implementación con el ejemplo
del capítulo.
4. Modifique el ejemplo de ED (Ejemplo 55) para que el usuario pueda controlar la actividad en el ED. Cree un
sistema de menú que permita al usuario elegir entre las siguientes actividades:
72 | Capítulo 5: Colas
[Link][Link]
Machine Translated by Google
CAPÍTULO 6
Listas enlazadas
Sin embargo, el principal problema con el uso de matrices en JavaScript es que las matrices en JavaScript
se implementan como objetos, lo que hace que sean menos eficientes que las matrices creadas en
lenguajes como C++ y Java (ver Crockford, Capítulo 6).
Cuando se determina que las operaciones realizadas en una matriz son demasiado lentas para su uso
práctico, se puede considerar el uso de la lista enlazada como una estructura de datos alternativa. La lista
enlazada se puede utilizar en casi todas las situaciones en las que se utiliza una matriz unidimensional,
excepto cuando se necesita acceso aleatorio a los elementos de una lista. Cuando se requiere acceso
aleatorio, una matriz es la mejor estructura de datos para utilizar.
73
[Link][Link]
Machine Translated by Google
Una lista enlazada es una colección de objetos llamados nodos. Cada nodo está enlazado a un nodo sucesor
en la lista mediante una referencia de objeto. La referencia a otro nodo se denomina enlace.
En la Figura 61 se muestra un ejemplo de una lista enlazada .
Mientras que los elementos de una matriz se referencian por su posición, los elementos de una lista enlazada
se referencian por su relación con los demás elementos de la lista enlazada. En la Figura 61, decimos que
“pan” sigue a “leche”, no que “pan” está en la segunda posición. Moverse a través de una lista enlazada implica
seguir los enlaces de la lista desde el nodo inicial hasta el nodo final (sin incluir el nodo de encabezado, que a
veces se utiliza como gancho para entrar en una lista enlazada). Algo más que se debe notar en la figura es
que marcamos el final de una lista enlazada apuntando a un nodo nulo.
Marcar el comienzo de una lista enlazada puede ser un problema. Muchas implementaciones de listas
enlazadas incluyen un nodo especial, llamado encabezado , para indicar el comienzo de una lista enlazada.
La lista enlazada que se muestra en la Figura 61 se rediseña en la Figura 62 para incluir un nodo principal.
Insertar un nuevo nodo en una lista enlazada es una tarea muy eficiente. Para insertar un nuevo nodo, el
enlace del nodo anterior al nodo insertado (el nodo anterior ) se modifica para que apunte al nuevo nodo, y el
enlace del nuevo nodo se establece en el nodo al que apuntaba el nodo anterior antes de la inserción. La
Figura 63 ilustra cómo se agrega “cookies” a la lista enlazada después de “eggs”.
[Link][Link]
Machine Translated by Google
También es fácil eliminar un elemento de una lista enlazada. El vínculo del nodo anterior al nodo
eliminado se redirige para que apunte al nodo al que apunta el nodo eliminado, y al mismo tiempo
apunta el nodo eliminado a null, lo que elimina efectivamente el nodo de la lista enlazada.
La figura 64 muestra cómo se elimina “tocino” de la lista vinculada.
Hay otras funciones que podemos realizar con una lista enlazada, pero la inserción y la eliminación
son las dos funciones que mejor describen por qué las listas enlazadas son tan útiles.
Nuestro diseño de una lista enlazada implicará la creación de dos clases. Crearemos una clase Node
para agregar nodos a una lista enlazada y crearemos una clase LinkedList , que proporcionará
funciones para insertar nodos, eliminar nodos, mostrar una lista y otras funciones de mantenimiento.
La clase de nodo
La clase Node consta de dos propiedades: element, que almacena los datos del nodo, y next, que
almacena un vínculo al siguiente nodo de la lista vinculada. Para crear nodos, utilizaremos una función
constructora que establece los valores de las dos propiedades:
función Nodo(elemento) {
[Link] = elemento;
[Link] = null;
}
[Link][Link]
Machine Translated by Google
La clase LList proporciona la funcionalidad para una lista enlazada. La clase incluye funciones para insertar nuevos
nodos, eliminar nodos y buscar un valor de datos particular en una lista.
También existe una función constructora que se utiliza para crear una nueva lista enlazada. La única propiedad
almacenada en una lista enlazada es un nodo que representa el encabezado de la lista.
función LList()
{ [Link] = new Node("head");
[Link] = buscar;
[Link] = insertar;
[Link] = eliminar;
[Link] = mostrar;
}
El nodo principal comienza con su siguiente propiedad establecida en nula y se cambia para apuntar al primer
elemento insertado en la lista, ya que creamos un nuevo elemento Nodo pero no modificamos su siguiente
propiedad aquí en el constructor.
un nuevo nodo, debe especificar antes o después de qué nodo desea insertar el nuevo nodo. Comenzaremos
demostrando cómo insertar un nuevo nodo después de un nodo existente.
Para insertar un nodo después de un nodo existente, primero tenemos que encontrar el nodo “después”. Para ello,
creamos una función auxiliar, find(), que busca en la lista enlazada los datos especificados. Cuando se encuentran
estos datos, la función devuelve el nodo que almacena los datos. Aquí está el código para la función find() :
función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];
} devuelve currNode;
}
La función find() demuestra cómo desplazarse por una lista enlazada. Primero, creamos un nuevo nodo y lo
asignamos como nodo principal . Luego, recorremos la lista enlazada, moviéndonos de un nodo al siguiente cuando
el valor de la propiedad del elemento del nodo actual no es igual a los datos que estamos buscando. Si la búsqueda
es exitosa, la función devuelve el nodo que almacena los datos. Si no se encuentran los datos, la función devuelve
null.
Una vez que se encuentra el nodo “después”, el nuevo nodo se inserta en la lista enlazada. Primero, la siguiente
propiedad del nuevo nodo se establece con el valor de la siguiente propiedad del nodo “después”.
[Link][Link]
Machine Translated by Google
Luego, la siguiente propiedad del nodo “después” se establece como una referencia al nuevo nodo. Aquí
se muestra la definición de la función insert() :
Ahora estamos listos para probar nuestro código de lista enlazada. Sin embargo, antes de hacerlo,
necesitamos una función que muestre los elementos de una lista enlazada. La función display() se define
a continuación:
Esta función comienza conectándose a la lista enlazada asignando el encabezado de la lista a un nuevo
nodo. Luego recorremos la lista enlazada y nos detenemos solo cuando el valor de la siguiente propiedad
del nodo actual se establece en nulo. Para mostrar solo los nodos que contienen datos (en otras palabras,
no el nodo principal ), accedemos a la propiedad del elemento del nodo al que apunta el nodo actual:
[Link]
Por último, necesitamos agregar algo de código para usar la lista enlazada. El ejemplo 61 contiene un
programa corto que configura una lista enlazada de ciudades en el oeste de Arkansas que se encuentran
en la Interestatal 40, junto con la definición completa de la clase LList hasta este punto. Observe que la
función remove() está comentada. Se definirá en la siguiente sección.
función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];
} devuelve currNode;
}
[Link][Link]
Machine Translated by Google
// programa principal
Conway
Russellville
Alma
eliminar un nodo de una lista enlazada, necesitamos encontrar el nodo que está justo antes del
nodo que queremos eliminar. Una vez que encontramos ese nodo, cambiamos su propiedad next
para que ya no haga referencia al nodo eliminado, y el nodo previous se modifica para que apunte
al nodo después del nodo eliminado. Podemos definir una función, findPrevious(), para realizar
esta tarea. Esta función recorre la lista enlazada, deteniéndose en cada nodo para ver si el
siguiente nodo almacena los datos que se van a eliminar. Cuando se encuentran los datos, la
función devuelve este nodo (el nodo “previous”), para que se pueda modificar su propiedad next .
Aquí está la definición de findPrevious():
} devuelve currNode;
}
[Link][Link]
Machine Translated by Google
función eliminar(elemento)
{ var prevNode = [Link](elemento); si (!
([Link] == null)) { [Link]
= [Link];
}
}
La línea principal de código de esta función parece extraña, pero tiene mucho sentido:
[Link] = [Link]
Simplemente omitimos el nodo que queremos eliminar y vinculamos el nodo “anterior” con el nodo que se
encuentra justo después del que estamos eliminando. Vuelva a consultar la Figura 64 si necesita ayuda
para visualizar esta operación.
Estamos listos para probar nuestro código nuevamente, pero primero necesitamos modificar la función
constructora de la clase LList para incluir estas nuevas funciones:
función LList()
{ [Link] = new Node("head");
[Link] = buscar;
[Link] = insertar;
[Link] = mostrar;
[Link] = buscarPrevious;
[Link] = eliminar;
}
Conway
Russellville
Carlisle
Alma
Pero Carlisle está en el este de Arkansas, por lo que debemos eliminarlo de la lista, lo que da como
resultado el siguiente resultado:
Conway
Russellville
Alma
[Link][Link]
Machine Translated by Google
El ejemplo 63 contiene una lista completa de la clase Node , la clase LList y nuestro programa de
prueba:
función LList()
{ [Link] = new Node("head");
[Link] = buscar;
[Link] = insertar;
[Link] = mostrar;
[Link] = buscarPrevious;
[Link] = eliminar;
}
función eliminar(elemento)
{ var prevNode = [Link](elemento); si (!
([Link] == null)) { [Link]
= [Link];
}
}
} devuelve currNode;
}
función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];
} devuelve currNode;
}
[Link][Link]
Machine Translated by Google
Aunque recorrer una lista enlazada desde el primer nodo hasta el último nodo es sencillo, no es tan fácil recorrer una
lista enlazada hacia atrás. Podemos simplificar este procedimiento si agregamos una propiedad a nuestra clase Node
que almacene un vínculo al nodo anterior. Cuando insertamos un nodo en la lista, tendremos que realizar más
operaciones para asignar los vínculos adecuados para los nodos siguiente y anterior, pero ganamos eficiencia cuando
tenemos que eliminar un nodo de la lista, ya que ya no tenemos que buscar el nodo anterior. La Figura 65 ilustra cómo
funciona una lista doblemente enlazada.
Nuestra primera tarea es asignar una propiedad previa a nuestra clase Node :
función Nodo(elemento) {
[Link] = elemento;
[Link] = nulo;
[Link] = nulo;
}
La función insert() para una lista doblemente enlazada es similar a la función insert() para la lista simple enlazada,
excepto que tenemos que configurar la propiedad previous del nuevo nodo para que apunte al nodo anterior. Aquí está
la definición:
[Link][Link]
Machine Translated by Google
La función remove() para una lista doblemente enlazada es más eficiente que para una lista simplemente
enlazada porque no tenemos que encontrar el nodo anterior. Primero tenemos que encontrar el nodo en la lista
que está almacenando los datos que queremos eliminar. Luego, establecemos la propiedad previous de ese
nodo en el nodo al que apunta la propiedad next del nodo eliminado . Luego, tenemos que redirigir la propiedad
previous del nodo al que apunta el nodo eliminado y apuntarla al nodo anterior al nodo eliminado. La Figura 66
hace que esta situación sea más fácil de entender.
función eliminar(elemento)
{ var currNode = [Link](elemento); si
(!([Link] == null))
{ [Link] = [Link];
[Link] = [Link];
[Link] = null;
[Link] = null;
}
}
Para realizar tareas como visualizar una lista enlazada en orden inverso, podemos utilizar una función de utilidad
que encuentre el último nodo de una lista doblemente enlazada. La siguiente función, findLast(), nos lleva al
último nodo de una lista sin pasar del final de la lista:
} devuelve currNode;
}
Con la función findLast() escrita, podemos escribir una función para mostrar los elementos de una lista
doblemente enlazada en orden inverso. Aquí está el código para la función dispReverse() :
[Link][Link]
Machine Translated by Google
función dispReverse() {
var currNode = [Link];
currNode = [Link](); mientras
(!([Link] == null))
{ print([Link]);
currNode = [Link];
}
}
La última tarea que se debe realizar es agregar estas nuevas funciones a la función constructora de
la clase de lista doblemente enlazada. El ejemplo 64 presenta este código, junto con el resto del
código para implementar una lista doblemente enlazada, así como un programa breve para probar el
código.
función LList()
{ [Link] = new Node("head");
[Link] = buscar;
[Link] = insertar;
[Link] = mostrar;
[Link] = eliminar;
[Link] = findLast;
[Link] = dispReverse;
}
función dispReverse() {
var currNode = [Link];
currNode = [Link](); mientras
(!([Link] == null))
{ print([Link]);
currNode = [Link];
}
}
} devuelve currNode;
}
[Link][Link]
Machine Translated by Google
[Link] = [Link];
[Link] = [Link];
[Link] = null;
[Link] = null;
}
}
función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];
} devuelve currNode;
}
[Link][Link]
Machine Translated by Google
imprimir();
[Link]();
Conway
Russellville
Carlisle
Alma
Conway
Russellville
Alma
Alma
Russellville
Conway
Una lista enlazada circularmente es similar a una lista enlazada simple y tiene el mismo tipo de nodos.
La única diferencia es que, cuando se crea una lista enlazada circularmente, la siguiente propiedad de
su nodo principal apunta a sí misma. Esto significa que la asignación:
[Link] = cabeza
se propaga por toda la lista enlazada circularmente de modo que cada nuevo nodo tiene su siguiente
propiedad apuntando al principio de la lista. En otras palabras, el último nodo de la lista siempre apunta
de nuevo al principio de la lista, lo que crea una lista circular, como se muestra en la Figura 67.
La razón por la que podría querer crear una lista enlazada circularmente es si desea tener la capacidad
de retroceder en una lista pero no desea la sobrecarga adicional que supone crear una lista doblemente
enlazada. Puede retroceder en una lista enlazada circularmente avanzando por el final de la lista hasta
el nodo al que intenta llegar.
Para crear una lista enlazada circularmente, cambie la función constructora de la clase LList para
que lea:
[Link][Link]
Machine Translated by Google
[Link] = insertar;
[Link] = mostrar;
[Link] = encontrarAnterior;
[Link] = eliminar;
}
Este es el único cambio que tenemos que hacer para convertir una lista enlazada simple en una lista enlazada
circularmente. Sin embargo, algunas de las otras funciones de lista enlazada no funcionarán correctamente
sin modificaciones. Por ejemplo, una función que necesita ser modificada es dis display(). Tal como está
escrito, si la función display() se ejecuta en una lista enlazada circularmente, la función nunca se detendrá. La
condición del bucle while debe cambiar para que se pruebe el elemento principal y el bucle se detenga cuando
llegue al encabezado.
Así es como se escribe la función display() para una lista enlazada circularmente:
Al ver cómo modificar la función display() , debería poder modificar otras funciones de una lista enlazada
estándar para que funcionen con una lista enlazada circular.
Hay otras funciones que puedes incluir para que una lista enlazada funcione correctamente. En los próximos
ejercicios, tendrás la oportunidad de desarrollar algunas de estas funciones, entre ellas:
avance(n)
Avanza n nodos en la lista enlazada
atrás(n)
Mueve n nodos hacia atrás en una lista doblemente enlazada
espectáculo()
Ceremonias
1. Implemente la función advance(n) de modo que, cuando se ejecute, el nodo actual sea
se movió n nodos hacia adelante en la lista.
[Link][Link]
Machine Translated by Google
2. Implemente la función back(n) para que, cuando se ejecute, se mueva el nodo actual.
n espacios hacia atrás en la lista.
3. Implemente la función show() , que muestra los datos asociados con la corriente
nodo.
4. Escriba un programa que utilice una lista enlazada simple para realizar un seguimiento de un conjunto de calificaciones de
exámenes ingresadas de forma interactiva en el programa.
5. Reescribe tu solución del Ejemplo 64 usando una lista doblemente enlazada.
6. Según la leyenda, el historiador judío del siglo I Flavio Josefo estaba a punto de ser capturado
junto con un grupo de 40 compatriotas por soldados romanos durante la guerra judeoromana.
Los soldados judíos decidieron que preferían suicidarse a ser capturados e idearon un plan para
su desaparición. Debían formar un círculo y matar a cada tercer soldado hasta que todos
estuvieran muertos. Josefo y otro decidieron que no querían ser parte de esto y calcularon
rápidamente dónde debían ubicarse para ser los últimos sobrevivientes. Escriba un programa que
le permita colocar n personas en un círculo y especifique que cada m persona será asesinada. El
programa debe determinar el número de las últimas dos personas que quedan en el círculo. Use
una lista enlazada circularmente para resolver el problema.
Ejercicios | 87
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 7
Diccionarios
Un diccionario es una estructura de datos que almacena datos como pares clavevalor , como la
forma en que una guía telefónica almacena sus datos como nombres y números de teléfono. Cuando
busca un número de teléfono, primero busca el nombre y, cuando lo encuentra, el número de teléfono
se encuentra junto al nombre. La clave es el elemento que se utiliza para realizar una búsqueda y el
valor es el resultado de la búsqueda.
La clase Object de JavaScript está diseñada para funcionar como un diccionario. En este capítulo,
utilizaremos las características de la clase Object para crear una clase Dictionary que simplifique el
trabajo con un objeto de tipo diccionario. Puede realizar las mismas funciones que se muestran en
este capítulo utilizando solo matrices y objetos de JavaScript, pero crear una clase Dictionary hace
que el trabajo sea más fácil y divertido. Por ejemplo, es mucho más fácil utilizar () para hacer
referencia a claves en lugar de tener que utilizar la notación [] . También existe, por supuesto, la
ventaja de poder definir funciones para realizar operaciones colectivas, como mostrar todas las
entradas de un diccionario, en lugar de tener que escribir bucles en el programa principal para realizar
las mismas operaciones.
función Diccionario()
{ [Link] = new Array();
}
89
[Link][Link]
Machine Translated by Google
La primera función que se debe definir es add(). Esta función acepta dos argumentos, una clave
y un valor. La clave es el índice del elemento de valor. Este es el código:
función remove(clave)
{ eliminar [Link][clave];
}
Finalmente, nos gustaría poder ver todos los pares clavevalor en un diccionario, así que aquí hay
una función que realiza esta tarea:
90 | Capítulo 7: Diccionarios
[Link][Link]
Machine Translated by Google
función remove(clave)
{ eliminar [Link][clave];
}
load("[Link]"); var
pbook = new Diccionario();
[Link]("Mike", "123");
[Link]("David", "345");
[Link]("Cynthia", "456");
"
print("Extensión de David: + [Link]("David")); [Link]("David");
[Link]();
} devuelve n;
}
Quizás te preguntes por qué no se utilizó la propiedad de longitud para la función count() .
La razón es que la longitud no funciona con claves de cadena. Por ejemplo:
[Link][Link]
Machine Translated by Google
Otra función auxiliar que podemos utilizar es la función clear() . Esta es la definición:
función clear() {
para cada (var clave en [Link]([Link])) {
eliminar [Link][clave];
}
}
función remove(clave)
{ eliminar [Link][clave];
}
función contar() {
var n = 0;
para cada (var clave en [Link]([Link])) {
++n;
} devuelve n;
}
función clear() {
92 | Capítulo 7: Diccionarios
[Link][Link]
Machine Translated by Google
load("[Link]"); var
pbook = new Dictionary();
[Link]("Raymond", "123");
[Link]("David", "345");
[Link]("Cynthia", "456");
"
print("Número de entradas: + [Link]());
"
print("Extensión de David: + [Link]("David"));
[Link]();
[Link]();
"
print("Número de entradas: + [Link]());
Número de entradas: 3
Extensión de David : 345
Raymond > 123
David > 345
Cintia > 456
Número de entradas: 0
Sin embargo, no podemos realizar la misma prueba con claves de cadena. La salida del programa
está vacía. Este es un problema muy similar al que tuvimos antes al intentar definir una función
count() .
Sin embargo, esto no es realmente un problema. Lo único que importa al usuario de la clase es que cuando se
muestran los contenidos del diccionario, los resultados están ordenados. Podemos usar el
[Link][Link]
Machine Translated by Google
Función [Link]() para resolver este problema. A continuación, se incluye una nueva definición para
la función show All() :
función showAll() {
para cada (var clave en [Link]([Link]).sort()) { imprimir(clave +
" > "
+ [Link][clave]);
}
}
La única diferencia entre esta definición de la función y nuestra definición anterior es que hemos agregado una
llamada a sort() después de obtener las claves de la matriz del almacén de datos .
El ejemplo 75 demuestra cómo se utiliza esta nueva definición de función para mostrar una lista
ordenada de nombres y números.
cargar("[Link]"); var
pbook = new Diccionario();
[Link]("Raymond", "123");
[Link]("David", "345");
[Link]("Cynthia", "456");
[Link]("Mike", "723");
[Link]("Jennifer", "987");
[Link]("Danny", "012");
[Link]("Jonathan", "666");
[Link]();
Ceremonias
1. Escriba un programa que tome un conjunto de nombres y números de teléfono de un archivo de texto y los
almacene en un objeto Dictionary . Incluya en su programa la capacidad de mostrar un número de teléfono,
mostrar todos los números de teléfono, agregar nuevos números de teléfono, eliminar números de teléfono y
borrar la lista de números.
2. Utilizando la clase Dictionary , escriba un programa que almacene la cantidad de veces que aparecen las
palabras en un texto. Su programa debe mostrar cada palabra de un texto solo una vez, así como la cantidad
de veces que aparece la palabra en el texto. Por ejemplo, dado el texto “el zorro marrón saltó sobre el zorro
azul”, la salida será:
94 | Capítulo 7: Diccionarios
[Link][Link]
Machine Translated by Google
el: 2
marrón: 1
zorro: 2
saltó: 1
sobre: 1
azul: 1
Ejercicios | 95
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 8
Hash (hash)
El hash es una técnica común para almacenar datos de manera que se puedan insertar y recuperar muy
rápidamente. El hash utiliza una estructura de datos denominada tabla hash.
Aunque las tablas hash permiten insertar, eliminar y recuperar datos rápidamente, su rendimiento es
deficiente en operaciones que implican búsquedas, como encontrar los valores mínimo y máximo en un
conjunto de datos. Para estas operaciones, otras estructuras de datos, como el árbol de búsqueda binaria,
son más apropiadas. En este capítulo aprenderemos a implementar una tabla hash y cuándo es apropiado
utilizar el hash como técnica de almacenamiento y recuperación de datos.
La estructura de datos de la tabla hash está diseñada en torno a una matriz. La matriz consta de elementos
desde el 0 hasta un tamaño predeterminado, aunque podemos aumentar el tamaño cuando sea necesario.
Cada elemento de datos se almacena en la matriz en función de un elemento de datos asociado llamado
clave, que es similar al concepto de clave que examinamos con la estructura de datos del diccionario. Para
almacenar un dato en una tabla hash, la clave se asigna a un número en el rango de 0 a través del tamaño
de la tabla hash, utilizando una función hash.
Lo ideal es que la función hash almacene cada clave en su propio elemento de matriz. Sin embargo, dado
que hay una cantidad ilimitada de claves posibles y una cantidad limitada de elementos de matriz
(teóricamente en JavaScript), un objetivo más realista de la función hash es intentar distribuir las claves de
la manera más uniforme posible entre los elementos de la matriz.
Incluso con una función hash eficiente, es posible que dos claves se conviertan en el mismo valor mediante
el hash (resultado de la función hash). Esto se denomina colisión y necesitamos una estrategia para
manejar las colisiones cuando ocurren. Analizaremos cómo manejar las colisiones en detalle más adelante
en este capítulo.
Lo último que tenemos que determinar al crear una función hash es qué tan grande debe ser la matriz que
crearemos para la tabla hash. Una restricción que generalmente se impone al tamaño de la matriz es que
debe ser un número primo. Explicaremos por qué este número debe ser primo cuando
97
[Link][Link]
Machine Translated by Google
Examine las diferentes funciones hash. Después de eso, existen varias estrategias diferentes para
determinar el tamaño correcto de la matriz, todas ellas basadas en la técnica utilizada para manejar
colisiones, por lo que examinaremos este tema cuando analicemos el manejo de colisiones. La Figura
81 ilustra el concepto de hash utilizando el ejemplo de una pequeña guía telefónica.
Necesitamos una clase para representar la tabla hash. La clase incluirá funciones para calcular
valores hash, una función para insertar datos en la tabla hash, una función para recuperar datos de la
tabla hash y una función para mostrar la distribución de datos en la tabla hash, así como varias
funciones de utilidad que podamos necesitar.
función HashTable()
{ [Link] = new Array(137);
[Link] = simpleHash;
[Link] = showDistro;
[Link] = put; //
[Link] = get;
}
La función get() está comentada por ahora; describiremos su definición más adelante en el capítulo.
elección de una función hash depende del tipo de datos de la clave. Si la clave es un entero, la función
hash más sencilla es devolver la clave módulo el tamaño de la matriz.
Existen circunstancias en las que no se recomienda esta función, como cuando todas las claves
terminan en 0 y el tamaño de la matriz es 10. Esta es una de las razones por las que el tamaño de la
matriz siempre debe ser un número primo, como 137, que es el valor que usamos en el constructor anterior.
98 | Capítulo 8: Hashing
[Link][Link]
Machine Translated by Google
función. Además, si las claves son números enteros aleatorios, la función hash debería distribuir
las claves de manera más uniforme. Este tipo de hash se conoce como hash modular .
En muchas aplicaciones, las claves son cadenas. Elegir una función hash para trabajar con
claves de cadena resulta más difícil y debe elegirse con cuidado.
Una función hash simple que a primera vista parece funcionar bien es la de sumar el valor ASCII
de las letras de la clave. El valor hash es entonces esa suma módulo el tamaño de la matriz.
Aquí está la definición de esta función hash simple:
Podemos finalizar este primer intento de la clase HashTable con definiciones para put() y
showDistro(), que colocan los datos en la tabla hash y muestran los datos de la tabla hash
respectivamente. Aquí está la definición completa de la clase:
función HashTable()
{ [Link] = new Array(137);
[Link] = simpleHash;
[Link] = showDistro; [Link]
= put; //[Link] =
get;
}
[Link][Link]
Machine Translated by Google
cargar("[Link]"); var
algunosNombres = ["David", "Jennifer", "Donnie", "Raymond ", " Cynthia", "Mike", "
Clayton", "Danny", "Jonathan"]; var hTable = new HashTable(); para (var i =
0; i < [Link]; ++i)
{ [Link](algunosNombres[i]);
}
[Link]();
35: Cintia
45: Clayton
57: Donnie
77: David
95: Danny
116: Mike
132: Jennifer
134: Jonatán
La función simpleHash() calcula un valor hash sumando el valor ASCII de cada nombre utilizando la
función JavaScript charCodeAt() para devolver el valor ASCII de un carácter.
La función put() recibe el valor del índice de la matriz de la función simpleHash() y almacena el elemento
de datos en esa posición. La función showDistro() muestra dónde se colocan realmente los nombres en
la matriz mediante la función hash. Como puede ver, los datos no están distribuidos de manera
particularmente uniforme. Los nombres están agrupados al principio y al final de la matriz.
Sin embargo, existe un problema aún mayor que la distribución desigual de los nombres en la matriz. Si
prestas atención a la salida, verás que no se muestran todos los nombres de la matriz original.
Investiguemos más a fondo agregando una declaración print() a la función simpleHash() :
}
" "
print(" Valor hash : " + datos + retorno total > + totales);
% [Link];
}
[Link][Link]
Machine Translated by Google
El problema ahora es evidente: las cadenas "Clayton" y "Raymond" se convierten en el mismo valor,
lo que provoca una colisión. Debido a la colisión, solo "Clayton" se almacena en la tabla hash. Podemos
mejorar nuestra función hash para evitar este tipo de colisiones, como se explica en la siguiente
sección.
Después de dimensionar correctamente la tabla hash, el siguiente paso para evitar colisiones de hash
es calcular un valor hash mejor. Un algoritmo conocido como el método de Horner hace el truco.
Sin adentrarnos demasiado en las matemáticas del algoritmo, nuestra nueva función hash sigue
funcionando sumando los valores ASCII de los caracteres de una cadena, pero añade un paso al
multiplicar el total resultante por una constante prima. La mayoría de los libros de texto de algoritmos
sugieren un número primo pequeño, como 31, pero para nuestro conjunto de nombres 31 no funcionó;
sin embargo, 37 funcionó sin provocar colisiones.
Ahora presentamos una función hash nueva y mejor que utiliza el método de Horner:
[Link][Link]
Machine Translated by Google
}
" "
print(" Valor hash : " + datos + retorno total > + totales);
% [Link];
}
función betterHash(cadena) {
constante H = 37;
var total = 0; para
(var i = 0; i < [Link]; ++i) {
total += H * total + [Link](i);
}
total = total % [Link]; si (total < 0)
{ total +=
[Link]1;
}
devuelve parseInt(total);
}
Tenga en cuenta que la función put() ahora utiliza betterHash() en lugar de simpleHash().
[Link][Link]
Machine Translated by Google
cargar("[Link]"); var
algunosNombres = ["David", "Jennifer", "Donnie", "Raymond", " Cynthia",
"Mike", "Clayton", "Danny", "Jonathan"];
var hTable = new HashTable(); para
(var i = 0; i < [Link]; ++i)
{ [Link](algunosNombres[i]);
} [Link]();
17: Cintia
25: Donnie
30: Mike
33: Jennifer
37: Jonatán
57: Clayton
65: David
66: Danny
99: Raymond
última sección trabajamos con claves de cadena. En esta sección, presentamos cómo hacer hashes de claves
enteras. El conjunto de datos con el que estamos trabajando son las calificaciones de los estudiantes. La clave
es un número de identificación de estudiante de nueve dígitos, que generaremos aleatoriamente, junto con la
calificación del estudiante. Estas son las funciones que usamos para generar los datos del estudiante (ID y calificación):
La función getRandomInt() nos permite especificar un número aleatorio máximo y mínimo. Para un conjunto de
calificaciones de estudiantes, es razonable decir que la calificación mínima es 50 y la calificación máxima es 100.
[Link][Link]
Machine Translated by Google
La función getStuData() genera datos de los estudiantes. El bucle interno genera el número de
identificación del estudiante y, justo después de que finaliza el bucle interno, se genera una calificación
aleatoria y se concatena con la identificación del estudiante. Nuestro programa principal separará la
identificación de la calificación. La función hash sumará los dígitos individuales de la identificación del
estudiante para calcular un valor hash utilizando la función simpleHash() .
El ejemplo 84 presenta un programa que utiliza las funciones anteriores para almacenar un conjunto de
estudiantes y sus calificaciones.
} número += getRandomInt(50,100);
arreglo[i] = número;
}
}
load("[Link]"); var
numEstudiantes = 10; var
arrSize = 97; var
idLen = 9; var
estudiantes = new Array(numEstudiantes);
genStuData(estudiantes);
print ("Datos del estudiante: \n");
for (var i = 0; i < [Link]; ++i)
{ print(estudiantes[i].substring(0,8) +
""
+
estudiantes[i].substring(9));
} [Link]();
24553918 70
08930891 70
41819658 84
04591864 82
[Link][Link]
Machine Translated by Google
75760587 91
78918058 87
69774357 53
52158607 59
60644757 81
60134879 58
Distribución de datos:
41: 52158607059
42: 08930891470
47: 60644757681
50: 41819658384
53: 60134879958
54: 75760587691
61: 78918058787
Una vez más, nuestra función hash crea una colisión y no todos los datos se almacenan en la
matriz. En realidad, si ejecuta el programa varias veces, a veces se almacenarán todos los datos,
pero los resultados distan de ser consistentes. Podemos experimentar con los tamaños de las
matrices para ver si podemos solucionar el problema o simplemente podemos cambiar la función
hash llamada por la función put() y usar betterHash(). Al usar betterHash() con los datos de los
estudiantes, obtenemos el siguiente resultado:
74980904 65
26410578 93
37332360 87
86396738 65
16122144 78
75137165 88
70987506 96
04268092 84
95220332 86
55107563 68
Distribución de datos:
10: 75137165888
34: 95220332486
47: 70987506996
50: 74980904265
51: 86396738665
53: 55107563768
67: 04268092284
81: 37332360187
82: 16122144378
85: 26410578393
[Link][Link]
Machine Translated by Google
La lección aquí es obvia: betterHash() es la función hash superior para cadenas y números enteros.
Ahora que hemos cubierto las funciones hash, podemos aplicar este conocimiento para utilizar una
tabla hash para almacenar datos. Para ello, tenemos que modificar la función put() para que acepte
tanto una clave como datos, convierta la clave en hash y luego utilice esa información para almacenar
los datos en la tabla. Aquí está la definición de la nueva función put() :
A continuación, debemos definir la función get() para poder recuperar los datos almacenados en una
tabla hash. Esta función debe, nuevamente, realizar un hash de la clave para poder determinar dónde
están almacenados los datos y luego recuperar los datos de su posición en la tabla. Aquí está la definición:
función obtener(clave) {
devuelve [Link][[Link](clave)];
}
Aquí hay un programa para probar las funciones put() y get() :
load("[Link]"); var
pnumbers = new HashTable(); var
nombre, numero;
para (var i = 0; i < 3; i++) {
putstr("Ingrese un nombre (espacio para salir): ");
nombre = readline();
putstr("Ingrese un número: ");
número = readline();
} name = "";
putstr("Nombre para el número (Ingrese quit para detener): ");
while (name != "quit") { name =
readline(); if (name ==
"quit") { break;
"
} print(name + "'s number es + [Link](nombre));
putstr("Nombre para el número (Ingrese quit para detener): ");
}
Este programa le permite ingresar tres nombres y números de teléfono y recuperará los números en
función de los nombres hasta que le indique al programa que salga.
[Link][Link]
Machine Translated by Google
Manejo de colisiones
Una colisión ocurre cuando una función hash genera la misma clave para dos o más valores. La segunda parte de un algoritmo
hash implica resolver las colisiones de modo que todas las claves se almacenen en la tabla hash. En esta sección, analizamos
dos medios de resolución de colisiones: encadenamiento independiente y sondeo lineal.
Encadenamiento separado
Cuando ocurre una colisión, todavía necesitamos poder almacenar la clave en el índice generado, pero es físicamente
imposible almacenar más de un dato en un elemento de matriz. El encadenamiento separado es una técnica en la que cada
elemento de matriz de una tabla hash almacena otra estructura de datos, como otra matriz, que luego se utiliza para almacenar
claves. Con esta técnica, si dos claves generan el mismo valor hash, cada clave se puede almacenar en una posición diferente
de la matriz secundaria. La Figura 82 ilustra cómo funciona el encadenamiento separado.
Para implementar el encadenamiento por separado, después de crear la matriz para almacenar las claves hash, llamamos a
una función que asigna una matriz vacía a cada elemento de la matriz de la tabla hash. Esto crea una matriz bidimensional
(consulte el Capítulo 3 para obtener una explicación de las matrices bidimensionales). El siguiente código define una función,
buildChains(), para crear la segunda matriz (también nos referiremos a esta matriz como una cadena), así como un pequeño
programa que demuestra cómo utilizar buildChains():
[Link][Link]
Machine Translated by Google
función buildChains() {
para (var i = 0; i < [Link]; ++i) { [Link][i] =
nueva Matriz();
}
}
Agregue el código anterior, junto con una declaración de la función, a la definición de la clase
HashTable .
cargar("[Link]"); var
hTable = new HashTable();
[Link](); var
algunosNombres = ["David", "Jennifer", "Donnie", "Raymond", "Cynthia", "Mike",
"Clayton", "Danny", "Jonathan"];
para (var i = 0; i < [Link]; ++i)
{ [Link](algunosNombres[i]);
} [Link]();
Para mostrar correctamente la distribución después del hash con encadenamiento separado, necesitamos modificar la
función showDistro() de la siguiente manera para reconocer que la tabla hash ahora es una matriz multidimensional:
}
}
}
60: David
68: Jennifer
69: Mike
70: Donnie, Jonathan
78: Cynthia, Danny
88: Raymond, Clayton
A continuación, debemos definir las funciones put() y get() que funcionarán con encadenamiento independiente. La
función put() genera un hash de la clave y luego intenta almacenar los datos en la primera celda de la cadena en la
posición del hash. Si esa celda ya tiene datos, la función busca la primera celda abierta y almacena los datos en esa
celda. Aquí está el código para la función put() :
[Link][Link]
Machine Translated by Google
var índice = 0; si
([Link][pos][índice] == indefinido) { [Link][pos]
[índice+1] = datos;
} ++índice;
de lo
contrario { mientras ([Link][pos][índice] != indefinido)
{ ++índice;
} [Link][pos][índice] = datos;
}
}
A diferencia del ejemplo anterior, cuando solo almacenábamos claves, esta función put() tiene que almacenar
tanto claves como valores. La función utiliza un par de celdas de la cadena para almacenar un par clave
valor; la primera celda almacena la clave y la celda adyacente de la cadena almacena el valor.
La función get() comienza por aplicar un algoritmo hash a la clave para obtener su posición en la tabla hash.
Luego, la función busca en las celdas hasta encontrar la clave que busca.
Cuando encuentra la clave correcta, devuelve los datos de la celda adyacente a la celda de la clave.
Si no se encuentra la clave, la función devuelve undefined. Aquí está el código:
} índice += 2;
de lo
contrario { mientras ([Link][pos][índice] != clave) {
índice += 2;
} devuelve [Link][pos][índice+1];
} devuelve indefinido;
}
segunda técnica para manejar colisiones se llama sondeo lineal. El sondeo lineal es un ejemplo de una
técnica de hash más general llamada hash de direccionamiento abierto. Con el sondeo lineal, cuando hay
una colisión, el programa simplemente mira para ver si el siguiente elemento de la tabla hash está vacío. Si
es así, la clave se coloca en ese elemento. Si el elemento no está vacío, el programa continúa buscando un
elemento vacío de la tabla hash hasta que se encuentra uno. Esta técnica aprovecha el hecho de que
cualquier tabla hash va a tener muchos elementos vacíos y tiene sentido utilizar el espacio para almacenar
claves.
Se debe elegir el sondeo lineal en lugar del encadenamiento por separado cuando la matriz para almacenar
datos puede ser bastante grande. Aquí hay una fórmula que se usa comúnmente para determinar qué colisión
[Link][Link]
Machine Translated by Google
método a utilizar: si el tamaño de la matriz puede ser hasta la mitad del número de elementos a
almacenar, debe utilizar encadenamiento separado; pero si el tamaño de la matriz puede ser el doble
del tamaño del número de elementos a almacenar, debe utilizar sondeo lineal.
Para demostrar cómo funciona el sondeo lineal, podemos reescribir las funciones put() y get() para que
funcionen con el sondeo lineal. Para crear un sistema de recuperación de datos realista, tenemos que
modificar la clase HashTable añadiendo una segunda matriz para almacenar valores. La matriz de
tablas y la matriz de valores funcionan en paralelo, de modo que cuando almacenamos una clave en
una posición de la matriz de tablas , almacenamos un valor en la posición correspondiente de la matriz de valores .
[Link] = [];
} else
{ mientras ([Link][pos] != indefinido) {
pos++;
} [Link][pos] = clave;
[Link][pos] = datos;
}
}
El código de la función get() comienza a buscar en la tabla hash en la posición hash de la clave. Si los
datos que se pasan a la función coinciden con la clave que se encuentra en esa posición, se devuelven
los datos correspondientes en la posición values . Si las claves no coinciden, la función recorre la tabla
hash hasta que encuentra la clave o llega a una celda que no está definida, lo que significa que la clave
nunca se colocó en la tabla hash. Este es el código:
función obtener(clave)
{ var hash = 1;
hash = [Link](clave); si (hash
> 1) { para (var i =
hash; [Link][hash] != indefinido; i++) {
si ([Link][hash] == clave) { devolver
[Link][hash];
}
}
} devuelve indefinido;
}
[Link][Link]
Machine Translated by Google
Ceremonias
1. Utilice el sondeo lineal para crear un diccionario simple para almacenar las definiciones de palabras.
El programa debe tener dos partes. La primera parte lee un archivo de texto que contiene una lista
de palabras y definiciones y las almacena en una tabla hash. La segunda parte del programa permite
al usuario ingresar una palabra y ver la definición de esa palabra.
3. Escriba un programa que utilice hashing que lea un archivo de texto y compile una lista de las palabras
en el archivo con la cantidad de veces que aparece cada palabra en el archivo.
Ejercicios | 111
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 9
Conjuntos
Un conjunto es una colección de elementos únicos. Los elementos de un conjunto se denominan miembros.
Las dos propiedades más importantes de los conjuntos son que los miembros de un conjunto no están
ordenados y que ningún miembro puede aparecer en un conjunto más de una vez. Los conjuntos desempeñan
un papel muy importante en la informática, pero no se consideran un tipo de datos en muchos lenguajes de programación.
Los conjuntos pueden resultar útiles cuando se desea crear una estructura de datos que consta únicamente de
elementos únicos, como una lista de cada palabra única de un texto. En este capítulo se explica cómo crear
una clase Set para JavaScript.
Un conjunto se denota matemáticamente como una lista de miembros rodeados de llaves, como
{0,1,2,3,4,5,6,7,8,9}. Podemos escribir un conjunto en cualquier orden, por lo que el conjunto anterior se puede
escribir como {9,0,8,1,7,2,6,3,5,4} o cualquier otra combinación de los miembros de manera que todos los
miembros se escriban solo una vez.
Definiciones de conjuntos
Aquí hay algunas definiciones que necesitas saber para trabajar con conjuntos:
• Un conjunto que no contiene ningún miembro se llama conjunto vacío. El universo es el conjunto de todos
los miembros posibles.
113
[Link][Link]
Machine Translated by Google
Operaciones de conjuntos
Unión
Un nuevo conjunto se obtiene combinando los miembros de un conjunto con los miembros de otro conjunto.
Intersección
Un nuevo conjunto se obtiene sumando todos los miembros de un conjunto que también existen en un segundo
colocar.
Diferencia Un
nuevo conjunto se obtiene sumando todos los miembros de un conjunto excepto aquellos que también existen en
un segundo conjunto.
función Set()
{ [Link] = []; [Link]
= añadir; [Link]
= eliminar; [Link] = tamaño;
[Link] = unión;
[Link] = intersectar;
[Link] = subconjunto;
[Link] = diferencia;
[Link] = mostrar;
función agregar(datos) {
si ([Link](datos) < 0)
{ [Link](datos);
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}
Debido a que un conjunto solo puede contener miembros únicos, antes de que la función add() pueda almacenar datos
en la matriz, debe verificar que los datos no estén ya en la matriz. Usamos la función indexOf() para verificar la matriz en
busca de los datos solicitados. Esta función devuelve la posición de un elemento en una matriz o el valor 1 si la matriz
no contiene los datos.
[Link][Link]
Machine Translated by Google
elemento. Si los datos no están almacenados en la matriz, la función inserta los datos en la matriz y
devuelve verdadero. De lo contrario, la función devuelve falso. Necesitamos escribir add() como una
función booleana para que tengamos una forma de saber con certeza si los datos se agregaron o no
al conjunto.
La función remove() funciona de manera similar a la función add() . Primero verificamos si los datos
solicitados están en la matriz. Si es así, llamamos a la función splice() para eliminar los datos y
devolver verdadero. De lo contrario, devolvemos falso, lo que indica que los datos solicitados no
están en el conjunto. Aquí está la definición de remove():
{ [Link](pos,1); devuelve
verdadero;
}
de lo
contrario { devolver falso;
}
}
Antes de poder probar estas funciones, definamos la función show() para que podamos ver los
miembros de un conjunto:
función mostrar()
{ devolver [Link];
}
} else
{ print("No se puede agregar Mike, ya debe estar en el conjunto");
} print([Link]());
var eliminado = "Mike"; if
([Link](eliminado))
{ print(eliminado + " eliminado.");
} else
"
{ imprimir(eliminado + no eliminado.");
[Link][Link]
Machine Translated by Google
} [Link]("Clayton");
print([Link]());
eliminado = "Alisa"; if
([Link]("Mike")) { print(eliminado
+ " eliminado.");
} else
"
{ imprimir(eliminado + no eliminado.");
}
Las funciones más interesantes para definir son union(), intersect(), subset() y difference(). La función
union() combina dos conjuntos utilizando la operación de unión de conjuntos para formar un nuevo
conjunto. La función primero construye un nuevo conjunto agregando todos los miembros del primer
conjunto. Luego, la función verifica cada miembro del segundo conjunto para ver si el miembro ya es
miembro del primer conjunto. Si lo es, el miembro se omite y, si no, el miembro se agrega al nuevo
conjunto.
Sin embargo, antes de definir union(), debemos definir una función auxiliar, contains(),
que busca ver si un miembro específico es parte de un conjunto. Aquí está la definición
de contains():
función contiene(datos) { si
([Link](datos) > 1) {
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}
[Link][Link]
Machine Translated by Google
}
}
La intersección de conjuntos se realiza mediante una función llamada intersect(). Esta función es
más fácil de definir. Cada vez que se descubre que un miembro del primer conjunto es miembro del
segundo conjunto, se agrega a un nuevo conjunto, que es el valor de retorno de la función. Aquí está
la definición:
función intersectar(conjunto) {
var tempSet = new Set(); para
(var i = 0; i < [Link]; ++i) { si
([Link]([Link][i]))
{ [Link]([Link][i]);
}
}
[Link][Link]
Machine Translated by Google
La siguiente operación que se debe definir es el subconjunto. La función subset() primero debe verificar que la
longitud del subconjunto propuesto sea menor que el conjunto más grande con el que estamos comparando el
subconjunto. Si la longitud del subconjunto es mayor que el conjunto original, entonces no puede ser un subconjunto.
Una vez que se determina que el tamaño del subconjunto es menor, la función verifica que cada
miembro del subconjunto sea miembro del conjunto mayor. Si algún miembro del subconjunto no está
en el conjunto mayor, la función devuelve falso y se detiene. Si la función llega al final del conjunto
mayor sin devolver falso, el subconjunto es de hecho un subconjunto y la función devuelve verdadero.
La definición se encuentra a continuación:
función subconjunto(conjunto)
{ si ([Link]() > [Link]()) { devolver
falso;
} else
{ para cada (var miembro en [Link]) { if (!
[Link](member)) { return false;
}
}
}
devuelve verdadero;
}
La función subset() utiliza la función size() antes de comprobar si todos los elementos de los conjuntos coinciden.
Aquí está el código para la función size() :
función tamaño()
{ devolver [Link];
}
Notarás que la función subset() utiliza un bucle for each en lugar de un bucle for , como hemos utilizado en las
otras definiciones. Cualquier bucle funcionará aquí, pero solo utilizamos el bucle for each para demostrar que su
uso está bien aquí.
cargar("[Link]");
var it = new Set();
[Link]("Cynthia");
[Link]("Clayton") ;
[Link]("Jennifer");
[Link]("Danny");
[Link]("Jonathan");
[Link]("Terrill");
[Link]("Raymond");
[Link]("Mike");
[Link][Link]
Machine Translated by Google
} else
{ print("DMP no es un subconjunto de TI.");
}
[Link]("Shirley");
La última función operativa es difference(). Esta función devuelve un conjunto que contiene aquellos
miembros del primer conjunto que no están en el segundo conjunto. La definición de difference() se
muestra a continuación:
}
}
cargar("[Link]"); var
cis = new Set(); var it = new
Set(); [Link]("Clayton") ;
[Link]("Jennifer");
[Link]("Danny");
[Link]("Bryan");
[Link]("Clayton");
[Link]("Jennifer"); var diff
= new Set(); diff =
[Link](it); imprimir("[" +
[Link]() + "] diferencia [" + [Link]()
+ "] > [" + [Link]() + "]");
[Link][Link]
Machine Translated by Google
Ceremonias
1. Modifique la clase Set para que almacene sus elementos en orden. Escriba un programa para probar
su implementación.
2. Modifique la clase Set para que utilice una lista enlazada para almacenar sus elementos en lugar de
una matriz. Escriba un programa para probar su implementación.
3. Agregue la función higher(elemento) a la clase Set . Esta función devuelve el elemento más pequeño
del conjunto que es estrictamente mayor que el elemento dado. Pruebe su función en un
programa.
4. Agrega la función lower(elemento) a la clase Set . Esta función devuelve el elemento más grande
del conjunto que sea estrictamente menor que el elemento dado. Prueba la función en un
programa.
[Link][Link]
Machine Translated by Google
CAPÍTULO 10
Los árboles son una estructura de datos de uso común en informática. Un árbol es una estructura de datos
no lineal que se utiliza para almacenar datos de forma jerárquica. Las estructuras de datos de árbol se
utilizan para almacenar datos jerárquicos, como los archivos de un sistema de archivos, y para almacenar
listas ordenadas de datos. En este capítulo, examinamos una estructura de árbol en particular: el árbol binario.
Se eligen árboles binarios en lugar de otras estructuras de datos más primarias porque se puede buscar
en un árbol binario muy rápidamente (a diferencia de una lista enlazada, por ejemplo) y se pueden insertar
y eliminar datos rápidamente de un árbol binario (a diferencia de una matriz).
Árboles definidos
Un árbol está formado por un conjunto de nodos conectados por aristas. Un ejemplo de árbol es el
organigrama de una empresa (véase la Figura 101).
121
[Link][Link]
Machine Translated by Google
La Figura 102 muestra otro árbol que define más de los términos que necesitamos cuando hablamos de
árboles. El nodo superior de un árbol se denomina nodo raíz . Si un nodo está conectado a otros nodos
que se encuentran debajo de él, el nodo anterior se denomina nodo padre y los nodos que lo siguen se
denominan nodos secundarios . Un nodo puede tener cero, uno o más nodos secundarios conectados a
él. Un nodo sin ningún nodo secundario se denomina nodo hoja .
Los árboles binarios , que son tipos especiales , limitan la cantidad de nodos secundarios a no más de
dos. Los árboles binarios tienen ciertas propiedades computacionales que los hacen muy eficientes para
muchas operaciones. Los árboles binarios se examinan en profundidad en las secciones siguientes.
Si continúa examinando la Figura 102, podrá ver que, al seguir ciertos bordes, puede viajar de un nodo
a otros nodos que no están conectados directamente. La serie de bordes que sigue para llegar de un
nodo a otro se denomina ruta . Las rutas se representan en la figura con líneas discontinuas. Visitar todos
los nodos de un árbol en un orden determinado se conoce como recorrido del árbol.
[Link][Link]
Machine Translated by Google
Un árbol se puede dividir en niveles. El nodo raíz está en el nivel 0, sus hijos están en el nivel 1, los hijos
de esos nodos están en el nivel 2, y así sucesivamente. Un nodo en cualquier nivel se considera la raíz de
un subárbol, que consta de los hijos de ese nodo raíz, los hijos de sus hijos, y así sucesivamente. Podemos
definir la profundidad de un árbol como el número de capas del árbol.
Este concepto de que el nodo raíz se encuentra en la parte superior de un árbol, mientras que en la vida
real la raíz de un árbol se encuentra en la parte inferior del mismo, es contraintuitivo, pero es una
convención consagrada en la informática dibujar árboles con la raíz en la parte superior. El científico
informático Donald Knuth intentó cambiar la convención, pero se dio por vencido después de unos meses
cuando descubrió que la mayoría de los científicos informáticos se negaban a adaptarse a la forma natural de dibujar árboles.
árboles.
Por último, cada nodo de un árbol tiene un valor asociado. Este valor se denomina a veces valor clave .
Antes de analizar la construcción de un árbol binario en JavaScript, debemos agregar dos términos a
nuestro léxico de árboles. Los nodos secundarios de un nodo primario se denominan nodo izquierdo y
nodo derecho . Para ciertas implementaciones de árboles binarios, se pueden almacenar ciertos valores de datos
[Link][Link]
Machine Translated by Google
solo en los nodos de la izquierda, y los demás valores de datos deben almacenarse en los nodos de la derecha. En la
Figura 103 se muestra un ejemplo de árbol binario.
La identificación de los nodos secundarios es importante cuando consideramos un tipo más específico de árbol binario,
el árbol binario de búsqueda. Un árbol binario de búsqueda es un árbol binario en el que los datos con valores menores
se almacenan en los nodos de la izquierda y los datos con valores mayores se almacenan en los nodos de la derecha.
Esta propiedad permite realizar búsquedas muy eficientes y admite tanto datos numéricos como no numéricos, como
palabras y cadenas.
búsqueda binaria está formado por nodos, por lo que el primer objeto que necesitamos crear es un objeto Nodo , que es
similar al objeto Nodo que usamos con las listas enlazadas. La definición de
La clase Node es:
función mostrar()
{ devolver [Link];
}
El objeto Nodo almacena datos y enlaces a otros nodos (izquierdo y derecho). También hay una función show() para
mostrar los datos almacenados en un nodo.
Ahora podemos crear una clase para representar un árbol binario de búsqueda (BST). La clase consta de un solo
miembro de datos: un objeto Node que representa el nodo raíz del BST. El constructor de la clase establece el nodo raíz
como nulo, lo que crea un nodo vacío.
[Link][Link]
Machine Translated by Google
La primera función que necesitamos para el BST es insert(), para agregar nuevos nodos al árbol. Esta
función es compleja y requiere una explicación. El primer paso de la función es crear un objeto Node ,
pasando los datos que almacenará el nodo.
El segundo paso de la inserción es comprobar si la BST tiene un nodo raíz. Si no existe ningún nodo
raíz, la BST es nueva y este nodo es el nodo raíz, lo que completa la definición de la función. De lo
contrario, la función pasa al siguiente paso.
Si el nodo que se va a insertar no es el nodo raíz, entonces debemos prepararnos para recorrer la
BST para encontrar el punto de inserción adecuado. Este proceso es similar a recorrer una lista enlazada.
La función utiliza un objeto Nodo que se asigna como nodo actual a medida que la función se mueve
de un nivel a otro en la BST. La función también debe posicionarse dentro de la BST en el nodo raíz.
Una vez dentro de la BST, el siguiente paso es determinar dónde colocar el nodo. Esto se realiza
dentro de un bucle que se rompe una vez que se determina el punto de inserción correcto. El algoritmo
para determinar el punto de inserción correcto para un nodo es el siguiente:
2. Si el valor de los datos en el nodo insertado es menor que el valor de los datos en el nodo actual,
configure el nuevo nodo actual como el hijo izquierdo del nodo actual. Si el valor de los datos en
el nodo insertado es mayor que el valor de los datos en el nodo actual, salte al paso
4.
3. Si el valor del hijo izquierdo del nodo actual es nulo, inserte el nuevo nodo aquí
y salir del bucle. De lo contrario, pasar a la siguiente iteración del bucle.
4. Establezca el nodo actual para que sea el hijo derecho del nodo actual.
5. Si el valor del hijo derecho del nodo actual es nulo, inserte el nuevo nodo aquí y salga del bucle.
De lo contrario, pase a la siguiente iteración del bucle.
Con este algoritmo completo, estamos listos para implementar esta parte de la clase BST .
El ejemplo 101 tiene el código para la clase, incluido el código para el objeto Node .
función mostrar()
{ devolver [Link];
}
función BST() {
[Link][Link]
Machine Translated by Google
[Link] = null;
[Link] = insertar;
[Link] = inOrder;
}
} else
{ var actual = [Link]; var padre;
while
(verdadero) { padre
= actual; if (datos <
[Link]) { actual =
[Link]; if (actual ==
null) { [Link] = n;
break;
} else
{ actual = [Link]; if
(actual == null) { [Link]
= n; break;
}
}
}
}
}
Ahora tenemos los inicios de la clase BST , pero todo lo que podemos hacer es insertar nodos
en el árbol. Necesitamos poder recorrer el BST para poder mostrar los datos en diferentes
órdenes, como numérico o alfabético.
Existen tres funciones de recorrido utilizadas con las BST: inorder, preorder y postorder. Un
recorrido inorder visita todos los nodos de una BST en orden ascendente de los valores de
clave de nodo. Un recorrido preorder visita primero el nodo raíz, seguido por los nodos en los
subárboles debajo del hijo izquierdo del nodo raíz, seguido por los nodos en los subárboles
debajo del hijo derecho del nodo raíz. Un recorrido postorder visita todos los nodos secundarios
del subárbol izquierdo hasta el nodo raíz y luego visita todos los nodos secundarios del
subárbol derecho hasta el nodo raíz.
Si bien es fácil entender por qué querríamos realizar un recorrido en orden, no es tan obvio
por qué necesitamos recorridos en preorden y posorden. Implementaremos las tres funciones
de recorrido ahora y explicaremos sus usos en una sección posterior.
[Link][Link]
Machine Translated by Google
El recorrido en orden se escribe mejor utilizando la recursión. Dado que la función visita cada nodo en
orden ascendente, la función debe visitar tanto el nodo izquierdo como el derecho de cada subárbol,
siguiendo los subárboles debajo del hijo izquierdo del nodo raíz antes de seguir los subárboles debajo del
hijo derecho de la raíz. Si no está seguro acerca del uso de la recursión, el Capítulo 1 analiza cómo escribir
una función recursiva.
función inOrder(nodo) { if (!
(nodo == null))
{ inOrder([Link]);
putstr([Link]() + " ");
inOrder([Link]);
}
}
Recorrido en orden:
3 16 22 23 37 45 99
[Link][Link]
Machine Translated by Google
Notarás que la única diferencia entre las funciones inOrder() y preOrder() es cómo se ordenan las tres líneas
de código dentro de la declaración if . La llamada a la función show() está intercalada entre las dos llamadas
recursivas en la función inOrder() , y la llamada a show() está antes de las dos llamadas recursivas en la función
preOrder() .
Si agregamos una llamada a preOrder() al programa anterior, obtenemos los siguientes resultados:
Recorrido en orden: 3 16
22 23 37 45 99
Recorrido de preorden:
23 16 3 22 45 37 99
[Link][Link]
Machine Translated by Google
Recorrido en orden:
3 16 22 23 37 45 99
Recorrido de preorden:
23 16 3 22 45 37 99
Recorrido postorder:
3 22 16 37 99 45 23
Más adelante en el capítulo demostraremos algunos ejemplos prácticos de programación utilizando BST que
hacen uso de estas funciones de recorrido.
Búsquedas BST
valor máximo
[Link][Link]
Machine Translated by Google
los valores mínimo y máximo almacenados son procedimientos relativamente sencillos. Dado que los
valores inferiores siempre se almacenan en los nodos secundarios izquierdos, para encontrar el valor
mínimo en una BST, solo hay que recorrer el borde izquierdo de la BST hasta llegar al último nodo.
Aquí está la definición de una función, getMin(), que encuentra el valor mínimo de una BST:
La función recorre el enlace izquierdo de cada nodo de la BST hasta llegar al extremo izquierdo de la BST,
que se define como:
[Link] = nulo;
Cuando se llega a este punto, los datos almacenados en el nodo actual deben ser el valor mínimo.
Para encontrar el valor máximo almacenado en una BST, la función simplemente debe recorrer los enlaces
correctos de nodos hasta que la función alcance el extremo derecho de la BST. El valor almacenado en
este nodo debe ser el valor máximo.
El ejemplo 103 prueba las funciones getMin() y getMax() con los datos BST que usamos anteriormente.
[Link][Link]
Machine Translated by Google
"
print("El valor mínimo de la BST es: print("\n"); var max + mín);
= [Link]();
print("El valor máximo de la BST
"
es: + máx);
Estas funciones devuelven los datos almacenados en las posiciones mínima y máxima,
respectivamente. En cambio, es posible que queramos que las funciones devuelvan los nodos donde
se almacenan los valores mínimo y máximo. Para realizar ese cambio, simplemente haga que las
funciones devuelvan el nodo actual en lugar del valor almacenado en el nodo actual.
búsqueda de un valor específico en una BST requiere que se realice una comparación entre los datos
almacenados en el nodo actual y el valor que se busca. La comparación determinará si la búsqueda
se realiza en el nodo secundario izquierdo o en el nodo secundario derecho si el nodo actual no
almacena el valor buscado.
Podemos implementar la búsqueda en una BST con la función find() , que se define aquí:
} else
{ actual = [Link];
} devuelve actual;
}
Esta función devuelve el nodo actual si el valor se encuentra en la BST y devuelve nulo si no se
encuentra el valor.
[Link][Link]
Machine Translated by Google
[Link](37);
[Link](3);
[Link](99);
[Link](22);
inOrder([Link]);
print("\n");
putstr("Ingrese un valor para buscar: "); var valor =
parseInt(readline()); var encontrado =
[Link](valor); if (encontrado != null)
{ print("Encontrado " + valor +
"
en el BST.");
}
else
"
{ imprimir(valor + No se encontró en el BST.");
}
3 16 22 23 37 45 99
Para ayudar a gestionar la complejidad de la eliminación, eliminamos nodos de un BST de forma recursiva.
Las dos funciones que definiremos son remove() y removeNode().
El primer paso que se debe dar al eliminar un nodo de una BST es verificar si el nodo actual contiene los
datos que estamos tratando de eliminar. Si es así, elimine ese nodo. Si no, entonces comparamos los
datos en el nodo actual con los datos que estamos tratando de eliminar. Si los datos que queremos eliminar
son menores que los datos en el nodo actual, nos movemos al hijo izquierdo del nodo actual y comparamos
los datos. Si los datos que queremos eliminar son mayores que los datos en el nodo actual, nos movemos
al hijo derecho del nodo actual y comparamos los datos.
El primer caso a considerar es cuando el nodo que se va a eliminar es una hoja (un nodo sin hijos).
Entonces, todo lo que tenemos que hacer es establecer el enlace que apunta al nodo del nodo padre como
nulo.
Cuando el nodo que queremos eliminar tiene un hijo, entonces el enlace que apunta al nodo que se va a
eliminar debe ajustarse para que apunte al nodo hijo del nodo eliminado.
[Link][Link]
Machine Translated by Google
Finalmente, cuando el nodo que queremos eliminar tiene dos hijos, la solución correcta es o bien
buscar el valor más grande en el subárbol a la izquierda del nodo eliminado, o bien buscar el valor
más pequeño en el subárbol a la derecha del nodo eliminado. Elegiremos ir a la derecha.
Necesitamos una función que encuentre el valor más pequeño de un subárbol, que luego utilizaremos
para crear un nodo temporal que contenga ese valor más pequeño. Copiamos ese valor en la posición
del nodo que estamos reemplazando y eliminamos el nodo temporal para completar la operación. La
Figura 107 ilustra este escenario.
El proceso de eliminación de nodos consta de dos funciones. La función remove() simplemente recibe
el valor que se va a eliminar y llama a la segunda función, removeNode(), que realiza todo el trabajo.
Las definiciones de las dos funciones se muestran aquí:
} si (datos == [Link]) {
// el nodo no tiene hijos si
([Link] == null && [Link] == null) {
[Link][Link]
Machine Translated by Google
devuelve nulo;
} else
{ [Link] = removeNode([Link], datos);
return nodo;
}
}
Contando ocurrencias
Un uso de una BST es realizar un seguimiento de las ocurrencias de datos en un conjunto de datos. Por ejemplo,
podemos utilizar una BST para registrar la distribución de calificaciones en un examen. Dado un conjunto de
calificaciones de exámenes, podemos escribir un programa que verifique si la calificación está en la BST,
agregando la calificación a la BST si no se encuentra e incrementando el número de ocurrencias de la misma si
la calificación se encuentra en la BST.
Para resolver este problema, necesitamos modificar el objeto Nodo para incluir un campo para realizar un
seguimiento de la cantidad de ocurrencias de una calificación en la BST, y necesitamos una función para
actualizar un nodo de modo que si encontramos una calificación en la BST, podamos incrementar el campo de
ocurrencias.
Comencemos modificando nuestra definición del objeto Nodo para incluir un campo para realizar un seguimiento
de las ocurrencias de calificaciones:
[Link][Link]
Machine Translated by Google
Cuando se inserta una calificación (un objeto Node ) en una BST, su conteo se establece en 1. La BST en la función
sert() funcionará bien tal como está, pero necesitamos agregar una función para actualizar la BST cuando sea
necesario incrementar el campo de conteo . Llamaremos a esta función update():
Las demás funciones de la clase BST están bien tal como están. Solo necesitamos un par de funciones para generar
un conjunto de calificaciones y mostrarlas:
función prArray(arr)
{ putstr(arr[0].toString() + ' '); para (var
i = 1; i < [Link]; ++i) { putstr(arr[i].toString()
+ si (i % 10 == 0) { putstr("\n"); ' ');
}
}
}
} devolver arr;
}
El ejemplo 105 presenta un programa para probar este nuevo código para contar ocurrencias de calificaciones.
función prArray(arr)
{ putstr(arr[0].toString() + ' '); para (var
i = 1; i < [Link]; ++i) { putstr(arr[i].toString()
+ si (i % 10 == 0) { putstr("\n"); ' ');
}
}
}
[Link][Link]
Machine Translated by Google
devolver arr;
}
} de lo
contrario { [Link](g);
}
} else
{ print("Ocurrencias de " + g + ": " + [Link]);
25 32 24 92 80 46 21 85 23 22 3
24 43 4 100 34 82 76 69 51 44 92 54 1 88
4 66 62 74 49 18
15 81 95 80 4 64 13 30 51 21
12 64 82 81 38 100 17 76 62 32
3 24 47 86 49 100 49 81 100 49
80 0 28 79 34 64 40 81 35 23 95 90 92 13
28 88 31 82 16 93
12 92 52 41 27 53 31 35 90 21
22 66 87 80 83 66 3 6 18
[Link][Link]
Machine Translated by Google
No hay ocurrencias de 65
¿Mirar otra calificación (s/n)? y
Ceremonias
1. Agregue una función a la clase BST que cuente la cantidad de nodos en una BST.
2. Agregue una función a la clase BST que cuente la cantidad de aristas en una BST.
3. Agregue una función max() a la clase BST que encuentre el valor máximo en una BST.
4. Agregue una función min() a la clase BST que encuentre el valor mínimo en una BST.
5. Escriba un programa que almacene las palabras de un archivo de texto grande en una BST y las muestre
el número de veces que aparece cada palabra en el texto.
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 11
El estudio de las redes se ha convertido en uno de los grandes focos científicos de este siglo, aunque
los matemáticos y otros científicos llevan estudiando las redes muchos cientos de años. Los recientes
avances en la tecnología informática (Internet, por ejemplo) y en la teoría social (la red social,
popularizada por el concepto de “seis grados de separación”), por no hablar de los medios sociales,
han puesto el foco en el estudio de las redes.
En este capítulo veremos cómo se modelan las redes con grafos. Definiremos qué es un grafo, cómo
representar grafos en JavaScript y cómo implementar algoritmos gráficos importantes. También
analizaremos la importancia de elegir la representación de datos correcta al trabajar con grafos, ya
que la eficiencia de los algoritmos gráficos depende en gran medida de la estructura de datos
utilizada para representar un grafo.
Definiciones de gráficos
139
[Link][Link]
Machine Translated by Google
Si un gráfico no está ordenado, se denomina gráfico desordenado o, simplemente, gráfico. En la figura 112 se muestra un
ejemplo de gráfico desordenado.
Un camino es una secuencia de vértices en un grafo de modo que todos los vértices del camino están conectados por
aristas. La longitud de un camino es el número de aristas desde el primer vértice del camino hasta el último vértice. Un
camino también puede constar de un vértice hacia sí mismo, lo que se denomina bucle.
Los bucles tienen una longitud de 0.
Un ciclo es un camino con al menos una arista cuyos vértices primero y último son iguales. Un ciclo simple es aquel que no
tiene aristas o vértices repetidos, tanto para grafos dirigidos como no dirigidos. Los caminos que repiten otros vértices
además del primero y el último se denominan ciclos generales.
Dos vértices se consideran fuertemente conexos si existe un camino desde el primer vértice hasta el segundo vértice, y
viceversa. Si el grafo es un grafo dirigido y todos sus vértices están fuertemente conexos, entonces el grafo dirigido se
considera fuertemente conexo.
[Link][Link]
Machine Translated by Google
Los gráficos se utilizan para modelar muchos tipos diferentes de sistemas del mundo real. Un ejemplo es el
flujo de tráfico. Los vértices representan las intersecciones de las calles y los bordes representan las calles.
Los bordes ponderados se pueden utilizar para representar los límites de velocidad o la cantidad de carriles.
Los modeladores pueden utilizar el sistema para determinar las mejores rutas y las calles con mayor
probabilidad de sufrir atascos.
Cualquier tipo de sistema de transporte se puede modelar mediante un gráfico. Por ejemplo, una aerolínea
puede modelar su sistema de vuelos mediante un gráfico. Cada aeropuerto es un vértice y cada vuelo de
un vértice a otro es una arista. Una arista ponderada puede representar el costo de un vuelo de un
aeropuerto a otro, o quizás la distancia de un aeropuerto a otro, dependiendo de lo que se esté modelando.
Las redes de computadoras, incluidas las redes de área local y redes mucho más amplias como Internet,
también se modelan frecuentemente con grafos. Otro ejemplo de un sistema real que se puede modelar
con un grafo es un mercado de consumo, donde los vértices representan tanto a las instituciones
(proveedores) como a los consumidores.
La clase gráfica
A primera vista, un gráfico se parece mucho a un árbol o a un árbol binario, y es posible que sientas la
tentación de intentar construir una clase de gráfico como un árbol, utilizando nodos para representar cada
vértice. Sin embargo, existen problemas con el uso de un enfoque basado en objetos como ese, porque los
gráficos pueden crecer bastante. Representar un gráfico utilizando solo objetos puede volverse ineficiente
rápidamente, por lo que analizaremos un esquema diferente para representar tanto vértices como aristas.
Representación de vértices El
primer paso para crear una clase Graph es crear una clase Vertex para almacenar los vértices de un
grafo. Esta clase tiene las mismas funciones que la clase Node tenía con las listas enlazadas y los
árboles de búsqueda binaria. La clase Vertex necesita dos miembros de datos: uno para identificar el
vértice y el otro para almacenar un valor booleano que indica si el vértice ha sido visitado o no. Estos
miembros se denominan label y wasVisited, respectivamente. La única función que necesitamos para
la clase es la función constructora que nos permite establecer los valores para los miembros de datos
de un vértice. Aquí está el código para la clase Vertex :
función Vertex(etiqueta)
{ [Link] = etiqueta;
}
Almacenaremos la lista de vértices en una matriz y haremos referencia a ellos en la clase Graph por su
posición en la matriz.
[Link][Link]
Machine Translated by Google
Representación de aristas
La información real sobre un grafo se almacena en las aristas, ya que las aristas describen la
estructura de un grafo. Como mencionamos anteriormente, es tentador representar un grafo como
un árbol binario, pero hacerlo es un error. Un árbol binario tiene una representación mayoritariamente
fija, ya que un nodo padre puede tener solo dos nodos hijos, mientras que una estructura de grafo
proporciona mucha más flexibilidad. Puede haber muchas aristas vinculadas a un solo vértice o
solo una arista, por ejemplo.
El método que utilizaremos para representar las aristas de un grafo se denomina lista de adyacencia o
matriz de listas de adyacencia. Con este método, las aristas se almacenan como una matriz indexada por
vértices (matrices) de los vértices adyacentes a cada vértice. Con este esquema, cuando hacemos
referencia a un vértice en un programa, podemos acceder de manera eficiente a la lista de todos los
vértices a los que está conectado. Por ejemplo, si el vértice 2 está conectado a los vértices 0, 1, 3 y 4, y
está almacenado en la posición de matriz 2, acceder a este elemento nos da acceso a una matriz
almacenada en la posición de matriz 2 que consta de los vértices 0, 1, 3 y 4. Este es el método de
representación que elegimos utilizar en este capítulo y se muestra en la Figura 113.
Otro método para representar los bordes de un gráfico se denomina matriz de adyacencia. Se trata de una
matriz bidimensional en la que los elementos de la matriz indican si existe un borde entre dos vértices.
[Link][Link]
Machine Translated by Google
Creación de un gráfico
Una vez que se ha tomado la decisión sobre cómo representar un gráfico en el código, es sencillo crear
una clase para representarlo. A continuación, se muestra una primera definición de una clase Graph :
función Graph(v) {
[Link] = v;
[Link] = 0;
[Link] = []; para
(var i = 0; i < [Link]; ++i) { [Link][i] = [];
[Link][i].push("");
} [Link] = addEdge;
[Link] = toString;
}
La clase lleva un registro de la cantidad de aristas representadas en un gráfico, así como de la cantidad de
vértices, mediante el uso de una matriz cuya longitud es igual a la cantidad de vértices del gráfico. En cada
elemento de la matriz, el bucle for agrega una submatriz para almacenar todos los vértices adyacentes e
inicializa cada elemento con la cadena vacía.
función addEdge(v,w)
{ [Link][v].push(w);
[Link][w].push(v);
[Link]++;
}
Cuando se llama a esta función con dos vértices, A y B, la función encuentra la lista de adyacencia para el
vértice A y agrega B a la lista; luego, encuentra la lista de adyacencia para B y agrega A a la lista.
Finalmente, la función incrementa el número de aristas en 1.
La función showGraph() muestra el gráfico mostrando una lista de todos los vértices y los vértices
adyacentes a ellos:
función showGraph() {
para (var i = 0; i < [Link]; ++i) { putstr(i + > ");
"
para (var j = 0; j <
[Link]; ++j) { si ([Link][i][j] != indefinido)
putstr([Link][i][j] + ' ');
} imprimir();
}
}
El ejemplo 111 muestra la definición completa de la clase Graph .
[Link][Link]
Machine Translated by Google
función Graph(v) {
[Link] = v;
[Link] = 0;
[Link] = []; para
(var i = 0; i < [Link]; ++i) { [Link][i] = [];
[Link][i].push("");
} [Link] = addEdge;
[Link] = showGraph;
}
función addEdge(v,w)
{ [Link][v].push(w);
[Link][w].push(v);
[Link]++;
}
función showGraph() {
para (var i = 0; i < [Link]; ++i) { putstr(i + > ");
"
para (var j = 0; j <
[Link]; ++j) { si ([Link][i][j] != indefinido)
putstr([Link][i][j] + ' ');
} imprimir();
}
}
Aquí hay un programa de prueba que demuestra cómo utilizar la clase Graph :
cargar("[Link]"); g
= nuevo Graph(5);
[Link](0,1);
[Link](0,2);
[Link](1,3);
[Link](2,4);
[Link]();
0 > 1 2
1 > 0 3
2 > 0 4 3
> 1
4 > 2
La salida muestra que el vértice 0 tiene aristas en los vértices 1 y 2; el vértice 1 tiene aristas en los vértices
0 y 3; el vértice 2 tiene aristas en los vértices 0 y 4; el vértice 3 tiene una arista en el vértice 1; y el vértice 4
tiene una arista en el vértice 2. Por supuesto, hay cierta redundancia en esta visualización, ya que una arista
[Link][Link]
Machine Translated by Google
entre 0 y 1, por ejemplo, es lo mismo que un borde entre 1 y 0. Solo para fines de visualización, esto está
bien, pero necesitaremos modificar esta salida cuando comencemos a explorar las rutas encontradas en un
gráfico.
Buscando un gráfico
Determinar a qué vértices se puede llegar desde un vértice específico es una actividad común que se realiza
en los grafos. Tal vez queramos saber qué caminos conducen de una ciudad a otras ciudades en el mapa, o
qué vuelos nos pueden llevar de un aeropuerto a otros aeropuertos.
Estas operaciones se realizan en un grafo mediante un algoritmo de búsqueda. Existen dos búsquedas
fundamentales que podemos realizar en un grafo: la búsqueda en profundidad y la búsqueda en amplitud .
En esta sección examinamos ambos algoritmos.
Búsqueda en profundidad
La búsqueda en profundidad implica seguir una ruta desde el vértice inicial hasta llegar al último vértice, luego
retroceder y seguir la siguiente ruta hasta llegar al último vértice, y así sucesivamente hasta que no queden
rutas. Aquí no estamos "buscando" un elemento en particular, sino buscando ver qué rutas podemos seguir
en un gráfico. La Figura 114 ilustra cómo funciona la búsqueda en profundidad.
El algoritmo para realizar una búsqueda en profundidad es relativamente simple: visitar un vértice que aún no
ha sido visitado, marcarlo como visitado y luego visitar recursivamente los otros vértices no visitados que
están en la lista de adyacencia del vértice original.
[Link][Link]
Machine Translated by Google
Para que este algoritmo funcione, necesitaremos agregar una matriz a nuestra clase Graph que almacene los
vértices visitados e inicializarla con todos los valores falsos . Aquí hay un fragmento de código de la clase
Graph que muestra esta nueva matriz y su inicialización:
[Link] = [];
para (var i = 0; i < [Link]; ++i) { [Link][i]
= falso;
}
función dfs(v)
{ [Link][v] = true; // si
no se requiere la declaración para imprimir if
([Link][v] != undefined) print("Vértice
"
visitado: + v); for each (var w in
[Link][v]) { if (![Link][w]) { [Link](w);
}
}
}
Tenga en cuenta que he incluido una función print() para que podamos ver los vértices a medida que se visitan.
Por supuesto, esta función no es necesaria para que la función dfs() funcione correctamente.
En el Ejemplo 112 se muestra un programa que demuestra la función depthFirst() , junto con la definición
completa de la clase Graph .
función Graph(v) {
[Link] = v;
[Link] = 0;
[Link] = []; para
(var i = 0; i < [Link]; ++i) { [Link][i] = [];
[Link][i].push("");
} [Link] = addEdge;
[Link] = showGraph;
[Link] = dfs;
[Link] = [];
para (var i = 0; i < [Link]; ++i) { [Link][i]
= falso;
}
}
función addEdge(v,w)
{ [Link][v].push(w);
[Link][w].push(v);
[Link]++;
[Link][Link]
Machine Translated by Google
función showGraph() {
para (var i = 0; i < [Link]; ++i) { putstr(i + > ");
"
para (var j = 0; j
} imprimir();
}
}
función dfs(v)
{ [Link][v] = true; if
([Link][v] != undefined) { print("Vértice
"
visitado: + v);
}
}
}
cargar("[Link]"); g
= nuevo Graph(5);
[Link](0,1);
[Link](0,2);
[Link](1,3);
[Link](2,4);
[Link]();
[Link](0);
[Link][Link]
Machine Translated by Google
Búsqueda en amplitud
Una búsqueda en amplitud comienza en un primer vértice e intenta visitar los vértices que estén lo más
cerca posible del primer vértice. En esencia, esta búsqueda recorre un gráfico capa por capa, primero
examinando las capas más cercanas al primer vértice y luego bajando hasta las capas más alejadas del
vértice inicial. La Figura 115 demuestra cómo funciona la búsqueda en amplitud.
El algoritmo de búsqueda en amplitud utiliza una abstracción de cola en lugar de una abstracción de
matriz para almacenar los vértices visitados. El algoritmo funciona de la siguiente manera:
1. Busque un vértice no visitado que sea adyacente al vértice actual, agréguelo a la lista de vértices
visitados y agréguelo a la cola.
3. Agregue todos los vértices no marcados que sean adyacentes a v y agréguelos a la cola.
[Link][Link]
Machine Translated by Google
[Link][w] = v;
[Link][w] = true;
[Link](w);
}
}
}
}
cargar("[Link]"); g
= nuevo Graph(5);
[Link](0,1);
[Link](0,2);
[Link](1,3);
[Link](2,4);
[Link]();
[Link](0);
0 > 1 2
1 > 0 3
2 > 0 4
3 > 1
4 > 2
Vértice visitado: 0
Vértice visitado: 1
Vértice visitado: 2
Vértice visitado: 3
Vértice visitado: 4
Cuando realizamos una búsqueda en amplitud, buscamos automáticamente los caminos más cortos desde
un vértice hasta otro vértice conectado. Por ejemplo, cuando queremos encontrar el camino más corto
desde el vértice A hasta el vértice D, primero buscamos cualquier camino de una arista desde A hasta
[Link][Link]
Machine Translated by Google
D, luego dos rutas de aristas de A a D, y así sucesivamente. Así es exactamente como funciona la búsqueda en
amplitud, por lo que podemos modificar fácilmente el algoritmo de búsqueda en amplitud para encontrar las rutas
más cortas.
encontrar la ruta más corta, debemos modificar el algoritmo de búsqueda en amplitud para que registre las rutas
que van de un vértice a otro. Esto requiere algunas modificaciones en la clase Graph .
Primero, necesitamos una matriz que lleve un registro de las aristas de un vértice al siguiente. Llamaremos a esta
matriz edgeTo. A medida que trabajamos con la función de búsqueda en amplitud, cada vez que nos encontramos
con un vértice que no está marcado, además de marcarlo, agregaremos una arista a ese vértice del vértice que
estamos explorando en la lista de adyacencia. Aquí está la nueva función bfs() , junto con el código que necesita
agregar a la clase Graph :
"
print("Vértice visitado: + v);
Ahora necesitamos una función que nos muestre las rutas que conectan los diferentes vértices de un grafo. Esta
función, pathTo(), crea una pila que almacena todos los vértices que tienen aristas en común con un vértice
especificado. Aquí está el código de la función, junto con una función auxiliar simple:
[Link][Link]
Machine Translated by Google
} [Link](s);
devolver ruta;
}
función tieneRutaA(v) {
devuelve [Link][v];
}
[Link] = pathTo;
[Link] = hasPathTo;
Con esta función, todo lo que tenemos que hacer es escribir un código de cliente para mostrar la ruta
más corta desde la fuente hasta un vértice en particular. El ejemplo 114 muestra un programa que
crea un gráfico y muestra la ruta más corta para un vértice específico.
cargar("[Link]"); g =
new Graph(5);
[Link](0,1);
[Link](0,2);
[Link](1,3);
[Link](2,4); var
vertice = 4; var rutas
= [Link](vertice); mientras ([Link]
> 0) { si ([Link] > 1)
{ putstr([Link]() + '');
} de lo
contrario { putstr([Link]());
}
}
024
Ordenamiento topológico
La clasificación topológica coloca los vértices de un grafo dirigido en un orden tal que todas las
aristas dirigidas apuntan desde un vértice anterior en el orden a un vértice posterior en el orden. Por
ejemplo, la Figura 116 muestra un modelo de grafo dirigido de un plan de estudios típico de
informática.
[Link][Link]
Machine Translated by Google
Una ordenación topológica de este gráfico daría como resultado la siguiente secuencia:
1. CS 1
2. CS 2
3. Lenguaje ensamblador
4. Estructuras de datos
5. Sistemas operativos 6.
Algoritmos
Los cursos 3 y 4 se pueden tomar al mismo tiempo, al igual que los cursos 5 y 6.
Este tipo de problema se denomina programación con restricciones de precedencia y todos los estudiantes
universitarios están familiarizados con él. No puedes tomar Redacción en inglés II hasta que hayas tomado
Redacción en inglés I.
para la ordenación topológica es similar al algoritmo para la búsqueda en profundidad. Sin embargo, en lugar
de imprimir inmediatamente un vértice a medida que se lo visita, el algoritmo visita todos los vértices
adyacentes al vértice actual y, una vez que se agota esa lista, colocamos el vértice actual en una pila.
ordenamiento topológico se divide en dos funciones. La primera función, topSort(), configura el proceso de
ordenamiento y llama a una función auxiliar, topSortHelper(), y luego muestra la lista ordenada de vértices.
El trabajo principal se realiza en la función recursiva topSortHelper(). Esta función marca el vértice actual
como visitado y luego visita recursivamente cada vértice adyacente en
[Link][Link]
Machine Translated by Google
la lista de adyacencias del vértice actual, marcándolos como visitados. Finalmente, el vértice actual
se coloca en una pila.
}
}
}
} [Link](v);
}
La clase Graph también ha sido modificada para que podamos trabajar con vértices simbólicos y no
solo con números. Dentro del código, cada vértice sigue estando solo numerado, pero agregamos
una matriz, vertexList, que asocia cada vértice con un símbolo (para nuestro ejemplo, es el nombre
de un curso).
Para asegurarnos de que la nueva definición de la clase sea clara, presentamos a continuación la
definición completa, incluidas las funciones para la ordenación topológica. La definición de la función
showGraph() ha cambiado para que se muestren los nombres simbólicos en lugar de solo los
números de vértice. El ejemplo 116 muestra el código.
[Link][Link]
Machine Translated by Google
} [Link] = addEdge;
[Link] = showGraph;
[Link] = dfs;
[Link] = [];
para (var i = 0; i < [Link]; ++i) { [Link][i]
= falso;
} [Link] = bfs;
[Link] = [];
[Link] = hasPathTo;
[Link] = pathTo;
[Link] = topSortHelper;
[Link] = topSort;
}
}
}
} [Link](v);
}
función addEdge(v,w)
{ [Link][v].push(w);
[Link][w].push(v);
[Link][Link]
Machine Translated by Google
[Link]++;
}
}
imprimir();
} }*/
}
}
} print();
[Link]();
}
}
función dfs(v)
{ [Link][v] = true; if
([Link][v] != undefined) { print("Vértice
"
visitado: + v);
}
}
}
[Link][Link]
Machine Translated by Google
función tieneRutaA(v) {
devuelve [Link][v];
}
} [Link](s);
devolver ruta;
}
[Link]();
[Link]();
CS1
CS2
Estructuras de datos
Lenguaje ensamblador
[Link][Link]
Machine Translated by Google
Sistemas operativos
Algoritmos
Ceremonias
1. Escriba un programa que determine qué tipo de búsqueda de gráficos es más rápida: búsqueda en amplitud
o búsqueda en profundidad. Pruebe el programa con gráficos de distintos tamaños.
4. Construye un gráfico que modele el mapa del área donde vives. Determina el trayecto más corto
ruta desde un vértice inicial hasta el último vértice.
5. Realice una búsqueda en profundidad y una búsqueda en amplitud del gráfico creado en ex
Amplio 4.
Ejercicios | 157
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 12
Algoritmos de ordenamiento
Dos de las operaciones más comunes que se realizan con los datos almacenados en una computadora son la
ordenación y la búsqueda. Esto ha sido así desde el comienzo de la industria informática, por lo que significa
que la ordenación y la búsqueda son dos de las operaciones más estudiadas en la ciencia informática. Muchas
de las estructuras de datos que se analizan en este libro están diseñadas principalmente para que la ordenación
y/o la búsqueda de los datos almacenados en la estructura de datos sean más fáciles y eficientes.
En este capítulo, le presentaremos algunos de los algoritmos básicos y avanzados para ordenar datos. Estos
algoritmos dependen únicamente de la matriz como medio de almacenamiento de datos. En este capítulo,
también veremos formas de cronometrar nuestros programas para determinar qué algoritmo es el más eficiente.
Comenzaremos este capítulo desarrollando un banco de pruebas de matrices para utilizarlo como apoyo a
nuestro estudio de algoritmos de ordenamiento básicos. Construiremos una clase para datos y funciones de
matrices que encapsule algunas de las operaciones normales de las matrices: insertar nuevos datos, mostrar
datos de matrices y llamar a los diferentes algoritmos de ordenamiento. En la clase se incluye una función
swap() que utilizaremos para intercambiar elementos en la matriz.
159
[Link][Link]
Machine Translated by Google
función CArray(numElements)
{ [Link] = [];
[Link] = 0;
[Link] = numElements;
[Link] = insertar;
[Link] = toString; [Link]
= clear; [Link] =
setData; [Link] = intercambiar;
función clear() {
para (var i = 0; i < [Link]; ++i) { [Link][i] = 0;
}
}
función insertar(elemento)
{ [Link][[Link]++] = elemento;
}
} devolver retstr;
}
[Link][Link]
Machine Translated by Google
Aquí hay un programa simple que usa la clase CArray (la clase se llama CArray porque
JavaScript ya tiene una clase Array ):
76 69 64 4 64 73 47 34 65 93 32
59 4 92 84 55 30 52 64 38 74 40 68
71 25 84 5 57 7 6 40
45 69 34 73 87 63 15 96 91 96
88 24 58 78 18 97 22 48 6 45
68 65 40 50 31 80 7 39 72 84
72 22 66 84 14 58 11 42 7 72 87 39
79 18 18 9 84 18 45 50
43 90 87 62 65 97 97 21 96 39
7 79 68 35 39 89 43 86 5
que la función setData() genera números aleatorios para almacenar en la matriz. La función random() , que es parte de
la clase Math , genera números aleatorios en un rango de 0 a 1, exclusivo. En otras palabras, ningún número aleatorio
generado por la función será igual a 0, y ningún número aleatorio será igual a 1. Estos números aleatorios no son muy
útiles, por lo que escalamos los números multiplicando el número aleatorio por la cantidad de elementos que queremos
más 1, y luego usamos la función floor() de la clase Math para finalizar el número. Como puedes ver en el resultado
anterior, esta fórmula logra generar un conjunto de números aleatorios entre 1 y 100.
Para obtener más información sobre cómo JavaScript genera números aleatorios, consulte la página de Mozilla, Uso
de la función [Link](), para la generación de números aleatorios.
El concepto fundamental de los algoritmos de ordenamiento básicos que se tratan a continuación es que existe una
lista de datos que se deben reorganizar en un orden determinado. La técnica que se utiliza en estos algoritmos para
reorganizar los datos de una lista es un conjunto de bucles for anidados . El bucle externo recorre la lista elemento por
elemento, mientras que el bucle interno se utiliza para comparar elementos. Estos algoritmos simulan muy de cerca la
forma en que los seres humanos ordenan los datos en la vida real, como por ejemplo, cómo un jugador de cartas
ordena las cartas cuando se le reparten las cartas o cómo un profesor ordena los trabajos en orden alfabético o de
grado.
[Link][Link]
Machine Translated by Google
Ordenamiento de burbujas
El primer algoritmo de ordenación que examinaremos es el de burbuja. Este es uno de los algoritmos de ordenación más
lentos, pero también uno de los más fáciles de implementar.
El ordenamiento de burbuja recibe su nombre porque cuando se ordenan los datos mediante este algoritmo, los valores
flotan como una burbuja de un extremo a otro de la matriz. Suponiendo que se ordena un conjunto de números en orden
ascendente, los valores más grandes flotan a la derecha de la matriz y los valores más bajos a la izquierda. Este
comportamiento es el resultado de que el algoritmo recorra la matriz muchas veces, compare valores adyacentes e
intercambie valores si el valor de la izquierda es mayor que el valor de la derecha.
A continuación se muestra un ejemplo sencillo de ordenación de burbuja. Empezamos con la siguiente lista:
AEDBH
Se intercambian los elementos primero y segundo. El siguiente paso de la ordenación da como resultado:
Se intercambian los elementos segundo y tercero. El siguiente paso da como resultado el siguiente orden:
ADBEH
A medida que se intercambian los elementos tercero y cuarto, y finalmente, se intercambian nuevamente los elementos
segundo y tercero, lo que da lugar al orden final:
ABDEH
La figura 121 ilustra cómo funciona la ordenación de burbuja con un conjunto de datos de números más grande. En la
figura, examinamos dos valores particulares insertados en la matriz: 2 y 72. Cada número está rodeado por un círculo.
Puede ver cómo 72 se mueve desde el principio de la matriz hasta el medio de la misma, y puede ver cómo 2 se mueve
desde justo después del medio de la matriz hasta el principio de la matriz.
[Link][Link]
Machine Translated by Google
Asegúrese de agregar una llamada a esta función al constructor CArray . El ejemplo 124 es un programa breve
que ordena 10 números utilizando la función bubbleSort() .
[Link][Link]
Machine Translated by Google
imprimir([Link]());
[Link]();
imprimir();
imprimir([Link]());
10 8 3 2 2 4 9 5 4 3
2 2 3 3 4 4 5 8 9 10
Podemos ver que el algoritmo de ordenamiento de burbuja funciona, pero sería bueno ver los
resultados intermedios del algoritmo, ya que un registro del proceso de ordenamiento es útil
para ayudarnos a comprender cómo funciona el algoritmo. Podemos hacerlo mediante la
colocación cuidadosa de la función toString() en la función bubbleSort() , que mostrará el estado
actual de la matriz a medida que avanza la función (como se muestra en el Ejemplo 125).
} imprimir([Link]());
}
}
Cuando volvemos a ejecutar el programa anterior con la función toString() incluida, obtenemos
el siguiente resultado:
1033545067
0133450567
0133405567
01330455670130
345567
0103345567
0013345567
0013345567
00133455670013
345567
0013345567
Con esta salida, puede ver más fácilmente cómo los valores más bajos se abren camino hasta
el principio de la matriz y cómo los valores más altos se abren camino hasta el final de la matriz.
[Link][Link]
Machine Translated by Google
Los bucles anidados se utilizan en el algoritmo de ordenación por selección. El bucle externo se desplaza
desde el primer elemento de la matriz hasta el penúltimo elemento; el bucle interno se desplaza desde
el segundo elemento de la matriz hasta el último elemento, buscando valores que sean más pequeños
que el elemento al que apunta actualmente el bucle externo. Después de cada iteración del bucle interno,
al valor más pequeño de la matriz se le asigna el lugar que le corresponde en la matriz. La Figura 122
ilustra cómo funciona el algoritmo de ordenación por selección.
A continuación se muestra un ejemplo sencillo de cómo funciona la ordenación por selección en una lista de cinco elementos. La lista
original es:
EADHB
La primera pasada busca el valor mínimo y lo intercambia con el valor al principio de la lista:
AEDHB
El siguiente paso encuentra el valor mínimo después del primer elemento (que ahora está en su lugar) y
lo intercambia:
ABDHE
La D está en su lugar, por lo que el siguiente paso intercambia la E y la H, lo que lleva a que la lista quede
en orden:
ABDEH
La figura 122 muestra cómo funciona la ordenación por selección en un conjunto de datos de números más grande.
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
A continuación se muestra el resultado de una ejecución de nuestro programa utilizando la función selectionSort() .
Agregue la siguiente línea justo después del intercambio:
imprimir([Link]());
6 8 0 6 7 4 3 1 5 10
0 8 6 6 7 4 3 1 5 10
0 1 6 6 7 4 3 8 5 10
0 1 3 6 7 4 6 8 5 10 0 1 3
4 7 6 6 8 5 10
0 1 3 4 5 6 6 8 7 10
0 1 3 4 5 6 6 8 7 10
0 1 3 4 5 6 6 8 7 10
0 1 3 4 5 6 6 7 8 10 0 1 3
4 5 6 6 7 8 10
0 1 3 4 5 6 6 7 8 10
La ordenación por inserción es análoga a la forma en que los humanos ordenan los datos numérica o
alfabéticamente. Digamos que he pedido a cada estudiante de una clase que entregue una tarjeta de índice con
su nombre, identificación de estudiante y una breve reseña biográfica. Los estudiantes devuelven las tarjetas en
orden aleatorio, pero quiero que estén en orden alfabético para poder compararlas fácilmente con la lista de mi
clase.
Llevo las cartas a mi oficina, limpio mi escritorio y tomo la primera carta. El apellido de la carta es
Smith. La coloco en la esquina superior izquierda del escritorio y tomo la segunda carta. El apellido
de la carta es Brown. Muevo a Smith hacia la derecha y pongo a Brown en el lugar de Smith. La
siguiente carta es Williams. Se puede insertar en el extremo derecho del escritorio sin tener que
mover ninguna de las otras cartas. La siguiente carta es Acklin. Tiene que ir al principio de la lista,
por lo que cada una de las otras cartas debe moverse una posición hacia la derecha para hacer
espacio para la carta de Acklin. Así es como funciona la ordenación por inserción.
La ordenación por inserción tiene dos bucles. El bucle externo mueve elemento por elemento a través de la matriz,
mientras que el bucle interno compara el elemento elegido en el bucle externo con el elemento que se encuentra
junto a él en la matriz. Si el elemento seleccionado por el bucle externo es menor que el elemento seleccionado
por el bucle interno, los elementos de la matriz se desplazan hacia la derecha para dejar espacio para el elemento
del bucle interno, tal como se describe en el ejemplo de la tarjeta de presentación anterior.
[Link][Link]
Machine Translated by Google
} [Link][interior] = temp;
}
}
Ahora veamos cómo funciona la ordenación por inserción ejecutando nuestro programa con un conjunto de datos:
6 10 0 6 5 8 7 4 2 7
0 6 10 6 5 8 7 4 2 7
0 6 6 10 5 8 7 4 2 7 0 5 6
6 10 8 7 4 2 7
0 5 6 6 8 10 7 4 2 7
0 5 6 6 7 8 10 4 2 7
0 4 5 6 6 7 8 10 2 7
0 2 4 5 6 6 7 8 10 7 0 2 4
5 6 6 7 7 8 10
0 2 4 5 6 6 7 7 8 10
Esta salida muestra claramente que la ordenación por inserción funciona no realizando intercambios de datos,
sino moviendo elementos de matriz más grandes hacia la derecha para hacer lugar para los elementos más
pequeños en el lado izquierdo de la matriz.
algoritmos de ordenación son muy similares en complejidad y, en teoría, deberían funcionar de manera similar.
Para determinar las diferencias de rendimiento entre estos tres algoritmos, podemos utilizar un sistema de tiempo
informal para comparar cuánto tiempo les lleva ordenar conjuntos de datos. Poder cronometrar estos algoritmos
es importante porque, si bien no verá mucha diferencia en los tiempos de los algoritmos de ordenación cuando
esté ordenando 100 elementos o incluso 1000 elementos, puede haber una gran diferencia en los tiempos que
estos algoritmos tardan en ordenar millones de elementos.
El sistema de cronometraje que utilizaremos en esta sección se basa en recuperar la hora del sistema mediante
la función getTime() del objeto Date de JavaScript . Así es como funciona la función:
La función getTime() devuelve la hora del sistema en milisegundos. El siguiente fragmento de código:
[Link][Link]
Machine Translated by Google
135154872720
Para registrar el tiempo que tarda el código en ejecutarse, iniciamos el temporizador, ejecutamos el código y, luego,
detenemos el temporizador cuando el código termina de ejecutarse. El tiempo que tarda en ordenar los datos es la
diferencia entre el tiempo de finalización registrado y el tiempo de inicio registrado.
El ejemplo 128 muestra un ejemplo de cronometraje de un bucle for que muestra los números del 1 al 100.
La salida, sin incluir los valores de tiempo de inicio y detención, del programa es:
Para nuestra comparación de los tres algoritmos de ordenamiento básicos, cronometraremos el ordenamiento de
matrices de los tres algoritmos con tamaños de conjuntos de datos de 100, 1000 y 10 000. No esperamos ver mucha
diferencia entre los algoritmos para tamaños de conjuntos de datos de 100 y 1000, pero sí esperamos que haya
alguna diferencia al usar un tamaño de conjunto de datos de 10 000.
Comencemos con una matriz de 100 números enteros elegidos al azar. También agregamos una función
para crear un nuevo conjunto de datos para cada algoritmo. El ejemplo 129 muestra el código para esta
nueva función.
[Link][Link]
Machine Translated by Google
[Link]();
detener = nueva Fecha().getTime();
transcurrido = parar iniciar;
" +
print("El tiempo transcurrido para la ordenación de selección de "
"
numElementos + elementos es: + transcurrido + "milisegundos.");
inicio = nueva Fecha().getTime();
[Link]();
detener = nueva Fecha().getTime();
transcurrido = parar iniciar;
" +
print("Tiempo transcurrido para la ordenación por inserción en
"
numElementos + " elementos es: + transcurrido + "milisegundos.");
Aquí están los resultados (tenga en cuenta que ejecuté estas pruebas en un procesador Intel de 2,4 GHz):
Para la siguiente prueba, cambiamos la variable numElements a 1000. Estos son los resultados:
El tiempo transcurrido para la ordenación de burbuja en 10000 elementos es: 1096 milisegundos.
El tiempo transcurrido para la ordenación de selección en 10000 elementos es: 591 milisegundos.
El tiempo transcurrido para la ordenación por inserción de 10000 elementos es: 471 milisegundos.
Los resultados para 10.000 números son consistentes con los resultados para 1.000 números.
La ordenación por selección y la ordenación por inserción son más rápidas que la ordenación por burbuja, y la ordenación por inserción es
El más rápido de los tres algoritmos de ordenación. Sin embargo, tenga en cuenta que estas pruebas deben
deben ejecutarse varias veces para que los resultados se consideren estadísticamente válidos.
En esta sección cubriremos algoritmos más avanzados para ordenar datos. Estos algoritmos de ordenación
Los algoritmos generalmente se consideran los más eficientes para grandes conjuntos de datos, donde los datos
Los conjuntos pueden tener millones de elementos en lugar de sólo cientos o incluso miles.
Los algoritmos que estudiamos en este capítulo incluyen Quicksort, Shellsort, Mergesort y Heapsort.
Ordenar. Analizamos la implementación de cada algoritmo y luego comparamos su eficiencia.
realizando pruebas de cronometraje.
[Link][Link]
Machine Translated by Google
Shellsort funciona definiendo una secuencia de espacios que indica la distancia entre los elementos
comparados al iniciar el proceso de ordenación. La secuencia de espacios se puede definir de forma
dinámica, pero para la mayoría de las aplicaciones prácticas, se puede predefinir la secuencia de
espacios que utilizará el algoritmo. Existen varias secuencias de espacios publicadas que producen diferentes resultados.
Vamos a utilizar la secuencia definida por Marcin Ciura en su artículo sobre los mejores incrementos
para el caso promedio de Shellsort (“Best Increments for the Average Case of Shell Sort”, 2001). La
secuencia de espacios es: 701, 301, 132, 57, 23, 10, 4, 1. Sin embargo, antes de escribir el código
para el caso promedio, vamos a examinar cómo funciona el algoritmo con un conjunto de datos
pequeño.
La figura 123 demuestra cómo funciona la secuencia de espacios con el algoritmo Shellsort.
función shellsort() {
para (var g = 0; g < [Link]; ++g) { para (var
i = [Link][g]; i < [Link]; ++i) {
var temp = [Link][i]; para
(var j = i; j >= [Link][g] &&
[Link][j[Link][g]] > temp; j =
[Link][g]) {
[Link][j] = [Link][j [Link][g]];
} [Link][j] = temp;
}
}
}
[Link][Link]
Machine Translated by Google
Para que este programa funcione con nuestro banco de pruebas de la clase CArray , necesitamos agregar una
definición de la secuencia de espacios en blanco a la definición de la clase. Agregue el siguiente código en la
función constructora de CArray:
[Link] = [5,3,1];
Y agrega esta función al código:
función setGaps(arr)
{ [Link] = arr;
}
Por último, agregue una referencia a la función shellsort() al constructor de la clase CArray así como al código
shellsort() en sí.
El bucle externo controla el movimiento dentro de la secuencia de espacios. En otras palabras, para la primera
pasada a través del conjunto de datos, el algoritmo examinará los elementos que están a cinco elementos de
distancia entre sí. La siguiente pasada examinará los elementos que están a tres elementos de distancia entre
sí. La última pasada realiza una ordenación por inserción estándar en los elementos que están a un lugar de
distancia, lo que significa que son adyacentes. Para cuando comience esta última pasada, muchos de los
elementos ya estarán en su lugar y el algoritmo no tendrá que intercambiar muchos elementos. Aquí es donde el
algoritmo gana eficiencia con respecto a la inserción.
[Link][Link]
Machine Translated by Google
Ordenar. La figura 123 ilustra cómo funciona el algoritmo Shellsort en un conjunto de datos de 10 números
aleatorios con una secuencia de espacios de 5, 3, 1.
Ahora, pongamos en funcionamiento el algoritmo en un ejemplo real. Añadimos una sentencia print() a shellsort()
para poder seguir el progreso del algoritmo mientras ordena el conjunto de datos. Se anota cada paso de espacio,
seguido del orden del conjunto de datos después de ordenar con ese espacio en particular. El programa se muestra
en el Ejemplo 1210.
load("[Link]") var
nums = new CArray(10);
[Link]();
print("Antes de Shellsort: \n");
print([Link]());
print("\nDurante Shellsort: \n");
[Link]();
print("\nDespués de Shellsort: \n");
print([Link]());
Antes de Shellsort:
6029358054
5 0 0 5 3 6 8 2 9 4 // brecha 5 4 0 0
5 2 6 5 3 9 8 // brecha 3 0 0 2 3 4 5
5 6 8 9 // brecha 1
Después de Shellsort:
0023455689
Para entender cómo funciona Shellsort, compare el estado inicial de la matriz con su estado después de ordenar la
secuencia de espacios de 5. El primer elemento de la matriz inicial, 6, se intercambió con el quinto elemento
después de él, 5, porque 5 < 6.
Ahora compare la línea del espacio 5 con la línea del espacio 3. El 3 en la línea del espacio 5 está intercambiado
con el 2 porque 2 < 3 y 2 es el tercer elemento después del 3. Con solo contar el número de secuencia del espacio
actual hacia abajo desde el elemento actual en el bucle y comparar los dos números, puede rastrear cualquier
ejecución del algoritmo Shellsort.
Ahora que hemos visto algunos detalles de cómo funciona el algoritmo Shellsort, utilicemos una secuencia de
espacios más grande y ejecutémosla con un conjunto de datos más grande (100 elementos). Este es el resultado:
Antes de Shellsort:
19 19 54 60 66 69 45 40 36 90 22
93 23 0 88 21 70 4 46 30 69
75 41 67 93 57 94 21 75 39 50
[Link][Link]
Machine Translated by Google
17 8 10 43 89 1 0 27 53 43
51 86 39 86 54 9 49 73 62 56
84 2 55 60 93 63 28 10 87 95
59 48 47 52 91 31 74 2 59 1 35 83
6 49 48 30 85 18 91 73
90 89 1 22 53 92 84 81 22 91
34 61 83 70 36 99 80 71 1
Después de Shellsort:
00111122468
9 10 10 17 18 19 19 21 21 22
22 22 23 27 28 30 30 31 34 35
36 36 39 39 40 41 43 43 45 46 47 48
48 49 49 50 51 52 53 53
54 54 55 56 57 59 59 60 60 61
62 63 66 67 69 69 70 70 71 73
73 74 75 75 80 81 83 83 84 84
85 86 86 87 88 89 89 90 90 91 91 91
92 93 93 93 94 95 99
dinámicos Robert Sedgewick, coautor de Algorithms, 4E (AddisonWesley), define una función shell
sort() que utiliza una fórmula para calcular dinámicamente la secuencia de espacios que se utilizará
con Shellsort. El algoritmo de Sedgewick determina el valor inicial del espacio utilizando el siguiente
fragmento de código:
Una vez que se determina el valor del espacio, la función funciona como nuestra función shellsort()
anterior , excepto que la última declaración antes de volver al bucle externo calcula un nuevo valor de
espacio:
h = (h1)/3;
El ejemplo 1211 proporciona la definición completa de esta nueva función shellsort() , junto con una
función swap() que utiliza y un programa para probar la definición de la función.
[Link][Link]
Machine Translated by Google
} mientras (h >= 1)
{ para (var i = h; i < N; i++) {
para (var j = i; j >= h && [Link][j] < [Link][jh];
j = h)
{ swap([Link], j, jh);
}
} h = (h1)/3;
}
}
cargar("[Link]")
var nums = new CArray(100);
[Link]();
imprimir("Antes de Shellsort1: \n");
imprimir([Link]());
nums.shellsort1();
imprimir("\nDespués de Shellsort1: \n");
imprimir([Link]());
Antes de Shellsort1:
92 31 5 96 44 88 34 57 44 72 20
83 73 8 42 82 97 35 60 9 26
14 77 51 21 57 54 16 97 100 55 24 86
70 38 91 54 82 76 78 35
22 11 34 13 37 16 48 83 61 2
5 1 6 85 100 16 43 74 21 96
44 90 55 78 33 55 12 52 88 13
64 69 85 83 73 43 63 1 90 86 29 96
39 63 41 99 26 94 19 12
84 86 34 8 100 87 93 81 31
Después de Shellsort1:
1 1 2 5 5 6 8 8 9 11 12
12 13 13 14 16 16 16 19 20 21
21 22 24 26 26 29 31 31 33 34
34 34 35 35 37 38 39 41 42 43
43 44 44 44 48 51 52 54 54 55 55 55
57 57 60 61 63 63 64 69
70 72 73 73 74 76 77 78 78 81
82 82 83 83 83 84 85 85 86 86
86 87 88 88 90 90 91 92 93 94
96 96 96 97 97 99 100 100 100
Antes de abandonar el algoritmo Shellsort, debemos comparar la eficiencia de nuestras dos funciones shellsort() . En el
Ejemplo 1212 se muestra un programa que compara los tiempos de ejecución de las dos funciones . Ambos algoritmos
utilizan la secuencia de Ciura para la secuencia de espacios.
[Link][Link]
Machine Translated by Google
Ambos algoritmos ordenaron los datos en el mismo tiempo. Aquí se muestra el resultado de ejecutar
el programa con 100.000 elementos de datos:
Claramente, ambos algoritmos ordenan los datos con la misma eficiencia, por lo que puedes usar
cualquiera de ellos con confianza.
Mergesort se llama así porque funciona fusionando sublistas ordenadas para formar una lista más
grande y completamente ordenada. En teoría, este algoritmo debería ser fácil de implementar.
Necesitamos dos submatrices ordenadas y una tercera matriz en la que fusionamos las dos
submatrices comparando elementos de datos e insertando el valor de elemento más pequeño. En la
práctica, sin embargo, Mergesort tiene algunos problemas porque si estamos tratando de ordenar un
conjunto de datos muy grande usando el algoritmo, la cantidad de espacio que necesitamos para
almacenar las dos submatrices fusionadas puede ser bastante grande. Dado que el espacio no es un
problema en estos días de memoria barata, vale la pena implementar Mergesort para ver cómo se
compara en eficiencia con otros algoritmos de ordenamiento.
Mergesort de arriba
hacia abajo Es habitual, aunque no necesario, implementar Mergesort como un algoritmo recursivo.
Sin embargo, no es posible hacerlo en JavaScript, ya que la recursión es demasiado profunda para
que el lenguaje la maneje. En su lugar, implementaremos el algoritmo de forma no recursiva, utilizando
una estrategia llamada Mergesort de abajo hacia arriba .
[Link][Link]
Machine Translated by Google
Mergesort ascendente
Antes de mostrarle el código JavaScript para Mergesort, aquí está el resultado de una consulta Java.
Programa de script que utiliza Mergesort de abajo a arriba para ordenar una matriz de 10 números enteros:
6,10,1,9,4,8,2,7,3,5
[Link][Link]
Machine Translated by Google
1,2,3,4,5,6,7,8,9,10
El valor Infinito se utiliza como valor centinela para indicar el final de la submatriz izquierda o derecha.
Cada elemento de la matriz comienza en su propia matriz izquierda o derecha. Luego, las dos matrices se
fusionan, primero en dos elementos cada una, luego en cuatro elementos cada una, excepto 3 y 5, que
permanecen separados hasta la última iteración, cuando se combinan en la matriz derecha y luego se
fusionan en la matriz izquierda para volver a formar la matriz original.
Ahora que hemos visto cómo funciona el Mergesort de abajo hacia arriba, el Ejemplo 1213 presenta el
código que creó la salida anterior.
función mergeSort(arr) { si
([Link] < 2) {
devolver;
} var paso = 1;
var izquierda,
derecha; mientras (paso < longitud
de arr.)
{ izquierda = 0;
derecha = paso; mientras (derecha + paso <= longitud de arr.) {
mergeArrays(arr, izquierda, izquierda+paso, derecha, derecha+paso);
izquierda = derecha +
paso; derecha = izquierda + paso;
} paso *= 2;
}
}
[Link][Link]
Machine Translated by Google
k = inicioIzquierda;
para (var i = 0; i < ([Link]1); ++i) { leftArr[i] = arr[k]; +
+k;
}
de lo
contrario { arr[k] = derechaArr[n];
n++;
}
La característica clave de la función mergeSort() es la variable step , que se utiliza para controlar el
tamaño de los subconjuntos leftArr y rightArr que se encuentran en la función mergeArrays() . Al
controlar el tamaño de los subconjuntos, el proceso de ordenación es relativamente eficiente, ya que
no lleva mucho tiempo ordenar un conjunto pequeño. Esto también hace que la fusión sea eficiente,
ya que es mucho más fácil fusionar datos en un orden ordenado cuando los datos no fusionados ya
están ordenados.
Nuestro próximo paso con Mergesort es agregarlo a la clase CArray y cronometrarlo en un conjunto
de datos más grande. El ejemplo 1214 muestra la clase CArray con las funciones mergeSort() y
mergeArrays() agregadas a su definición.
[Link][Link]
Machine Translated by Google
k = inicioIzquierda;
para (var i = 0; i < ([Link]1); ++i) { leftArr[i] = arr[k]; ++k;
m++;
} de lo
contrario { arr[k] = derechaArr[n];
n++;
}
función mergeSort() { si
([Link] < 2) { devolver;
[Link][Link]
Machine Translated by Google
} paso *= 2;
}
}
Quicksort es uno de los algoritmos de clasificación más rápidos para grandes conjuntos de datos.
Quicksort es un algoritmo de divide y vencerás que divide recursivamente una lista de datos en sublistas
sucesivamente más pequeñas que constan de los elementos más pequeños y los elementos más grandes.
El algoritmo continúa este proceso hasta que todos los datos de la lista estén ordenados.
[Link][Link]
Machine Translated by Google
2. Reordene la lista de modo que todos los elementos menores que el elemento pivote se coloquen antes del pivote
y todos los elementos mayores que el pivote se coloquen después de él.
3. Repita los pasos 1 y 2 tanto en la lista con elementos más pequeños como en la lista de elementos más grandes.
elementos.
función qSort(lista) { si
([Link] == 0) { devolver
[];
[Link][Link]
Machine Translated by Google
La función primero prueba si la matriz tiene una longitud de 0. Si es así, no es necesario ordenar la
matriz y la función retorna. De lo contrario, se crean dos matrices, una para contener los elementos
menores que el pivote y la otra para contener los elementos mayores que el pivote. Luego, se
selecciona el pivote seleccionando el primer elemento de la matriz. A continuación, la función recorre
los elementos de la matriz y los coloca en su matriz adecuada según su valor en relación con el valor
del pivote. Luego, la función se llama de forma recursiva tanto en la matriz menor como en la matriz
mayor. Cuando se completa la recursión, la matriz mayor se concatena con la matriz menor para formar
la matriz ordenada y se retorna desde la función.
Probemos el algoritmo con algunos datos. Como nuestro programa qSort utiliza recursión, no
utilizaremos el banco de pruebas de matrices; en su lugar, crearemos una matriz de números aleatorios
y ordenaremos la matriz directamente. El programa se muestra en el Ejemplo 1215.
si ([Link] == 0)
{ devolver [];
} var izquierda =
[]; var derecha =
[]; var pivote = arr[0];
para (var i = 1; i < [Link]; i++) {
[Link](arr[i]); } de
lo contrario {
[Link](arr[i]);
}
} var a = [];
para (var i = 0; i < 10; ++i) {
a[i] = Matemá[Link]((Matemá[Link]()*100)+1);
} imprimir(a);
[Link][Link]
Machine Translated by Google
imprimir();
imprimir(qSort(a));
68,80,12,80,95,70,79,27,88,93
12,27,68,70,79,80,80,88,93,95
El algoritmo Quicksort se utiliza mejor en conjuntos de datos grandes; su rendimiento se degrada para conjuntos
de datos más pequeños.
Para demostrar mejor cómo funciona Quicksort, el siguiente programa resalta el pivote a medida
que se elige y cómo se ordenan los datos alrededor del pivote:
función qSort(arr) {
si ([Link] == 0) { devolver
[];
} var izquierda =
[]; var derecha = [];
var pivote = arr[0]; para
(var i = 1; i < [Link]; i++) { print("pivote: " +
"
pivote + si (arr[i] < pivote) elemento actual: " + arr[i]);
{ print("moviendo " + arr[i]
"
+ [Link](arr[i]); } de lo A la izquierda");
contrario
"
{ print("moviendo " + arr[i] + A la derecha");
[Link](arr[i]);
}
} var a = [];
para (var i = 0; i < 10; ++i) { a[i] =
[Link](([Link]()*100)+1);
} imprimir(a);
imprimir();
imprimir(qSort(a));
9,3,93,9,65,94,50,90,12,65
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
Ceremonias
1. Ejecute los tres algoritmos analizados en este capítulo con datos de cadena en lugar de datos
numéricos y compare los tiempos de ejecución de los diferentes algoritmos. ¿Son los resultados
consistentes con los resultados obtenidos al utilizar datos numéricos?
2. Cree una matriz de 1000 números enteros ya ordenados en orden numérico. Escriba un programa que
ejecute cada algoritmo de ordenación con esta matriz, cronometrando cada algoritmo y comparando
los tiempos. ¿Cómo se comparan estos tiempos con los tiempos para ordenar una matriz en orden
aleatorio?
3. Crea una matriz de 1000 números enteros ordenados en orden numérico inverso. Escribe un programa
que ejecute cada algoritmo de ordenación con esta matriz, cronometrando cada algoritmo y
comparando los tiempos.
4. Cree una matriz de más de 10 000 números enteros generados aleatoriamente y ordene la matriz
utilizando Quicksort y la función de ordenamiento incorporada de JavaScript, cronometrando cada función.
¿Existe una diferencia horaria entre las dos funciones?
[Link][Link]
Machine Translated by Google
CAPÍTULO 13
Algoritmos de búsqueda
Existen dos formas de buscar datos en una lista: búsqueda secuencial y búsqueda binaria. La búsqueda
secuencial se utiliza cuando los elementos de una lista están en orden aleatorio; la búsqueda binaria se
utiliza cuando los elementos de una lista están en orden ordenado. La búsqueda binaria es el algoritmo
más eficiente, pero también hay que tener en cuenta el tiempo adicional que lleva ordenar el conjunto
de datos antes de poder buscar un valor en él.
Búsqueda secuencial
La forma más obvia de buscar datos en una lista es comenzar por el primer elemento y avanzar por
cada elemento de la lista hasta encontrar los datos que se buscan o hasta llegar al final de la lista. Esto
se denomina búsqueda secuencial, a veces también llamada búsqueda lineal.
Es un ejemplo de una técnica de búsqueda de fuerza bruta , donde potencialmente se visita cada
elemento de la estructura de datos en el camino hacia una solución.
Una búsqueda secuencial es muy fácil de implementar. Simplemente inicie un bucle al principio de la
lista y compare cada elemento con los datos que está buscando. Si encuentra una coincidencia, la
búsqueda finaliza. Si llega al final de la lista sin generar una coincidencia, entonces los datos
buscados no están en la lista.
El ejemplo 131 muestra una función para realizar una búsqueda secuencial en una matriz.
187
[Link][Link]
Machine Translated by Google
} devuelve falso;
}
El ejemplo 132 presenta un programa para probar nuestra función de búsqueda secuencial, que incluye una
función para facilitar la visualización del contenido de la matriz. Al igual que en el capítulo 12, utilizamos la
generación de números aleatorios para llenar una matriz con números aleatorios en el rango de 1 a 100.
También utilizamos una función para mostrar el contenido de la matriz, tal como lo hicimos en el Capítulo 12.
} si (i % 10 != 0) { putstr("\n");
}
}
} dispArr(nums);
putstr("Ingrese un número para buscar: "); var num =
parseInt(readline()); print(); if (seqSearch(nums,
num))
{ print(num + está en la matriz.");
"
de lo
"
contrario { imprimir(num + no está en la matriz.");
} imprimir();
dispArr(nums);
Este programa crea una matriz con números aleatorios en el rango de 0 a 100. El usuario ingresa un valor,
se busca el valor y se muestra el resultado. Finalmente, el programa muestra la matriz completa como
prueba de la validez del valor de retorno de la función. A continuación, se muestra un ejemplo de ejecución
del programa:
23 está en la matriz.
[Link][Link]
Machine Translated by Google
13 95 72 100 94 90 29 0 66 2 29
42 20 69 50 49 100 34 71 4 26
85 25 5 45 67 16 73 64 58 53 66 73 46
55 64 4 84 62 45 99
77 62 47 52 96 16 97 79 55 94
88 54 60 40 87 81 56 22 30 91
99 90 23 18 33 100 63 62 46 6
10 5 25 48 9 8 95 33 82 32 56 23
47 36 88 84 33 4 73 99
60 23 63 86 51 87 63 54 62
También podemos escribir la función de búsqueda secuencial de modo que devuelva la posición en la
que se encuentra una coincidencia. El ejemplo 133 proporciona la definición de esta nueva versión de
seq Search().
Ejemplo 133. Modificación de la función seqSearch() para que devuelva la posición encontrada (o 1)
} devuelve 1;
}
Tenga en cuenta que si no se encuentra el elemento buscado, la función devuelve 1. Este es el mejor
valor que puede devolver la función, ya que un elemento de matriz no se puede almacenar en la posición
1.
El ejemplo 134 presenta un programa que utiliza esta segunda definición de seqSearch().
de lo
"
contrario { imprimir(num + no está en la matriz.");
} imprimir();
dispArr(nums);
[Link][Link]
Machine Translated by Google
35 36 38 50 24 81 78 43 26 26 89
88 39 1 56 92 17 77 53 36 73 61 54 32
97 27 60 67 16 70 59
4 76 7 38 22 87 30 42 91 79
6 61 56 84 6 82 55 91 10 42
37 46 4 85 37 18 27 76 29 2
76 46 87 16 1 78 6 43 72 2 51 65 70
91 73 67 1 57 53 31
16 64 89 84 76 91 15 39 38 3
19 66 44 97 29 6 1 72 62
Tenga en cuenta que la función seqSearch() no es tan rápida como la función incorporada [Link]
Of() , pero se muestra aquí para demostrar cómo funciona la búsqueda.
programación informática suelen implicar la búsqueda de valores mínimos y máximos. En una estructura
de datos ordenada, la búsqueda de estos valores es una tarea trivial.
Por otro lado, buscar una estructura de datos no ordenada es una tarea más desafiante.
Comencemos por determinar cómo debemos buscar un valor mínimo en una matriz. A continuación, se
muestra un algoritmo:
2. Comience a recorrer la matriz, comenzando con el segundo elemento, comparando cada uno
elemento con el valor mínimo actual.
3. Si el elemento actual tiene un valor menor que el valor mínimo actual, asigne
el elemento actual como el nuevo valor mínimo.
[Link][Link]
Machine Translated by Google
Este algoritmo se transforma fácilmente en una función de JavaScript, como se muestra en el Ejemplo
135.
} devuelve min;
}
Lo más importante a tener en cuenta sobre esta función es que comienza con el segundo elemento de la
matriz, ya que estamos asignando el primer elemento de la matriz como el valor mínimo actual.
[Link][Link]
Machine Translated by Google
89 30 25 32 72 70 51 42 25 24 53
55 78 50 13 40 48 32 26 2 14 33 45
72 56 44 21 88 27 68 15
93 98 73 28 16 46 87 28 65 38
67 16 85 63 23 69 64 91 9 70
81 27 97 82 6 88 3 7 46 13
11 64 31 26 38 28 13 17 69 90 1 6 7
64 43 9 73 80 98 46
27 22 87 49 83 6 39 42 51 54
84 34 53 78 40 14 5 76 62
El algoritmo para encontrar el valor máximo funciona de manera similar. Asignamos el primer elemento de
la matriz como valor máximo y luego recorremos el resto de la matriz, comparando cada elemento con el
valor máximo actual. Si el elemento actual es mayor que el valor máximo actual, el valor de ese elemento
se almacena en la variable.
El ejemplo 137 muestra la definición de la función.
El ejemplo 138 muestra un programa que encuentra tanto el valor mínimo como el valor máximo de una
matriz.
[Link][Link]
Machine Translated by Google
26 94 40 40 80 85 74 6 6 87 56 91 86
21 79 72 77 71 99 45 5
5 35 49 38 10 97 39 14 62 91
42 7 31 94 38 28 6 76 78 94
30 47 74 20 98 5 68 33 32 29
93 18 67 8 57 85 66 49 54 28 17 42
75 67 59 69 6 35 86 45
62 82 48 85 30 87 99 46 51 47
71 72 36 54 77 19 11 52 81 52
41 16 70 55 97 88 92 2 77
búsquedas secuenciales más rápidas y exitosas en datos desordenados se producen cuando los datos que
se buscan se encuentran al principio del conjunto de datos. Puede asegurarse de que un elemento de datos
encontrado correctamente se encuentre rápidamente en el futuro moviéndolo al principio de un conjunto de
datos después de que se haya encontrado en una búsqueda.
El concepto detrás de esta estrategia es que podemos minimizar los tiempos de búsqueda al ubicar
los elementos que se buscan con frecuencia al comienzo de un conjunto de datos. Por ejemplo, si
usted es bibliotecario y le piden varias veces al día el mismo libro de referencia, mantendrá ese libro
cerca de su escritorio para facilitar el acceso. Después de muchas búsquedas, los elementos
buscados con más frecuencia se habrán movido desde donde estaban almacenados al comienzo
del conjunto de datos. Este es un ejemplo de datos autoorganizados: datos que no son organizados
por el programador antes de que se ejecute el programa, sino por el programa mismo mientras se
ejecuta.
Tiene sentido permitir que los datos se autoorganicen, ya que los datos que se buscan probablemente
sigan la “regla 8020”, lo que significa que el 80 % de las búsquedas realizadas en un conjunto de
datos buscan solo el 20 % de los datos del conjunto. La autoorganización eventualmente colocará
ese 20 % al principio del conjunto de datos, donde una búsqueda secuencial simple los encontrará
rápidamente.
[Link][Link]
Machine Translated by Google
Podemos modificar nuestra función seqSearch() para incluir la autoorganización con bastante facilidad.
El ejemplo 139 es nuestro primer intento de definición de función.
{ intercambiar(arr,i,i1);
}
devuelve verdadero;
}
} devuelve falso;
}
Notarás que la función verifica que los datos encontrados no estén ya en la posición 0.
La definición anterior utiliza una función swap() para intercambiar los datos encontrados con los datos
almacenados actualmente en la posición anterior. A continuación, se muestra la definición de la
función swap() :
Notarás que al usar esta técnica, que es similar a cómo se ordenan los datos con el algoritmo de
ordenamiento de burbuja, los elementos a los que se accede con más frecuencia terminarán llegando
al principio del conjunto de datos. Por ejemplo, este programa:
5,1,7,4,2,10,9,3,6,8
5,1,4,7,2,10,9,3,6,8
[Link][Link]
Machine Translated by Google
5,4,1,7,2,10,9,3,6,8
4,5,1,7,2,10,9,3,6,8
Observe cómo el valor 4 “burbujea” hasta el principio de la lista porque se busca tres veces seguidas.
Esta técnica también garantiza que si un elemento ya está al principio del conjunto de datos, no se moverá
más abajo.
Otra forma de escribir la función seqSearch() con datos autoorganizados es mover un elemento encontrado
al principio del conjunto de datos, aunque no querríamos intercambiar un elemento si ya está cerca del
principio. Para lograr este objetivo, podemos intercambiar elementos encontrados solo si están ubicados al
menos a una distancia especificada del principio del conjunto de datos. Solo tenemos que determinar qué
se considera lo suficientemente lejos del principio del conjunto de datos como para justificar mover el
elemento más cerca del principio.
Siguiendo nuevamente la regla 8020, podemos crear una regla que establezca que un elemento de datos
se reubica al comienzo del conjunto de datos solo si su ubicación se encuentra fuera del primer 20% de los
elementos del conjunto de datos.
} devuelve falso;
}
El ejemplo 1311 muestra un programa que prueba esta definición en un pequeño conjunto de datos de 10
elementos.
} dispArr(nums);
print();
putstr("Ingrese un valor a buscar: "); var val =
parseInt(readline()); if (seqSearch(nums,
val)) { print("Elemento encontrado:
");
[Link][Link]
Machine Translated by Google
imprimir();
dispArr(nums);
}
de lo
"
contrario { imprimir(val + no está en la matriz.");
}
4 5 1 8 10 1 3 10 0 1
Introduzca un valor para buscar : 3
Elemento encontrado:
3 5 1 8 10 1 4 10 0 1
Ejecutemos el programa nuevamente y busquemos un elemento más cercano al frente del conjunto de datos:
4295069456
Introduzca un valor para buscar : 2
Elemento encontrado:
4295069456
Debido a que el elemento buscado está tan cerca del frente del conjunto de datos, la función no cambia su posición.
Las búsquedas que hemos analizado hasta ahora requieren que los datos que se buscan se mantengan en una secuencia
desordenada. Sin embargo, podemos acelerar significativamente las búsquedas en conjuntos de datos grandes si primero
ordenamos el conjunto de datos antes de realizar una búsqueda. En la siguiente sección, analizamos un algoritmo para buscar
datos ordenados: la búsqueda binaria.
Búsqueda binaria
Cuando los datos que estás buscando están ordenados, una búsqueda más eficiente que la búsqueda secuencial es la
búsqueda binaria. Para entender cómo funciona la búsqueda binaria, imagina que estás jugando a un juego de adivinar números
en el que el número posible está entre 1 y 100, y tienes que adivinar el número elegido por un amigo. Según las reglas, por
cada intento que hagas, tu amigo tiene tres respuestas:
1. La suposición es correcta.
Siguiendo estas reglas, la mejor estrategia es elegir el número 50 como primera opción.
Si esa suposición es demasiado alta, elija 25. Si 50 es demasiado baja, debe adivinar 75. Para cada suposición, elija un punto
medio ajustando el rango inferior o el rango superior de los números (dependiendo de si su suposición es demasiado baja o
demasiado alta). Este punto medio se convierte en su
[Link][Link]
Machine Translated by Google
Nueva conjetura. Si sigue esta estrategia, adivinará el número correcto en el menor número posible de
intentos. La figura 132 demuestra cómo funciona esta estrategia si el número que debe adivinar es 82.
Podemos implementar esta estrategia como algoritmo de búsqueda binaria. Este algoritmo solo funciona
en un conjunto de datos ordenados. Este es el algoritmo:
2. Establezca un límite superior para el último elemento de la matriz (longitud de la matriz menos 1).
3. Mientras el límite inferior sea menor o igual que el límite superior, haga lo siguiente
Pasos:
a. Establezca el punto medio como (límite superior menos límite inferior) dividido por 2.
[Link][Link]
Machine Translated by Google
b. Si el elemento de punto medio es menor que los datos que se buscan, establezca un nuevo valor inferior.
ligado al punto medio más 1.
c. Si el elemento del punto medio es mayor que los datos que se buscan, establezca un nuevo límite
superior para el punto medio menos 1. d. De lo
El ejemplo 1312 muestra la definición de JavaScript para el algoritmo de búsqueda binaria, junto con un programa
para probar la definición.
de lo
}
}
devuelve 1;
}
" "
En posición + retVal);
}
de lo
"
contrario { imprimir(val + no está en la matriz.");
}
0 1 2 3 5 7 7 8 8 9 10
11 11 13 13 13 14 14 14 15 15
18 18 19 19 19 19 20 20 20 21
[Link][Link]
Machine Translated by Google
22 22 22 23 23 24 25 26 26 29
31 31 33 37 37 37 38 38 43 44
44 45 48 48 49 51 52 53 53 58
59 60 61 61 62 63 64 65 68 69 70 72 72
74 75 77 77 79 79 79
83 83 84 84 86 86 86 91 92 93
93 93 94 95 96 96 97 98 100
Introduzca un valor para buscar : 37
Se encontraron 37 en la posición 45
Será interesante observar la función a medida que avanza a través del espacio de búsqueda en busca del valor
especificado, así que en el Ejemplo 1313, agreguemos una declaración al contenedor.
Función Search() que muestra el punto medio cada vez que se recalcula:
de lo
} devuelve 1;
}
0 0 2 3 5 6 7 7 7 10 11
14 14 15 16 18 18 19 20 20 21
21 21 22 23 24 26 26 27 28 28 30 31 32
32 32 32 33 34 35 36
36 37 37 38 38 39 41 41 41 42
43 44 47 47 50 51 52 53 56 58
59 59 60 62 65 66 66 67 67 67
68 68 68 69 70 74 74 76 76 77 78 79 79
81 81 81 82 82 87 87
87 87 92 93 95 97 98 99 100
Introduzca un valor para buscar : 82
Punto medio actual: 49
Punto medio actual: 74
Punto medio actual: 87
Se encontraron 82 en la posición 87
[Link][Link]
Machine Translated by Google
A partir de este resultado, vemos que el valor del punto medio original era 49. Es demasiado bajo, ya que
estamos buscando 82, por lo que se calcula que el siguiente punto medio será 74. Sigue siendo demasiado
bajo, por lo que se calcula un nuevo punto medio, 87, y ese valor contiene el elemento que estamos
buscando, por lo que la búsqueda finaliza.
Contando ocurrencias
Cuando la función binSearch() encuentra un valor, si existen otras instancias del mismo valor en el conjunto
de datos, la función se posicionará en las inmediaciones de otros valores similares. En otras palabras, otras
instancias del mismo valor estarán a la izquierda inmediata de la posición del valor encontrado o a la derecha
inmediata de la posición del valor encontrado.
Si este hecho no le resulta evidente, ejecute la función binSearch() varias veces y tome nota de la posición
del valor encontrado que devuelve la función. A continuación, se incluye un ejemplo de una ejecución de
muestra de un ejemplo anterior en este capítulo:
0 1 2 3 5 7 7 8 8 9 10
11 11 13 13 13 14 14 14 15 15
18 18 19 19 19 19 20 20 20 21
22 22 22 23 23 24 25 26 26 29 31 31 33
37 37 37 38 38 43 44
44 45 48 48 49 51 52 53 53 58
59 60 61 61 62 63 64 65 68 69
70 72 72 74 75 77 77 79 79 79
83 83 84 84 86 86 86 91 92 93 93 93 94
95 96 96 97 98 100
Introduzca un valor para buscar : 37
Se encontraron 37 en la posición 45
Si cuenta la posición de cada elemento, el número 37 que encuentra la función es el que está en el medio
de las tres ocurrencias de 37. Esta es simplemente la naturaleza de cómo funciona la función binSearch() .
Entonces, ¿qué debe hacer una función que cuenta las ocurrencias de valores en un conjunto de datos para
asegurarse de que cuenta todas las ocurrencias? La solución más fácil es escribir dos bucles que se
desplacen hacia abajo, o hacia la izquierda, del conjunto de datos, contando las ocurrencias, y hacia arriba,
o hacia la derecha, del conjunto de datos, contando las ocurrencias. El ejemplo 1314 muestra una definición
de la función count() .
[Link][Link]
Machine Translated by Google
++contar;
}
de lo
contrario { romper;
}
}
para (var i = posición+1; i < [Link]; ++i) { si (arr[i] == datos) { +
+conteo;
de lo
contrario { romper;
}
}
} devolver recuento;
}
0 1 3 5 6 8 8 9 10 10 10
12 12 13 15 18 18 18 20 21 21
22 23 24 24 24 25 27 27 30 30
30 31 32 35 35 35 36 37 40 40 41 42 42 44
44 45 45 46 47 48
51 52 55 56 56 56 57 58 59 60
61 61 61 63 64 66 67 69 69 70
70 72 72 73 74 74 75 77 78 78
78 78 82 82 83 84 87 88 92 92
[Link][Link]
Machine Translated by Google
93 94 94 96 97 99 99 99 100 Ingrese un
valor para contar: 45 Se encontraron 2
ocurrencias de 45.
01122367777
8 8 8 11 11 11 11 11 12 14
15 17 18 18 18 19 19 23 25 27 28 29 30 30 31
32 32 32 33 36
36 37 37 38 38 39 43 43 43 45
47 48 48 48 49 50 53 55 55 55
59 59 60 62 65 66 67 67 71 72
73 73 75 76 77 79 79 79 79 83 85 85 87 88 89
92 92 93 93 93
94 94 94 95 96 97 98 98 99 Ingrese un
valor para contar: 56 Se encontraron 0
ocurrencias de 56.
El nacionalismo de Hamilton no era democrático. La democracia de Jefferson era, en sus comienzos, provinciana. Con el
tiempo, la misión histórica de unir el nacionalismo y la democracia fue confiada a nuevos líderes de una región situada más allá
de las montañas, poblada por hombres y mujeres de todos los sectores y libre de aquellas tradiciones estatales que se
remontaban a los primeros días de la colonización. La voz del nacionalismo democrático alimentado en Occidente se escuchó
cuando Clay de Kentucky abogó por su sistema americano de protección de las industrias; cuando Jackson de Tennessee
condenó la nulidad en una proclama resonante que ha ocupado su lugar entre los grandes documentos estatales
americanos; y cuando Lincoln de Illinois, en una hora fatídica, llamó a un pueblo desconcertado a enfrentar la prueba
suprema de si esta era una nación destinada a sobrevivir o a perecer. Y se recordará que el partido de Lincoln eligió como
bandera ese emblema anterior el republicano que Jefferson había convertido en signo de poder. El "divisor ferroviario" de
Illinois unió el nacionalismo de Hamilton con la democracia de Jefferson, y su atractivo estaba revestido del lenguaje sencillo
del pueblo, no de la retórica sonora que Webster aprendió en las escuelas.
Este párrafo de texto fue tomado del archivo [Link] que se encuentra en el sitio web de Peter Norvig.
Este archivo se almacena como un archivo de texto (.txt) que se encuentra en el mismo directorio que el intérprete
de JavaScript ([Link]).
Esta línea almacena el texto en una matriz leyendo el texto del archivo ( read("[Link]")) y luego dividiendo el
archivo en palabras usando split()
[Link][Link]
Machine Translated by Google
Función que utiliza el espacio entre cada palabra como delimitador. Este código no es perfecto porque la
puntuación se deja en el archivo y se almacena con la palabra más cercana, pero será suficiente para nuestros
propósitos.
Una vez que el archivo está almacenado en una matriz, podemos comenzar a buscar palabras en la matriz.
Comencemos con una búsqueda secuencial y busquemos la palabra "retórica", que se encuentra en el párrafo
cercano al final del archivo. También cronometremos la búsqueda para poder compararla con una búsqueda
binaria. Ya abordamos el código de cronometraje en el Capítulo 12, si desea volver atrás y revisar ese material.
El Ejemplo 1316 muestra el código.
}
}
devuelve 1;
}
} else
"
{ imprimir(palabra + no está en el archivo.");
}
Aunque la búsqueda binaria es más rápida, no podremos medir ninguna diferencia entre seqSearch() y
binSearch(), pero ejecutaremos el programa utilizando la búsqueda binaria de todos modos para asegurarnos
de que la función binSearch() funcione correctamente con el texto. El ejemplo 1317 muestra el código y el
resultado.
[Link][Link]
Machine Translated by Google
var mid = [Link]((límite superior + límite inferior) / 2); if (arr[mid] < data) {
de lo
}
}
devuelve 1;
}
}
arr[interior] = temp;
}
}
En esta era de procesadores ultrarrápidos, es cada vez más difícil medir la diferencia entre la búsqueda
secuencial y la búsqueda binaria en cualquier conjunto de datos que no sean los más grandes.
Sin embargo, se ha demostrado matemáticamente que la búsqueda binaria es más rápida que la
búsqueda secuencial en grandes conjuntos de datos simplemente debido al hecho de que el algoritmo de
búsqueda binaria elimina la mitad del espacio de búsqueda (los elementos de la matriz) con cada iteración
del bucle que controla el algoritmo.
[Link][Link]
Machine Translated by Google
Ceremonias
2. Compare el tiempo que lleva realizar una búsqueda secuencial con el tiempo total que lleva ordenar un
conjunto de datos mediante ordenación por inserción y realizar una búsqueda binaria en el conjunto de
datos. ¿Cuáles son los resultados?
3. Cree una función que encuentre el segundo elemento más pequeño en un conjunto de datos. ¿Puede
generalizar la definición de la función para el tercero más pequeño, el cuarto más pequeño, etc.?
Pruebe sus funciones con un conjunto de datos de al menos 1000 elementos. Pruebe tanto con números
como con texto.
Ejercicios | 205
[Link][Link]
Machine Translated by Google
[Link][Link]
Machine Translated by Google
CAPÍTULO 14
Algoritmos avanzados
En este capítulo veremos dos temas avanzados: programación dinámica y algoritmos voraces. La
programación dinámica es una técnica que a veces se considera lo opuesto a la recursión. Mientras
que una solución recursiva comienza desde arriba y descompone el problema, resolviendo todos los
problemas pequeños hasta que se resuelve el problema completo, una solución de programación
dinámica comienza desde abajo, resolviendo los problemas pequeños y combinándolos para formar
una solución general al problema grande. Este capítulo se diferencia de la mayoría de los otros
capítulos de este libro en que realmente no analizamos una estructura de datos organizativa para
trabajar con estos algoritmos que no sea la matriz. A veces, una estructura de datos simple es
suficiente para resolver un problema si el algoritmo que está utilizando es lo suficientemente potente.
Un algoritmo voraz es un algoritmo que busca “buenas soluciones” a medida que avanza hacia la
solución completa. Estas buenas soluciones, llamadas óptimos locales, con suerte conducirán a la
solución final correcta, llamada óptimo global. El término “voraz” proviene del hecho de que estos
algoritmos toman cualquier solución que parezca mejor en ese momento. A menudo, los algoritmos
voraces se utilizan cuando es casi imposible encontrar una solución completa, debido a
consideraciones de tiempo y/o espacio, y sin embargo una solución subóptima es aceptable.
Una buena fuente para obtener más información sobre algoritmos avanzados y estructuras de datos
es Introducción a los algoritmos (MIT Press).
Programación dinámica
Las soluciones recursivas a los problemas suelen ser elegantes pero ineficientes. Muchos lenguajes,
incluido JavaScript, no pueden traducir de manera eficiente el código recursivo a código de máquina,
lo que da como resultado un programa informático ineficiente pero elegante. Esto no quiere decir que
el uso de la recursión sea malo, per se, sino que algunos lenguajes de programación imperativos y
orientados a objetos no hacen un buen trabajo al implementar la recursión, ya que no la presentan
como una técnica de programación de alta prioridad.
207
[Link][Link]
Machine Translated by Google
Muchos problemas de programación que tienen soluciones recursivas pueden reescribirse utilizando las
técnicas de programación dinámica. Una solución de programación dinámica crea una tabla, generalmente
utilizando una matriz, que contiene los resultados de las muchas subsoluciones a medida que se descompone
el problema. Cuando se completa el algoritmo, la solución se encuentra en un lugar específico de la tabla,
como veremos en el siguiente ejemplo de Fibonacci.
Como puede ver, la secuencia se genera sumando los dos números anteriores de la secuencia. Esta
secuencia tiene una larga historia que se remonta al menos al año 700 d. C. y recibe su nombre del
matemático italiano Leonardo Fibonacci, quien en 1202 utilizó la secuencia para describir el crecimiento
idealizado de una población de conejos.
Existe una solución recursiva sencilla que se puede utilizar para generar cualquier número específico en la
secuencia. Aquí se muestra el código JavaScript para una función de Fibonacci:
} de lo
contrario { devolver recurFib(n1) + recurFib(n2);
}
}
print(recurFib(10)); // muestra 55
El problema de esta función es que es extremadamente ineficiente. Podemos ver exactamente cuán
ineficiente es examinando el árbol de recursión que se muestra en la Figura 141 para fib(5).
[Link][Link]
Machine Translated by Google
Está claro que se vuelven a calcular demasiados valores durante las llamadas recursivas. Si el
compilador pudiera llevar un registro de los valores que ya se han calculado, la función no sería tan
ineficiente. Podemos diseñar un algoritmo mucho más eficiente utilizando técnicas de programación
dinámica.
Un algoritmo diseñado con programación dinámica comienza resolviendo el subproblema más simple
que puede resolver y luego usa esa solución para resolver subproblemas más complejos hasta que se
resuelve todo el problema. Las soluciones de cada subproblema se almacenan normalmente en una
matriz para facilitar el acceso.
} si (n == 1 || n == 2) { devolver
1;
} más
{ val[1] = 1;
valor[2] = 2;
para (var i = 3; i <= n; ++i) {
val[i] = val[i1] + val[i2];
} devuelve val[n1];
}
}
La matriz val es donde almacenamos los resultados intermedios. La primera parte de la sentencia if
devuelve el valor 1 si el número de Fibonacci que se va a calcular es 1 o 2. De lo contrario, los
valores 1 y 2 se almacenan en las posiciones 1 y 2 de val. El bucle for se ejecuta desde el punto 3
hasta el argumento de entrada, asignando a cada elemento de la matriz la suma de los dos elementos
de la matriz anteriores y, cuando se completa el bucle, el último valor de la matriz será el último
número de Fibonacci calculado, que es el número solicitado y el valor devuelto por la función.
Comparemos el tiempo que lleva calcular un número de Fibonacci utilizando tanto la función recursiva
como la función de programación dinámica. El ejemplo 141 muestra el código para la prueba de
tiempo.
[Link][Link]
Machine Translated by Google
Ejemplo 141. Prueba de tiempo para versiones de programación recursiva y dinámica de la función de
Fibonacci
} de lo
contrario { devolver recurFib(n1) + recurFib(n2);
}
}
} si (n == 1 || n == 2) { devolver
1;
} más
{ val[1] = 1;
valor[2] = 2;
para (var i = 3; i <= n; ++i) {
val[i] = val[i1] + val[i2];
} devuelve val[n1];
}
}
832040
tiempo recursivo 0 milisegundos
832040
tiempo de programación dinámica 0 milisegundos
6765
tiempo recursivo 1 milisegundo
[Link][Link]
Machine Translated by Google
6765
tiempo de programación dinámica 0 milisegundos
832040
tiempo de programación dinámica 0 milisegundos
Claramente, la solución de programación dinámica es mucho más eficiente que la solución recursiva
cuando calculamos cualquier cosa sobre fib(20).
Finalmente, es posible que ya hayas descubierto que no es necesario utilizar una matriz al calcular
un número de Fibonacci mediante la solución iterativa. La matriz se utilizó porque los algoritmos de
programación dinámica suelen almacenar los resultados intermedios en una matriz. A continuación,
se muestra la definición de una función iterativa de Fibonacci que no utiliza una matriz:
}
devolver resultado;
}
Esta versión de la función calculará los números de Fibonacci con tanta eficiencia como la versión de
programación dinámica.
que se presta a una solución de programación dinámica es encontrar la subcadena común más larga
en dos cadenas. Por ejemplo, en las palabras “raven” y “hav oc”, la subcadena común más larga
es “av”. Un uso común de encontrar la subcadena común más larga es en genética, donde las
moléculas de ADN se describen utilizando la primera letra de la nucleobase de un nucleótido.
Comenzaremos con la solución de fuerza bruta para este problema. Dadas dos cadenas, A y B,
podemos encontrar la subcadena común más larga comenzando en el primer carácter de A y
comparando cada carácter con el carácter correspondiente de B. Cuando se encuentra una no
coincidencia, pasamos al segundo carácter de A y comenzamos de nuevo con el primer carácter de
B, y así sucesivamente.
Existe una mejor solución que utiliza programación dinámica. El algoritmo utiliza una matriz
bidimensional para almacenar los resultados de las comparaciones de los caracteres en el mismo
[Link][Link]
Machine Translated by Google
Posición en las dos cadenas. Inicialmente, cada elemento de la matriz se establece en 0. Cada vez que
se encuentra una coincidencia en la misma posición de las dos matrices, el elemento en la fila y columna
correspondiente de la matriz se incrementa en 1; de lo contrario, el elemento permanece establecido en 0.
A lo largo del proceso, una variable lleva un registro de cuántas coincidencias se encuentran. Esta
variable, junto con una variable de indexación, se utilizan para recuperar la subcadena común más larga
una vez que finaliza el algoritmo.
El ejemplo 142 presenta la definición completa del algoritmo. Después del código, explicaremos cómo
funciona.
Ejemplo 142. Función para determinar la subcadena común más larga de dos cadenas
} de lo
contrario { si (palabra1[i1] == palabra2[j1]) {
lcsarr[i][j] = lcsarr[i1][j1] + 1;
} de lo
contrario { lcsarr[i][j] = 0;
}
}
}
} de lo
contrario { para (var i = índicemáximo; i <= máximo;
++i) { str += palabra2[i];
} devuelve str;
[Link][Link]
Machine Translated by Google
}
}
}
}
} de lo
contrario { si (palabra1[i1] == palabra2[j1])
{ lcsarr[i][j] = lcsarr[i1][j1] + 1;
} de lo
contrario { lcsarr[i][j] = 0;
}
}
}
}
La segunda sección crea la tabla que lleva el registro de las coincidencias de caracteres. Los primeros
elementos de la matriz siempre se establecen en 0. Luego, si los caracteres correspondientes de las
dos cadenas coinciden, el elemento de la matriz actual se establece en 1 más el valor almacenado
en el elemento de la matriz anterior. Por ejemplo, si las dos cadenas son "back" y "cace", y el
algoritmo está en el segundo carácter, entonces se coloca un 1 en el elemento actual, ya que el
elemento anterior no coincidió y se almacena un 0 en ese elemento (0 + 1). Luego, el algoritmo se
mueve a la siguiente posición y, dado que también coincide con ambas cadenas, se coloca un 2 en
el elemento de la matriz actual (1 + 1). Los últimos caracteres de las dos cadenas no coinciden, por
lo que la subcadena común más larga es 2. Finalmente, si max es menor que el valor ahora almacenado en
[Link][Link]
Machine Translated by Google
el elemento de matriz actual, se le asigna el valor del elemento de matriz actual y el índice se establece
en el valor actual de i. Estas dos variables se utilizarán en la última sección para determinar dónde
comenzar a recuperar la subcadena común más larga.
Por ejemplo, dadas las dos cadenas “abbcc” y “dbbcc”, este es el estado de la matriz lcsarr a medida
que avanza el algoritmo:
00000
00000
01100
01200
000310
0014
La última sección construye la subcadena común más larga determinando dónde comenzar.
El valor del índice menos max es el punto de inicio y el valor de max es el punto de finalización:
} de lo
contrario { para (var i = índicemáximo; i <= máximo;
++i) { str += palabra2[i];
} devuelve str;
}
Dadas nuevamente las dos cadenas “abbcc” y “dbbcc”, el programa devuelve “bbcc”.
clásico en el estudio de algoritmos es el problema de la mochila. Imagina que eres un ladrón de cajas
fuertes y abres una caja fuerte llena de todo tipo de tesoros, pero lo único que tienes para llevar el
botín es una pequeña mochila. Los objetos que hay en la caja fuerte difieren tanto en tamaño como en valor.
Quieres maximizar tu equipaje llenando la mochila con aquellos artículos que más valen.
Por supuesto, existe una solución de fuerza bruta para este problema, pero la solución de programación
dinámica es más eficiente. La idea clave para resolver el problema de la mochila con una solución de
programación dinámica es calcular el valor máximo de cada elemento hasta la capacidad total de la
mochila.
Si la caja fuerte de nuestro ejemplo tiene cinco artículos, los artículos tienen un tamaño de 3, 4, 7, 8 y
9, respectivamente, y valores de 4, 5, 10, 11 y 13, respectivamente, y la mochila tiene una capacidad
de 16, entonces la solución adecuada es elegir los artículos 3 y 5 con un tamaño total de 16 y un valor
total de 23.
[Link][Link]
Machine Translated by Google
El código para resolver este problema es bastante breve, pero no tendrá mucho sentido sin el
contexto de todo el programa, así que echemos un vistazo al programa para resolver el problema
de la mochila. Nuestra solución utiliza una función recursiva:
} else
{ return max(valor[n1] +
mochila(capacidadtamaño[n1], tamaño, valor, n1),
mochila(capacidad, tamaño, valor, n1));
}
}
23
[Link][Link]
Machine Translated by Google
K[i][w] = 0;
} demás {
K[i][w] = K[i1][w];
} imprimir();
}
devuelve K[n][capacidad];
}
A medida que se ejecuta el programa, se muestran los valores que se almacenan en la tabla a medida que el
algoritmo avanza hacia una solución. Este es el resultado:
000000000000000000004444
4444444444
00045559999999999
0 0 0 4 5 5 5 10 10 10 14 15 15 15 19 19 19
0 0 0 4 5 5 5 10 11 11 14 15 16 16 19 21 21
0 0 0 4 5 5 5 10 11 13 14 15 17 18 19 21 23 23
La solución óptima al problema se encuentra en la última celda de la tabla bidimensional, que se encuentra en
la esquina inferior derecha de la tabla. También notará que al usar esta técnica no se le indica qué elementos
elegir para maximizar la producción, sino
[Link][Link]
Machine Translated by Google
inspección, la solución es escoger los artículos 3 y 5, ya que la capacidad es 16, el artículo 3 tiene un
tamaño 7 (valor 10) y el artículo 5 tiene un tamaño 9 (valor 13).
Algoritmos voraces
En las secciones anteriores, examinamos algoritmos de programación dinámica que se pueden utilizar
para optimizar soluciones que se encuentran utilizando un algoritmo subóptimo, soluciones que a
menudo se basan en la recursión. Para muchos problemas, recurrir a la programación dinámica es
excesivo y bastará con un algoritmo más simple.
Un ejemplo de un algoritmo más simple es el algoritmo voraz . Un algoritmo voraz es aquel que siempre
elige la mejor solución en el momento, sin tener en cuenta cómo esa elección afectará a las elecciones
futuras. El uso de un algoritmo voraz generalmente indica que el implementador espera que la serie de
“mejores” elecciones locales realizadas conduzca a una “mejor” elección final. Si es así, entonces el
algoritmo ha producido una solución óptima; si no, se ha encontrado una solución subóptima. Sin
embargo, para muchos problemas, simplemente no vale la pena el esfuerzo de encontrar una solución
óptima, por lo que el uso de un algoritmo voraz funciona perfectamente.
clásico de seguir un algoritmo codicioso es dar el cambio. Digamos que compras algunos artículos en
la tienda y el cambio de tu compra es de 63 centavos. ¿Cómo determina el empleado el cambio que te
dará? Si el empleado sigue un algoritmo codicioso, te da dos monedas de veinticinco centavos, una de
diez centavos y tres de un centavo. Esa es la cantidad más pequeña de monedas que equivaldrá a 63
centavos sin usar monedas de medio dólar.
El ejemplo 144 demuestra un programa que utiliza un algoritmo codicioso para dar cambio (bajo el
supuesto de que la cantidad de cambio es menor a un dólar).
Ejemplo 144. Un algoritmo voraz para resolver el problema del cambio de monedas
función makeChange(origAmt, monedas) {
var restoAmt = 0; if
(AmtOrig % .25 < AmtOrig) { monedas[3]
= parseInt(AmtOrig / .25); importe restante =
importe original % .25;
importeorigen = importepermanecer;
[Link][Link]
Machine Translated by Google
función showChange(monedas) {
si (monedas[3] > 0) {
" " "
print("Número de cuartos + monedas[3] + + monedas[3] * .25);
}
si (monedas[2] > 0) {
" " "
print("Número de monedas de diez centavos + monedas[2] + + monedas[2] * .10);
}
si (monedas[1] > 0) {
" " "
print("Número de monedas de cinco centavos + monedas[1] + + monedas[1] * .05);
}
si (monedas[0] > 0) {
" " "
print("Número de centavos + monedas[0] + + monedas[0] * .01);
}
}
La función makeChange() comienza con la denominación más alta, los cuartos y los intentos.
para hacer el mayor cambio posible con ellos. Se almacena el número total de cuartos
en la matriz de monedas . Una vez que la cantidad restante sea inferior a un cuarto, el algoritmo
se mueve a monedas de diez centavos, dando tanto cambio con monedas de diez centavos como sea posible. El número total de
Luego, las monedas de diez centavos se almacenan en la matriz de monedas . Luego, el algoritmo pasa a las monedas de cinco centavos y de un centavo.
De la misma manera.
Esta solución siempre encuentra la solución óptima siempre que la denominación normal de la moneda sea la adecuada.
y soluciones de programación dinámica para ello. En esta sección, examinaremos cómo podemos
Se puede utilizar un algoritmo codicioso para resolver el problema de la mochila si los elementos que estamos colocando
en la mochila son de naturaleza continua. En otras palabras, los elementos deben ser cosas que
No se pueden contar de forma discreta, como la tela o el polvo de oro. Si utilizamos elementos continuos,
Podemos simplemente dividir el precio unitario por el volumen unitario para determinar el valor del producto.
[Link][Link]
Machine Translated by Google
artículo. Una solución óptima en este caso es colocar la mayor cantidad posible del artículo con el valor más alto en la
mochila hasta que el artículo se agote o la mochila esté llena, seguido de la mayor cantidad posible del segundo artículo
de mayor valor, y así sucesivamente. La razón por la que no podemos encontrar una solución codiciosa óptima
utilizando artículos discretos es porque no podemos poner "la mitad de un televisor" en una mochila. Los problemas de
mochila discretos se conocen como problemas 01 porque debes tomar todo o nada de un artículo.
Este tipo de problema de mochila se denomina problema de mochila fraccionario. A continuación, se muestra el
algoritmo para resolver problemas de mochila fraccionarios:
Artículo ABCD
Valor 50 140 60 60
Talla 5 20 10 12
Relación 10 7 65
Dada la tabla anterior y suponiendo que la mochila que se utiliza tiene una capacidad de 30, la
solución óptima para el problema de la mochila es tomar todo el artículo A, todo el artículo B y la
mitad del artículo C. Esta combinación de artículos dará como resultado un valor de 220.
El código para encontrar la solución óptima a este problema de la mochila se muestra a continuación:
} else
{ var r = (capacidadcarga)/pesos[i];
w + = r * valores[i];
carga += pesos[i];
}+
+i; } devolver w;
}
[Link][Link]
Machine Translated by Google
Ceremonias
1. Escriba un programa que utilice una técnica de fuerza bruta para encontrar el número común más largo.
subcadena.
2. Escriba un programa que permita al usuario cambiar las restricciones de un problema de mochila
para explorar cómo el cambio de las restricciones cambiará los resultados de la solución. Por
ejemplo, puede cambiar la capacidad de la mochila, los valores de los elementos o los pesos
de los elementos. Probablemente sea una buena idea cambiar solo una de estas restricciones
a la vez.
3. Utilizando la técnica del algoritmo codicioso para el cambio de monedas, pero sin permitir que el algoritmo
utilice monedas de diez centavos, encuentre la solución para 30 centavos. ¿Es esta solución óptima?
[Link][Link]
Machine Translated by Google
Índice
de, 21
Un tipo de datos abstracto (ADT), 35
funciones de acceso, 17 agregar elementos a, 19
Nos gustaría conocer sus sugerencias para mejorar nuestros índices. Envíe un correo electrónico a index@[Link].
221
[Link][Link]
Machine Translated by Google
222 | Índice
[Link][Link]
Machine Translated by Google
Objeto de cliente, aplicación de película (ejemplo), soluciones, 209 definición, 207 búsqueda de la
45 subcadena
ciclos (en gráficos), 140 común más larga, 211–214 problema de la mochila, 215
Índice | 223
[Link][Link]
Machine Translated by Google
floor() función, clase Math, 161 for each programa de prueba de la implementación, 156
loop, usando con la función subset(), 118 for loop, 7 acceder algoritmos voraces, xi, 217–220
a elementos problema del cambio de monedas,
de matriz, 15 contar ocurrencias en 217 definido,
una búsqueda binaria, 201 inicializar matriz bidimensional, 207 problema de la mochila, solución a, 218
213 iterar a través de una lista, 42 anidados for
loops, 28 tiempo de ejecución H
de, 169 forEach() función
funciones hash, 97
iterar sobre matrices, 23 map()
tablas hash, 97
función versus, 25
almacenar y recuperar datos, 106 hash,
problemas de mochila
97–111 manejo de
fraccionaria, 219 front() función,
colisiones, 107–111 sondeo lineal,
40
109 encadenamiento
224 | Índice
[Link][Link]
Machine Translated by Google
función intersect(), clase Set, 117 intersección, listas enlazadas, xi, 73–87, 73
114 iteradores , enlazadas circularmente,
iteración a través de una 85 definidas,
Índice | 225
[Link][Link]
Machine Translated by Google
función peek(), 50
Sitio web de Mozilla Developer Network, 19 matrices
implementación para la clase Stack, 51
multidimensionales, 27, 108
comparación de
rendimiento para algoritmos de ordenamiento básicos, 168
N
comparación de funciones shellsort(), 175 pivote,
siguiente propiedad, clase Node, 75 181 función
función next(), 40 pop(), 20 implementación
Página web de Nightly Build, 1 para la clase Stack, 51 sacar elementos de las
Clase de nodo, 75, 80 pilas, 49 recorrido postorder, 126 función
definición para árbol de búsqueda binaria, 124 de recorrido postOrder(), 128
campo para rastrear ocurrencias, 134 programación restringida por precedencia, 152
propiedad anterior, 81 nodos, precisión para números (fijos), 4 recorrido preorder, 126
74 función de recorrido preOrder(), 128 función
determinar el punto de inserción correcto en el árbol de prev(), 40 función print() mostrar
búsqueda binaria, 125 el contenido de la matriz, 17 mostrar
en árboles, 121 representaciones de
nodos izquierdo y derecho, 123 cadena de matrices,
tipos de nodos, 122
inserción y eliminación en listas enlazadas, 74 inserción
en una lista enlazada, 76 eliminación 18
del árbol binario de búsqueda, 132 nodo hoja, 132 colas de prioridad, 70–72
nodo con dos hijos, entorno de programación, propiedades
133 x , vinculación a instancia de objeto, 11 función
eliminar de la lista enlazada, 78 push(), 19 agregar
Norvig, Peter, 202 elementos a pilas, 49 implementación
números, conversión de una base a otra, para la clase Stack, 51 insertar datos en una
53 matriz, 115 usar con add(), 32 usar con
matrices, 60 función put(),
Función keys()
definir para trabajar con encadenamiento separado, 108
de la clase de objeto, 94
modificar para aceptar clave y datos, 106 reescribir
operación como diccionario, 89
para sondeo lineal, 110
programación orientada a objetos, 10 objetos,
10 matrices
en, 31
226 | Índice
[Link][Link]
Machine Translated by Google
Q S
Clase de cola, 60 alcance, 8
definición completa y prueba de implementación, 62 búsquedas, árbol de búsqueda binaria, 129–132
definición para valor mínimo y máximo, 130 para valor
37 eliminar nodos del árbol de búsqueda binaria, 133 eliminar Concha, Donald, 171
nodos de la lista enlazada, 78
Algoritmo Shellsort, 171–176
Conjunto de
comparando matrices antes y después de la ordenación,
clases, 115 función removeNode(), árbol de búsqueda binaria, 173
133 construcciones de
comparando la eficiencia de las funciones shellsort(),
repetición, 6 nodos 175
derechos, 123 nodo raíz, 122
secuencia de espacios, 171,
174 agregando a la definición de clase CArray, 172
Índice | 227
[Link][Link]
Machine Translated by Google
153 ciclos simples, 140 simpleHash() función, 99, 100 concatenación, usar la función reduce(), 25 crear
matrices a partir de,
15 determinar si son palíndromos, 54
agregando la declaración print() a, 100 encontrar
colisiones, 101 hash la subcadena común más larga, 211–214 función hash para
de claves enteras, 104 función claves de cadena, 99 representar
size(), usando con conjuntos, 118 función some(), matrices como, 18 ordenar, 22 usar la función filter() con,
24 función sort() 27 usar la función map() con, 25 vértices fuertemente conectados,
140 subprocedimientos, 7 función subset(),
Diccionario, clase, 94 Set class, 118 subconjuntos, 113
ordenar cadenas, 22 usar subárboles,
para ordenar números, 22 ordenar, xi 123 función swap(), uso en búsqueda
agregar a secuencial, 194 uso en Shellsort, 174
Diccionario, clase, 93 topológico, 151–157 declaración switch, 5
usar colas, 67–70 algoritmos
de ordenamiento, 159–186
avanzado, 170–186
228 | Índice
[Link][Link]
Machine Translated by Google
Índice | 229
[Link][Link]
Machine Translated by Google
Colofón El
animal que aparece en la portada de Estructuras de datos y algoritmos con JavaScript es un erizo de Amur
(Erinaceus amurensis), también conocido como erizo chino. Esta especie es una de las 14 que se pueden
encontrar en todo el mundo en la actualidad y es originaria del Krai de Amur y Primorie en Rusia, Manchuria
en China y la península de Corea. Como la mayoría de los erizos, el erizo chino prefiere las hierbas altas y
la maleza. En la naturaleza, se alimenta de gusanos, ciempiés, insectos, ratones, caracoles, ranas y
serpientes. Su nombre se debe al ruido distintivo que hacen cuando buscan comida; cazan principalmente
utilizando sus sentidos del olfato y el oído.
Su olfato a menudo se asemeja a un gruñido de cerdo.
El erizo de Amur pesa una media de 600 a 1 kg y mide entre 14 y 30 cm de largo, de los cuales su
cola mide entre 2,5 y 5 cm. Para disuadir a los depredadores (como pájaros o perros salvajes), los
erizos están cubiertos de espinas cortas y lisas. Si se sienten amenazados, se enrollan formando una
bola, dejando expuestas solo las espinas; esta es también la posición en la que duermen, normalmente
en depresiones o agujeros oscuros y frescos.
Los erizos son animales solitarios que no suelen socializar con otros erizos, ni siquiera cuando se encuentran
con ellos mientras buscan comida. El único momento en que los erizos socializan es durante la temporada
de apareamiento, después de la cual cada uno toma su camino y deja que la hembra críe a las crías que
hayan concebido. Las hembras son muy protectoras de sus crías; se sabe que los erizos machos se las
comen.
La imagen de la portada es de origen desconocido. Las fuentes de la portada son URW Typewriter y
Guardian Sans. La fuente del texto es Adobe Minion Pro; la fuente del encabezado es Adobe Myriad
Condensed; y la fuente del código es Ubuntu Mono de Dalton Maag.
[Link][Link]