0% encontró este documento útil (0 votos)
35 vistas246 páginas

Data Structures and Algorithms With Java-2

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

Data Structures and Algorithms With Java-2

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

Machine Translated by Google

[Link]­[Link]
Machine Translated by Google

[Link]­[Link]
Machine Translated by Google

Estructuras de datos y algoritmos


con JavaScript

Michael McMillan

[Link]­[Link]
Machine Translated by Google

Estructuras de datos y algoritmos con JavaScript


por Michael McMillan

Copyright © 2014 Michael McMillan. Reservados todos los derechos.

Impreso en los Estados Unidos de América.

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: 800­998­9938 o corporate@[Link].

Editores: Brian MacDonald y Meghan Blanchette Diseñador de la portada: Karen Montgomery


Editora de producción: Melanie Yarbrough Diseñador de interiores: David Futato
Correctora de estilo: Becca Freed Ilustradoras: Rebecca Demarest y Cynthia Clarke
Correctora de pruebas: Amanda Kersey
Fehrenbach

Indexador: Ellen Troutman­Zaig

Marzo de 2014: Primera edición

Historial de revisiones de la primera edición:

06­03­2014: Primer lanzamiento

Consulte [Link] para obtener detalles sobre la versión.

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: 978­1­449­36493­9

[LSI]

[Link]­[Link]
Machine Translated by Google

Tabla de contenido

Prefacio. ... ix

1. El entorno y el modelo de programación de JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . 1


El entorno de JavaScript 1

Prácticas de programación en JavaScript 2

Declaración e inicialización de variables 3

Funciones de biblioteca aritméticas y matemáticas en JavaScript 3


Construcciones de decisión 4

Construcciones de repetición 6
Funciones 7

Alcance variable 8
Recursión 10

Objetos y programación orientada a objetos 10

Resumen 12

2. Matrices. ...
Matrices de JavaScript definidas 13

mediante 13

matrices Creación 14

de matrices Acceso y escritura de elementos de 15

matriz Creación de matrices a partir 15

de cadenas Operaciones de 16
agregación de matrices Funciones de acceso 17

En busca de un valor 17

Representaciones de cadenas de matrices 18

Creación de nuevas matrices a partir de matrices existentes 18


Funciones del mutador 19

Agregar elementos a una matriz 19

Eliminar elementos de una matriz 20

iii

[Link]­[Link]
Machine Translated by Google

Cómo agregar y eliminar elementos del medio de una matriz 21

Poner en orden los elementos de una matriz 22


Funciones iterativas 23

Funciones iterativas que no generan matrices 23

Funciones iterativas que devuelven una nueva matriz 25

Matrices bidimensionales y multidimensionales 27

Creación de matrices bidimensionales 27

Procesamiento de elementos de matriz bidimensional 28

Matrices irregulares 30

Matrices de objetos 30

Matrices en objetos 31
Ceremonias 33

3. Listas. ...
Una lista ADT 35

Implementación de una clase de lista 36

Anexar: Agregar un elemento a una lista 37

Eliminar: eliminar un elemento de una lista 37

Buscar: Cómo encontrar un elemento en una lista 38

Longitud: Determinación del número de elementos en una lista toString: 38

Recuperación de los elementos de una lista 38

Insertar: Insertar un elemento en una lista 39

Borrar: eliminar todos los elementos de una lista 39

Contiene: Determinar si un valor dado está en una lista 40

Recorriendo una lista 40

Iterando a través de una lista 41

Una aplicación basada en listas 42

Lectura de archivos de texto 42

Uso de listas para gestionar un quiosco 43


Ceremonias 47

4. Pilas. ...
Operaciones de pila 49

Una implementación de pila 50

Usando la clase Stack 53

Conversiones de bases múltiples 53


Palíndromos 54

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

Implementación de una clase de cola basada en matrices 60

Uso de la clase Queue: asignación de compañeros en un baile de cuadrilla 63

Ordenar datos con colas 67

Colas de prioridad 70
Ceremonias 72

6. Listas enlazadas. ...


Deficiencias de las matrices 73
Listas enlazadas definidas 74

Un diseño de lista enlazada basado en objetos 75


La clase Node La 75
clase de lista enlazada 76

Inserción de nuevos nodos 76

Cómo eliminar nodos de una lista enlazada 78

Listas doblemente enlazadas 81

Listas enlazadas circularmente 85


Otras funciones de listas enlazadas 86
Ceremonias 86

7. Diccionarios. ...
La clase del diccionario 89

Funciones auxiliares para la clase de diccionario 91

Añadiendo ordenación a la clase de diccionario 93


Ceremonias 94

8. Hash. ...
Descripción general del hash Una 97
clase de tabla hash 98

Elección de una función hash Una 98


mejor función hash Hash de 101

claves enteras 103

Almacenamiento y recuperación de datos en una tabla hash 106

Manejo de colisiones 107

Encadenamiento separado 107

Sondeo lineal 109


Ceremonias 111

9. Conjuntos. ...
Definiciones, operaciones y propiedades de conjuntos fundamentales 113
Definiciones de conjuntos 113

Operaciones de conjuntos 114

La implementación de la clase Set 114

Índice de contenidos | v

[Link]­[Link]
Machine Translated by Google

Más operaciones de conjuntos 116


Ceremonias 120

10. Árboles binarios y árboles binarios de búsqueda. ...


Árboles definidos 121

Árboles binarios y árboles binarios de búsqueda 123

Implementación de un árbol de búsqueda binaria 124

Recorriendo un árbol binario de búsqueda 126


Búsquedas BST 129

Buscando el valor mínimo y máximo 130

Buscando un valor específico 131

Eliminación de nodos de una BST 132

Contando ocurrencias 134


Ceremonias 137

11. Gráficos y algoritmos de gráficos. ...


Definiciones de gráficos 139

Sistemas del mundo real modelados por gráficos La 141

clase Graph 141

Representación de vértices 141

Representación de aristas 142

Construcción de un 143

gráfico Búsqueda de un gráfico 145

Búsqueda en profundidad 145


Búsqueda en amplitud 148

Encontrar el camino más corto 149


La búsqueda en amplitud conduce a rutas más cortas 149

Determinación de caminos 150

Ordenamiento topológico 151

Un algoritmo para la ordenación topológica 152

Implementación del algoritmo de ordenamiento topológico 152


Ceremonias 157

12. Algoritmos de ordenamiento. ...


Un banco de pruebas de 159

matriz que genera datos aleatorios 161

Algoritmos básicos de ordenación 161


Ordenamiento de burbujas 162
Ordenación por selección 165
Ordenación por inserción 167

Comparaciones temporales de los algoritmos básicos de ordenación 168

Algoritmos de ordenamiento avanzados 170

vi | Tabla de contenidos

[Link]­[Link]
Machine Translated by Google

El algoritmo Shellsort 171

El algoritmo Mergesort 176

El algoritmo Quicksort 181


Ceremonias 186

13. Algoritmos de búsqueda. ...


Búsqueda secuencial 187

Búsqueda de valores mínimos y máximos mediante datos 190

autoorganizados Búsqueda binaria 193

Conteo de 196

ocurrencias 200

Búsqueda de datos textuales 202


Ceremonias 205

14. Algoritmos avanzados. ...


Programación dinámica Un 207

ejemplo de programación dinámica: cálculo de números de Fibonacci Encontrar la 208

subcadena común más larga El problema de la 211

mochila: una solución recursiva El problema de la mochila: 214

una solución de programación dinámica 215

Algoritmos voraces 217

Un primer ejemplo de algoritmo voraz: el problema del cambio de monedas 217

Una solución de algoritmo voraz para el problema de la mochila 218


Ceremonias 220

Índice. ... 221

Índice de contenidos | viii

[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.

¿Por qué estudiar estructuras de datos y algoritmos?


Supongo que muchos de los que leéis este libro no tenéis una formación académica en informática. Si es
así, ya sabéis por qué es importante estudiar estructuras de datos y algoritmos. Si no tenéis un título en
informática o no habéis estudiado estos temas de forma formal, deberíais leer esta sección.

El científico informático Nicklaus Wirth escribió un libro de texto de programación informática titulado
Algoritmos + Estructuras de datos = Programas (Prentice­Hall). 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.

Lo que necesitas para este libro


El entorno de programación que utilizamos en este libro es el shell de JavaScript basado en el motor
JavaScript SpiderMonkey. El capítulo 1 proporciona instrucciones para descargar el shell para su entorno.
También funcionarán otros shells, como el shell de Java Script de [Link], aunque tendrá que realizar
algunas traducciones para que los programas del libro funcionen en Node. Además del shell, lo único que
necesitará es un editor de texto para escribir sus programas de JavaScript.

x | Prefacio

[Link]­[Link]
Machine Translated by Google

Organización del Libro


• El capítulo 1 presenta una descripción general del lenguaje JavaScript, o al menos de las características del
lenguaje JavaScript que se utilizan en este libro. Este capítulo también demuestra mediante su uso el estilo de
programación que se utiliza en los demás capítulos.

• 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.

• El capítulo 3 presenta la primera estructura de datos implementada: la lista. • El capítulo 4

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 clave­valor.

• 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.

Convenciones utilizadas en este libro

En este libro se utilizan las siguientes convenciones tipográficas:


Itálico
Indica nuevos términos, URL, direcciones de correo electrónico, nombres de archivos y extensiones de archivos.

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.

Negrita de ancho constante

Muestra comandos u otro texto que el usuario debe escribir literalmente.

Cursiva de ancho constante

Muestra texto que debe reemplazarse con valores proporcionados por el usuario o por valores
determinados por el contexto.

Usando ejemplos de código


El material complementario (ejemplos de código, ejercicios, etc.) está disponible para descargar en
[Link]

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 CD­ROM 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, 978­1­449­36493­9”.

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

Libros en línea de Safari®

Safari Books Online es una biblioteca digital a pedido que ofrece


contenido especializado en formato de libros y videos de los principales
autores del mundo en tecnología y negocios.

Los profesionales de tecnología, desarrolladores de software, diseñadores web y profesionales de


negocios y creativos utilizan Safari Books Online como su recurso principal para investigación, resolución
de problemas, aprendizaje y capacitación para la certificación.

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, Addison­Wesley
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, McGraw­Hill, 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:

O'Reilly Media, Inc.


1005 Gravenstein Highway North
Sebastopol, CA 95472
800­998­9938 (en Estados Unidos o Canadá)
707­829­0515 (internacional o local)
707­829­0104 (fax)

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]

Encuéntrenos en Facebook: [Link]

Síganos en Twitter: [Link]

Míranos en YouTube: [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 y el modelo de programación de


JavaScript

Este capítulo describe el entorno de programación de JavaScript y las construcciones de programación


que utilizaremos en este libro para definir las diversas estructuras de datos y algoritmos examinados.

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.

La siguiente es una interacción típica con el shell:

js> 1 1

js> 1+2 3

js> var número = 1; js>


número*124 124

[Link]­[Link]
Machine Translated by Google

js> para (var i = 1; i < 6; ++i) { imprimir(i);

}
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

Una vez ejecutado el programa, el control vuelve al símbolo del sistema.

Prácticas de programación en JavaScript En esta sección,

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.

2 | Capítulo 1: El entorno y el modelo de programación de JavaScript

[Link]­[Link]
Machine Translated by Google

Declaración e inicialización de variables Las

variables de JavaScript son globales por defecto y, estrictamente hablando, no es necesario


declararlas antes de usarlas. Cuando una variable de JavaScript se inicializa sin declararla primero,
se convierte en una variable global. En este libro, sin embargo, seguimos la convención que se
utiliza con lenguajes compilados como C++ y Java, declarando todas las variables antes de su
primer uso. El beneficio adicional de hacer esto es que las variables declaradas se crean como
variables locales. Hablaremos más sobre el alcance de las variables más adelante en este capítulo.

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;

Funciones de biblioteca aritméticas y matemáticas en JavaScript

JavaScript utiliza los operadores aritméticos estándar:

• + (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.

El ejemplo 1­1 muestra algunos ejemplos de realización de operaciones aritméticas en JavaScript,


así como ejemplos de uso de varias de las funciones matemáticas.

Ejemplo 1­1. Funciones aritméticas y matemáticas en JavaScript


var x = 3; var
y = 1,1;
imprimir(x + y);
imprimir(x * y);
imprimir((x+y)*(xy)); var
z = 9;
imprimir([Link](z));
imprimir([Link](y/x));

El resultado de este programa es:

Prácticas de programación en JavaScript | 3

[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 x = 3; var y = 1.1;

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 declaración if viene en tres formas:

• La simple declaración if
• La declaración if­else

• La declaración if­else if

El ejemplo 1­2 muestra cómo escribir una declaración if simple.

Ejemplo 1­2. La simple sentencia if


var media = 25; var
alta = 50; var baja =
1; var actual = 13;
var encontrada = ­1; si
(actual < media)
{ media = (actual­baja) / 2;

El ejemplo 1­3 demuestra la declaración if­else .

Ejemplo 1­3. La sentencia if­else


var media = 25; var
alta = 50; var baja =
1; var actual = 13;
var encontrada = ­1; si
(actual < media)
{ media = (actual­baja) / 2;

4 | Capítulo 1: El entorno y el modelo de programación de JavaScript

[Link]­[Link]
Machine Translated by Google

de lo
contrario { medio = (actual+alto) / 2;
}

El ejemplo 1­4 ilustra la declaración if­else if .

Ejemplo 1­4. La sentencia if­else if


var media = 25; var
alta = 50; var baja =
1; var actual = 13;
var encontrada = ­1; si
(actual < media)
{ media = (actual­baja) / 2;

} de lo contrario si (actual > medio) {


medio = (actual+alto) / 2;
}
else
{ encontrado = actual;
}

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 1­5 demuestra cómo funciona la sentencia switch .

Ejemplo 1­5. La sentencia switch


putstr("Ingrese un número de mes: "); var
monthNum = readline(); var monthName;
switch (monthNum)
{ case "1": monthName =
"Enero";
break; case "2":

nombreMes = "Febrero"; break;


case "3":

nombreMes = "Marzo"; break;


case "4":

nombreMes = "Abril"; break;


case "5":

nombreMes = "Mayo";
break;
case "6":
nombreMes = "Junio"; break;
case "7":

Prácticas de programación en JavaScript | 5

[Link]­[Link]
Machine Translated by Google

nombreMes = "Julio"; break;


case "8":

monthName = "Agosto"; break;


case
"9":
monthName = "Septiembre"; break;
case
"10":
monthName = "Octubre"; break;
case
"11":
monthName = "Noviembre"; break;
case
"12":
monthName = "Diciembre"; break;
default:

print(" Entrada incorrecta");


}

¿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 1­6 demuestra cómo funciona el bucle while .

Ejemplo 1­6. El bucle while


var numero = 1; var
suma = 0; mientras
(numero < 11) { suma +=
numero; ++numero;

}
print(suma); // muestra 55

6 | Capítulo 1: El entorno y el modelo de programación de JavaScript

[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 1­7 se utiliza un bucle for para sumar los números enteros del 1 al 10.

Ejemplo 1­7. Sumar números enteros mediante un bucle for

var numero = 1; var


suma = 0; para
(var numero = 1; numero < 11; numero++) {
suma += numero;

} 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 1­8.

Ejemplo 1­8. Uso de un bucle for con una matriz

var numeros = [3, 7, 12, 22, 100]; var suma =


0; para (var i =
0; i < nú[Link]; ++i) { suma += números[i];

} print(suma); // muestra 144

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 1­9 demuestra cómo se definen y llaman las funciones que devuelven valores en JavaScript.

Ejemplo 1­9. Una función que devuelve un valor

función factorial(numero) { var producto


= 1; para (var i =
numero; i >= 1; ­­i) { producto *= i;

} devolver producto;
}

print(factorial(4)); // muestra 24 print(factorial(5)); //


muestra 120 print(factorial(10)); // muestra
3628800

El ejemplo 1­10 ilustra cómo escribir una función que se utiliza no por su valor de retorno, sino por las operaciones
que realiza.

Prácticas de programación en JavaScript | 7

[Link]­[Link]
Machine Translated by Google

Ejemplo 1­10. Un subprocedimiento o función void en JavaScript


función curva(arr, cantidad) { para (var i
= 0; i < [Link]; ++i) { arr[i] += cantidad;

}
}

var calificaciones = [77, 73, 74, 81, 90];


curva(calificaciones,
5); print(calificaciones); // muestra 82,78,79,86,95

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 1­10.

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 ;
}

var scope = "global";


print(scope); // muestra "global"
print(showScope()); // muestra "global"

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() :

función showScope() { var


alcance = "local"; devolver
alcance;
}

var scope = "global";


print(scope); // muestra "global"
print(showScope()); // muestra "local"

8 | Capítulo 1: El entorno y el modelo de programación de JavaScript

[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 1­11 demuestra las ramificaciones de omitir la palabra clave var al definir variables.

Ejemplo 1­11. Las consecuencias del uso excesivo de variables globales

función showScope() { alcance


= "local"; devolver
alcance;
}

alcance = "global";
print(alcance); // muestra "global"
print(showScope()); // muestra "local" print(alcance); //
muestra "local"

En el ejemplo 1­11, 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:

para (int i = 1; i <=10; ++i) {


cout << "¡Hola, mundo!" << endl;
}

Aunque JavaScript no tiene alcance de bloque, pretendemos que lo tiene cuando escribimos bucles for
en este libro:

para (var i = 1; i <= 10; ++i ) { print("¡Hola,


mundo!");
}

No queremos ser la causa de que adquieras malos hábitos de programación.

Prácticas de programación en JavaScript | 9

[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:

función factorial(número) { si (número


== 1) { devolver número;

} else
{ return numero * factorial(numero­1);
}
}

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()

cuando el argumento pasado a la función es:


La ción es 5:

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.

Objetos y programación orientada a objetos


Las estructuras de datos que se analizan en este libro se implementan como objetos. JavaScript ofrece muchas formas
diferentes de crear y utilizar objetos. En esta sección, demostramos las técnicas que se utilizan en este libro para crear objetos
y para crear y utilizar las funciones y propiedades de un objeto.

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:

10 | Capítulo 1: El entorno y el modelo de programación de JavaScript

[Link]­[Link]
Machine Translated by Google

función Cheque(monto) { [Link] =


monto; // propiedad [Link] = deposito; //
función [Link] = retirar; // función [Link]
= toString; // función

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 deposito(monto) { [Link]


+= monto;
}

función retirar(cantidad) {
si (cantidad <= [Link]) { [Link] ­=
cantidad;
}
si (cantidad > [Link]) {
print("Fondos insuficientes");
}
}

función toString() { devolver


"Saldo: " + [Link];
}

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 1­12 proporciona la definición completa del objeto de verificación junto con un programa de
prueba.

Ejemplo 1­12. Definición y uso del objeto Checking


función Cheque(monto) { [Link] =
monto; [Link] = deposito;
[Link] = retirar; [Link] =
toString;

función deposito(monto) { [Link]


+= monto;
}

función retirar(cantidad) {
si (cantidad <= [Link]) { [Link] ­=
cantidad;
}
si (cantidad > [Link]) {
print("Fondos insuficientes");
}

Objetos y programación orientada a objetos | 11

[Link]­[Link]
Machine Translated by Google

función toString() { devolver


"Saldo: " + [Link];
}

var cuenta = new Cheques(500);


[Link](1000);
print([Link]()); // Saldo: 1500 [Link](750);
print([Link]()); // Saldo:
750 [Link](800); // muestra "Fondos insuficientes"
print([Link]()); // Saldo: 750

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.

12 | Capítulo 1: El entorno y el modelo de programación de JavaScript

[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.

Matrices de JavaScript definidas


La definición estándar de una matriz es una colección lineal de elementos, a los que se puede acceder
mediante índices, que suelen ser números enteros que se utilizan para calcular desplazamientos. La mayoría
de los lenguajes de programación informática tienen este tipo de matrices. JavaScript, por otro lado, tiene un
tipo de matriz completamente diferente.

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:

var números = [];

Cuando creas una matriz de esta manera, obtienes una matriz con una longitud de 0. Puedes verificar esto llamando a la

propiedad de longitud incorporada :

print([Link]); // muestra 0

Otra forma de crear una matriz es declarar una variable de matriz con un conjunto de elementos dentro del operador [] :

var números = [1,2,3,4,5];


print(nú[Link]); // muestra 5

También puedes crear una matriz llamando al constructor Array :

var números = new Array();


print(nú[Link]); // muestra 0

Puede llamar al constructor Array con un conjunto de elementos como argumentos para el constructor:

var números = new Array(1,2,3,4,5);


print(nú[Link]); // muestra 5

Finalmente, puedes crear una matriz llamando al constructor Array con un único argumento que especifique la longitud de
la matriz:

var números = new Array(10);


print(nú[Link]); // muestra 10

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:

var objetos = [1, "Joe", verdadero, nulo];

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 Java­Script: Las partes buenas [O'Reilly]).

Acceso y escritura de elementos de una matriz Los datos se

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:

números var = [];


para (var i = 0; i < 100; ++i) {
nums[i] = i+1;
}

También se accede a los elementos de la matriz mediante el operador [] . Por ejemplo:

var números = [1,2,3,4,5]; var suma =


números[0] + números[1] + números[2] + números[3] + números[4]; print(suma); // muestra 15

Por supuesto, acceder a todos los elementos de una matriz de forma secuencial es mucho más fácil utilizando un
bucle for :

var numeros = [1,2,3,5,8,13,21]; var suma = 0;


para (var i = 0; i <
[Link]; ++i) { suma += numeros[i];

}
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.

Creación de matrices a partir de cadenas Las

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

El resultado de este programa es:

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

Operaciones de agregación de matrices

Hay varias operaciones de agregación que se pueden realizar en matrices. En primer lugar, se puede asignar
una matriz a otra matriz:

var nums = [];


para (var i = 0; i < 10; ++i) { nums[i] =
i+1;
}
var samenums = nums;

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:

var nums = [];


para (var i = 0; i < 100; ++i) { nums[i] =
i+1;
}
var samenums = nums; nums[0]
= 400;
print(samenums[0]); // muestra 400

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:

función copiar(arr1, arr2) { para


(var i = 0; i < [Link]; ++i) {
arr2[i] = arr1[i];
}
}

Ahora el siguiente fragmento de código produce el resultado esperado:

números var = [];


para (var i = 0; i < 100; ++i) {
nums[i] = i+1;

} var mismosnums = [];

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:

var nums = [1,2,3,4,5];


imprimir(nums);

producirá el siguiente resultado:

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.

Búsqueda de un valor Una de las

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:

var names = ["David", "Cynthia", "Raymond", "Clayton", "Jennifer"]; putstr("Ingrese un nombre


para buscar: "); var name = readline(); var position =
[Link](name); if
(position >= 0) { print("Encontrado "

" "
+ nombre + En posición + posición);

} else
"
{ imprimir(nombre + no encontrado en la matriz.");
}

Si ejecuta este programa e ingresa Cynthia, el programa generará el siguiente resultado:

Encontré a Cynthia en la posición 1

Si ingresas Joe, el resultado es:

Joe no se encuentra 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);

El resultado de este programa es:

Encontré a Mike por primera vez en la posición 1


Último encontrado Mike en la posición 5

Representaciones de cadenas de matrices

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:

imprimir(nombres); // David, Cynthia, Raymond, Clayton, Mike, Jennifer

Creación de nuevas matrices a partir de matrices

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:

Mike, Clayton, Terrill, Danny, Jennifer, Raymond, Cynthia y Bryan


Raymond, Cynthia, Bryan, Mike, Clayton, Terrill, Danny y Jennifer

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.

Funciones del mutador


JavaScript tiene un conjunto de funciones de mutación que permiten modificar el contenido de una
matriz sin hacer referencia a los elementos individuales. Estas funciones suelen facilitar las
técnicas difíciles, como verá a continuación.

Cómo agregar elementos a una matriz

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:

var nums = [1,2,3,4,5];


imprimir(nums); // 1,2,3,4,5
[Link](6);
imprimir(nums); // 1,2,3,4,5,6

Usar push() es más intuitivo que usar la propiedad de longitud para extender una matriz:

var nums = [1,2,3,4,5];


imprimir(nums); // 1,2,3,4,5
nums[[Link]] = 6;
imprimir(nums); // 1,2,3,4,5,6

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

Funciones del mutador | 19

[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:

var nums = [2,3,4,5]; var


nuevonum = 1; var
N = [Link]; para (var
i = N; i >= 0; ­­i) { nums[i] = nums[i­1];

} 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:

var nums = [2,3,4,5];


imprimir(nums); // 2,3,4,5 var
nuevonum = 1;
[Link](nuevonum);
imprimir(nums); // 1,2,3,4,5 nums
= [3,4,5];
[Link](nuevonum,1,2);
imprimir(nums); // 1,2,3,4,5

La segunda llamada a unshift() demuestra que puedes agregar múltiples elementos a una matriz con una sola llamada
a la función.

Eliminar elementos de una matriz


Eliminar un elemento del final de una matriz es fácil utilizando la función mutadora pop() :

var nums = [1,2,3,4,5,9];


[Link]();
print(nums); // 1,2,3,4,5

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:

var nums = [9,1,2,3,4,5];


imprimir(nums);
para (var i = 0; i < [Link]; ++i) {
números[i] = números[i+1];

} 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:

var nums = [9,1,2,3,4,5];


[Link]();
print(nums); // 1,2,3,4,5

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:

var nums = [6,1,2,3,4,5]; var


first = [Link](); // first obtiene el valor 9 [Link](first);
print(nums); //
1,2,3,4,5,6

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:

• El índice inicial (donde desea comenzar a agregar elementos) • La cantidad de

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:

var nums = [1,2,3,7,8,9]; var


nuevosElementos = [4,5,6];
[Link](3,0,nuevoElementos);
print(nums); // 1,2,3,4,5,6,7,8,9

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:

var nums = [1,2,3,7,8,9];


[Link](3,0,4,5,6);
print(nums);

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:

Funciones del mutador | 21

[Link]­[Link]
Machine Translated by Google

var nums = [1,2,3,100,200,300,400,4,5];


[Link](3,4);
print(nums); // 1,2,3,4,5

Cómo ordenar los elementos de una matriz Las

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:

var nums = [1,2,3,4,5];


[Link]();
print(nums); // 5,4,3,2,1

A menudo necesitamos ordenar los elementos de una matriz. La función mutadora para esta tarea, sort(),
funciona muy bien con cadenas:

var nombres = ["David", "Mike", "Cynthia", "Clayton", "Bryan", "Raymond"]; [Link]();


print(nums); //
Bryan,Clayton,Cynthia,David,Mike,Raymond

Pero sort() no funciona tan bien con números:

var nums = [3,1,2,100,4,200];


[Link]();
print(nums); // 1,100,2,200,3,4

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:

función comparar(num1, num2) { devolver


num1 ­ num2;
}

var nums = [3,1,2,100,4,200];


[Link](comparar);
print(nums); // 1,2,3,4,100,200

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.

Funciones iteradoras que no generan matrices El primer grupo

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);
}

var nums = [1,2,3,4,5,6,7,8,9,10];


[Link](cuadrado);

El resultado de este programa es:

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;
}

var nums = [2,4,6,8,10]; var


even = [Link](isEven); if (even)
{ print("todos
los números son pares");

} else
{ print("no todos los números son pares");
}

Funciones iterativas | 23

[Link]­[Link]
Machine Translated by Google

El programa muestra:

todos los números son pares

Si cambiamos la matriz a:

var números = [2,4,6,7,8,10];

El programa muestra:

No todos los números son pares

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:

función isEven(num) { devolver


num % 2 == 0;
}

var nums = [1,2,3,4,5,6,7,8,9,10]; var someEven =


[Link](isEven); if (someEven) { print("algunos
números son pares");

} 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");
}

El resultado de este programa es:

Algunos números son pares,


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:

función add(runningTotal, currentValue) { return runningTotal +


currentValue;
}

var nums = [1,2,3,4,5,6,7,8,9,10]; var suma =


[Link](add); print(suma); //
muestra 55

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

También podemos usar reduce() con cadenas para realizar la concatenación:

función concat(accumulatedString, item) { return


acumuladoString + item;
}

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:

función concat(accumulatedString, item) { return


acumuladoString + item;
}

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 una nueva matriz Hay dos

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;
}

var calificaciones = [77, 65, 81, 92, 83]; var


nuevascalificaciones =
[Link](curva); print(nuevascalificaciones); // 82, 70, 86, 97, 88

A continuación se muestra un ejemplo que utiliza cadenas:

Funciones iterativas | 25

[Link]­[Link]
Machine Translated by Google

función primera(palabra)
{ devolver palabra[0];
}

var words = ["para", "tu", "información"]; var acrónimo =


[Link](first); print([Link]("")); //
muestra "fyi"

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;
}

var nums = []; para


(var i = 0; i < 20; ++i) { nums[i] = i+1;

} var evens = [Link](isEven); print("


Números pares: "); print(evens);
var odds =
[Link](isOdd); print(" Números impares:
"); print(odds);

Este programa devuelve la siguiente salida:

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

Aquí hay otro uso interesante de filter():

función pasando(num) { return


num >= 60;
}

var calificaciones = [];


para (var i = 0; i < 20; ++i) { calificaciones[i]
= [Link]([Link]() * 101);

26 | Capítulo 2: Matrices

[Link]­[Link]
Machine Translated by Google

} var passGrades = [Link](passing); print("Todas


las calificaciones: );
print(grades);
print("Calificaciones aprobatorias:
"); print(passGrades);

Este programa muestra:

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

Matrices bidimensionales y multidimensionales


Las matrices de JavaScript son unidimensionales, pero se pueden crear matrices multidimensionales
creando matrices de matrices. En esta sección, describiremos cómo crear matrices bidimensionales
en JavaScript.

Creación de matrices bidimensionales Una

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:

var twod = []; var


filas = 5; para (var
i = 0; i < filas; ++i) { twod[i] = [];

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:

Matrices bidimensionales y multidimensionales | 27

[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:

[Link] = función(numfilas, numcols, inicial) { var arr = []; para (var i


= 0; i < numfilas;
++i) {
var columnas = []; para
(var j = 0; j < numcols; ++j) { columnas[j] = inicial;

} arr[i] = columnas;

} devolver arr;
}

Aquí hay un código para probar la definición:

var nums = [Link](5,5,0); print(nums[1]


[1]); // muestra 0 var names =
[Link](3,3,""); names[1][2] = "Joe";
print(names[1][2]); // muestra
"Joe"

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.

Procesamiento de elementos de una matriz bidimensional Existen dos

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;
}

El bucle interno está controlado por la expresión:

col < calificaciones[fila].longitud

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.

El promedio de calificaciones de cada estudiante se redondea a dos decimales utilizando toFixed(n)


función.

Aquí está el resultado del programa:

Promedio del estudiante 1 : 81,33


Promedio del estudiante 2 : 79,67
Promedio del estudiante 3 : 91,33

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:

var grados = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];


var total = 0;
var media = 0,0;
para (var col = 0; col < [Link]; ++col) {
para (var fila = 0; fila < calificaciones[col].length; ++fila) {
total += calificaciones[fila][col];
}

promedio = total / calificaciones[col].length;


" " +
print("Prueba " + parseInt(col+1) + promedio: [Link](2));

total = 0;
promedio = 0.0;
}

El resultado de este programa es:

Promedio de la prueba 1 : 85,33


Promedio de la prueba 2 : 84,33
Promedio de la prueba 3 : 82,67

Matrices bidimensionales y multidimensionales | 29

[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];

} promedio = total / calificaciones[fila].longitud;


" " +
print("Estudiante " + parseInt(fila+1) + promedio:
[Link](2));
total = 0;
promedio = 0.0;
}

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:

Promedio del estudiante 1 : 83.00


Promedio del estudiante 2 : 79.67
Promedio del estudiante 3 : 93.25

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:

función Punto(x,y) { este.x


= x; este.y = y;

función displayPts(arr) { para (var


i = 0; i < [Link]; ++i) {

30 | Capítulo 2: Matrices

[Link]­[Link]
Machine Translated by Google

imprimir(arr[i].x + ", " + arr[i].y);


}
}

var p1 = nuevo Punto(1,2);


var p2 = nuevo Punto(3,5);
var p3 = nuevo Punto(2,8);
var p4 = nuevo Punto(4,4);
var puntos = [p1,p2,p3,p4]; for
(var i = 0; i < [Link]; ++i) { print("Punto " +
parseInt(i+1) + ": " + puntos[i].x + ", " puntos[i].y); +

} var p5 = new Point(12,­3);


[Link](p5);
print("Después de empujar:
"); displayPts(puntos);
[Link]();
print("Después del
desplazamiento: "); displayPts(puntos);

El resultado de este programa es:

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

El punto 12, ­3 se agrega a la matriz usando push(), y el punto 1, 2 se elimina de la matriz


usando shift().

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);
}

función promedio() { var


total = 0; para
(var i = 0; i < [Link]; ++i) {
total += [Link][i];

} devuelve total / [Link];


}

var estaSemana = new weekTemps();


[Link](52) ;
[Link](55 );
[Link]( 61);
[Link]( 65);
[Link](55);
[Link](50);
[Link](52);
[Link](49);
print([Link]()); // muestra 54.875

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.

Una lista ADT

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.

En la Tabla 3­1 se muestra la Lista ADT completa.

Tabla 3­1. Lista de ADT

Número de elementos en la lista


listSize (propiedad)

pos (propiedad) Posición actual en la lista

Devuelve el número de elementos en la lista.


longitud (propiedad)

borrar (función) Borra todos los elementos de la lista

toString (función) Devuelve la representación de cadena de la lista

getElement (función) Devuelve el elemento en la posición actual

insertar (función) Inserta un nuevo elemento después del elemento existente

Añade un nuevo elemento al final de la lista.


añadir (función)

eliminar (función) Elimina elemento de la lista

frente (función) Establece la posición actual en el primer elemento de la lista.

fin (función) Establece la posición actual en el último elemento de la lista.

prev (función) Mueve la posición actual un elemento hacia atrás

siguiente (función) Mueve la posición actual un elemento hacia adelante

currPos (función) Devuelve la posición actual en la lista.

moverA (función) Mueve la posición actual a la posición especificada

Implementación de una clase de lista

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;
}

Append: Cómo agregar un elemento a una lista La

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;
}

Después de agregar el elemento, listSize se incrementa en 1.

Eliminar: eliminar un elemento de una lista


A continuación, veamos cómo eliminar un elemento de una lista. remove() es una de las funciones
más difíciles de implementar en la clase List . Primero, tenemos que encontrar el elemento en la lista
y luego tenemos que eliminarlo y ajustar el espacio en la matriz subyacente para llenar el hueco que
queda al eliminar un elemento. Sin embargo, podemos simplificar el proceso utilizando la función
mutadora splice() . Para comenzar, definamos una función auxiliar, find(), para encontrar el elemento
que se va a eliminar:

función buscar(elemento) {
para (var i = 0; i < [Link]; ++i) { si ([Link][i]
== elemento) { devolver i;

} devuelve ­1;
}

Implementación de una clase de lista | 37

[Link]­[Link]
Machine Translated by Google

Find: Cómo encontrar un elemento en una lista La

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

usar este valor para la comprobación de errores en la función remove() .

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:

función eliminar(elemento) { var


foundAt = [Link](elemento); si (foundAt > ­1)

{ [Link](foundAt,1); ­­[Link];
devolver verdadero;

} devuelve falso;
}

Longitud: Cómo determinar el número de elementos de una lista

La función length() devuelve el número de elementos de una lista:

función longitud() { devolver


[Link];
}

toString: Recuperación de los elementos de una lista Ahora

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:

función toString() { devolver


[Link];
}

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:

var nombres = new List();


[Link]("Cynthia");
[Link]("Raymond");

38 | Capítulo 3: Listas

[Link]­[Link]
Machine Translated by Google

[Link]("Barbara");
print([Link]());
[Link]("Raymond");
print([Link]());

El resultado de este programa es:

Cynthia, Raymond y Barbara


Cynthia, Bárbara

Insertar: Inserción de un elemento en una lista La siguiente

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() :

función insertar(elemento, después) {


var insertPos = [Link](after); si (insertPos >
­1) {
[Link](insertPos+1, 0, elemento); ++[Link];
devuelve verdadero;

} 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.

Borrar: eliminar todos los elementos de una lista


A continuación, necesitamos una función para borrar los elementos de una lista y permitir que se ingresen
nuevos elementos:

función clear() { eliminar


[Link]; [Link]
= []; [Link] = [Link]
= 0;
}

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.

Implementación de una clase de lista | 39

[Link]­[Link]
Machine Translated by Google

Contiene: determinar si un valor dado está en una lista La función

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:

función contiene(elemento) { para (var


i = 0; i < [Link]; ++i) { si ([Link][i] == elemento)
{ devuelve verdadero;

} devuelve falso;
}

Recorriendo una lista

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 front() { [Link]


= 0;
}

función fin() { [Link]


= [Link]­1;
}

función prev() { si
([Link] > 0) { ­­[Link];

}
}

función siguiente() { si
([Link] < [Link]­1) { ++[Link];

}
}

función currPos() { devolver


[Link];
}

función moveTo(posición) { [Link] =


posición;
}

función getElement() { devolver


[Link][[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

var nombres = new List();


[Link]("Clayton");
[Link]("Raymond");
[Link]("Cynthia");
[Link]("Jennifer");
[Link]("Bryan");
[Link]("Danny");

Ahora pasemos al primer elemento de la lista y mostrémoslo:

[Link]();
print([Link]()); // muestra Clayton

A continuación, avanzamos un elemento y mostramos el valor del elemento:

[Link](); print([Link]()); // muestra Raymond

Ahora avanzaremos dos veces y retrocederemos una vez, mostrando el elemento actual para demostrar cómo
funciona la función prev() :

[Link](); [Link](); [Link](); print([Link]()); // muestra a Cynthia

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.

Iterando a través de una lista


Un iterador nos permite recorrer una lista sin hacer referencia al mecanismo de almacenamiento
interno de la clase List . Las funciones front(), end(), prev(), next() y currPos proporcionan una
implementación de un iterador para nuestra clase List . Algunas ventajas de usar iteradores en
lugar de usar indexación de matrices incluyen:

• No tener que preocuparse por la estructura de almacenamiento de datos subyacente al acceder


elementos de lista

• Poder actualizar la lista y no tener que actualizar el iterador, donde un índice


se vuelve inválido cuando se agrega un nuevo elemento a la lista

• 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:

para([Link](); [Link]() < [Link](); [Link]()) {


imprimir([Link]());
}

Iterando a través de una lista | 41

[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:

para([Link](); [Link]() >= 0; [Link]()) {


imprimir([Link]());
}

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.

Una aplicación basada en listas

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.

Lectura de archivos de texto

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):

1. Cadena perpetua 2. El padrino 3. El

padrino: Parte II 4.

Tiempos violentos 5. El bueno,

el feo y el malo 6.

Doce hombres sin piedad 7. La lista de

Schindler

8. El caballero oscuro 9.

El señor de los anillos: El retorno del rey

10. El club de la

lucha 11. La guerra de las galaxias: Episodio V ­ El Imperio


contraataca 12. Alguien voló sobre el nido del cuco

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

17. Los siete samuráis

18. La Matriz

19. Forrest Gump 20.

Ciudad de Dios

Ahora necesitamos un fragmento de código para leer el contenido del archivo en nuestro programa:

var peliculas = read([Link]).split("\n");

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:

función createArr(archivo) { var arr =


read(archivo).split("\n"); para (var i = 0; i <
[Link]; ++i) { arr[i] = arr[i].trim();

} devolver arr;
}

Uso de listas para administrar un quiosco El

siguiente paso es tomar la matriz de películas y almacenar su contenido en una lista. Así es como lo hacemos:

var movieList = new List(); para (var i


= 0; i < [Link]; ++i) { [Link](peliculas[i]);

Ahora podemos escribir una función para mostrar la lista de películas disponibles en el quiosco:

Una aplicación basada en listas | 43

[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:

var clientes = nueva Lista();

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 :

función Cliente(nombre, película)


{ [Link] =
nombre; [Link] = película;
}

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.

Aquí está la definición de una función para revisar una película:

función checkOut(nombre, película, listaPelículas, listaClientes) { if


(listaPelí[Link](película)) {
var c = new Cliente(nombre, película);
[Link](c);
listaDePelí[Link](película);

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.

Podemos probar la función checkOut() con un programa corto:

var peliculas = createArr("pelí[Link]"); var


listaDePelículas = new List(); var
clientes = new List(); for (var i = 0; i <
[Link]; ++i) { listaDePelí[Link](películas[i]);

} print("Películas disponibles: \n");


displayList(movieList);
checkOut("Jane Doe", "El Padrino", movieList, clientes); print("\nAlquileres de clientes:
\n"); displayList(clientes);

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:

var peliculas = createArr("pelí[Link]"); var


listaDePelículas = new List(); var
clientes = new List(); for (var i = 0; i <
[Link]; ++i) { listaDePelí[Link](películas[i]);

} print("Películas disponibles: \n");


displayList(movieList);
putstr("\nIngrese su nombre: "); var name =
readline(); putstr("¿Qué película
le gustaría? "); var movie = readline(); checkOut(name,
movie, movieList, clients);
print("\nAlquileres de clientes: \n"); displayList(customers);
print("\nPelículas disponibles ahora\n");
displayList(movieList);

Aquí está el resultado de ejecutar este programa:

Películas disponibles:

Una aplicación basada en listas | 45

[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

La guerra de las galaxias

Los siete samuráis


La matriz
Forrest Gump
Ciudad de Dios

Ingresa tu nombre: Jane Doe ¿Qué


película te gustaría ver? El Padrino

Alquileres para clientes:

Jane Doe, El Padrino

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

La guerra de las galaxias

Los siete samuráis


La matriz
Forrest Gump
Ciudad de Dios

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

operaciones se ilustran en la Figura 4­1.

49

[Link]­[Link]
Machine Translated by Google

Figura 4­1. Empujar y hacer estallar elementos de una pila

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.

Una implementación de pila


Para crear una pila, primero debemos decidir la estructura de datos subyacente que utilizaremos para
almacenar los elementos de la pila. En nuestra implementación, utilizaremos una matriz.

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;
}

El ejemplo 4­1 muestra la implementación completa de la clase Stack .

Ejemplo 4­1. La clase Stack

función Stack()
{ [Link] = [];

Una implementación de pila | 51

[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 clear() { [Link]


= 0;
}

función longitud()
{ devolver [Link];
}

El ejemplo 4­2 demuestra un programa que prueba esta implementación.

Ejemplo 4­2. Prueba de la implementación de la clase Stack


var s = new Stack();
[Link]("David");
[Link]("Raymond");
[Link]("Bryan");
print("longitud: " + [Link]());
print([Link]()); var
popped = [Link](); print("El
"
elemento extraído es: print([Link]()); + apareció);
[Link]("Cynthia");
print([Link]()); [Link]();
print("longitud: "
print([Link]());
[Link]("Clayton"); + [Link]());
print([Link]());

El resultado del ejemplo 4­2 es:

longitud: 3
Bryan

52 | Capítulo 4: Pilas

[Link]­[Link]
Machine Translated by Google

El elemento resaltado es: Bryan


Raymond
Cynthia
longitud: 0
indefinido
Clayton

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 .

Usando la clase Stack


Existen varios problemas para los cuales una pila es la estructura de datos perfecta necesaria para
la solución. En esta sección, analizamos varios de estos problemas.

Conversiones de bases múltiples

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:

1. El dígito más a la derecha de n es n % b. Coloque este dígito en la pila.

2. Reemplaza n por n/ b.

3. Repita los pasos 1 y 2 hasta que n = 0 y no queden dígitos significativos.

4. Construya la cadena de números convertida haciendo estallar la pila hasta que esté vacía.

Este algoritmo sólo funcionará con las bases 2 a 9.

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:

función mulBase(num, base) { var s = new


Stack(); hacer { [Link](num
%
base); num = [Link](num /
= base); } mientras (num > 0); var convertido
= ""; mientras ([Link]() >
0) { convertido += [Link]();

Uso de la clase Stack | 53

[Link]­[Link]
Machine Translated by Google

}
devuelve convertido;
}

El ejemplo 4­3 demuestra cómo utilizar esta función para conversiones de base 2 y base 8.

Ejemplo 4­3. Conversión de números a base 2 y base 8

función mulBase(num, base) {


var s = nueva pila();
hacer {
[Link](num % base);
num = [Link](num /= base);
} mientras (num > 0);
var convertido = "";
mientras ([Link]() > 0) {
convertido += [Link]();
}
devuelve convertido;
}

var num = 32;


var base = 2;
var nuevoNum = mulBase(núm, base);
" "
imprimir(num "convertido a base" + base + es + nuevoNum);
+ num = 125;
base = 8;
var nuevoNum = mulBase(núm, base);
" "
imprimir(num + "convertido a base" + base + es + nuevoNum);

El resultado del ejemplo 4­3 es:

32 convertido a base 2 es 100000


125 convertido a base 8 es 175

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 4­2.

54 | Capítulo 4: Pilas

[Link]­[Link]
Machine Translated by Google

Figura 4­2. 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 4­4 presenta un programa, menos el código de la clase Stack , que determina si una cadena
dada es un palíndromo.

Ejemplo 4­4. Determinar si una cadena es un palíndromo

función isPalindrome(palabra) { var s =


new Stack(); para (var i = 0;
i < [Link]; ++i) { [Link](palabra[i]);

} var rword = "";


mientras ([Link]() > 0) { rword +=
[Link]();

} si (palabra == rpalabra)
{ devuelve verdadero;

} demás {

Uso de la clase Stack | 55

[Link]­[Link]
Machine Translated by Google

devuelve falso;
}
}

var palabra = "hola";


si (isPalindrome(palabra)) {
"
print(palabra + es un palíndromo.");
}
demás {
"
imprimir(palabra + no es un palíndromo.");
}
palabra = "coche de carreras"

si (isPalindrome(palabra)) {
"
print(palabra + es un palíndromo.");
}
demás {
"
imprimir(palabra + no es un palíndromo.");
}

El resultado de este programa es:

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(n­1);
}
}

Cuando se llama con el argumento 5, la función devuelve 120.

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 4­5 contiene el código de la función,
junto con un programa de prueba.

Ejemplo 4­5. Simulación de procesos recursivos mediante una pila


función hecho(n) { var
s = new Stack(); mientras
(n > 1) { [Link](n­­);

} var producto = 1;
mientras ([Link]() > 0)
{ producto *= [Link]();

} devolver producto;
}

print(factorial(5)); // muestra 120 print(fact(5)); //


muestra 120

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.

2. Un evaluador de expresiones postfijas trabaja con expresiones aritméticas tomando lo siguiente


forma:

operador op1 op2

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 de cola Las dos

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 5­1 ilustra estas operaciones.

59

[Link]­[Link]
Machine Translated by Google

Figura 5­1. Inserción y eliminación de elementos de una cola

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 .

Implementación de una clase de cola basada en matrices


Implementar la clase Queue usando una matriz es sencillo. El uso de matrices de JavaScript es
una ventaja que muchos otros lenguajes de programación no tienen porque JavaScript contiene
una función para agregar fácilmente datos al final de una matriz, push(), y una función para
eliminar fácilmente datos del principio de una matriz, shift().

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

Luego podemos eliminar el elemento del frente de la matriz usando shift():

[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;

La función enqueue() agrega un elemento al final de una cola:

función enqueue(elemento) {
[Link](elemento);
}

La función dequeue() elimina un elemento del frente de una cola:

función dequeue() { devolver


[Link]();
}

Podemos examinar los elementos frontales y posteriores de una cola utilizando estas funciones:

función front() { devolver


[Link][0];
}

función back()
{ devolver [Link][[Link]­1];
}

También necesitamos una función toString() para mostrar todos los elementos en una cola:

función toString() { var retStr


= ""; para (var i = 0; i
< [Link]; ++i) { retStr += [Link][i] + "\n";

} 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;
}
}

Implementación de una clase de cola basada en matrices | 61

[Link]­[Link]
Machine Translated by Google

El ejemplo 5­1 presenta la definición completa de la clase Queue junto con un programa de prueba.

Ejemplo 5­1. Definición de la clase de cola y 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 dequeue() { devolver


[Link]();
}

función front() { devolver


[Link][0];
}

función back()
{ devolver [Link][[Link]­1];
}

función toString() { var retStr


= ""; para (var i = 0; i
< [Link]; ++i) { retStr += [Link][i] + "\n";

} devuelve retStr;
}

función vacía() {
si ([Link] == 0) {
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}

// programa de prueba

var q = nueva cola();


[Link]("Meredith");
[Link]("Cynthia");
[Link]("Jennifer");

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]());

El resultado del ejemplo 5­1 es:

Meredith
Cintia
Jennifer

Cintia
Jennifer

Al frente de la cola: Cynthia


Al final de la cola: Jennifer

Uso de la clase Queue: asignación de socios en un cuadrado


Bailar

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

Cada bailarín se almacena en un objeto Bailarín :

función Bailarín(nombre, sexo)


{ [Link] = nombre;

Uso de la clase Queue: asignación de parejas en un baile de cuadrilla | 63

[Link]­[Link]
Machine Translated by Google

[Link] = sexo;
}

A continuación necesitamos una función para cargar los bailarines del archivo al programa:

función getDancers(hombres, mujeres) {


var nombres = read("[Link]").split("\n"); for (var i =
0; i < [Link]; ++i) { nombres[i] =
nombres[i].trim();

} para (var i = 0; i < [Link]; ++i) { var


bailarina = nombres[i].split(" "); var sexo =
bailarina[0]; var nombre =
bailarina[1]; si (sexo == "F")
{ [Link](new
Bailarina(nombre, sexo));

} 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:

función baile(hombres, mujeres) {


print("Los compañeros de baile son: \n"); while
(![Link]() && ![Link]()) { person =
[Link](); putstr("La
"
bailarina es: person = + [Link]);
[Link](); print(" y el
"
bailarín es: + [Link]);

} imprimir();
}

El ejemplo 5­2 presenta todas las funciones anteriores, así como un programa de prueba y la clase
Queue .

Ejemplo 5­2. Simulación de baile en cuadrilla

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 dequeue() { devolver


[Link]();
}

función front() { devolver


[Link][0];
}

función back()
{ devolver [Link][[Link]­1];
}

función toString() { var retStr


= ""; para (var i = 0; i
< [Link]; ++i) { retStr += [Link][i] + "\n";

} devuelve retStr;
}

función vacía() {
si ([Link] == 0) {
devuelve verdadero;
}
de lo
contrario { devolver falso;
}
}

función Bailarín(nombre, sexo)


{ [Link] = nombre;
[Link] = sexo;
}

función getDancers(hombres, mujeres) { var


nombres = read("[Link]").split("\n"); para (var i = 0; i
< [Link]; ++i) { nombres[i] = nombres[i].trim();

} para (var i = 0; i < [Link]; ++i) { var bailarina


= nombres[i].split(" "); var sexo = bailarina[0];
var nombre = bailarina[1];
si (sexo == "F") {

[Link](new Bailarina(nombre, sexo));

Uso de la clase Queue: asignación de parejas en un baile de cuadrilla | 65

[Link]­[Link]
Machine Translated by Google

} else
{ [Link](new Dancer(nombre, sexo));
}
}
}

función baile(hombres, mujeres) {


print("Los compañeros de baile son: \n"); while (!
[Link]() && ![Link]()) { person = [Link]();
putstr("La bailarina es: person =
"
[Link](); print(" y el bailarín + [Link]);
es:
"
+ [Link]);

} imprimir();
}

// programa de prueba

var BailarinesMasculinos = new Queue();


var BailarinasFemeninas = new Queue();
obtenerBailarines(BailarinesMasculinos,
BailarinasFemeninas); bailar(BailarinesMasculinos,
BailarinasFemeninas); si (!
"
[Link]ío()) { imprimir([Link]().nombre +
está esperando para bailar.");

} if (![Link]())
"
{ print([Link]().name + está esperando para bailar.");
}

El resultado del ejemplo 5­2 es:

Las parejas de baile son:

La bailarina es: Allison y el bailarín es: Frank


La bailarina es: Cheryl y el bailarín es: Mason
La bailarina es: Jennifer y el bailarín es: Clayton
La bailarina es: Aurora y el bailarín es: Raymond

Bryan está esperando para bailar.

Un cambio que podríamos hacer al programa es mostrar la cantidad de bailarines masculinos y


femeninos que esperan para bailar. No tenemos una función que muestre la cantidad de elementos
en una cola, por lo que debemos agregarla a la definición de la clase Queue :

función count() { devolver


[Link];
}

Asegúrese de agregar la siguiente línea a la función constructora de la clase Queue :

[Link] = contar;

66 | Capítulo 5: Colas

[Link]­[Link]
Machine Translated by Google

En el ejemplo 5­3, cambiamos el programa de prueba para utilizar esta nueva función.

Ejemplo 5­3. Proporcionar un recuento de bailarines que esperan para bailar

var bailarineshombres = new Queue(); var


bailarinesmujeres = new Queue();
obtenerBailadoreshombres(bailarineshombres,
bailarinasmujeres); bailar(bailarineshombres,
bailarinasmujeres); if ([Link]()
"
> 0) { print("Hay + [Link]() + " bailarines
hombres esperando para bailar.");

} if ([Link]() > 0) { print("Hay +


"
[Link]() + " bailarinas esperando para bailar.");

Cuando ejecutamos el Ejemplo 5­3, obtenemos lo siguiente:

La bailarina es: Allison y el bailarín es: Frank


La bailarina es: Cheryl y el bailarín es: Mason
La bailarina es: Jennifer y el bailarín es: Clayton
La bailarina es: Aurora y el bailarín es: Raymond

Hay 3 bailarines masculinos esperando para bailar.

Ordenar datos con colas


Las colas no sólo son útiles para simulaciones, sino que también se pueden utilizar para ordenar datos. En los
viejos tiempos de la informática, los programas se introducían en un programa de mainframe mediante tarjetas
perforadas, en las que cada tarjeta contenía una única instrucción de programa. Las tarjetas se clasificaban
mediante un clasificador mecánico que utilizaba estructuras similares a contenedores para almacenarlas. Podemos simular

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:

91, 46, 85, 15, 92, 35, 31, 22

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:

Contenedor 5: 85, 15, 35

Ordenar datos con colas | 67

[Link]­[Link]
Machine Translated by Google

Contenedor 6:46

Contenedor 7:

Contenedor 8:

Contenedor 9:

Ahora los números se ordenan según el contenedor en el que se encuentran:

91, 31, 92, 22, 85, 15, 35, 46

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:

15, 22, 31, 35, 46, 85, 91, 92

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):

función distribuir(nums, colas, n, dígito) { // dígito representa los 1


// o lugar de las decenas
para (var i = 0; i < n; ++i) { si (dígito == 1)
{ colas[nums[i]
%10].enqueue(nums[i]);
}
de lo
contrario { colas[[Link](nums[i] / 10)].enqueue(nums[i]);
}
}
}

Aquí está la función para recopilar números de las colas:

función recopilar(colas, números) { var i = 0; para


(var dígito = 0;
dígito < 10; ++dígito) { mientras (!colas[dígito].empty()) {

68 | Capítulo 5: Colas

[Link]­[Link]
Machine Translated by Google

nums[i++] = colas[dígito].dequeue();
}
}
}

El ejemplo 5­4 presenta un programa completo para realizar una ordenación por base, junto con una función
para mostrar el contenido de una matriz.

Ejemplo 5­4. Realizar una ordenación por base

función distribuir(nums, colas, n, dígito) { para (var i = 0; i


< n; ++i) { si (dígito == 1) { colas[nums[i]
%10].enqueue(nums[i]);

} de lo
contrario { colas[[Link](nums[i] / 10)].enqueue(nums[i]);
}
}
}

función recopilar(colas, números) { var i =


0; para (var
dígito = 0; dígito < 10; ++dígito) { mientras (!
colas[dígito].empty()) {
nums[i++] = colas[dígito].dequeue();
}
}
}

función dispArray(arr) { para (var


i = 0; i < [Link]; ++i) { putstr(arr[i] + " ");

}
}

// programa principal

var colas = []; para


(var i = 0; i < 10; ++i) { colas[i] = new
Cola();

} var nums = [];


para (var i = 0; i < 10; ++i) { nums[i] =
[Link]([Link]([Link]() * 101));

} print("Antes de la ordenación por


base: ");
dispArray(nums); distributed(nums,
queues, 10, 1); collect(queues,
nums); distributed(nums, queues, 10, 10);
collect(queues, nums);

Ordenar datos con colas | 69

[Link]­[Link]
Machine Translated by Google

print("\n\nDespués de ordenar por base: ");


dispArray(numeros);

A continuación se muestran un par de ejecuciones del programa:

Antes de la ordenación por base:


45 72 93 51 21 16 70 41 27 31

Después de ordenar por


base: 16 21 27 31 41 45 51 70 72 93

Antes de la ordenación por base:


76 77 15 84 79 71 69 99 6 54

Después de la ordenación por base:

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:

función Paciente(nombre, código) { [Link]


= nombre; this.código =
código;
}

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 :

función toString() { var retStr =


""; para (var i = 0; i <
[Link]; ++i) { retStr += [Link][i].name +
" código: "
+ [Link][i].code + "\n";

} devuelve retStr;
}

El ejemplo 5­5 demuestra cómo funciona el sistema de cola de prioridad.

Ejemplo 5­5. Implementación de una cola de prioridad

var p = nuevo Paciente("Smith",5); var ed =


nueva Cola(); [Link](p);
p = nuevo
Paciente("Jones", 4); [Link](p); p =
nuevo
Paciente("Fehrenbach", 6); [Link](p); p =
nuevo
Paciente("Brown", 1); [Link](p); p =
nuevo
Paciente("Ingram", 1); [Link](p);
print([Link]());
var visto = [Link]();
print("Paciente en tratamiento: "
print("Pacientes en espera de ser atendidos: + visto[0].nombre);
") print([Link]()); // otra ronda var seen =
[Link](); print("Paciente
en tratamiento: "
print("Pacientes en espera de ser
atendidos: ") print([Link]()); var seen = + visto[0].nombre);
[Link](); print("Paciente en tratamiento: "

+ visto[0].nombre);

Colas prioritarias | 71

[Link]­[Link]
Machine Translated by Google

print("Pacientes esperando ser atendidos: ")


print([Link]());

El ejemplo 5­5 genera la siguiente salida:

Código Smith: 5
Código de Jones: 4
Código de Fehrenbach: 6
Código marrón: 1
Código Ingram: 1

Paciente que está siendo tratado: Jones


Pacientes que esperan ser atendidos:
Código Smith: 5
Código de Fehrenbach: 6
Código marrón: 1
Código Ingram: 1

Paciente en tratamiento: Ingram


Pacientes que esperan ser atendidos:
Código Smith: 5
Código de Fehrenbach: 6
Código marrón: 1

Paciente en tratamiento: Brown


Pacientes que esperan ser atendidos:
Código Smith: 5
Código de Fehrenbach: 6

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 5­1 para determinar si una palabra dada es una
palíndromo.

3. Modifique el ejemplo de cola de prioridad del Ejemplo 5­5 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 5­5) 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:

a. El paciente ingresa al servicio de urgencias.

b. El médico atiende al paciente. c.

Muestra la lista de pacientes que esperan ser atendidos.

72 | Capítulo 5: Colas

[Link]­[Link]
Machine Translated by Google

CAPÍTULO 6

Listas enlazadas

En el Capítulo 3 analizamos el uso de listas para almacenar datos. El mecanismo de almacenamiento de


datos subyacente que utilizamos para las listas es la matriz. En este capítulo analizaremos un tipo diferente
de lista, la lista enlazada. Explicaremos por qué las listas enlazadas a veces se prefieren a las matrices y
desarrollaremos una implementación de lista enlazada basada en objetos. Terminaremos el capítulo con
varios ejemplos de cómo las listas enlazadas pueden resolver muchos problemas de programación que no sabrá.
encontrar.

Desventajas de las matrices


Existen varias razones por las que las matrices no siempre son la mejor estructura de datos para organizar
los datos. En muchos lenguajes de programación, las matrices tienen una longitud fija, por lo que es difícil
agregar nuevos datos cuando se llega al último elemento de la matriz. Agregar y eliminar datos de una
matriz también es difícil porque hay que mover los elementos de la matriz hacia arriba o hacia abajo para
reflejar una adición o una eliminación. Sin embargo, estos problemas no surgen con las matrices de
JavaScript, ya que podemos usar la función split() sin tener que realizar accesos adicionales a los elementos
de la matriz.

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

Definición de listas enlazadas

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 6­1 se muestra un ejemplo de una lista enlazada .

Figura 6­1. 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 6­1, 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 6­1 se rediseña en la Figura 6­2 para incluir un nodo principal.

Figura 6­2. Lista enlazada con 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 6­3 ilustra cómo se agrega “cookies” a la lista enlazada después de “eggs”.

74 | Capítulo 6: Listas enlazadas

[Link]­[Link]
Machine Translated by Google

Figura 6­3. Inserción de “cookies” en la lista enlazada

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 6­4 muestra cómo se elimina “tocino” de la lista vinculada.

Figura 6­4. Eliminación de “bacon” de la lista enlazada

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.

Un diseño de lista enlazada basado en objetos

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;
}

Un diseño de lista enlazada basado en objetos | 75

[Link]­[Link]
Machine Translated by Google

La clase de lista enlazada

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.

Aquí está la definición de la función constructora:

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.

Inserción de nuevos nodos


La primera función que examinaremos es la función de inserción , que inserta un nodo en una lista. Para insertar

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”.

76 | Capítulo 6: Listas enlazadas

[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() :

función insertar(nuevoElemento, elemento) {


var nuevoNodo = nuevo Nodo(nuevoElemento);
var actual = [Link](elemento);
[Link] =
[Link]; [Link] = nuevoNodo;
}

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:

función display() { var


currNode = [Link]; mientras
(!([Link] == null))
{ imprimir([Link]);
currNode = [Link];
}
}

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 6­1 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.

Ejemplo 6­1. La clase LList y un programa de prueba


función LList()
{ [Link] = new Node("head");
[Link] = buscar;
[Link] = insertar; //
[Link] = eliminar;
[Link] = mostrar;
}

función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];

} devuelve currNode;
}

Un diseño de lista enlazada basado en objetos | 77

[Link]­[Link]
Machine Translated by Google

función insertar(nuevoElemento, elemento) {


var nuevoNodo = nuevo Nodo(nuevoElemento);
var actual = [Link](elemento);
[Link] =
[Link]; [Link] = nuevoNodo;
}

función display() { var


currNode = [Link]; mientras (!
([Link] == null))
{ imprimir([Link]);
currNode = [Link];
}
}

// programa principal

var ciudades = new LList();


[Link]("Conway", "head");
[Link]("Russellville", "Conway");
[Link]("Alma", "Russellville");
[Link]()

El resultado del ejemplo 6­1 es:

Conway
Russellville
Alma

Eliminar nodos de una lista enlazada Para

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():

función findPrevious(item) { var


currNode = [Link]; mientras (!
([Link] == null) &&
([Link] != item)) { currNode
= [Link];

} devuelve currNode;
}

Ahora estamos listos para escribir la función remove() :

78 | Capítulo 6: Listas enlazadas

[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 6­4 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;
}

El ejemplo 6­2 proporciona un programa corto que prueba la función remove() :

Ejemplo 6­2. Prueba de la función remove()

var ciudades = new LList();


[Link]("Conway", "head");
[Link]("Russellville", "Conway");
[Link]("Carlisle", "Russellville");
[Link]("Alma", "Carlisle");
[Link]();
[Link]();
[Link]("Carlisle");
[Link]();

El resultado del Ejemplo 6­2 antes de la eliminación es:

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

Un diseño de lista enlazada basado en objetos | 79

[Link]­[Link]
Machine Translated by Google

El ejemplo 6­3 contiene una lista completa de la clase Node , la clase LList y nuestro programa de
prueba:

Ejemplo 6­3. La clase Node y la clase LList


función Nodo(elemento) {
[Link] = elemento;
[Link] = null;
}

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];
}
}

función findPrevious(item) { var


currNode = [Link]; mientras
(!([Link] == null) &&
([Link] != item)) { currNode
= [Link];

} devuelve currNode;
}

función display() { var


currNode = [Link]; mientras
(!([Link] == null))
{ imprimir([Link]);
currNode = [Link];
}
}

función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];

} devuelve currNode;
}

función insertar(nuevoElemento, elemento) {

80 | Capítulo 6: Listas enlazadas

[Link]­[Link]
Machine Translated by Google

var nuevoNodo = nuevo Nodo(nuevoElemento);


var actual = [Link](elemento);
[Link] = [Link];
[Link] = nuevoNodo;
}

var ciudades = new LList();


[Link]("Conway", "head");
[Link]("Russellville", "Conway");
[Link]("Carlisle", "Russellville");
[Link]("Alma", "Carlisle");
[Link]();
[Link]();
[Link]("Carlisle");
[Link]();

Listas doblemente enlazadas

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 6­5 ilustra cómo
funciona una lista doblemente enlazada.

Figura 6­5. 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:

función insertar(nuevoElemento, elemento) {


var nuevoNodo = nuevo Nodo(nuevoElemento);

Listas doblemente enlazadas | 81

[Link]­[Link]
Machine Translated by Google

var actual = [Link](item);


[Link] =
[Link]; [Link]
= actual; [Link] = nuevoNodo;
}

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 6­6
hace que esta situación sea más fácil de entender.

Figura 6­6. Eliminación de un nodo de una lista doblemente enlazada

Aquí está el código para la función remove() :

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:

función findLast() { var


currNode = [Link]; mientras
(!([Link] == null)) { currNode =
[Link];

} 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() :

82 | Capítulo 6: Listas enlazadas

[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 6­4 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.

Ejemplo 6­4. La clase LList como lista doblemente enlazada


función Nodo(elemento) {
[Link] = elemento;
[Link] = nulo;
[Link] = nulo;
}

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];
}
}

función findLast() { var


currNode = [Link]; mientras
(!([Link] == null)) { currNode =
[Link];

} devuelve currNode;
}

función remove(item) { var


currNode = [Link](item); si (!
([Link] == null)) {

Listas doblemente enlazadas | 83

[Link]­[Link]
Machine Translated by Google

[Link] = [Link];
[Link] = [Link];
[Link] = null;
[Link] = null;
}
}

// findPrevious ya no es necesario /*función


findPrevious(item) { var currNode =
[Link]; mientras (!
([Link] == null) &&
([Link] != item)) { currNode
= [Link];
}
devuelve currNode; }
*/

función display() { var


currNode = [Link]; mientras
(!([Link] == null))
{ imprimir([Link]);
currNode = [Link];
}
}

función buscar(elemento)
{ var currNode = [Link];
mientras ([Link] != elemento)
{ currNode = [Link];

} devuelve currNode;
}

función insertar(nuevoElemento, elemento) {


var nuevoNodo = nuevo Nodo(nuevoElemento);
var actual = [Link](elemento);
[Link] =
[Link]; [Link]
= actual; [Link] = nuevoNodo;
}

var ciudades = new LList();


[Link]("Conway", "head");
[Link]("Russellville", "Conway");
[Link]("Carlisle", "Russellville");
[Link]("Alma", "Carlisle");
[Link]();
print();
[Link]("Carlisle");
[Link]();

84 | Capítulo 6: Listas enlazadas

[Link]­[Link]
Machine Translated by Google

imprimir();
[Link]();

El resultado del ejemplo 6­4 es:

Conway
Russellville
Carlisle
Alma

Conway
Russellville
Alma

Alma
Russellville
Conway

Listas enlazadas circularmente

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 6­7.

Figura 6­7. Lista enlazada circularmente

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:

función LList() { [Link]


= new Node("head"); [Link] =
[Link]; [Link] = find;

Listas enlazadas circularmente | 85

[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:

función display() { var


currNode = [Link]; mientras
(!([Link] == null) && !
([Link] == "head"))
{ imprimir([Link]);
currNode = [Link];
}
}

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.

Otras funciones de listas enlazadas

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()

Muestra solo el nodo actual

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.

86 | Capítulo 6: Listas enlazadas

[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 6­4 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 judeo­romana.
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 clave­valor , 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.

La clase del diccionario


La base de la clase Dictionary es la clase Array en lugar de la clase Object . Más adelante en este
capítulo, vamos a querer ordenar las claves de un diccionario, y JavaScript no puede ordenar las
propiedades de un Object. Sin embargo, tenga en cuenta que todo en JavaScript es un objeto, por lo
que una matriz es un objeto.

Comenzaremos nuestra definición de la clase Diccionario con este código:

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 add(clave, valor) {


[Link][clave] = valor;
}
A continuación, definimos la función find() . Esta función toma una clave como argumento y devuelve
el valor asociado a ella. El código se ve así:

función find(clave) { devolver


[Link][clave];
}
Para eliminar un par clave­valor de un diccionario, es necesario utilizar una función integrada de
JavaScript: delete. Esta función forma parte de la clase Object y toma una referencia a una clave
como argumento. La función elimina tanto la clave como el valor asociado. Aquí se muestra la
definición de nuestra función remove() :

función remove(clave)
{ eliminar [Link][clave];
}
Finalmente, nos gustaría poder ver todos los pares clave­valor en un diccionario, así que aquí hay
una función que realiza esta tarea:

función showAll() { para


cada (var clave en [Link]([Link])) { + [Link][clave]);
" ­> "
imprimir(tecla +
}
}
La función keys() , cuando se llama con un objeto, devuelve todas las claves almacenadas en ese
objeto.

El ejemplo 7­1 proporciona la definición de la clase Diccionario hasta este punto.

Ejemplo 7­1. La clase Dictionary

función Diccionario() { [Link]


= add; [Link]
= new Array(); [Link] = find; [Link]
= remove; [Link]
= showAll;

función add(clave, valor) {


[Link][clave] = valor;
}

función find(clave) { devolver


[Link][clave];

90 | Capítulo 7: Diccionarios

[Link]­[Link]
Machine Translated by Google

función remove(clave)
{ eliminar [Link][clave];
}

función showAll() { para


cada (var clave en [Link]([Link])) { + [Link][clave]);
" ­> "
imprimir(tecla +
}
}

En el Ejemplo 7­2 se muestra un programa que utiliza la clase Diccionario .

Ejemplo 7­2. Uso de la clase Dictionary

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]();

El resultado de este programa es:

Extensión de David : 345


Mike ­> 123
Cintia ­> 456

Funciones auxiliares para la clase de diccionario


Podemos definir varias funciones que pueden ayudar en situaciones especiales. Por ejemplo, es útil
saber cuántas entradas hay en un diccionario. A continuación, se muestra una definición de la función
count() :

función count() { var n =


0; para cada
(var clave en [Link]([Link])) {
++n;

} 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:

var nums() = new Array(); nums[0]


= 1; nums[1] =

print([Link]); // muestra 2

Funciones auxiliares para la clase Diccionario | 91

[Link]­[Link]
Machine Translated by Google

var pbook = new Array();


pbook["David"] = 1;
pbook["Jennifer"] = 2;
print([Link]); // muestra 0

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];
}
}

El ejemplo 7­3 actualiza la definición completa de la clase Diccionario .

Ejemplo 7­3. Definición de la clase Diccionario actualizada


función Diccionario() { [Link]
= add; [Link]
= new Array(); [Link] = find; [Link]
= remove; [Link]
= showAll; [Link] = count;
[Link] = clear;

función add(clave, valor) {


[Link][clave] = valor;
}

función find(clave) { devolver


[Link][clave];
}

función remove(clave)
{ eliminar [Link][clave];
}

función showAll() { para


cada (var clave en [Link]([Link])) { + [Link][clave]);
" ­> "
imprimir(tecla +
}
}

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

para cada (var clave en [Link]([Link])) {


eliminar [Link][clave];
}
}

El ejemplo 7­4 ilustra cómo funcionan estas nuevas funciones auxiliares.

Ejemplo 7­4. Uso de las funciones count() y clear()

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]());

El resultado de este código es:

Número de entradas: 3
Extensión de David : 345
Raymond ­> 123
David ­> 345
Cintia ­> 456
Número de entradas: 0

Añadiendo ordenación a la clase de diccionario


El objetivo principal de un diccionario es recuperar un valor haciendo referencia a su clave. El orden real en el que
se almacenan los elementos del diccionario no es una preocupación principal. Sin embargo, a muchas personas
les gusta ver una lista de un diccionario en orden ordenado. Veamos qué se necesita para mostrar los elementos
de nuestro diccionario en orden ordenado.

Las matrices se pueden ordenar. Por ejemplo:

var a = new Array(); a[0]


= "Mike"; a[1] =
"David"; print(a); //
muestra Mike,David [Link](); print(a); //
muestra
David,Mike

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

Adición de la ordenación a la clase de diccionario | 93

[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 7­5 demuestra cómo se utiliza esta nueva definición de función para mostrar una lista
ordenada de nombres y números.

Ejemplo 7­5. Visualización de un diccionario ordenado

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]();

Aquí está el resultado del programa:

Cintia ­> 456


Danny ­> 012
David ­> 345
Jennifer ­> 987
Jonatán ­> 666
Mike ­> 723
Raymond ­> 123

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

3. Reescribe el ejercicio 2 para que muestre las palabras en orden.

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.

Una descripción general del hash

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
8­1 ilustra el concepto de hash utilizando el ejemplo de una pequeña guía telefónica.

Figura 8­1. Hashing de nombres y números de teléfono

Una clase de tabla hash

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.

Aquí está la función constructora para nuestra clase HashTable :

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 La

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:

función simpleHash(datos) { var total


= 0; para (var i =
0; i < [Link]; ++i) { total += [Link](i);

} devuelve el total % [Link];


}

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;
}

función put(datos) { var pos


= [Link](datos); [Link][pos] =
datos;
}

función simpleHash(datos) { var total


= 0; para (var i =
0; i < [Link]; ++i) { total += [Link](i);

} devuelve el total % [Link];


}

función showDistro() { var n =


0; para (var i
= 0; i < [Link]; ++i) { si ([Link][i] != indefinido)
{ imprimir(i + ": "
+ [Link][i]);
}
}
}

Una clase de tabla hash | 99

[Link]­[Link]
Machine Translated by Google

El ejemplo 8­1 demuestra cómo funciona la función simpleHash() .

Ejemplo 8­1. Hashing utilizando una función hash simple

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]();

Aquí está el resultado del Ejemplo 8­1:

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() :

función simpleHash(datos) { var total =


0; para (var i = 0; i <
[Link]; ++i) { total += [Link](i);

}
" "
print(" Valor hash : " + datos + retorno total ­> + totales);
% [Link];
}

Cuando ejecutamos nuevamente el programa, vemos el siguiente resultado:

Valor hash: David ­> 488


Valor hash: Jennifer ­> 817
Valor hash: Donnie ­> 605
Valor hash: Raymond ­> 730

100 | Capítulo 8: Hashing

[Link]­[Link]
Machine Translated by Google

Valor hash: Cynthia ­> 720


Valor hash: Mike ­> 390
Valor hash: Clayton ­> 730
Valor hash: Danny ­> 506
Valor hash: Jonathan ­> 819
35: Cintia
45: Clayton
57: Donnie
77: David
95: Danny
116: Mike
132: Jennifer
134: Jonatán

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.

Una función hash mejor


Para evitar colisiones, primero debe asegurarse de que la matriz que está utilizando para la tabla hash
tenga un tamaño de número primo. Esto es necesario debido al uso de aritmética modular para calcular
la clave. El tamaño de la matriz debe ser mayor que 100 para distribuir de manera más uniforme las
claves en la tabla. A través de la experimentación, descubrimos que el primer número primo mayor
que 100 que no causó colisiones para el conjunto de datos utilizado en el Ejemplo 8­1 es 137. Cuando
se utilizaron números primos más pequeños cercanos a 100, aún hubo colisiones en el conjunto de
datos.

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:

función betterHash(cadena, arr) {


constante H =
37; var total = 0;
para (var i = 0; i < [Link]; ++i) {
total += H * total + [Link](i);

} total = total % [Link]; devolver


parseInt(total);
}

Una clase sobre tablas hash | 101

[Link]­[Link]
Machine Translated by Google

El ejemplo 8­2 contiene la definición actual de la clase HashTable .

Ejemplo 8­2. La clase HashTable con la función betterHash()


función HashTable() { [Link]
= new Array(137); [Link] =
simpleHash; [Link] = betterHash;
[Link] = showDistro; [Link] =
put; //[Link] = get;

función put(datos) { var pos =


[Link](datos); [Link][pos] = datos;

función simpleHash(datos) { var total =


0; para (var i = 0; i <
[Link]; ++i) { total += [Link](i);

}
" "
print(" Valor hash : " + datos + retorno total ­> + totales);
% [Link];
}

función showDistro() { var n = 0;


para (var i = 0;
i < [Link]; ++i) { si ([Link][i] != indefinido) { imprimir(i +
": "
+ [Link][i]);
}
}
}

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().

El programa del Ejemplo 8­3 prueba nuestra nueva función hash.

102 | Capítulo 8: Hashing

[Link]­[Link]
Machine Translated by Google

Ejemplo 8­3. Prueba de la función betterHash()

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]();

El resultado de ejecutar este programa es:

17: Cintia
25: Donnie
30: Mike
33: Jennifer
37: Jonatán
57: Clayton
65: David
66: Danny
99: Raymond

Los nueve nombres ya están presentes y contabilizados.

Hashing de claves enteras En la

ú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):

función getRandomInt (min, max) {


devuelve [Link]([Link]() * (max ­ min + 1)) + min;
}

función genStuData(arr) { para


(var i = 0; i < [Link]; ++i) { var num = ""; para
(var j = 1; j <= 9;
++j) { num += [Link]([Link]()
* 10);

} número += getRandomInt(50, 100);


arreglo[i] = número;
}
}

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.

Una clase de tabla hash | 103

[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 8­4 presenta un programa que utiliza las funciones anteriores para almacenar un conjunto de
estudiantes y sus calificaciones.

Ejemplo 8­4. Hashing de claves enteras

función getRandomInt (min, max) {


devuelve [Link]([Link]() * (max ­ min + 1)) + min;
}

función genStuData(arr) { para


(var i = 0; i < [Link]; ++i) { var num = ""; para
(var j = 1; j <= 9;
++j) { num += [Link]([Link]()
* 10);

} 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));

} print("\n\nDistribución de datos: \n"); var


hTable = new HashTable(); for (var i
= 0; i < [Link]; ++i) { [Link](students[i]);

} [Link]();

El resultado del ejemplo 8­4 es:

Datos del estudiante:

24553918 70
08930891 70
41819658 84
04591864 82

104 | Capítulo 8: Hashing

[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:

Datos del estudiante:

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

Una clase de tabla hash | 105

[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.

Almacenamiento y recuperación de datos en una tabla hash

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() :

función put(clave, datos) { var


pos = [Link](clave);
[Link][pos] = datos;
}
La función put() convierte la clave en hash y luego almacena los datos en la posición de la tabla
calculada por la función hash.

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.

106 | Capítulo 8: Hashing

[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 8­2 ilustra cómo funciona el encadenamiento separado.

Figura 8­2. Encadenamiento independiente

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():

Manejo de colisiones | 107

[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 .

En el Ejemplo 8­5 se muestra un programa para probar el encadenamiento separado .

Ejemplo 8­5. Uso de encadenamiento independiente para evitar colisiones

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:

función showDistro() { var n =


0; para (var i
= 0; i < [Link]; ++i) { si ([Link][i][0] != indefinido)
{ imprimir(i + ": " + [Link][i]);

}
}
}

Cuando ejecutamos el programa del Ejemplo 8­5, obtenemos el siguiente resultado:

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() :

función put(clave, datos) { var pos


= [Link](clave);

108 | Capítulo 8: Hashing

[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:

función get(clave) { var


índice = 0; var
hash = [Link](clave); if
([Link][pos][índice] = clave) { return
[Link][pos][índice+1];

} índice += 2;
de lo
contrario { mientras ([Link][pos][índice] != clave) {
índice += 2;

} devuelve [Link][pos][índice+1];

} devuelve indefinido;
}

Sondeo lineal Una

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

Manejo de colisiones | 109

[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 .

Agregue el siguiente código al constructor HashTable :

[Link] = [];

Ahora podemos definir el método put() para el sondeo lineal:

función put(clave, datos) { var pos


= [Link](clave); if ([Link][pos]
== undefined) { [Link][pos] = clave;
[Link][pos] = datos;

} 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;
}

110 | Capítulo 8: Hashing

[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.

2. Repita el ejercicio 1 utilizando cadenas separadas.

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.

Definiciones, operaciones y propiedades de conjuntos fundamentales


Un conjunto es una colección desordenada de miembros relacionados en la que ningún miembro aparece más

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.

• Dos conjuntos se consideran iguales si contienen exactamente los mismos miembros.


• Un conjunto se considera un subconjunto de otro conjunto si todos los miembros del primer conjunto son
contenido en el segundo conjunto.

113

[Link]­[Link]
Machine Translated by Google

Operaciones de conjuntos

Las operaciones fundamentales que se realizan sobre los conjuntos son:

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.

La implementación de la clase Set


La implementación de la clase Set se basa en una matriz para almacenar los datos. También creamos funciones para
cada una de las operaciones de conjunto descritas anteriormente. Aquí se muestra la definición de la función constructora:

función Set()
{ [Link] = []; [Link]
= añadir; [Link]
= eliminar; [Link] = tamaño;
[Link] = unión;
[Link] = intersectar;
[Link] = subconjunto;
[Link] = diferencia;
[Link] = mostrar;

Veamos primero la función add() :

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.

114 | Capítulo 9: Conjuntos

[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():

función remove(datos) { var pos


= [Link](datos); if (pos > ­1)

{ [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];
}

El ejemplo 9­1 demuestra cómo funciona la clase Set hasta ahora.

Ejemplo 9­1. Uso de la clase Set


load("[Link]"); var
names = new Set();
[Link]("David");
[Link]( "Jennifer") ;
[Link]("Cynthia");
[Link]("Mike");
[Link]("Raymond"); if
([Link]("Mike")) { print("Mike
agregó")

} 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.");

La implementación de la clase Set | 115

[Link]­[Link]
Machine Translated by Google

} [Link]("Clayton");
print([Link]());
eliminado = "Alisa"; if
([Link]("Mike")) { print(eliminado
+ " eliminado.");

} else
"
{ imprimir(eliminado + no eliminado.");
}

El resultado del ejemplo 9­1 es:


No se puede agregar a Mike, ya debe estar en el conjunto
David, Jennifer, Cynthia, Mike, Raymond Mike
eliminado.
David, Jennifer, Cynthia, Raymond, Clayton Alisa no
eliminado.

Más operaciones de conjuntos

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;
}
}

Ahora podemos definir la función union() :


función unión(conjunto) { var
tempSet = new Set(); para (var i =
0; i < [Link]; ++i) {
[Link]([Link][i]);

} para (var i = 0; i < [Link]; ++i) { si (!


[Link]([Link][i])) {
[Link]([Link][i]);

116 | Capítulo 9: Conjuntos

[Link]­[Link]
Machine Translated by Google

}
}

devolver conjunto de temperaturas;

El ejemplo 9­2 demuestra el uso de union():

Ejemplo 9­2. Cálculo de la unión de dos conjuntos


cargar("[Link]");
var cis = new Set();
[Link]("Mike");
[Link]("Clayton") ;
[Link]("Jennifer");
[Link]("Raymond");
var dmp = new Set();
[Link]("Raymond");
[Link]("Cynthia");
[Link]("Jonathan");
var it = new Set(); it =
[Link](dmp);
print([Link]()); //
muestra Mike,Clayton,Jennifer,Raymond,Cynthia,Jonathan

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]);
}
}

devolver conjunto de temperaturas;

El cálculo de la intersección de dos conjuntos se muestra en el Ejemplo 9­3.

Ejemplo 9­3. Cálculo de la intersección de dos conjuntos


cargar("[Link]");
var cis = new Set();
[Link]("Mike");
[Link]("Clayton") ;
[Link]("Jennifer");
[Link]("Raymond");
var dmp = new Set();
[Link]("Raymond");
[Link]("Cynthia");
[Link]("Bryan");

Más operaciones de conjuntos | 117

[Link]­[Link]
Machine Translated by Google

var inter = [Link](dmp);


print([Link]()); // muestra Raymond

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í.

El ejemplo 9­4 calcula el subconjunto de dos conjuntos.

Ejemplo 9­4. Cálculo del subconjunto de dos conjuntos

cargar("[Link]");
var it = new Set();
[Link]("Cynthia");
[Link]("Clayton") ;
[Link]("Jennifer");
[Link]("Danny");
[Link]("Jonathan");
[Link]("Terrill");
[Link]("Raymond");
[Link]("Mike");

118 | Capítulo 9: Conjuntos

[Link]­[Link]
Machine Translated by Google

var dmp = new Set();


[Link]("Cynthia");
[Link]("Raymond");
[Link]("Jonathan"); if
([Link](it)) { print("DMP
es un subconjunto de IT.");

} else
{ print("DMP no es un subconjunto de TI.");
}

El ejemplo 9­4 muestra el siguiente resultado:

DMP es un subconjunto de TI.

Si agregamos un nuevo miembro al conjunto dmp :

[Link]("Shirley");

Luego el programa muestra:

DMP no es un subconjunto de TI.

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:

función diferencia(conjunto) { var


tempSet = new Set(); para (var i =
0; i < [Link]; ++i) { si (![Link]([Link][i]))
{ [Link]([Link][i]);

}
}

devolver conjunto de temperaturas;

El ejemplo 9­5 calcula la diferencia de dos conjuntos.

Ejemplo 9­5. Cálculo de la diferencia de dos conjuntos

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]() + "]");

Más operaciones de conjuntos | 119

[Link]­[Link]
Machine Translated by Google

El ejemplo 9­5 muestra:

[Clayton,Jennifer,Danny] diferencia [Bryan,Clayton,Jennifer] ­>


[Danny]

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.

120 | Capítulo 9: Conjuntos

[Link]­[Link]
Machine Translated by Google

CAPÍTULO 10

Árboles binarios y árboles binarios de búsqueda

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 10­1).

El propósito de un organigrama es comunicar la estructura de una organización. En la Figura 10­1, cada


cuadro es un nodo y las líneas que conectan los cuadros son los bordes. Los nodos representan los
puestos que conforman una organización y los bordes representan las relaciones entre esos puestos. Por
ejemplo, el CIO reporta directamente al CEO, por lo que hay un borde entre esos dos nodos. El gerente de
desarrollo reporta al CIO, por lo que hay un borde que conecta esos dos puestos. El vicepresidente de
ventas y el gerente de desarrollo no tienen un borde directo que los conecte, por lo que no hay una relación
directa entre esos dos puestos.

121

[Link]­[Link]
Machine Translated by Google

Figura 10­1. Un organigrama es una estructura de árbol.

La Figura 10­2 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 10­2, 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.

122 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[Link]­[Link]
Machine Translated by Google

Figura 10­2. Las partes de un árbol

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 .

Árboles binarios y árboles binarios de búsqueda


Como se mencionó anteriormente, un árbol binario es aquel en el que cada nodo no puede tener más de
dos hijos. Al limitar el número de hijos a dos, podemos escribir programas eficientes para insertar datos,
buscar datos y eliminar datos en un árbol.

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

Árboles binarios y árboles binarios de búsqueda | 123

[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 10­3 se muestra un ejemplo de árbol binario.

Figura 10­3. Un á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.

Creación de una implementación de árbol de búsqueda binaria Un árbol de

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 Nodo(datos, izquierda, derecha) {


[Link] = datos;
[Link] =
izquierda; [Link] =
derecha; [Link] = mostrar;
}

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.

124 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[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:

1. Establezca el nodo raíz como el nodo actual.

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 10­1 tiene el código para la clase, incluido el código para el objeto Node .

Ejemplo 10­1. Las clases BST y Node


función Nodo(datos, izquierda, derecha) {
[Link] = datos;
[Link] =
izquierda; [Link] =
derecha; [Link] = mostrar;
}

función mostrar()
{ devolver [Link];
}

función BST() {

Árboles binarios y árboles binarios de búsqueda | 125

[Link]­[Link]
Machine Translated by Google

[Link] = null;
[Link] = insertar;
[Link] = inOrder;
}

función insertar(datos) { var n =


nuevo Nodo(datos, null, null); si ([Link] ==
null) { [Link] = n;

} 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;

}
}
}
}
}

Recorriendo un árbol de búsqueda binaria

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.

126 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[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.

Aquí está el código para la función de recorrido en orden:

función inOrder(nodo) { if (!
(nodo == null))
{ inOrder([Link]);
putstr([Link]() + " ");
inOrder([Link]);
}
}

El ejemplo 10­2 proporciona un programa corto para probar la función.

Ejemplo 10­2. Recorrido en orden de una BST

var nums = new BST();


[Link](23);
[Link](45) ;
[Link](16);
[Link](37);
[Link](3);
[Link](99);
[Link](22);
print("Recorrido en orden: ");
inOrder([Link]);

El resultado del ejemplo 10­2 es:

Recorrido en orden:
3 16 22 23 37 45 99

La figura 10­4 ilustra la ruta que siguió la función inOrder() .

Figura 10­4. Trayectoria de recorrido en orden

Árboles binarios y árboles binarios de búsqueda | 127

[Link]­[Link]
Machine Translated by Google

La definición de la función de recorrido de preorden es:

función preOrder(nodo) { si (!(nodo


== null)) {
putstr([Link]() + " ");
preOrden([Link]);
preOrden([Link]);
}
}

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() .

La figura 10­5 ilustra la ruta de recorrido de preorden.

Figura 10­5. Trayectoria de recorrido en preorden

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

La ruta de un recorrido postorder se muestra en la Figura 10­6.

128 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[Link]­[Link]
Machine Translated by Google

Figura 10­6. Camino del recorrido posterior al orden

Aquí está la implementación de la función postOrder() :

función postOrder(nodo) { si (!(nodo


== null)) {
postOrden([Link]);
postOrden([Link]);
putstr([Link]() + " ");
}
}

Y aquí está el resultado cuando agregamos la función a nuestro programa:

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

Hay tres tipos de búsquedas que normalmente se realizan con un BST:

1. Búsqueda de un valor específico 2.

Búsqueda del valor mínimo 3. Búsqueda del

valor máximo

Exploramos estas tres búsquedas en las siguientes secciones.

Búsquedas BST | 129

[Link]­[Link]
Machine Translated by Google

Búsqueda del valor mínimo y máximo Las búsquedas en una BST de

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:

función getMin() { var


actual = [Link]; mientras (!
([Link] == null)) { actual =
[Link];

} devuelve datos actuales;


}

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.

La definición de la función getMax() se muestra a continuación:

función getMax() { var


actual = [Link]; mientras (!
([Link] == null)) { actual =
[Link];

} devuelve datos actuales;


}

El ejemplo 10­3 prueba las funciones getMin() y getMax() con los datos BST que usamos anteriormente.

Ejemplo 10­3. Prueba de getMin() y getMax()


var nums = new BST();
[Link](23);
[Link](45) ;
[Link] (16);
[Link](37) ;
[Link](3);
[Link](99);
[Link](22);
var min = [Link]();

130 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[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);

El resultado de este programa es:


El valor mínimo del BST es: 3
El valor máximo del BST es: 99

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 La

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í:

función buscar(datos) { var


actual = [Link]; mientras
([Link] != datos) { si (datos <
[Link]) { actual = [Link];

} else
{ actual = [Link];

} si (actual == nulo) { devolver


nulo;
}

} 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.

El ejemplo 10­4 proporciona un programa para probar la función find() .

Ejemplo 10­4. Uso de find() para buscar un valor


cargar("BST");
var nums = new BST();
[Link](23);
[Link](45);
[Link](16);

Búsquedas BST | 131

[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.");
}

El resultado de este programa es:

3 16 22 23 37 45 99

Introduzca un valor a buscar : 23 Se encontraron


23 en el BST.

Eliminación de nodos de una BST


La operación más compleja en una BST es eliminar un nodo. La complejidad de la eliminación de un nodo
depende del nodo que desee eliminar. Si desea eliminar un nodo sin hijos, la eliminación es bastante
simple. Si el nodo tiene solo un nodo hijo, ya sea izquierdo o derecho, la eliminación es un poco más
compleja de realizar. La eliminación de un nodo con dos hijos es la operación de eliminación más compleja
de realizar.

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.

132 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[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 10­7 ilustra este escenario.

Figura 10­7. Eliminación de un nodo con dos hijos

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í:

función remove(datos) { raíz


= removeNode([Link], datos);
}

función removeNode(nodo, datos) { si


(nodo == null) { devolver
null;

} si (datos == [Link]) {
// el nodo no tiene hijos si
([Link] == null && [Link] == null) {

Eliminación de nodos de una BST | 133

[Link]­[Link]
Machine Translated by Google

devuelve nulo;

} // el nodo no tiene ningún hijo


izquierdo si ([Link] ==
null) { return [Link];

} // el nodo no tiene hijo derecho si


([Link] == null) { return
[Link];

} // El nodo tiene dos hijos var


tempNode = getSmallest([Link]); [Link] =
[Link]; [Link] =
removeNode([Link], [Link]); return node;

} de lo contrario si (datos < [Link])


{ [Link] = removeNode([Link], datos);
devolver nodo;

} 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:

función Nodo(datos, izquierda, derecha)


{ [Link] = datos;
[Link] = 1; [Link]
= izquierda; [Link]
= derecha; [Link] =
mostrar;
}

134 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[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():

función actualizar(datos) { var


calificación = [Link](datos);
calificació[Link]+
+; devolver calificación;
}

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"); ' ');

}
}
}

función genArray(longitud) { var


arr = []; para
(var i = 0; i < longitud; ++i) { arr[i] =
[Link]([Link]() * 101);

} devolver arr;
}

El ejemplo 10­5 presenta un programa para probar este nuevo código para contar ocurrencias de calificaciones.

Ejemplo 10­5. Conteo de ocurrencias de calificaciones en un conjunto de datos

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"); ' ');

}
}
}

función genArray(longitud) { var


arr = []; para
(var i = 0; i < longitud; ++i) { arr[i] =
[Link]([Link]() * 101);
}

Contando ocurrencias | 135

[Link]­[Link]
Machine Translated by Google

devolver arr;
}

load("BST"); // asegúrese de agregar +update()+ a la definición de BST

var calificaciones = genArray(100);


prArray(calificaciones);
var gradedistro = new BST(); para (var i =
0; i < [Link]; ++i) { var g = calificaciones[i]; var
calificación =
[Link](g); si (calificación == null)
{ [Link](g);

} de lo
contrario { [Link](g);
}

} var cont = "y";

mientras (cont == "y")


{ putstr("\n\nIngrese una calificación: "); var
g = parseInt(readline()); var aGrade =
[Link](g); si (aGrade == null) { print("No
hay ocurrencias de " + g);

} else
{ print("Ocurrencias de " + g + ": " + [Link]);

} putstr("¿Mira otra calificación (s/n)? "); cont = readline();

Aquí está el resultado de una ejecución del programa:

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

Ingresa una calificación:


78 No hay ocurrencias de 78
¿Miras otra calificación (s/n)? y

Introduzca una calificación: 65

136 | Capítulo 10: Árboles binarios y árboles binarios de búsqueda

[Link]­[Link]
Machine Translated by Google

No hay ocurrencias de 65
¿Mirar otra calificación (s/n)? y

Ingresa una calificación:


23 Ocurrencias de 23: 2
¿Miras otra calificación (s/n)? y

Ingresar una calificación:


89 No hay ocurrencias de 89
¿Mirar otra calificación (s/n)? y

Introduzca una calificación: 100


Ocurrencias de 100: 4
Mira otra nota (t/n)? n

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.

Conteo de ocurrencias | 137

[Link]­[Link]
Machine Translated by Google

[Link]­[Link]
Machine Translated by Google

CAPÍTULO 11

Gráficos y algoritmos gráficos

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

Un gráfico consta de un conjunto de vértices y un conjunto de aristas. Piense en un mapa de un


estado de EE. UU. Cada ciudad está conectada con otras ciudades a través de algún tipo de
carretera. Un mapa es un tipo de gráfico en el que cada ciudad es un vértice y una carretera que
conecta dos ciudades es una arista. Las aristas se definen como un par (v1, v2), donde v1 y v2 son
dos vértices en un gráfico. Un vértice también puede tener un peso, que a veces se denomina costo.
Un gráfico cuyos pares están ordenados se denomina gráfico dirigido o simplemente dígrafo. Cuando
los pares están ordenados en un gráfico dirigido, se dibuja una flecha de un par a otro par. Los
gráficos dirigidos indican la dirección del flujo de vértice a vértice. Un diagrama de flujo que indica la
dirección de los cálculos en un programa informático es un ejemplo de gráfico dirigido. Un gráfico
dirigido se muestra en la Figura 11­1.

139

[Link]­[Link]
Machine Translated by Google

Figura 11­1. Un dígrafo (grafo dirigido)

Si un gráfico no está ordenado, se denomina gráfico desordenado o, simplemente, gráfico. En la figura 11­2 se muestra un
ejemplo de gráfico desordenado.

Figura 11­2. Un 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.

140 | Capítulo 11: Gráficos y algoritmos gráficos

[Link]­[Link]
Machine Translated by Google

Sistemas del mundo real modelados mediante gráficos

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.

Sistemas del mundo real modelados mediante gráficos | 141

[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 11­3.

Figura 11­3. Una lista de adyacencia

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.

142 | Capítulo 11: Gráficos y algoritmos gráficos

[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.

La función addEdge() se define como:

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 11­1 muestra la definición completa de la clase Graph .

La clase de grafos | 143

[Link]­[Link]
Machine Translated by Google

Ejemplo 11­1. 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;
}

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]();

El resultado de este programa es:

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

144 | Capítulo 11: Gráficos y algoritmos gráficos

[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 11­4 ilustra cómo funciona la búsqueda en profundidad.

Figura 11­4. 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.

Búsqueda de un gráfico | 145

[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;
}

Ahora podemos escribir la función de búsqueda en profundidad:

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 11­2 se muestra un programa que demuestra la función depthFirst() , junto con la definición
completa de la clase Graph .

Ejemplo 11­2. Realizar una búsqueda en profundidad

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]++;

146 | Capítulo 11: Gráficos y algoritmos gráficos

[Link]­[Link]
Machine Translated by Google

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();
}
}

función dfs(v)
{ [Link][v] = true; if
([Link][v] != undefined) { print("Vértice
"
visitado: + v);

} para cada (var w en [Link][v]) { si (!


[Link][w]) { [Link](w);

}
}
}

// programa para probar la función dfs()

cargar("[Link]"); g
= nuevo Graph(5);
[Link](0,1);
[Link](0,2);
[Link](1,3);
[Link](2,4);
[Link]();
[Link](0);

El resultado de este programa es:


0 ­> 1 2
1 ­> 0 3
2 ­> 0 4
3 ­> 1 4
­> 2
Vértice visitado: 0
Vértice visitado: 1
Vértice visitado: 3
Vértice visitado: 2
Vértice visitado: 4

Búsqueda de un gráfico | 147

[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 11­5 demuestra cómo funciona la búsqueda en amplitud.

Figura 11­5. 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.

2. Tome el siguiente vértice, v, del gráfico y agréguelo a la lista de vértices visitados.

3. Agregue todos los vértices no marcados que sean adyacentes a v y agréguelos a la cola.

Aquí está la definición de la función de búsqueda en amplitud:

función bfs(s) { var


cola = [];
[Link][s] = true;
[Link](s); // agregar al final de la cola while
([Link] > 0) {
var v = [Link](); // eliminar del frente de la cola if (v ==
undefined) {
"
print("Vértice visitado: + v);

} para cada (var w en [Link][v]) { si (!


[Link][w]) {

148 | Capítulo 11: Gráficos y algoritmos gráficos

[Link]­[Link]
Machine Translated by Google

[Link][w] = v;
[Link][w] = true;
[Link](w);
}
}
}
}

En el Ejemplo 11­3 se muestra un programa de prueba para la función de búsqueda en amplitud .

Ejemplo 11­3. Realizar una búsqueda en amplitud

cargar("[Link]"); g
= nuevo Graph(5);
[Link](0,1);
[Link](0,2);
[Link](1,3);
[Link](2,4);
[Link]();
[Link](0);

El resultado de este programa es:

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

Encontrar el camino más corto


Una de las operaciones más comunes que se realizan en los grafos es encontrar el camino más corto de
un vértice a otro. Considere el siguiente ejemplo: para las vacaciones, va a viajar a 10 ciudades de las
grandes ligas para ver partidos de béisbol durante un período de dos semanas. Quiere minimizar la
cantidad de millas que debe conducir para visitar las 10 ciudades utilizando un algoritmo de ruta más
corta. Otro problema de ruta más corta implica crear una red de computadoras, donde el costo podría ser
el tiempo para transmitir datos entre dos computadoras o el costo de establecer y mantener la conexión.
Un algoritmo de ruta más corta puede determinar la forma más efectiva de construir la red.

La búsqueda en amplitud conduce a rutas más cortas

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

Encontrar el camino más corto | 149

[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.

Determinación de rutas Para

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 :

// agrega esto a la clase Graph


[Link] = [];

// Función bfs función


bfs(s) { var queue = [];
[Link][s] =
true; [Link](s); // agregar
al final de la cola while ([Link] > 0) {

var v = [Link](); // eliminar del frente de la cola if (v == undefined) {

"
print("Vértice visitado: + v);

} para cada (var w en [Link][v]) { si (!


[Link][w])
{ [Link][w] = v;
[Link][w] = verdadero;
[Link](w);
}
}
}
}

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:

función pathTo(v) { var


source = 0; si (!
[Link](v)) { devolver
indefinido;

} var ruta = [];

150 | Capítulo 11: Gráficos y algoritmos gráficos

[Link]­[Link]
Machine Translated by Google

para (var i = v; i != fuente; i = [Link][i]) { [Link](i);

} [Link](s);
devolver ruta;
}

función tieneRutaA(v) {
devuelve [Link][v];
}

Asegúrese de agregar las declaraciones apropiadas a la función Graph() :

[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 11­4 muestra un programa que
crea un gráfico y muestra la ruta más corta para un vértice específico.

Ejemplo 11­4. Hallar el camino más corto para un vértice

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]());
}
}

El resultado de este programa es:

0­2­4

cual es el camino más corto desde el vértice de origen 0 al vértice 4.

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 11­6 muestra un modelo de grafo dirigido de un plan de estudios típico de
informática.

Ordenamiento topológico | 151

[Link]­[Link]
Machine Translated by Google

Figura 11­6. Modelo de gráfico dirigido de un programa de informática

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.

Un algoritmo para la ordenación topológica El algoritmo

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.

Implementación del algoritmo de ordenamiento topológico El algoritmo de

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

152 | Capítulo 11: Gráficos y algoritmos gráficos

[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.

El ejemplo 11­5 muestra el código para las dos funciones.

Ejemplo 11­5. topSort() y topSortHelper()


función topSort() { var
pila = []; var
visitado = []; para
(var i = 0; i < [Link]; i++) {
visitado[i] = falso;

} para (var i = 0; i < [Link]; i++) { si (visitado[i]


== falso) { [Link](i,
visitado, pila);
}

} para (var i = 0; i < [Link]; i++) { si (pila[i] !=


indefinido && pila[i] != falso) { imprimir([Link][pila[i]]);

}
}
}

función topSortHelper(v, visitado, pila) { visitado[v] =


verdadero; para cada
(var w en [Link][v]) { si (!visitado[w])

{ [Link](visitado[w], visitado, 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 11­6 muestra el código.

Ejemplo 11­6. La clase Graph


función Graph(v) {
[Link] = v;
[Link] = [];
[Link] = 0;

Ordenamiento topológico | 153

[Link]­[Link]
Machine Translated by Google

[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;

} [Link] = bfs;
[Link] = [];
[Link] = hasPathTo;
[Link] = pathTo;
[Link] = topSortHelper;
[Link] = topSort;

función topSort() { var


pila = []; var
visitado = []; para
(var i = 0; i < [Link]; i++) {
visitado[i] = falso;

} para (var i = 0; i < [Link]; i++) { si (visitado[i]


== falso) { [Link](i,
visitado, pila);
}

} para (var i = 0; i < [Link]; i++) { si (pila[i] !=


indefinido && pila[i] != falso) { imprimir([Link][pila[i]]);

}
}
}

función topSortHelper(v, visitado, pila) { visitado[v] =


verdadero; para cada
(var w en [Link][v]) { si (!visitado[w]) {

[Link](visitado[w], visitado, pila);


}

} [Link](v);
}

función addEdge(v,w)
{ [Link][v].push(w);
[Link][w].push(v);

154 | Capítulo 11: Gráficos y algoritmos gráficos

[Link]­[Link]
Machine Translated by Google

[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();

} }*/

// una nueva función para mostrar nombres simbólicos en lugar de números


function showGraph() { var
visits = []; for (var i =
0; i < [Link]; ++i) { putstr([Link][i] +
"
­> "); [Link]([Link][i]); for (var j
= 0; j < [Link]; ++j) { if ([Link][i][j] !
= undefined) { if ([Link]([Link][j])
< 0) { putstr([Link][j] + ' ');

}
}

} print();
[Link]();
}
}

función dfs(v)
{ [Link][v] = true; if
([Link][v] != undefined) { print("Vértice
"
visitado: + v);

} para cada (var w en [Link][v]) { si (!


[Link][w]) { [Link](w);

}
}
}

función bfs(s) { var


cola = [];
[Link][s] = true;
[Link](s);
mientras ([Link] > 0) { var
v = [Link](); si (typeof(v) !
= "string") { print("Vértice visitado:
"
+ v);

Ordenamiento topológico | 155

[Link]­[Link]
Machine Translated by Google

} para cada (var w en [Link][v]) { si (!


[Link][w])
{ [Link][w] = v;
[Link][w] = verdadero;
[Link](w);
}
}
}
}

función tieneRutaA(v) {
devuelve [Link][v];
}

función pathTo(v) { var


source = 0; si (!
[Link](v)) { devolver
indefinido;

} var ruta = []; para


(var i = v; i != fuente; i = [Link][i]) { [Link](i);

} [Link](s);
devolver ruta;
}

En el Ejemplo 11­7 se muestra un programa que prueba nuestra implementación de ordenamiento


topológico .

Ejemplo 11­7. Ordenamiento topológico


load("[Link]"); g =
new Graph(6);
[Link](1,2);
[Link](2,5);
[Link](1,3);
[Link](1,4);
[Link](0,1);
[Link] = ["CS1", "CS2", "Estructuras de datos", "Lenguaje
ensamblador", "Sistemas operativos", "Algoritmos"];

[Link]();
[Link]();

El resultado de este programa es:

CS1
CS2
Estructuras de datos
Lenguaje ensamblador

156 | Capítulo 11: Gráficos y algoritmos gráficos

[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.

2. Escriba un programa que almacene un gráfico en un archivo.

3. Escriba un programa que lea un gráfico de un archivo.

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.

Un banco de pruebas de matriz

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.

El ejemplo 12­1 muestra el código para esta clase.

159

[Link]­[Link]
Machine Translated by Google

Ejemplo 12­1. Clase de banco de pruebas de matriz

función CArray(numElements)
{ [Link] = [];
[Link] = 0;
[Link] = numElements;
[Link] = insertar;
[Link] = toString; [Link]
= clear; [Link] =
setData; [Link] = intercambiar;

para (var i = 0; i < numElements; ++i) { [Link][i]


= i;
}
}

función setData() { para (var


i = 0; i < [Link]; ++i) {
[Link][i] = [Link]([Link]() *
([Link]+1));
}
}

función clear() {
para (var i = 0; i < [Link]; ++i) { [Link][i] = 0;

}
}

función insertar(elemento)
{ [Link][[Link]++] = elemento;
}

función toString() { var retstr


= ""; para (var i = 0; i
< [Link]; ++i) { retstr += [Link][i] + " "; si (i > 0
&& i % 10 == 0) { retstr += "\n";

} devolver retstr;
}

función swap(arr, índice1, índice2) { var temp =


arr[índice1]; arr[índice1] =
arr[índice2]; arr[índice2] = temp;

160 | Capítulo 12: Algoritmos de ordenamiento

[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 ):

Ejemplo 12­2. Uso de la clase de banco de pruebas

var numElements = 100;


var myNums = new CArray(numElements);
[Link]();
print([Link]());

El resultado de este programa es:

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

Generación de datos aleatorios Notarás

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.

Algoritmos básicos de ordenación

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.

Algoritmos básicos de ordenación | 161

[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:

Asociación Estadounidense de Bajíos

El primer paso de la clasificación produce la siguiente lista:

AEDBH

Se intercambian los elementos primero y segundo. El siguiente paso de la ordenación da como resultado:

Asociación de Bajíos de Bosnia y Herzegovina (ADEBH)

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 12­1 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.

162 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

Figura 12­1. Ordenamiento de burbuja en acción

El ejemplo 12­3 muestra el código para la ordenación de burbuja:

Ejemplo 12­3. La función bubbleSort()

función bubbleSort() { var


numElements = [Link]; var temp; para
(var exterior
= numElements; exterior >= 2; ­­exterior) { para (var interior = 0; interior
<= exterior­1; ++interior) {
si ([Link][interior] > [Link][interior+1])
{ intercambiar([Link], interior, interior+1);
}
}
}
}

Asegúrese de agregar una llamada a esta función al constructor CArray . El ejemplo 12­4 es un programa breve
que ordena 10 números utilizando la función bubbleSort() .

Ejemplo 12­4. Ordenar 10 números con bubbleSort()

var numElements = 10; var


mynums = new CArray(numElements);
[Link]();

Algoritmos básicos de ordenación | 163

[Link]­[Link]
Machine Translated by Google

imprimir([Link]());
[Link]();
imprimir();
imprimir([Link]());

El resultado de este programa es:

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 12­5).

Ejemplo 12­5. Agregar una llamada a la función toString() a bubbleSort()

función bubbleSort() { var


numElements = [Link]; var temp; para
(var exterior
= numElements; exterior >= 2; ­­exterior) { para (var interior = 0; interior
<= exterior­1; ++interior) {
si ([Link][interior] > [Link][interior+1])
{ intercambiar([Link], interior, interior+1);
}

} 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.

164 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

Ordenación por selección

El siguiente algoritmo de ordenación que examinamos es el ordenamiento por selección. Este


ordenamiento comienza desde el principio de la matriz y compara el primer elemento con los elementos
restantes. Después de examinar todos los elementos, el elemento más pequeño se coloca en la primera
posición de la matriz y el algoritmo se mueve a la segunda posición. Este proceso continúa hasta que el
algoritmo llega a la penúltima posición de la matriz, momento en el que se ordenan todos los datos.

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 12­2
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 12­2 muestra cómo funciona la ordenación por selección en un conjunto de datos de números más grande.

Algoritmos básicos de ordenación | 165

[Link]­[Link]
Machine Translated by Google

Figura 12­2. Algoritmo de ordenación por selección

El ejemplo 12­6 muestra el código para la función selectionSort() .

Ejemplo 12­6. La función selectionSort()

función selectionSort() { var min,


temp; para (var
exterior = 0; exterior <= [Link]­2; ++exterior) {
min = exterior;
para (var interior = exterior + 1;
interior <= [Link]­1; ++interior) { si
([Link][interior] < [Link][min]) { min = interior;

166 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

} swap([Link], exterior, min);


}
}

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

Ordenación por inserción

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.

El ejemplo 12­7 muestra el código para la ordenación por inserción.

Algoritmos básicos de ordenación | 167

[Link]­[Link]
Machine Translated by Google

Ejemplo 12­7. La función insertionSort()

función insertionSort() { var temp,


interior; para (var
exterior = 1; exterior <= [Link]­1; ++exterior) { temp =
[Link][exterior]; interior =
exterior; mientras
(interior > 0 && ([Link][interior­1] >= temp)) { [Link][interior]
= [Link][interior­1]; ­­interior;

} [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.

Comparaciones de tiempos de los algoritmos básicos de ordenación Estos tres

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:

var inicio = nueva Fecha().getTime();

La función getTime() devuelve la hora del sistema en milisegundos. El siguiente fragmento de código:

168 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

var inicio = nueva Fecha().getTime();


imprimir(inicio);

da como resultado el siguiente resultado:

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 12­8 muestra un ejemplo de cronometraje de un bucle for que muestra los números del 1 al 100.

Ejemplo 12­8. Sincronización de un bucle for

var inicio = nueva Fecha().getTime(); para (var


i = 1; i < 100; ++i) { imprimir(i);

} var stop = new Date().getTime(); var elapsed


= stop ­ start; print("El tiempo
"
transcurrido fue: " milisegundos."); + transcurrido +

La salida, sin incluir los valores de tiempo de inicio y detención, del programa es:

El tiempo transcurrido fue: 91 milisegundos.


Ahora que tenemos una herramienta para medir la eficiencia de estos algoritmos de clasificación, ejecutemos algunas
pruebas para compararlos.

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 12­9 muestra el código para esta
nueva función.

Ejemplo 12­9. Sincronización de funciones de ordenamiento con 100 elementos de matriz

var numElements = 100; var


nums = new CArray(numElements); [Link]();
var start = new
Date().getTime(); [Link](); var stop =
new Date().getTime(); var
elapsed = stop ­ start; print("El tiempo transcurrido
para la ordenación de burbuja en "
" +
elementos es: + elapsed +
"
numElementos + "milisegundos.");
inicio = nueva Fecha().getTime();

Algoritmos básicos de ordenación | 169

[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):

El tiempo transcurrido para la ordenación de burbuja en 100 elementos es: 0 milisegundos.


El tiempo transcurrido para la ordenación de selección de 100 elementos es: 1 milisegundo.
El tiempo transcurrido para la ordenación por inserción de 100 elementos es: 0 milisegundos.
Claramente no hay ninguna diferencia significativa entre los tres algoritmos.

Para la siguiente prueba, cambiamos la variable numElements a 1000. Estos son los resultados:

El tiempo transcurrido para la ordenación de burbuja en 1000 elementos es: 12 milisegundos.


El tiempo transcurrido para la ordenación de selección de 1000 elementos es: 7 milisegundos.
El tiempo transcurrido para la ordenación por inserción de 1000 elementos es: 6 milisegundos.
Para 1000 números, la ordenación por selección y la ordenación por inserción son casi el doble de rápidas.
La clasificación de burbuja.

Finalmente, probamos los algoritmos con 10.000 números:

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.

Algoritmos de ordenamiento avanzados

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.

170 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

El algoritmo Shellsort El primer

algoritmo de ordenación avanzado que examinaremos es el algoritmo Shellsort. Shellsort recibe su


nombre de su inventor, Donald Shell. Este algoritmo se basa en la ordenación por inserción, pero
supone una gran mejora con respecto a ese algoritmo de ordenación básico. El concepto clave de
Shellsort es que compara primero los elementos distantes, en lugar de los adyacentes, como se
hace en la ordenación por inserción. Los elementos que están muy fuera de lugar se pueden poner
en su lugar de forma más eficiente utilizando este esquema que simplemente comparando elementos
vecinos. A medida que el algoritmo recorre el conjunto de datos, la distancia entre cada elemento
disminuye hasta que, al final del conjunto de datos, el algoritmo compara los elementos adyacentes.

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 12­3 demuestra cómo funciona la secuencia de espacios con el algoritmo Shellsort.

Comencemos con un vistazo al código del 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;
}
}
}

Algoritmos de ordenamiento avanzados | 171

[Link]­[Link]
Machine Translated by Google

Figura 12­3. El algoritmo Shellsort con una secuencia de espacios inicial de 3

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.

172 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

Ordenar. La figura 12­3 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 12­10.

Ejemplo 12­10. Ejecución de shellsort() en un conjunto de datos pequeño

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]());

El resultado de este programa es:

Antes de Shellsort:

6029358054

Durante el sorteo de conchas:

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

Algoritmos de ordenamiento avanzados | 173

[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

Revisaremos nuevamente el algoritmo shellsort() cuando lo comparemos con otros algoritmos de


clasificación avanzados más adelante en el capítulo.

Cálculo de una secuencia de espacios

dinámicos Robert Sedgewick, coautor de Algorithms, 4E (Addison­Wesley), 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:

var N = [Link]; var h =


1; mientras
(h < N/3) { h = 3 * h +

}

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 = (h­1)/3;

El ejemplo 12­11 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.

Ejemplo 12­11. shellsort() con una secuencia de espacios calculada dinámicamente

función shellsort1() { var N =


[Link]; var h = 1;
mientras (h
< N/3) { h = 3 * h + 1;

174 | Capítulo 12: Algoritmos de ordenamiento

[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 = (h­1)/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]());

El resultado de este programa es:

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 12­12 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.

Algoritmos de ordenamiento avanzados | 175

[Link]­[Link]
Machine Translated by Google

Ejemplo 12­12. Comparación de algoritmos shellsort()


load("[Link]"); var
nums = new CArray(10000);
[Link](); var
start = new Date().getTime(); [Link]();
var stop = new
Date().getTime(); var elapsed = stop ­ start;
print("Shellsort con secuencia de
"
espacios codificada: [Link](); [Link](); start = new + transcurrido + " EM.");
Date().getTime();
nums.shellsort1();
stop = new Date().getTime();
print("Shellsort con
secuencia de espacios dinámica:
"
+ transcurrido + " EM.");

Los resultados de este programa son:

Shellsort con secuencia de espacios codificados: 3 ms.


Shellsort con secuencia de brecha dinámica: 3 ms.

Ambos algoritmos ordenaron los datos en el mismo tiempo. Aquí se muestra el resultado de ejecutar
el programa con 100.000 elementos de datos:

Shellsort con secuencia de espacios codificados: 43 ms.


Shellsort con secuencia de brecha dinámica: 43 ms.

Claramente, ambos algoritmos ordenan los datos con la misma eficiencia, por lo que puedes usar
cualquiera de ellos con confianza.

El algoritmo Mergesort El algoritmo

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 .

176 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

Mergesort ascendente

La versión no recursiva o iterativa de Mergesort se conoce como proceso ascendente. El algoritmo


comienza descomponiendo el conjunto de datos que se está ordenando en un conjunto de matrices de un
elemento. Luego, estas matrices se fusionan lentamente creando un conjunto de submatrices izquierda y
derecha, cada una de las cuales contiene los datos parcialmente ordenados hasta que todo lo que queda
es una matriz con los datos perfectamente ordenados. La Figura 12­4 ilustra cómo funciona el algoritmo
Mergesort ascendente.

Figura 12­4. Algoritmo Mergesort de abajo a arriba

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

matriz izquierda ­ 6, matriz


derecha infinita ­ 10, matriz
izquierda infinita ­ 1, matriz
derecha infinita ­ 9, matriz
izquierda infinita ­ 4, matriz
derecha infinita ­ 8, matriz
izquierda infinita ­ 2, matriz
derecha infinita ­ 7, matriz
izquierda infinita ­ 3, matriz
derecha infinita ­ 5, matriz
izquierda infinita ­ 6,10, matriz
derecha infinita ­ 1,9, infinito

Algoritmos de ordenamiento avanzados | 177

[Link]­[Link]
Machine Translated by Google

matriz izquierda ­ 4,8, Infinito matriz


derecha ­ 2,7, Infinito matriz izquierda
­ 1,6,9,10, Infinito matriz derecha ­ 2,4,7,8,
Infinito matriz izquierda ­ 1,2,4,6,7,8,9,10,
Infinito matriz derecha ­ 3,5, Infinito

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 12­13 presenta el
código que creó la salida anterior.

Ejemplo 12­13. Implementación de ordenación por fusión ascendente en JavaScript

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;

} si (derecha < longitud de arr.) {


mergeArrays(arr, izquierda, izquierda+paso, derecha, [Link]);

} paso *= 2;
}
}

función mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) {


var arrDerecha = new Array(detenerDerecha ­ iniciarDerecha + 1); var
arrIzquierda = new Array(detenerIzquierda ­ iniciarIzquierda + 1); k
= iniciarDerecha;
para (var i = 0; i < ([Link]­1); ++i) { arrDerecha[i] =
arr[k]; ++k;

178 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

k = inicioIzquierda;
para (var i = 0; i < ([Link]­1); ++i) { leftArr[i] = arr[k]; +
+k;

rightArr[[Link]­1] = Infinity; // un valor centinela leftArr[[Link]­1] = Infinity; // un valor


centinela var m = 0; var n = 0; for (var k = startLeft; k < stopRight; ++k) { if (leftArr[m] <= rightArr[n])
{ arr[k] =
leftArr[m]; m++;

}
de lo
contrario { arr[k] = derechaArr[n];
n++;
}

} print(" matriz izquierda ­ ", leftArr);


print("matriz derecha ­ ", rightArr);
}

var nums = [6,10,1,9,4,8,2,7,3,5];


imprimir(nums);
imprimir();
mergeSort(nums);
imprimir();
imprimir(nums);

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 12­14 muestra la clase CArray con las funciones mergeSort() y
mergeArrays() agregadas a su definición.

Ejemplo 12­14. Mergesort añadido a la clase CArray


función CArray(numElements)
{ [Link] = [];
[Link] = 0;
[Link] = [5,3,1];
[Link] = numElements;
[Link] = insertar;

Algoritmos de ordenamiento avanzados | 179

[Link]­[Link]
Machine Translated by Google

[Link] = toString; [Link]


= clear; [Link] =
setData; [Link] =
setGaps; [Link] =
shellsort; [Link] = mergeSort;
[Link] = mergeArrays;
para (var i = 0; i < numElements; ++i)
{ [Link][i] = 0;

// otras definiciones de funciones van aquí

función mergeArrays(arr,startLeft, stopLeft, startRight, stopRight) {


var arrDerecha = new Array(detenerDerecha ­ iniciarDerecha + 1); var
arrIzquierda = new Array(detenerIzquierda ­ iniciarIzquierda + 1); k =
iniciarDerecha; para
(var i = 0; i < ([Link]­1); ++i) { arrDerecha[i] = arr[k]; +
+k;

k = inicioIzquierda;
para (var i = 0; i < ([Link]­1); ++i) { leftArr[i] = arr[k]; ++k;

rightArr[[Link]­1] = Infinity; // un valor centinela leftArr[[Link]­1] =


Infinity; // un valor centinela var m = 0; var n = 0; for (var k = startLeft; k <
stopRight; +
+k) { if
(leftArr[m] <= rightArr[n]) { arr[k] = leftArr[m];

m++;

} de lo
contrario { arr[k] = derechaArr[n];
n++;
}

} print(" matriz izquierda ­ ", leftArr);


print("matriz derecha ­ ", rightArr);
}

función mergeSort() { si
([Link] < 2) { devolver;

180 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

var paso = 1; var


izquierda, derecha;
mientras (paso < [Link]) { izquierda
= 0; derecha
= paso; mientras
(derecha + paso <= [Link])
{ mergeArrays([Link], izquierda, izquierda+paso, derecha, derecha+paso);
izquierda = derecha +
paso; derecha = izquierda + paso;

} si (derecha < [Link]) {


mergeArrays([Link], izquierda, izquierda+paso, derecha, [Link]);

} paso *= 2;
}
}

var nums = new CArray(10);


[Link]();
imprimir([Link]());
[Link]();
imprimir([Link]());

El algoritmo Quicksort El algoritmo

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.

El algoritmo divide la lista en sublistas seleccionando un elemento de la lista como pivote.


Los datos se ordenan alrededor del pivote moviendo los elementos menores que el pivote hacia la parte
inferior de la lista y los elementos mayores que el pivote hacia la parte superior de la lista.

La figura 12­5 demuestra cómo se ordenan los datos alrededor de un pivote.

Algoritmos de ordenamiento avanzados | 181

[Link]­[Link]
Machine Translated by Google

Figura 12­5. Ordenación de datos en torno a un pivote

Algoritmo y pseudocódigo para el algoritmo Quicksort


El algoritmo para Quicksort es:

1. Elija un elemento pivote que divida la lista en dos sublistas.

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.

Este algoritmo luego se traduce en el siguiente programa JavaScript:

función qSort(lista) { si
([Link] == 0) { devolver
[];

} var menor = [];


var mayor = []; var
pivote = lista[0];

182 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

para (var i = 1; i < [Link]; i++) { si (lista[i]


< pivote) {
[Link](lista[i]); } de
lo
contrario { [Link](lista[i]);
}

} return qSort(menor).concat(pivot, qSort(mayor));


}

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 12­15.

Ejemplo 12­15. Ordenar datos con Quicksort


función qSort(arr) {

si ([Link] == 0)
{ devolver [];

} var izquierda =
[]; var derecha =
[]; var pivote = arr[0];
para (var i = 1; i < [Link]; i++) {

si (arr[i] < pivote) {

[Link](arr[i]); } de
lo contrario {

[Link](arr[i]);
}

} return qSort(izquierda).concat(pivot, qSort(derecha));

} var a = [];
para (var i = 0; i < 10; ++i) {
a[i] = Matemá[Link]((Matemá[Link]()*100)+1);

} imprimir(a);

Algoritmos de ordenamiento avanzados | 183

[Link]­[Link]
Machine Translated by Google

imprimir();
imprimir(qSort(a));

El resultado de este programa es:

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]);
}

} return qSort(izquierda).concat(pivot, qSort(derecha));

} var a = [];
para (var i = 0; i < 10; ++i) { a[i] =
[Link](([Link]()*100)+1);

} imprimir(a);
imprimir();
imprimir(qSort(a));

El resultado de este programa es:

9,3,93,9,65,94,50,90,12,65

pivote: 9 elemento actual: 3


moviendo 3 a la izquierda
pivote: 9 elemento actual: 93
moviendo 93 a la derecha
pivote: 9 elemento actual: 9
moviendo 9 a la derecha

184 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

pivote: 9 elemento actual: 65


moviendo 65 a la derecha
pivote: 9 elemento actual: 94
moviendo 94 a la derecha
pivote: 9 elemento actual: 50
moviendo 50 a la derecha
pivote: 9 elemento actual: 90
moviendo 90 a la derecha
pivote: 9 elemento actual: 12
moviendo 12 a la derecha
pivote: 9 elemento actual: 65
moviendo 65 a la derecha
pivote: 93 elemento actual: 9
moviendo 9 a la izquierda
pivote: 93 elemento actual: 65
moviendo 65 a la izquierda
pivote: 93 elemento actual: 94
moviendo 94 a la derecha
pivote: 93 elemento actual: 50
moviendo 50 a la izquierda
pivote: 93 elemento actual: 90
moviendo 90 a la izquierda
pivote: 93 elemento actual: 12
moviendo 12 a la izquierda
pivote: 93 elemento actual: 65
moviendo 65 a la izquierda
pivote: 9 elemento actual: 65
moviendo 65 a la derecha
pivote: 9 elemento actual: 50
moviendo 50 a la derecha
pivote: 9 elemento actual: 90
moviendo 90 a la derecha
pivote: 9 elemento actual: 12
moviendo 12 a la derecha
pivote: 9 elemento actual: 65
moviendo 65 a la derecha
pivote: 65 elemento actual: 50
moviendo 50 a la izquierda
pivote: 65 elemento actual: 90
moviendo 90 a la derecha
pivote: 65 elemento actual: 12
moviendo 12 a la izquierda
pivote: 65 elemento actual: 65
moviendo 65 a la derecha
pivote: 50 elemento actual: 12
moviendo 12 a la izquierda
pivote: 90 elemento actual: 65
moviendo 65 a la izquierda
3,9,9,12,50,65,65,90,93,94

Algoritmos de ordenamiento avanzados | 185

[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?

186 | Capítulo 12: Algoritmos de ordenamiento

[Link]­[Link]
Machine Translated by Google

CAPÍTULO 13

Algoritmos de búsqueda

La búsqueda de datos es una tarea fundamental de programación informática que se ha estudiado


durante muchos años. En este capítulo se analiza solo un aspecto del problema de búsqueda: la
búsqueda de un valor específico en una lista.

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 13­1 muestra una función para realizar una búsqueda secuencial en una matriz.

Ejemplo 13­1. La función seqSearch()

función seqSearch(arr, datos) { para


(var i = 0; i < [Link]; ++i) { si (arr[i] == datos)
{
devuelve verdadero;
}

187

[Link]­[Link]
Machine Translated by Google

} devuelve falso;
}

Si el argumento de datos se encuentra en la matriz, la función devuelve "verdadero" inmediatamente. Si la


función llega al final de la matriz sin encontrar una coincidencia, devuelve " falso".

El ejemplo 13­2 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.

Ejemplo 13­2. Ejecución de la función +seqSearch()

función dispArr(arr) { para (var i =


0; i < [Link]; ++i) { putstr(arr[i] + " "); si (i % 10 == 9)
{ putstr("\n");

} si (i % 10 != 0) { putstr("\n");

}
}

var nums = []; para


(var i = 0; i < 100; ++i) { nums[i] =
[Link]([Link]() * 101);

} 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:

Introduzca un número para buscar : 23

23 está en la matriz.

188 | Capítulo 13: Algoritmos de búsqueda

[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 13­3 proporciona la definición de esta nueva versión de
seq Search().

Ejemplo 13­3. Modificación de la función seqSearch() para que devuelva la posición encontrada (o ­1)

función seqSearch(arr, datos) { para (var i


= 0; i < [Link]; ++i) { si (arr[i] == datos) { devolver

} 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 13­4 presenta un programa que utiliza esta segunda definición de seqSearch().

Ejemplo 13­4. Prueba de la función seqSearch() modificada


var nums = []; para
(var i = 0; i < 100; ++i) { nums[i] =
[Link]([Link]() * 101);

} putstr("Ingrese un número para buscar: "); var num =


readline(); print(); var position
=
seqSearch(nums, num); if (position > ­1)
{ print(num +
" "
está en la matriz en la posición + posición);
}

de lo
"
contrario { imprimir(num + no está en la matriz.");

} imprimir();
dispArr(nums);

Búsqueda secuencial | 189

[Link]­[Link]
Machine Translated by Google

A continuación se muestra una ejecución del programa:

Introduzca un número para buscar : 22

22 está en la matriz en la posición 35

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.

Búsqueda de valores mínimos y máximos Los problemas de

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:

1. Asigna el primer elemento de la matriz a una variable como valor mínimo.

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.

4. Pase al siguiente elemento y repita el paso 3.

5. El valor mínimo se almacena en la variable cuando finaliza el programa.

El funcionamiento de este algoritmo se demuestra en la Figura 13­1.

190 | Capítulo 13: Algoritmos de búsqueda

[Link]­[Link]
Machine Translated by Google

Figura 13­1. Búsqueda del valor mínimo de una matriz

Este algoritmo se transforma fácilmente en una función de JavaScript, como se muestra en el Ejemplo
13­5.

Ejemplo 13­5. La función findMin()

función findMin(arr) { var min


= arr[0]; para (var i =
1; i < [Link]; ++i) {
si (arr[i] < min) { min =
arr[i];
}

} 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.

Búsqueda secuencial | 191

[Link]­[Link]
Machine Translated by Google

Probemos la función en un programa, como se muestra en el Ejemplo 13­6.

Ejemplo 13­6. Hallar el valor mínimo de una matriz

var nums = [];


para (var i = 0; i < 100; ++i) { nums[i] =
[Link]([Link]() * 101);

} var minValue = findMin(nums);


dispArr(nums);
print();
"
print("El valor mínimo es: + minValor);

Aquí está el resultado de ejecutar este programa:

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 valor mínimo es: 1

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 13­7 muestra la definición de la función.

Ejemplo 13­7. La función findMax()

función findMax(arr) { var


max = arr[0]; para
(var i = 1; i < [Link]; ++i) {
si (arr[i] > máx) { máx =
arr[i];
}
}
devuelve máximo;
}

El ejemplo 13­8 muestra un programa que encuentra tanto el valor mínimo como el valor máximo de una
matriz.

Ejemplo 13­8. Uso de la función findMax()

números var = [];


para (var i = 0; i < 100; ++i) {

192 | Capítulo 13: Algoritmos de búsqueda

[Link]­[Link]
Machine Translated by Google

nums[i] = [Link]([Link]() * 101);

} var minValue = findMin(nums);


dispArr(nums);
print( );
print() ;
"
print("El valor mínimo es: var + minValor);
maxValue = findMax(nums); print();
print("El
"
valor máximo es: + valormáximo);

El resultado de este programa es:

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

El valor mínimo es: 2

El valor máximo es: 99

Uso de datos autoorganizados Las

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 80­20”, 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.

Búsqueda secuencial | 193

[Link]­[Link]
Machine Translated by Google

Las distribuciones de probabilidad, como la regla 80­20, se denominan distribuciones de Pareto, en


honor a Vilfredo Pareto, quien descubrió estas distribuciones al estudiar la distribución de los ingresos
y la riqueza a fines del siglo XIX. Consulte The Art of Computer Programming: Volume 3, Sorting and
Searching de Donald Knuth (Addison­Wesley, 399­401) para obtener más información sobre las
distribuciones de probabilidad en conjuntos de datos.

Podemos modificar nuestra función seqSearch() para incluir la autoorganización con bastante facilidad.
El ejemplo 13­9 es nuestro primer intento de definición de función.

Ejemplo 13­9. Función seqSearch() con autoorganización


función seqSearch(arr, datos) { para
(var i = 0; i < [Link]; ++i) { si (arr[i] == datos)
{ si (i > 0)

{ intercambiar(arr,i,i­1);
}
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() :

función swap(arr, índice, índice1) {


temp = arr[índice];
arr[índice] = arr[índice1];
arr[índice1] = temp;
}

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:

var numeros = [5,1,7,4,2,10,9,3,6,8];


print(numeros); for
(var i = 1; i <= 3; i++)
{ seqSearch(numeros, 4);
print(numeros);
}

genera la siguiente salida:

5,1,7,4,2,10,9,3,6,8
5,1,4,7,2,10,9,3,6,8

194 | Capítulo 13: Algoritmos de búsqueda

[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 80­20, 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.

El ejemplo 13­10 muestra la definición de esta nueva versión de seqSearch().

Ejemplo 13­10. seqSearch() con mejor autoorganización


función seqSearch(arr, datos) { para
(var i = 0; i < [Link]; ++i) { si (arr[i] == datos
&& i > ([Link] * 0.2)) {
swap(arr,i,0);
devuelve verdadero;

} de lo contrario si (arr[i] == datos)


{ devuelve verdadero;
}

} devuelve falso;
}

El ejemplo 13­11 muestra un programa que prueba esta definición en un pequeño conjunto de datos de 10
elementos.

Ejemplo 13­11. Búsqueda con autoorganización


números var = [];
para (var i = 0; i < 10; ++i) {
nums[i] = [Link]([Link]() * 11);

} dispArr(nums);
print();
putstr("Ingrese un valor a buscar: "); var val =
parseInt(readline()); if (seqSearch(nums,
val)) { print("Elemento encontrado:
");

Búsqueda secuencial | 195

[Link]­[Link]
Machine Translated by Google

imprimir();
dispArr(nums);
}
de lo
"
contrario { imprimir(val + no está en la matriz.");
}

A continuación se muestran los resultados de una ejecución de muestra de este programa:

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.

2. La conjetura es demasiado alta.

3. La conjetura es demasiado baja.

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

196 | Capítulo 13: Algoritmos de búsqueda

[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 13­2 demuestra cómo funciona esta estrategia si el número que debe adivinar es 82.

Figura 13­2. Algoritmo de búsqueda binaria aplicado para adivinar un número

Podemos implementar esta estrategia como algoritmo de búsqueda binaria. Este algoritmo solo funciona
en un conjunto de datos ordenados. Este es el algoritmo:

1. Establezca un límite inferior para la primera posición de la matriz (0).

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.

Búsqueda binaria | 197

[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

contrario, devuelva el punto medio como el elemento encontrado.

El ejemplo 13­12 muestra la definición de JavaScript para el algoritmo de búsqueda binaria, junto con un programa
para probar la definición.

Ejemplo 13­12. Utilización del algoritmo de búsqueda binaria

función binSearch(arr, datos) {


var límite superior = [Link]­1; var límite
inferior = 0; mientras (límite
inferior <= límite superior) { var medio =
[Link]((límite superior + límite inferior) / 2); si (arr[medio] < datos) {

límite inferior = medio + 1;


}
de lo contrario si (arr[mid] > data) { límite
superior = mid ­ 1;
}

de lo

contrario { devolver medio;

}
}
devuelve ­1;
}

var nums = []; para


(var i = 0; i < 100; ++i) { nums[i] =
[Link]([Link]() * 101);
}
insertionsort(nums);
dispArr(nums);
print();
putstr("Ingrese un valor para buscar: "); var val =
parseInt(readline()); var retVal = binSearch(nums,
val); if (retVal >= 0) { print("Se encontró " + val +

" "
En posición + retVal);
}
de lo
"
contrario { imprimir(val + no está en la matriz.");
}

Aquí está el resultado de una ejecución del programa:

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

198 | Capítulo 13: Algoritmos de búsqueda

[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 13­13, agreguemos una declaración al contenedor.
Función Search() que muestra el punto medio cada vez que se recalcula:

Ejemplo 13­13. binSearch() muestra el valor del punto medio

función binSearch(arr, datos) {


var límite superior = [Link]­1; var límite
inferior = 0; mientras (límite
inferior <= límite superior) { var medio =
[Link]((límite superior + límite inferior) / 2); print("Punto medio actual: " si
(arr[medio] < datos) { + medio);

límite inferior = medio + 1;

} de lo contrario si (arr[mid] > data)


{ límite superior = mid ­ 1;
}

de lo

contrario { devolver medio;

} devuelve ­1;
}

Ahora ejecutemos el programa nuevamente:

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

Búsqueda binaria | 199

[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 13­14 muestra una definición
de la función count() .

Ejemplo 13­14. La función count()

función count(arr, datos) { var count =


0; var position =
binSearch(arr, datos); if (position > ­1) { ++count; for
(var i = position­1; i > 0; ­­i)
{ if (arr[i]
== datos) {

200 | Capítulo 13: Algoritmos de búsqueda

[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;
}

La función comienza llamando a la función binSearch() para buscar el valor especificado.


Si se encuentra el valor en el conjunto de datos, la función comienza a contar las ocurrencias
llamando a dos bucles for . El primer bucle recorre la matriz, contando las ocurrencias del valor
encontrado, y se detiene cuando el siguiente valor de la matriz no coincide con el valor encontrado.
El segundo bucle for recorre la matriz, contando las ocurrencias y se detiene cuando el siguiente
valor de la matriz no coincide con el valor encontrado.

El ejemplo 13­15 muestra cómo podemos usar count() en un programa.

Ejemplo 13­15. Uso de la función count()

var nums = []; para


(var i = 0; i < 100; ++i) { nums[i] =
[Link]([Link]() * 101);
}
insertionsort(nums);
dispArr(nums);
print();
putstr("Ingrese un valor para contar: "); var val =
parseInt(readline()); var retVal = count(nums,
val); print("Se encontró " + retVal +
"
ocurrencias de " + val + ".");

A continuación se muestra un ejemplo de ejecución del programa:

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

Búsqueda binaria | 201

[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.

A continuación se muestra otro ejemplo de ejecución:

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.

Búsqueda de datos textuales


Hasta este punto, todas nuestras búsquedas se han realizado sobre datos numéricos. También podemos utilizar
los algoritmos analizados en este capítulo con datos textuales. En primer lugar, definamos el conjunto de datos
que estamos utilizando:

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]).

Para leer el archivo en un programa, solo necesitamos una línea de código:

var palabras = read("[Link]").split(" ");

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()

202 | Capítulo 13: Algoritmos de búsqueda

[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 13­16 muestra el código.

Ejemplo 13­16. Búsqueda de un archivo de texto mediante seqSearch()

función seqSearch(arr, datos) { para (var i = 0;


i < [Link]; ++i) { si (arr[i] == datos) { devolver i;

}
}
devuelve ­1;
}

var palabras = read("[Link]").split(" "); var palabra =


"retórica"; var inicio = new
Date().getTime(); var posición = seqSearch(palabras,
palabra); var parada = new Date().getTime(); var
transcurrido = parada ­ inicio; si (posición >= 0)
{ print("Se encontró " + palabra + en la
posición + posición + ".");
" "
print("La búsqueda secuencial tomó " + transcurrido + " milisegundos.");

} else
"
{ imprimir(palabra + no está en el archivo.");
}

El resultado de este programa es:

Retórica encontrada en la posición 174.


La búsqueda secuencial tardó 0 milisegundos.

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 13­17 muestra el código y el
resultado.

Ejemplo 13­17. Búsqueda de datos textuales con binSearch()

función binSearch(arr, datos) {


var límite superior = [Link]­1; var límite
inferior = 0; mientras (límite
inferior <= límite superior) {

Búsqueda de datos textuales | 203

[Link]­[Link]
Machine Translated by Google

var mid = [Link]((límite superior + límite inferior) / 2); if (arr[mid] < data) {

límite inferior = medio + 1;


}
de lo contrario si (arr[mid] > data) { límite
superior = mid ­ 1;
}

de lo

contrario { devolver medio;

}
}
devuelve ­1;
}

función insertionsort(arr) { var temp, interior;


for (var exterior = 1;
exterior <= [Link]­1; ++exterior) { temp = arr[exterior]; interior = exterior; while
(interior > 0 &&
(arr[interior­1] >=
temp)) { arr[interior] = arr[interior­1]; ­­interior;

}
arr[interior] = temp;
}
}

var palabras = read("[Link]").split(" ");


insertionsort(palabras); var
palabra = "retórica"; var inicio =
new Date().getTime(); var posición =
binSearch(palabras, palabra); var parada = new
Date().getTime(); var transcurrido = parada ­
inicio; if (posición >= 0) { print("Se
encontró " + palabra + en la
" "
posición + posición + "."); print("La búsqueda binaria tomó " + transcurrido + "
milisegundos.");
}
else
"
{ imprimir(palabra + no está en el archivo.");
}

Retórica encontrada en la posición 124.


La búsqueda binaria tardó 0 milisegundos.

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.

204 | Capítulo 13: Algoritmos de búsqueda

[Link]­[Link]
Machine Translated by Google

Ceremonias

1. El algoritmo de búsqueda secuencial siempre encuentra la primera aparición de un elemento en un


conjunto de datos. Reescriba el algoritmo de modo que se devuelva la última aparición de un elemento.

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.

Un ejemplo de programación dinámica: cálculo de números de Fibonacci


Los números de Fibonacci se pueden definir mediante la siguiente secuencia:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55,…

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:

función recurFib(n) { si (n < 2)


{ devolver n;

} de lo
contrario { devolver recurFib(n­1) + recurFib(n­2);
}
}

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 14­1 para fib(5).

Figura 14­1. Árbol de recursión generado por la función recursiva de Fibonacci

208 | Capítulo 14: Algoritmos avanzados

[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.

Podemos demostrar la esencia de la programación dinámica examinando la solución de programación


dinámica para calcular los números de Fibonacci, como se muestra en la definición de la función en la
siguiente sección:

función dynFib(n) { var val


= []; para (var i =
0; i <= n; ++i) { val[i] = 0;

} si (n == 1 || n == 2) { devolver

} más
{ val[1] = 1;
valor[2] = 2;
para (var i = 3; i <= n; ++i) {
val[i] = val[i­1] + val[i­2];

} devuelve val[n­1];
}
}

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.

La disposición de la secuencia de Fibonacci en la matriz val se muestra aquí:

valor[0] = 0 valor[1] = 1 valor[2] = 2 valor[3] = 3 valor[4] = 5 valor[5] = 8 valor[6] = 13

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 14­1 muestra el código para la prueba de
tiempo.

Programación dinámica | 209

[Link]­[Link]
Machine Translated by Google

Ejemplo 14­1. Prueba de tiempo para versiones de programación recursiva y dinámica de la función de
Fibonacci

función recurFib(n) { si (n < 2)


{ devolver n;

} de lo
contrario { devolver recurFib(n­1) + recurFib(n­2);
}
}

función dynFib(n) { var val =


[]; para (var i = 0;
i <= n; ++i) { val[i] = 0;

} si (n == 1 || n == 2) { devolver

} más
{ val[1] = 1;
valor[2] = 2;
para (var i = 3; i <= n; ++i) {
val[i] = val[i­1] + val[i­2];

} devuelve val[n­1];
}
}

var start = new Date().getTime();


print(recurFib(10)); var stop
= new Date().getTime(); print("tiempo
"
recursivo ­ print(); start = new + (parada­inicio) + "milisegundos");

Date().getTime(); print(dynFib(10)); stop


= new Date().getTime();
print("tiempo de programación dinámica
"
­ + (parada­inicio) + " milisegundos");

El resultado de este programa es:

832040
tiempo recursivo ­ 0 milisegundos

832040
tiempo de programación dinámica ­ 0 milisegundos

Si ejecutamos el programa nuevamente, esta vez calculando fib(20), obtenemos:

6765
tiempo recursivo ­ 1 milisegundo

210 | Capítulo 14: Algoritmos avanzados

[Link]­[Link]
Machine Translated by Google

6765
tiempo de programación dinámica ­ 0 milisegundos

Finalmente, calculamos fib(30) y obtenemos:


832040
tiempo recursivo ­ 17 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:

función iterFib(n) { var último = 1;


var siguienteÚltimo
= 1; var resultado = 1; para
(var i = 2; i < n; ++i)
{ resultado = último + siguienteÚltimo;
siguienteÚltimo = último; último =
resultado;

}
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.

Encontrar la subcadena común más larga Otro problema

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

Programación dinámica | 211

[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 14­2 presenta la definición completa del algoritmo. Después del código, explicaremos cómo
funciona.

Ejemplo 14­2. Función para determinar la subcadena común más larga de dos cadenas

función lcs(palabra1, palabra2) { var


max = 0; var
index = 0; var lcsarr
= new Array([Link]+1); para (var i = 0; i <=
[Link]+1; ++i) { lcsarr[i] = new Array([Link]+1);
para (var j = 0; j <= [Link]+1; ++j) { lcsarr[i]
[j] = 0;

} para (var i = 0; i <= [Link]; ++i) {


para (var j = 0; j <= [Link]; ++j) { si (i == 0 || j
== 0) { lcsarr[i][j] = 0;

} de lo
contrario { si (palabra1[i­1] == palabra2[j­1]) {
lcsarr[i][j] = lcsarr[i­1][j­1] + 1;

} de lo
contrario { lcsarr[i][j] = 0;
}

} si (máximo < lcsarr[i][j]) { máximo


= lcsarr[i][j]; índice = i;

}
}

} var str = ""; si


(máximo == 0)
{ devolver "";

} de lo
contrario { para (var i = índice­máximo; i <= máximo;
++i) { str += palabra2[i];

} devuelve str;

212 | Capítulo 14: Algoritmos avanzados

[Link]­[Link]
Machine Translated by Google

}
}

La primera sección de la función configura un par de variables y la matriz bidimensional. La mayoría


de los lenguajes tienen una declaración simple para matrices bidimensionales, pero JavaScript te
obliga a superar algunos obstáculos al declarar una matriz dentro de otra matriz. El último bucle for
del fragmento de código inicializa la matriz. Esta es la primera sección:

función lcs(palabra1, palabra2) { var


max = 0; var
index = 0; var
lcsarr = new Array([Link]+1); para (var i =
0; i <= [Link]+1; ++i) { lcsarr[i] = new
Array([Link]+1); para (var j = 0; j <=
[Link]+1; ++j) { lcsarr[i][j] = 0;

}
}

Ahora aquí está el código para la segunda sección de la función:

para (var i = 0; i <= [Link]; ++i) {


para (var j = 0; j <= [Link]; ++j) { si (i == 0 || j
== 0) { lcsarr[i][j] = 0;

} de lo
contrario { si (palabra1[i­1] == palabra2[j­1])
{ lcsarr[i][j] = lcsarr[i­1][j­1] + 1;

} de lo
contrario { lcsarr[i][j] = 0;
}

} si (máximo < lcsarr[i][j]) { máximo


= lcsarr[i][j]; índice = i;

}
}
}

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

Programación dinámica | 213

[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:

var str = ""; si


(máximo == 0)
{ devolver "";

} de lo
contrario { para (var i = índice­máximo; i <= máximo;
++i) { str += palabra2[i];

} devuelve str;
}

Dadas nuevamente las dos cadenas “abbcc” y “dbbcc”, el programa devuelve “bbcc”.

El problema de la mochila: una solución recursiva Un problema

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.

214 | Capítulo 14: Algoritmos avanzados

[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:

función max(a, b) { return


(a > b) ? a : b;
}

función mochila(capacidad, tamaño, valor, n) { si (n == 0 ||


capacidad == 0) { devolver 0;

} if (size[n­1] > capacidad) { return


mochila(capacidad, tamaño, valor, n­1);

} else
{ return max(valor[n­1] +
mochila(capacidad­tamaño[n­1], tamaño, valor, n­1),
mochila(capacidad, tamaño, valor, n­1));
}
}

var valor = [4,5,10,11,13]; var tamaño


= [3,4,7,8,9]; var capacidad =
16; var n = 5;

print(mochila(capacidad, tamaño, valor, n));

El resultado de este programa es:

23

El problema de esta solución recursiva al problema de la mochila es que, como es recursiva, se


vuelven a examinar muchos subproblemas durante el transcurso de la recursión. Una mejor solución
al problema de la mochila es utilizar una técnica de programación dinámica para resolver el
problema, como se muestra a continuación.

El problema de la mochila: una solución de programación dinámica Siempre que

encontramos una solución recursiva a un problema, generalmente podemos reescribir la solución


utilizando una técnica de programación dinámica y terminar con un programa más eficiente.
El problema de la mochila definitivamente puede reescribirse mediante programación dinámica.
Todo lo que tenemos que hacer es usar una matriz para almacenar soluciones temporales hasta que lleguemos a la
solución final.

El siguiente programa demuestra cómo se puede resolver el problema de la mochila que


encontramos anteriormente mediante programación dinámica. El valor óptimo para las restricciones
dadas es, nuevamente, 23. El ejemplo 14­3 muestra el código.

Programación dinámica | 215

[Link]­[Link]
Machine Translated by Google

Ejemplo 14­3. Solución de programación dinámica al problema de la mochila

función max(a, b) { return


(a > b) ? a : b;
}

función dKnapsack(capacidad, tamaño, valor, n) { var K = [];


para (var i = 0;
i <= capacidad+1; i++) {
K[i] = [];

} para (var i = 0; i <= n; i++) { para (var w


= 0; w <= capacidad; w++) { si (i == 0 || w == 0) {

K[i][w] = 0;

} de lo contrario si (tamaño[i­1] <= w) {


K[i][w] = máx(valor[i­1] + K[i­1][w­tamaño[i­1]],
K[i­1][w]);

} demás {
K[i][w] = K[i­1][w];

} putstr(K[i][w] + " ");

} imprimir();
}

devuelve K[n][capacidad];
}

var valor = [4,5,10,11,13]; var tamaño


= [3,4,7,8,9]; var capacidad =
16; var n = 5;

print(dKnapsack(capacidad, tamaño, valor, n));

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

216 | Capítulo 14: Algoritmos avanzados

[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.

Un primer ejemplo de algoritmo codicioso: el problema del cambio de monedas Un ejemplo

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 14­4 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 14­4. 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;

} si (origAmt % .1 < origAmt)


{ monedas[2] = parseInt(origAmt / .1);
remainAmt = origAmt % .1;
origAmt = remainAmt;

} si (origAmt % .05 < origAmt)


{ monedas[1] = parseInt(origAmt / .05);
remainAmt = origAmt % .05;
origAmt = remainAmt;
}

Algoritmos voraces | 217

[Link]­[Link]
Machine Translated by Google

monedas[0] = parseInt(origAmt / .01);


}

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);
}
}

var origAmt = .63;


var monedas = [];
makeChange(origAmt, monedas);
showChange(monedas);

El resultado de este programa es:

Número de cuartos ­ 2 ­ 0,5


Número de monedas de diez centavos : 1 ­ 0,1

Número de centavos ­ 3 ­ 0,03

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.

Se utilizan todas las denominaciones posibles y están disponibles. No poder utilizar


Una denominación particular, como las monedas de cinco centavos, puede llevar a una solución subóptima.

Una solución de algoritmo voraz para el problema de la mochila


Anteriormente en este capítulo examinamos el problema de la mochila y proporcionamos respuestas recursivas.

y soluciones de programación dinámica para ello. En esta sección, examinaremos cómo podemos

Implementar un algoritmo codicioso para resolver este problema.

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.

218 | Capítulo 14: Algoritmos avanzados

[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 0­1 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:

1. La mochila tiene una capacidad W y los artículos tienen valores V y pesos w.

2. Clasifique los artículos según la relación v/w .

3. Considere los artículos en términos de proporción decreciente.

4. Tome la mayor cantidad posible de cada artículo.

En la Tabla 14­1 se muestran los pesos, valores y proporciones de cuatro elementos.

Tabla 14­1. Artículos 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:

función ksack(valores, pesos, capacidad) {


var carga = 0; var
i = 0; var w =
0; mientras
(carga < capacidad && i < 4) {
si (pesos[i] <= (capacidad­carga)) { w += valores[i];
carga += pesos[i];

} else
{ var r = (capacidad­carga)/pesos[i];
w + = r * valores[i];
carga += pesos[i];

}+

+i; } devolver w;
}

Algoritmos voraces | 219

[Link]­[Link]
Machine Translated by Google

var items = ["A", "B", "C", "D"]; var values


= [50, 140, 60, 60]; var weights = [5,
20, 10, 12]; var capacity = 30;
print(ksack(values,
weights, capacity)); // muestra 220

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?

220 | Capítulo 14: Algoritmos avanzados

[Link]­[Link]
Machine Translated by Google

Índice

usando con reduce(), 25


Símbolos
addEdge() función, clase Graph, 143 listas de
% (signo de
adyacencia, 142 matriz
porcentaje) operador módulo, 3, 99
de adyacencia, 142 función
() (paréntesis), que se utilizan para hacer referencia a claves de matriz,
advance(), 86 algoritmos
89
voraces (ver
* (asterisco)
algoritmos voraces) razones para
operador de multiplicación, 3 +
estudiar, ix append() función,
(signo más)
listas, 35, 37 operadores aritméticos,
operador de suma, 3 ­
3 aritmética, desempeño en
(signo menos)
JavaScript, 3 matriz de listas de adyacencia, 142
operador de resta, 3 / (barra)
banco de pruebas de matriz, 159
operador
CArray implementación
de división, 3 0­1
de la clase, 159 generar datos aleatorios,
problemas, 219 23 /
161 programa que usa la clase
12 + (3.14159 * .24, 57 [ ]
CArray, 161 Constructor Array(), 14 Función
(corchetes) acceder a
[Link](), 14 matrices,
elementos de matriz, 15 crear
13–33 acceder y escribir elementos
matrices con el operador [], 14 hacer
de matriz, 15
referencia a claves de matriz, 89
acceder a elementos con bucles, 7 agregar y
eliminar elementos del medio

de, 21
Un tipo de datos abstracto (ADT), 35
funciones de acceso, 17 agregar elementos a, 19

búsqueda de un valor, 17 operaciones de agregación en, 16


función add() crear, 14 crear

Clase de diccionario, 90 a partir de cadenas, 15 crear


Establecer nuevas matrices a partir de matrices existentes, 18
definido, 13
clase, 114 usando la función push() con, 32

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

Clase de diccionario basada en, 89 búsquedas, 129–132


soluciones provisionales de programación dinámica en, para un valor específico, 131
211 para el valor mínimo y máximo, 130 árboles binarios,
en tablas hash xi, 121, 122 ejemplo de, 123
encadenamiento separado de elementos, 107 representar gráficos
usando sondeo lineal con, 109 funciones como, 141 ámbito de bloque,
de iterador que devuelven nuevas matrices, 25 funciones simulando en JavaScript, 9 búsqueda en amplitud,
de iterador, que no generan matrices, 23 irregulares, 30 148 que conduce a las rutas
limitaciones más cortas, 149 búsqueda de fuerza
de, 73 de objetos, 30 bruta, 187 (véase también
ordenación de búsqueda secuencial) encontrar la
elementos de matriz, 22 subcadena común más larga, 211 ordenación de
Implementación de clase de cola basada en, 60 burbuja, 162 función
eliminación de elementos de, 20 bubbleSort(), 163 añadir llamada
búsqueda de valor mínimo y máximo, 192 búsqueda a la función toString(), 164 ordenar 10 números
de valor con bubbleSort(), 163
máximo, 192 búsqueda de valor mínimo, 190 Función buildChain(), clase HashTable, 107
almacenamiento de datos en objetos, 31
almacenamiento de archivo de
texto en, 203 representaciones
Cadena C ,
de cadena de, 18 bidimensional,
107 función charCodeAt(), 100
213 creación, 27
función checkOut(), aplicación de película (ejemplo), 45 nodos
procesamiento
de elementos, 28 uso, 13 uso
secundarios, 122
para tablas
nodos izquierdo y derecho, 123
hash, 101 lista de vértices, 153
asignación de una listas enlazadas circularmente,
85 creación, 85
matriz a otra matriz, 16
Ciura, Marcin, 171
Función clear()
Clase de diccionario, 92
Función B back(), 86 ejemplo de uso, 93
Clase de cola, 61 listas, 35
conversiones de base para números, 53 colas, 60
función betterHash(), 102 eliminar todos los elementos de una lista, 39
claves enteras de hash, 105 Clase de pila, 51
búsqueda binaria, 196–204 ejemplos de código y materiales complementarios, xii
algoritmo, 197 problema de cambio de monedas,
función binSearch() que muestra el valor del punto 217 colisiones,
medio, 199 97 evitar, 101
conteo de ocurrencias, 200 manejo en hash, 107–111 sondeo
Definición de algoritmo en JavaScript, 198 búsqueda lineal, 109 encadenamiento
de datos textuales, 202 árboles de separado, 107 en hash de
búsqueda binaria claves enteras, 105 en función hash
creación de una implementación, 124–129 simple, 100 función compare(), 22
BST y clases de nodos, 125 función concat(), 18
recorrer un árbol binario de búsqueda, 126 concatenación de cadenas,
contar ocurrencias, 134 definido, usando la función reduce(),
124 eliminar 25

nodos de formulario, 132 funciones constructoras, 10

222 | Índice

[Link]­[Link]
Machine Translated by Google

función contains() que función display() para


comprueba si hay miembros de un listas enlazadas, 77 función displayList() para
conjunto, 116 listas enlazadas circularmente, 86
listas, 40, 44 copia, copias superficiales frente a listas doblemente enlazadas, 81–
copias profundas, 16 campo de conteo 85 mostrar elementos en orden
en un objeto Node, inverso , 82
Clase
134 función count() que cuenta las ocurrencias en una búsqueda binaria, LList como, 83
200
Clase de diccionario, 91 Programación dinámica, xi, 207–217
ejemplo de uso, 93 cálculo de números de Fibonacci, 208–211 solución
Clase de cola, 66 de programación dinámica, 209 ineficiencia de la
contar ocurrencias en un conjunto de datos, 134, 200 solución recursiva, 208 solución recursiva, 208
función curPos(), 40 comparación temporal de

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

Estructuras de datos D , razones para estudiar, tipos


de datos ix , elementos de matriz, 14 E
Objeto de fecha, función getTime(), 168 aristas que conectan nodos en árboles,
construcciones de decisión, 121 en gráficos,
4 copias profundas, 139 representar, 142
16 operador de propiedad de elemento, clase Node, 75
eliminación, 39 función elementos (lista), 35
delete(), 90 búsqueda en listas vacías, 35
profundidad, 145 función conjuntos vacíos,
dequeue(), 59 definición para cola de 113 función empty(), comprobar si la cola está vacía, 61 función
prioridad, 70 implementación para clase Queue, end(), 40 función
61 diccionarios, xi, 89–95 enqueue(), 59 implementación
Clase de diccionario, 89 para la clase Queue, 61 función every(), 23
función add(), 90
capacidad de ordenación por adición,
93 funciones auxiliares, 91
definición de clase actualizada con, 92
Función factorial (), definición recursiva de, 56
clear(), 92
Números de Fibonacci, computación, 208–211 función
count(), 91
filter() uso con
ejemplos de uso de count() y clear(), 93 función
matrices, 26 uso con
delete(), 90 función find(),
cadenas, 27 función find()
90 función keys(), 90
árbol de búsqueda
programa que utiliza, 91
binaria, 131
función showAll(), 90
Clase de diccionario, 90
diferencia, 114 función
difference(), clase encontrar elementos en una lista, 38
encontrar nodos en listas enlazadas, 76
Set, 119 dígrafos, 139 gráficos dirigidos, 139
función findLast(), lista doblemente enlazada, 82 función
ordenación
findMax(), 192 función findMin(),
topológica de, 151
191 función findPrevious(), listas
problemas discretos de mochila
enlazadas, 78 estructuras de datos FIFO (primero
(problemas 0­1), 219
en entrar, primero en salir), 70

Í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

Clase de cola, 61 separado, 107


Clase HashTable, 98
alcance de función, 8
mejor función hash, 101 elección
funciones, 7
constructor, 10 de una función hash, 98 definición
completa, 99 hash de claves
parámetros, 8
recursivo, 10 enteras, 103 función
simpleHash(), 100 almacenamiento
y recuperación de datos en una tabla hash,
G 106

ciclos generales, 140 Prueba de la función betterHash(), 102


función get(), clase HashTable, 106 definir nombres y números de teléfono, 98 nodo
para trabajar con encadenamiento separado, 109 principal, 74
reescritura para sondeo lineal, 110 función Método de Horner, uso para una mejor función hash, 101
getElement() que muestra el
elemento de lista actual, 36 uso en recorrido
de lista, 40 función getMax(),
árbol de búsqueda binaria, 130 función getMin(), árbol
I
sentencias if, 4
de búsqueda binaria, 130 función getRandomInt(),
sentencia if­else if, 5
103 función getTime(), objeto Date,
sentencia if­else, 4
168 alcance global, 8 variables globales, 8
función indexOf(), 17
algoritmos gráficos,
comprobar la matriz para los datos solicitados,
xi gráficos, xi, 139–157
114 índices, matriz, 13
definiciones de términos,
Infinito, 178
139 encontrar la ruta
recorrido inorder, 126
más corta, 149
función de recorrido inOrder(), 127 función
insert()
Clase de gráficos, 141–145
BST (árbol de búsqueda binaria), clase 125
construcción de un gráfico,
para lista doblemente enlazada,
143 representación de aristas,
81 inserción de elementos de
142 representación de vértices,
lista, 39 inserción de nodos en listas
141 sistemas del mundo real modelados por,
enlazadas,
141 búsqueda, 145–149
76 listas, 35 ordenación
búsqueda en amplitud, 148
por inserción, 167 operador
búsqueda en profundidad,
instanceof, 44 claves enteras,
145 ordenamiento topológico, 151–
hash, 103 modo interactivo (shell de JavaScript), 1
157 algoritmo para, 152

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,

lista, 41 funciones iterativas , 23 74 enlazadas doblemente,


que no generan matrices, 23 que 81–85 mostrando elementos en orden inverso, 82
devuelven Clase LList como lista doblemente enlazada,
nuevas matrices, 25 some() 83 nodo principal,

74 inserción y eliminación de nodos, 74


diseño basado en objetos, 75–81
código completo para la clase Node y la clase LList,
J matrices dentadas, 30 80 inserción
Entorno de programación JavaScript, 1
de nodos, 76 clase de
Prácticas de programación en JavaScript, 2
lista enlazada (LList), 76
Shell de JavaScript,
Clase LList y programa de prueba, 77
sitio de descarga x ,
Clase de nodo, 75
1 modo interactivo, 1
eliminación de nodos, 78
función join(), 18 otras funciones para, 86 uso

en lugar de matrices, 73 enlaces,


74
Clase de lista, 36–42
Valor clave K , 123
pares clave­valor, 89 función append(), 37 función
clear(), 39 función
función keys(), 90, 94 solución
contains(), 40 encontrar un
de programación
dinámica del problema de la mochila, 215 solución elemento en una lista, 38 función
insert(), 39 función length(),
del algoritmo voraz, 218 solución
recursiva, 214 38 función remove(), 38
función auxiliar find(), 37
recorrer, 40 listas, 35–47 tipo de
L
datos abstractos
estructuras de datos de último en entrar, primero en salir (ADT), 35 crear
(LIFO), 49 función lastIndexOf(), 18 una aplicación basada en listas, 42–
nodos de hoja, 122 47 administrar un quiosco de alquiler de videos, 43–
eliminar del árbol de búsqueda binaria, 132 nodos 46 leer archivos de texto, 42 iterar a través de, 41
izquierdos, 123 propiedad listSize, 35
matrices de decrementar después de
propiedades de eliminar un elemento de
longitud, la lista, 38 incrementar después de agregar un elemento de
14, 15 listas, la lista,
35 colas, 60 37
pilas, 50 claves de cadena en diccionarios y, ámbito local, 9
91 usar push() en lugar de extender matrices, 19 función bucles, 6
length() que devuelve en gráficos, 140
el número de elementos en la lista, 38
Clase de pila, 51
niveles (en árboles), 123
sondeo lineal, 109 Función M map(), 25
Clase de matemáticas
búsqueda lineal, 187
Función floor(), 161 Función
(véase también búsqueda secuencial)
random(), 161 Funciones
matemáticas, ejemplos de uso, 3

Índice | 225

[Link]­[Link]
Machine Translated by Google

biblioteca de matrices de, 30


matemáticas, 3 valor máximo, búsqueda de, 130, 192 definiendo, ejemplo completo, 11
miembros (de conjuntos), 113 Hashing de direccionamiento abierto, 109
Algoritmo de ordenación por fusión, 176–181

de abajo hacia arriba, 177

Implementación de JavaScript de abajo hacia arriba


Palíndromos P , uso de una pila para comprobar, 54
Mergesort, 178
parámetros, 8
agregar mergeSort() y mergeArrays() a la clase
nodos principales, 122
CArray, 179 función
Distribuciones de Pareto, 194
mergeSort(), 179 de arriba hacia
caminos
abajo, 176 valor
en gráficos, 140
mínimo, búsqueda, 130, 190 hash modular, 99 función
determinar, 150 en
moveTo(), 40
árboles, 122

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(),

Oh clase HashTable, 99, 102

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

de la función constructora para, 61 función específico, 131 búsqueda, xi


dequeue(), 61 función algoritmos de
empty(), 61 función búsqueda, 187–205 búsqueda binaria,
enqueue(), 61 funciones 196–204 búsqueda secuencial,
front() y back(), 61 función toString(), 61 187–196
uso, asignación de parejas búsqueda de gráficos, 145–149
para baile cuadrado, 63–67 búsqueda en amplitud, 148 que

conduce a los caminos más cortos, 149


colas, xi, 59–72 búsqueda en profundidad, 145
implementación de la clase Queue basada en matrices, 60 Sedgewick, Robert, 174
inserción y eliminación de elementos, 60 prioridad, ordenamiento por

70–72 ordenación selección, 165 función selectionSort(),


de datos con, 67–70 166 datos autoorganizados, 193
implementación de ordenación por encadenamiento separado,
radix, 68 uso de la implementación de la clase Queue, 63–67 107 búsqueda secuencial, 187–196
Algoritmo de ordenación rápida, 181– ejecución de la función seqSearch(), 188
186 algoritmo y pseudocódigo para, 182 modificación de seqSearch() para devolver la posición
elección del pivote y ordenación de datos en torno a él, encontrada,
184 189 ejecución en una matriz con la función seqSearch(),
Ordenar datos con la función qSort(), 183 187 prueba

de programa de seqSearch() modificado, 189 búsqueda


de un archivo de texto, 203
R
búsqueda de valores mínimos y máximos, 190 uso de
ordenamiento por
datos
radix, 67 implementación usando colas, 68–70
autoorganizados, 193 conjuntos, xi, 113–
números aleatorios, 103
120 definiciones de
función random(), clase Math, 161 función
términos, 113 operaciones
read(), 43, 202 sistemas del
realizadas en, 114
mundo real modelados por gráficos, 141 recursión, 10
Implementación de la clase Set, 114
demostración con
Función add(), 114
la clase Stack, 56 función reduce(), 24 función
Función difference(), 119 Función
reduceRight(), 25 función
intersect(), 117 Más operaciones
remove()
de conjunto, 116 Función
remove(), 115 Función subset(),
Clase de diccionario, 90
118 Función union(), 116
para lista doblemente enlazada, 82
Copias superficiales, 16
Clase de lista,

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

ejecutar shellsort() en un conjunto de datos pequeño, Stack class, 50

función shellsort() 173 , agregar a la clase CArray, implementación completa, 51 matriz


172 dataStore, 51 función
usando una secuencia de espacios más grande y un peek(), 51 función pop(),
conjunto 51 función push(), 51, 51
de datos más grande, 173 probar la implementación, 52
función shift(), 21 eliminación de un elemento del frente de una usar, 53 verificar palíndromos, 54 demostrar
matriz, 60 algoritmo de ruta más corta, recursión,
149 función show(), 86 56 conversiones de bases múltiples, 53
Nodo de clase en árbol de búsqueda binaria, 124 pilas, xi, 49–57 implementación de, Stack
showAll() función, clase Dictionary, 90, 94 showDistro() función, class, 50–53 operaciones, 49 usar la clase
clase HashTable, 99 showGraph() función, clase Graph, 143, Stack, 53–57 cadenas

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

Algoritmo de ordenación por fusión, 176–181


Algoritmo de ordenación rápida, 181–186
Algoritmo Shellsort, 171–176 banco de
pruebas de matriz, 159
básico, 161–170
ordenación de burbuja,
162 ordenación por
inserción, 167 ordenación Archivos de texto T , lectura,
42 datos textuales, búsqueda, 202 esta
por selección, 165 comparaciones de tiempos de, 168
palabra clave, 11
Motor JavaScript SpiderMonkey, sitio de descarga
x , 1 función splice(), comparaciones de tiempo de algoritmos de ordenamiento básicos,
168
18, 37 agregar o eliminar
función toFixed(), 29 elemento
elementos de matriz, 21 definir para la cola de prioridad,
superior de una pila, 49 ordenación
70 otros usos para, 19 función split() dividir
un archivo de texto en topológica, 151–157 algoritmo para,
implementación, 152–156 prueba de programa
palabras, 203 crear
implementación de, 156 función toString(), 18 agregar a
matrices a partir de cadenas, 15
la llamada de función
bubbleSort(), 164 definir para la cola de prioridad, 71

228 | Índice

[Link]­[Link]
Machine Translated by Google

visualización de elementos en una cola, 61 gráficos desordenados, 140


visualización de elementos de lista, 36, función unshift(), 20 función
38 recorrido de una lista, update(), clase BST, 135
40 recorrido de árboles binarios de búsqueda,
126 recorrido de árboles,
V
122 árbol de búsqueda binaria, 126
árboles funciones que devuelven valores, definición, 7
palabra clave var
árboles binarios, 122
en declaraciones de variables, 3
(ver también árboles binarios)
definidos, 121 omitir al definir variables, 9 declaración e
inicialización
niveles en, 123
de variables, 3 global, 9 alcance, 8
partes de, 122
vértices,
función trim(), 43 matrices
139
bidimensionales, 27–30 creación, 27
fuertemente
procesamiento
conectado, 140
de elementos de, 28
Representación de clases de vértice,
141 funciones vacías, 7
U

indefinido, elementos de matriz, 27


unión, 114
Página web de este libro, xiii bucle
función union(), clase Set, 116 universo,
113 while, 6

Índice | 229

[Link]­[Link]
Machine Translated by Google

Acerca del autor


Michael McMillan es profesor de sistemas informáticos en el Pulaski Technical College de North Little Rock,
Arkansas. También es profesor adjunto de ciencias de la información en la Universidad de Arkansas en Little
Rock. Antes de dedicarse al mundo académico, fue programador y analista en el Hospital Infantil de
Arkansas, donde trabajó en informática estadística y análisis de datos.

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]

También podría gustarte