Você está na página 1de 127

Contenido

1. Definicin de trminos utilizados en computacin....................................................................2


2. Descripcin de archivos, registros y campos..........................................................................13
3. Etapas para la implementacin de un sistema.......................................................................15
4. Organizacin de datos............................................................................................................ 15
Recursividad........................................................................................................................... 17
Arrays..................................................................................................................................... 23
Listas...................................................................................................................................... 25
Pilas........................................................................................................................................ 37
Colas....................................................................................................................................... 41
Conjuntos................................................................................................................................ 49
rboles.................................................................................................................................... 65
Grafos..................................................................................................................................... 75
5. Manejo de algoritmos.............................................................................................................. 80
Introduccin a los algoritmos.................................................................................................. 80
Ordenacin............................................................................................................................. 82
Bsqueda................................................................................................................................ 92
Backtracking........................................................................................................................... 96
Divide y vencers................................................................................................................. 103
6. Manejo de Diagramas de flujo............................................................................................... 115
7. Dispositivos perifricos........................................................................................................... 120
Fuentes de informacin............................................................................................................... 123

1. Definicin de trminos utilizados en computacin


BIOS
Sistema bsico de entrada y salida.
Dual-channel

Es una tcnica que suma las velocidades de dos memorias, resultando en una mayor performance.
Tipos de fuentes existentes
AT y ATX.
UPS
Es un dispositivo que en el caso de falta de energa elctrica, contina alimentando la PC durante un
tiempo determinado.

Jerarqua de datos
El almacenamiento de datos sigue una jerarqua lineal, comenzando con la porcin ms pequea y
terminando con la ms grande.

Carcter
Un carcter es una letra del alfabeto, dgito numrico o un smbolo especial. Tambin puede ser un
espacio de almacenamiento para un carcter ("BYTE"). Es el dato ms pequeo utilizado por el hombre.
Campo
El campo consiste de un grupo de caracteres unidos tratados como una sola unidad. Define la
localizacin fsica de almacenamiento de una unidad de datos o informacin. Describen una propiedad de
una entidad o sujeto para el cual los datos han sido almacenados. Un ejemplo es: la nmina. Hay un campo
para el nombre, un campo de horas trabajadas, un campo de cuotas, un campo para la tasa de impuestos, etc.
Durante el proceso, los caracteres de cada uno sern usados como una unidad.

Registros
Es la coleccin de campos unidos o grupos de datos que son tratados como una sola unidad. En nuestro ejemplo de
nmina, un rcord podra tener los siguientes campos: nmero de seguro social, nombre
del empleado, direccin del empleado, das acumulados por enfermedad, das acumulados por vacaciones
regulares, pago de cuotas, descuentos de seguro social, descuentos de tasa de impuestos, etc. Se tendra un
registro por cada empleado.
Archivo
Un archivo o fichero es una estructura de datos que reside en memoria secundaria, consistente en un conjunto
de informaciones estructuradas en unidades de acceso denominadas registros, todos del mismo tipo y en
nmero indeterminado.
Base de Datos
La gestin de archivo ha demostrado ser ineficaz para cubrir las necesidades de almacenamiento y proceso de datos
en la actualidad. Los sistemas de informacin de las empresas son cada vez ms grandes y
complejos, y su gestin mediante el uso de archivos presenta algunos inconvenientes, como el hecho de que los
datos estn repartidos por distintos departamentos de la empresa, lo que supone tener que desplazarse o
solicitar cada vez que lo necesitan en un lugar diferente de donde se generan; adems, pueden estar repartidos
varias veces, con el inconveniente de que su actualizacin dar lugar a inconsistencias sino se realiza en todos los

lugares en que se ubican. Las aplicaciones que se crean debern tener en cuenta las
distintas estructuraciones de los datos, en los cambios que se realicen tanto en hardware como software,
presentarn numerosas complicaciones. Otro problema derivado de la dispersin de los datos radica en la dificultad
para establecer sistemas de control y seguridad en los mismos.

Los sistemas de base de datos pretenden dar solucin a los problemas expuestos mediante la integracin de los
archivos de datos, de su estructura y de las aplicaciones que los manejan.
Podemos definir una base de datos como un conjunto integrado de datos interrelacionados, junto con una serie de
aplicaciones accesibles simultneamente por diferentes usuarios y programas.

Los programas de computacin que permiten instalar una base de datos y trabajar con ella de forma eficaz reciben
el nombre de programas gestores de bases de datos, aunque simplemente se les denomina tambin bases de datos.
Por tanto, aunque les designemos con el mismo nombre, no hay que confundir el
programa que nos permite trabajar con una base de datos con la base de datos propiamente dicha. Existen muchos
programas gestores de bases de datos: Claris FileMaker, que proviene del entorno Macintosh; Visual dBase, sucesor
del legendario dBase, que fue durante muchos aos el preferido de los usuarios; Lotus Approach, Borland Paradox,
Oracle, etc., pero el ms utilizado en computadoras personales el MS-Acces.

En una base de datos la informacin se puede organizar siguiendo diferentes criterios: de forma jerrquica, en
red, relacionales; siendo las ltimas las ms utilizadas en computadoras personales. En una base de datos
relacional la informacin se estructura en ficheros, registros y campos.

Componentes funcionales:
1. Unidad central de proceso: Es el elemento principal o centro neurlgico de una computadora y su misin consiste en
realizar y coordinar todas las operaciones del sistema informtico. Costa de los siguiente elementos:
a.) Procesador: Es el elemento encargado del control y ejecucin de las operaciones y est formado por

a.1. Unidad de control: Es la parte del procesador encargada de gobernar al resto de las unidades, a dems de
interpretar y ejecutar las instrucciones controlando su secuencia
a.2. Unidad Aritmtica lgica: Es la parte del procesador encargado de realizar todas las operaciones elementales de
tipo aritmtico y de tipo lgico
b.) Memoria Central: Tambin denominada memoria interna o principal, es el elemento de la unidad central del proceso
encargado de almacenar los programas y los datos necesarios para que el sistema informtico realice un determinado
trabajo. Es importante decir aqu que para que un programa pueda ser ejecutado en una computadora tiene que estar en
esta memoria, as como los datos que necesitan ser procesados en ese momento.
2. Elementos de entrada: Tambin llamadas perifricos o unidades de entrada, son los dispositivos encargados de
introducir los datos y los programas desde el exterior a la memoria central para su utilizacin. Estos dispositivos, adems
de recibir la informacin del exterior, la preparan para que la mquina pueda entenderla de la forma correcta. Un ejemplo
lo constituye un teclado.
3. Memoria Auxiliar: Son los dispositivos de almacenamiento masivo de informacin que se utilizan para guardar datos y
programas en el tiempo para su posterior utilizacin. La caracterstica principal de los soportes que manejan estos
dispositivos es la de retener la informacin a lo largo del tiempo mientras que se desee, recuperndose cuando lo sea
requerida y sin que se pierda, aunque el dispositivo quede desconectado de la red elctrica. Tambin se denomina
memoria secundaria. Ejemplo; disquete, disco duros, discos pticos, cintas, etc.

4. Elemento de salida: Son aquellos dispositivos cuya misin es recoger y proporcionar al exterior los datos de salida o
resultado de los procesos que se realicen en el sistema informtico. Tambin se denomina perifricos o unidades de
salida, Ejemplo; monitor, impresora, etc.

Atendiendo a la configuracin o estructura interna de una computadora puede, clasificares de la siguiente forma:

COMPUTADORAS ANALGICAS. Son aquellas que manejan seales elctricas analgicas proporcionales a medidas
fsicas de tipo continuo. Su programacin en la mayora de los casos est en su propio cableado y se utiliza
fundamentalmente para controlar procesos y en problema de simulacin.
COMPUTADORAS DIGITALES. Maneja seales elctricas de tipo digital. Se programa por medio de lenguajes de
programacin y su utilizacin contiene cualquier tipo de trabajos; por tanto, configuran el grupo de computadoras de tipo
general. En la actualidad, mas de 95 por 100 de las computadoras son de este tipo.
COMPUTADORAS HBRIDAS. Poseen caractersticas de las dos anteriores. Suelen estar constituidas de una
computadora digital que procesa informacin analgica, para lo cual tiene sus entradas y salidas controladas por medio
de convertidores analgico - digitales y digitales analgicos. Las computadoras digitales por su potencia de calculo,
capacidad de almacenamiento interno y numero de perifricos que pueden soportar

TEOREMA FUNDAMENTAL DE LA NUMERACIN


Se trata de un teorema que relaciona una cantidad expresada en cualquier sistema de numeracin con la misma
cantidad expresada en el sistema decimal.
Supongamos una cantidad expresada en un sistema cuya base es B y representamos por Xi cada uno de los dgitos que
contiene dicha cantidad, donde el subndice indica la posicin del dgito con respecto a la coma decimal, posicin que
hacia la izquierda de la coma se numera desde O en adelante y de 1 en 1, Y hacia la derecha se numera desde -1 y con
incremento -l.
El Teorema Fundamental de la Numeracin dice que el valor decimal de una cantidad expresada en otros sistemas de
numeracin, viene dado por la frmula:

... + X4 * B4 + X3 * B3 + X2 * B2 + X1 * B1 + Xo * B0+ X-l * B -1 + X-2 * B-2 + ...

EJEMPLOS
a) Supongamos la cantidad 201.1 expresada en el sistema de numeracin de base 3 que utiliza los dgitos O. 1 y 2 para
la representacin de cantidades. Cul ser la representacin de la misma cantidad en el sistema decimal?
201.1(3 = 2 * 32 + 0* 31 + 1 * 30 + 1 * 3-1 = 18 + O + 1 + 0.333 = 19.333(10

b) Supongamos la cantidad 516 expresada en el sistema de numeracin de base 7 que utiliza los dgitos O, 1, 2, 3, 4, 5 Y
6 para la representacin de cantidades. Cul ser la representacin de la misma cantidad en el sistema decimal?
516(7 = 5 * 72 + 1 * 71 + 6 * 70 = 245 + 7 + 6 = 258(10

c) Supongamos la cantidad 0.111 expresada en el sistema de numeracin de base 2 que '.utiliza los dgitos O y 1 para la
representacin de cantidades. Cul ser la representacin de la misma cantidad en el sistema decimal?
0,111(2 = 1 * 2-1 + 1 * 2-2 + 1 * 2-3 = 0.5 + 0.25 + 0.125 = 0.875(10
El teorema aplicado a la inversa nos sirve para obtener la representacin de una cantidad decimal en cualquier otro
sistema de numeracin, por medio de divisiones sucesivas por la base.
EL SISTEMA BINARIO
EI sistema binario es el sistema de numeracin que utilizan internamente los circuitos Digtales que configuran el
hardware de las computadoras actuales; por ello ser el sistema al que prestaremos mayor atencin y estudio.
La base 0 numero de smbolos que utiliza el sistema binario es 2, siendo estos los siguientes:
01
Cada cifra 0 digito de un numero representado en este sistema se denomina bit -contraccin de binary digit).
Para la medida de cantidades de informacin representadas en binario se utilizan una serie de mltiplos del bit que
poseen nombre propio; estos son los siguientes:
* Nibble 0 cuarteto. Es eI conjunto de cuatro bits (100 I).
* Byte u octeto. Es el conjunto de ocho bits (10101010).
* Kilobyte (Kb). Es el conjunto de 1024 bytes (1024 * 8 bits).
* Megabyte (Mb). Es el conjunto de 1024 kilobytes (10242 * 8 bits).
* Gigabyte (Gb). Es el conjunto de 1024 megabytes (10243 * 8 bits).
* Terabyte (Tb). Es el conjunto de 1024 gigabytes (10244 * 8 bits).
La razn por la que se utiliza el factor multiplicador 1024 en lugar de 1000, como sucede en otras magnitudes fsicas, es
por ser el mltiplo de 2 mas prximo a 1000, cuestin importante desde el punto de vista electrnico.
210 = 1.024
La tabla de equivalencias entre los mltiplos del bit es la siguiente:
1 nibble = 4 bits.
I byte = 2 nibbles = 8 bits.
1 kilobyte = 1024 bytes = 1024 * 8 bits = 8192 bits.
1 megabyte = 1024 kilobytes = 10242 bytes = 10242 * 8 bits = 8 388 608 bits.
I gigabyte = 1024 megabytes = 10242 kilobytes = 10243 bytes = 10243 * 8 bits = 8. 589 .934.592 bits.
1 terabyte = 1024 gigabytes = 10242 megabytes = 10243 kilobytes = 10244 bytes = 10244 * 8 bits = 8 .796.093 .022. 208
bits.
EI byte u octeto es considerado como la unidad bsica de medida de la informacin representada mediante este sistema.
EJEMPLOS
a) Que numero decimal representa el numero binario 1001.1 utilizando el TFN?
1001.1(2= 1 * 23 + 0 * 22 + 0 * 21 + 1 * 20 + 1 * 2-1 = 8 + 0 + 0 + I + 0.5 = 9.5(10
b) Una computadora personal posee una memoria interna de 8 megabytes. ,Que capacidad de memoria tiene,

expresada en numero de bytes y bits?


Capacidad = 8 * 1024 * 1024 = 8 388 608 bytes * 8 = 67 .108.864 bits

Suma binaria
Es semejante a sumar en el sistema decimal, con la diferencia de que se manejan solo 2 dgitos (0 y 1), de tal forma que
cuando el resultado excede de los smbolos utilizados se agrega el exceso (denominado acarreo) a la suma parcial
siguiente hacia la izquierda.
Las tablas de sumar en el sistema binario son las siguientes:

Tabla
0 +

del

1 +

+
0

Tabla

del
0

10

(0

con acarreo

1)
..

NOTA
Realizamos en paralelo a la aritmtica binaria su equivalente en decimal que nos servir como comprobacin.
EJEMPLOS
a) Sumar los nmeros binarios 100100 (36) y 10010 (18).

1 0 0 1 0 0

36

1 0 0 1 0

18

1 1 0 1 1 0

54

Obsrvese que no hemos tenido ningn acarreo en las sumas parciales.

b) Sumar los nmeros binarios 11001 (25) y 10011 (19).


11

Acarreos

1 1 0

1 0 0

1 .

1 1

25

19
44

c) Sumar los nmeros binarios 101110 (46) Y 1110 (14).


1 0 1 1 1 0
+

1 1 1

1 1 1 1 0 0

46
+

14
60

Almacenamiento prioritario
La memoria primaria est directamente conectada a la unidad central de proceso de la computadora. Debe estar
presente para que la CPU funcione correctamente. El almacenamiento primario consiste en tres tipos de
almacenamiento:
* Los registros del procesador son internos de la unidad central de proceso. Contienen informacin que las unidades
aritmtico-lgicas necesitan llevar a la instruccin en ejecucin. Tcnicamente, son los ms rpidos de los
almacenamientos de la computadora, siendo transistores de conmutacin integrados en el chip de silicio de la CPU que
funcionan como "flipo-flopi" electrnicos.

* La memoria cach es un tipo especial de memoria interna usada en muchas unidades centrales de proceso para
mejorar su eficiencia o rendimiento. Parte de la informacin de la memoria principal se duplica en la memoria cach.
Comparada con los registros, la cach es ligeramente ms lenta pero de mayor capacidad. Sin embargo, es ms rpida
pero de mucha menor capacidad que la memoria principal. Tambin es de uso comn la memoria cach multi-nivel - la
"cach primaria" es ms pequea, rpida y cercana al dispositivo de procesamiento; la "cach secundaria" es ms
grande y lenta, pero ms rpida y mucho ms pequea que la memoria principal.

* La memoria principal contiene los programas en ejecucin y los datos con que operan. La unidad aritmtico-lgica
puede transferir informacin muy rpidamente entre un registro del procesador y localizaciones del almacenamiento
principal, tambin conocidas como "direcciones de memoria". En las computadoras modernas se usan memorias de
acceso aleatorio basadas en electrnica del estado slido, que est directamente conectada a la CPU a travs de un
"bus de memoria" (como se ve en el diafragma) y de un "pus de patos". Al bus de memoria tambin se le llama bus de
direccin o bus frontal, (Front Size Bus) y ambos buses son "superautopistas" digitales de alta velocidad. Los mtodos de
acceso y la velocidad son dos de las diferencias tcnicas fundamentales entre memoria y dispositivos de
almacenamiento masivo.

El computador es una mquina automtica para el tratamiento electrnico de la informacin. Procesador de datos que
puede ejecutar clculos complejos, incluyendo operaciones aritmticas y lgicas sin intervencin de un operador humano
durante la ejecucin. Se compone bsicamente de Memoria, CPU y dispositivos de entrada/salida.
Memoria (Llamada
tambin
almacenamiento
primario).
Es la unidad donde estn almacenadas las instrucciones y los datos necesarios para poder realizar un determinado
proceso. Esta constituido por muchas celdas o posiciones de memoria, numeradas de forma consecutiva, capaces de
retener, mientras la computadora esta encendida, la informacin depositada en ella. A la numeracin de cada celda se lo
denomina direccin de memoria y mediante esta direccin se puede acceder a ellas independiente de su posicin.

S utiliza para cuatro funciones. Tres de ellas se relacionan con los datos que se estn procesando.
1. Los datos se introducen a un rea de almacenamiento de entrada y permanecen en ese lugar hasta el momento en
que
se
vayan
a
procesar.
2. Un espacio de memoria de trabajo es como una hoja de papel para hacer cuentas y contendr los datos que se estn
procesando,
as
como
los
resultados
intermedios
de
dicho
procesamiento.
3. Un rea de almacenamiento de salida guarda los resultados finales del procesamiento hasta que puedan ser liberados.
4. Adems de estas reas relacionadas con los datos, la seccin de almacenamiento primario tambin contiene un rea
de
almacenamiento
de
programas,
que
guarda
las
instrucciones
de
procesamiento.
Las distintas reas que se destinan a las cuatro funciones generales mencionadas no se fijan por lmites fsicos
incorporados
en
la
memoria,
sino
que
pueden
variar
de
acuerdo
a
las
aplicaciones.
rea
de
Entrada
rea
de
Trabajo
rea
de
Programas
rea
de
Salida
Existen dos tipos de memorias construidas internamente y hay mas de una manera de clasificarlas. Una manera de
clasificaras es por su permanencia. Algunos chips de memoria siempre conservan los datos que tienen aun cuando la
computadora esta apagada. Esta memoria se llama no voltil. Otros chips, que forman la mayor parte de la memoria de
una microcomputadora, s pierden su contenido cuando la energa de la computadora se apaga. La memoria de estos
chips es voltil.

Otra

forma

de

clasificarse

es

por

su

utilizacin:

1. Memoria
RAM
(Random
Access
Memory)
Es la memoria voltil del computador, es decir que ante la ausencia de energa los datos se pierden. A este tipo de
almacenamiento primario se lo denomina pastillas de memoria de acceso aleatorio (RAM) debido a que pueden elegirse
al azar cualquiera de las localidades de la pastilla y emplearse y leer datos e instrucciones en forma directa.
2. Memoria
ROM
(Read
Only
Memory)
A diferencia de la Memoria RAM, estas conservan la informacin almacenada cuando se interrumpe el suministro de
energa. Es posible leer de una memoria ROM las instrucciones de microprograma de control, pero no aceptar la
introduccin
de
datos
o
instrucciones
por
parte
de
los
usuarios
de
la
computadora.
Una de las principales razones por las que una computadora necesita el ROM es para saber que hacer cuando se
enciende.
El ROM contiene una serie de instrucciones de arranque que verifica que el resto de la memoria este funcionando
correctamente,
que
busca
dispositivos
de
hardware
y
el
sistema
operativo.
El tipo ms sencillo de memoria ROM viene incluido en la computadora desde su fabricacin como parte del sistema y
no puede ser modificado ni alterado por los usuarios. Estas pastillas se utilizan ampliamente como medio de
almacenamiento
de
juegos
de
vdeo,
etc.
Otros

tipos

de

memoria:

Memoria
de
alta
velocidad (memoria
cache
o
buffer)
Es un tipo de memoria ms rpida y ms costosa por carcter almacenado que el almacenamiento primario. Estos

circuitos de alta velocidad se emplean como libreta de notas para almacenar temporalmente datos e instrucciones con
alta
probabilidad
de
utilizarse
muchas
veces
durante
un
proceso.
Funciona de la siguiente manera, cuando la CPU solicita informacin de la RAM, el controlador de la cache intercepta la
solicitud y busca la informacin dentro de su propia memoria. Si la informacin no se encuentra all, la CPU extrae los
datos requeridos de la memoria RAM y tambin manda una copia de regreso a la cach. La siguiente vez que la CPU
necesita la misma informacin la cache encuentra esta informacin y la enva rpidamente a la CPU, dejando la RAM
fuera
del
ciclo.
Clasificacin

de

la

memoria

ROM

PROM.
(programmable
read-only
memory)
Las operaciones muy largas o importantes que se haban estado ejecutando con lentitud mediante programas, pueden
convertirse en microprogramas y grabarse en una pastilla PROM de tal manera que las tareas se ejecuten en una
fraccin del tiempo que se requera antes. Las grabaciones hechas en una de estas pastillas, ya no es posible
modificarlas.
EPROM (erasable
and
programmable
read-only)
Es igual que la pastilla PROM con la diferencia que debe ser expuesto a la luz ultravioleta por un periodo de tiempo para
que
el
programa
que
fue
grabado
se
borre
y
pueda
ser
utilizado
nuevamente.
EEPROM (electrically
erasable
programmable
read-only
memory)
Igual a los anteriores con la diferencia que se puede volver a programar mediante ciertos impulsos elctricos especiales
Clasificacin
RAM

de

la

memoria

RAM
DINMICA

Un tipo de memoria fsica usado en la mayora de los ordenadores personales. El trmino dinmico indica que la
memoria
debe
ser
restaurado
constantemente
(re
energizada)
o
perder
su
contenido.
DRAM
utiliza
tiempos
de
acceso
de
cerca
de
60
nanosegundos
RAM

ESTTICA

Abreviatura para la memoria de acceso al azar esttica. SRAM es un tipo de memoria que es ms rpida y ms confiable
que la DRAM ms comn (RAM dinmica). El trmino se deriva del hecho de que no necesitan ser restaurados como
RAM
dinmica.
La
RAM

RAM
esttica
dinmica,
pero

es

ms
requiere

rpida
ms

y
potencia

menos
y

voltil
es

que
ms

la
costosa.

SRAM puede dar los tiempos de acceso de hasta slo 10 nanosegundos.


ALMACENAMIENTO SECUNDARIO
UNIDADES DE DISCO DISCOS DUROS:
Los discos duros son dispositivos de almacenamiento secundario con una superficie circular y plana, que se utilizan
para registrar informacin masiva, programas y datos en computadores personales o microcomputadoras.
El disco duro es conocido tambin como Hard Disk, el disco fijo como Fixed Disk y la unidad de disco duro como Hard
Drive.
Estos discos consisten en un soporte rgido sobre el que se deposita una pequea pelcula de material magnetizable
(xidos o metales), que permite la grabacin de los datos por magnetizacin.

Los avances en las tecnologas de pelcula magntica delgada, permiten que los datos sean grabados en dominios cada
vez ms pequeos y que estos dispositivos sufran menos daos durante el proceso de lectura-escritura, gracias a que la
dureza de sus superficies de grabacin es dos veces superior a la de las tradicionales superficies de xido de hierro.
Todas estas mejoras estn facilitando disponer de discos con mayores densidades de almacenamiento y con unos
tiempos de acceso sensiblemente inferiores.
Los soportes de estos dispositivos giran a gran velocidad, tpica mente 3.000 rpm. No obstante, y al contrario de lo que
sucede con los disquetes, las cabezas de lectura-escritura no tocan el soporte sino que se desplazan a una distancia del
orden de 10-4 mm. de la superficie del disco, gracias al aire que desplaza el disco al girar a gran velocidad, evitando as
su desgaste. Para evitar el choque de la cabeza con la superficie del disco en los cortes de alimentacin, se dispone de
un sistema que separa las cabezas antes de que el disco pierda velocidad.
Los discos duros magnticos representan el medio de almacenamiento ms extendido entre ordenadores personales,
estaciones de trabajo, servidores, miniordenadores y grandes ordenadores centrales, debido a sus excelentes
caractersticas de capacidad, fiabilidad y velocidad de acceso a los datos. En definitiva, los discos duros son el
dispositivo de almacenamiento masivo que ofrece la mxima relacin capacidad de almacenamiento/coste, con tiempos
de acceso muy rpidos.
Junto con las cabezas de lectura-escritura va asociada toda una circuitera electrnica que se encarga de gestionar las
tareas de almacenamiento. Esta circuitera es la controladora, cuya funcin es el proceso del flujo de datos que pasan a
travs de ella con objeto de darle formato para su transmisin y registro, pero sin alterar su significado.
Entre los principales estndares que definen estos dispositivos de almacenamiento figuran el SCSI (Small Computer
Systems Interfase), el ESDI (Enhanced Small Disk Interfase), el IDE (Integrated Drive Electronics) y el EIDE (Enhanced
Integrated Drive Electronics).

Los discos presentan las siguientes caractersticas:


1. Las diferencias conceptuales entre las unidades de discos duros y las de discos "flexibles" no son tan notorias.
2. Los discos duros son siempre fijos, es decir, no se pueden ni insertar ni extraer y se instalan en el interior de la
microcomputadora.
3. Se pueden encontrar sin embargo, discos removibles o disk pack, cuyas caractersticas resultan de la combinacin de
las presentadas por los discos duros y los discos flexibles.
4. El material utilizado en su fabricacin est hecho a base de una aleacin de aluminio, recubierto con una capa
magntica.
5. El tiempo medio de acceso est relacionado al sistema actuador del cabezal, del dimetro del disco y la velocidad de
giro.
6. El dimetro de los discos duros ms comn es de 3.5".
7. Algunos fabricantes de discos duros incorporan la memoria cach, por el cual se almacenan los sectores ms ledos
en una memoria RAM dedicada para este fin.
8. Los tipos de sistema de codificacin que ms se usan son:
MFM (Modificacin de Frecuencia Modulada)
RLL (Largo Recorrido Limitado)
ARLL (Largo Recorrido Avanzado Limitado)
9. El rendimiento de un disco duro se puede justificar por la velocidad de rotacin del disco duro y el tamao del buffer o
cach integrado.
10. Entre las interfases de un disco duro se pueden encontrar, en el mercado, los estndares IDE, EIDE y SCSI.
La interfase IDE/EIDE o super IDE soporta hasta 2 discos. Son baratos, rpidos, y la unidad y el controlador son
compatibles. Adems, se pueden conectar a la tarjeta madre por medio de la ranura o SLOT ISA que se encuentra en la
MAIN BOARD.
La interfase SCSI soporta hasta 7 dispositivos como discos duros, unidades de CD-ROM, TAPES, etc. A diferencia del

anterior que slo soporta dos dispositivos.


DISCO DE 3/2 HD:
Ha permitido disponer de una memoria de masa a bajo coste y que ofrece una gran rapidez en la lectura y grabacin de
los datos almacenados o que se desea almacenar. Es un medio de almacenamiento magntico removible y el mtodo
principal para distribuir software para computadoras personales. Poseen menos capacidad de almacenamiento que los
discos de 5 .
DISCO MAGNTICO: Dispositivo de almacenamiento primario para computadores. Al igual que la cinta, este disco se
graba de forma magntica y puede volver a usarse una y otra vez.
Son los dispositivos de almacenamiento secundario ms utilizados. Van desde los pequeos diskettes de 3 hasta los
discos duros de cientos de MB de capacidad.
DISCOS PTICOS
Los discos pticos funcionan de manera similar que los magnticos pero con rayos lser. Al escribir informacin en un
disco de plstico o metlico, un rayo lser va perforando o marcando la superficie del disco; cada una de las marcas o
perforaciones indican un bit de informacin. Los discos pticos se fabrican en diferentes tamaos (3.5, 4.75, 5.25, 8, 12,
14 de dimetro) y se manejan tres tipos de unidades:

CD-ROM(Compact Disk- Read Only Memory): Los discos compactos de slo lectura, ms conocidos como CD-ROM, son
soportes que el usuario puede utilizar nicamente para extraer informacin, que pueda leer directamente o que pueda
copiar en otras unidades. Son ledos por medio de luz. Por lo general son ms lentos que los discos magnticos, pero
sus capacidades de almacenamiento son mayores por pulgada cuadrada. Hay fijos y removibles.
WORM( Write Once, Read Many): Estas unidades de disco permiten al usuario grabar informacin sin que esta se pueda
borrar; es decir, slo registran informacin una vez. Si el usuario satura el disco, deber comprar otro para poder guardan
ms informacin. La capacidad de almacenamiento de estas unidades varna desde 122 hasta 6400 MB.
DISCO PTICO BORRABLE: Es lo ms avanzado en almacenamiento ptico y aprovecha todas las ventajas que ofrece
la tecnologa; por tanto puede grabar y borrar informacin, permitiendo que un disco se use muchas veces como si fuera
un diskette, pero con una capacidad de almacenamiento que va de 281 hasta 3200 MB. Esta tecnologa, sin embargo,
aun no ha logrado la precisin, la velocidad y el costo de los discos magnticos, pr lo que se utiliza poco.

DISCO COMPACTO (CD):


Son fsicamente muy parecidos los CD-ROM. Se diferencian de estos en la manera de guardar informacin. Son
empleados en las grabaciones musicales. Utilizan un lser para grabar permanentemente informacin sobre una
superficie plstica. Se emplean para almacenar unos 600 Megabytes.

VIDEO DISCO DIGITAL (DVD):


Son una variante de los CD se emplean para almacenar videos.
Su principal utilidad en una computadora est en las aplicaciones de multimedia, ya que incluye imgenes en movimiento
y sonido.
Permite guardar ms informacin en un mnimo espacio. Se diferencia del CD-ROM en la forma de almacenar datos. Hay
de dos clases: DVD de slo lectura y DVD video para pelculas.

CD-WRITER:

Es la unidad lectora escritora de CD recordable, la cual se utiliza para escribir sobre la superficie en blanco del CD.
Esta tambin puede ser usada como unidad de CD-ROM.
Es conocida tambin como quemador.
DISCO VIRTUAL:
Son unidades creadas en la memoria RAM y sirven para simular una unidad de disco duro.
Los discos virtuales son muy rpidos, pero son temporales; toda la informacin que contengan se perder al apagar la
computadora.
DISCO CACH:
Porcin de la memoria en que la computadora almacena la informacin que de usa de manera frecuente. Al copiar la
informacin del disquete o del disco duro y almacenarla en la cach del disco , la computadora podr tener acceso a la
informacin de manera ms rpida. Algunas computadoras cuentan con una memoria cach de disco integrada. Por lo
general entre mayor sea la cach inmediata del disco, ms rpido podr correr la computadora.

UNIDADES DE CINTA
Debido a que el tamao de los discos duros est en aumento constante, la prctica de hacer copias de seguridad de los
archivos en la computadora usando disquetes se ha vuelto costosa y tediosa. No obstante, la unidad para cinta
constituye un dispositivo ideal para hacer copias de seguridad hacer copias de seguridad, permitindole almacenar varios
Gigabytes de informacin en una sola cinta.
Adems, las unidades para cinta para las computadoras en casa son bastante econmicas, y algunos programas de
software para hacer copias de seguridad le permite programar el copiado durante la noche o en el fin de semana sin
necesidad de que usted est presente.
De todos los tipos diferentes de unidades para cinta, el cartucho de unidad para cinta de un cuarto de pulgada (QIC) es el
ms comn. Estas unidades almacenan datos magnticamente en cinta que es de un cuarto de pulgada de ancho.
Cuando usted ejecuta una aplicacin de respaldo en cinta, el programa lee la tabla de asignacin de archivos (FAT) en la
unidad de disco duro para encontrar los archivos que usted le indic que deba copiar.
El controlador de la unidad para cinta inicia el movimiento de la cinta. Se envan los datos al cabezal de lectura de la
unidad para cinta. Por lo general, la cinta contiene de 20 a 32 pistas paralelas. Los datos se escriben en una sola pista
hasta llegar al fin de la cinta y entonces pasa a la prxima pista exterior. Sigue este proceso hasta que todos los datos
quedan escritos en la cinta. Despus el programa de respaldo actualiza el directorio de la cinta con las ubicaciones de
pista y segmento correspondientes a todos los archivos.

LENGUAJE DE PROGRAMACIN
Un lenguaje de programacin consiste en todos los smbolos, caracteres y reglas de uso que permiten a las personas
"comunicarse" con las computadoras.
Lenguajes de bajo nivel.
Son lenguajes totalmente dependientes de la mquina, es decir que el programa que se realiza con este tipo de
lenguajes no se pueden migrar o utilizar en otras maquinas, al estar prcticamente diseados a la medida del hardware,
aprovechan al mximo las caractersticas del mismo.
Dentro de este grupo se encuentran:
El lenguaje maquina: este lenguaje ordena a la mquina las operaciones fundamentales para su funcionamiento, consiste
en la combinacin de 0's y 1's para formar las ordenes entendibles por el hardware de la maquina, es mucho ms rpido

que los lenguajes de alto nivel, la desventaja es que son bastantes difciles de manejar y usar, adems de tener cdigos
fuente enormes donde encontrar un fallo es casi imposible.
El lenguaje ensamblador es un derivado del lenguaje maquina y est formado por abreviaturas de letras y nmeros
llamadas mnemotcnicos, con la aparicin de este lenguaje se crearon los programas traductores para poder pasar los
programas escritos en lenguaje ensamblador a lenguaje mquina, como ventaja con respecto al cdigo mquina es que
los cdigos fuentes eran ms cortos y los programas creados ocupaban menos memoria, las desventajas de este
lenguaje siguen siendo prcticamente las mismas que las del lenguaje ensamblador, aadiendo la dificultad de tener que
aprender un nuevo lenguaje difcil de probar y mantener.
Lenguajes de alto nivel
Se encuentran ms cercanos al lenguaje natural que al lenguaje mquina, estn dirigidos a solucionar problemas
mediante el uso de EDDs (Estructuras Dinmicas de Datos), son estructuras que pueden cambiar de tamao durante la
ejecucin del programa, permiten crear estructuras de datos que se adapten a las necesidades reales de un programa,
son lenguajes independientes de la arquitectura de la computadora, es decir que en principio, un programa escrito en un
lenguaje de alto nivel, se puede migrar de una mquina a otra sin ningn tipo de problema.
Estos lenguajes permiten al programador olvidarse por completo del funcionamiento interno de la maquinas para la que
estn diseando el programa, solo necesitan un traductor que entiendan el cdigo fuente como las caractersticas de la
maquina, suelen usar diferentes tipos de datos para la programacin, hay lenguajes de propsito general y de propsito
especifico.
Lenguajes de Medio nivel
Estos lenguajes se encuentran en un punto medio entre los dos anteriores, dentro de estos lenguajes podra situarse C
ya que puede acceder a los registros del sistema, trabajar con direcciones de memoria, todas ellas caractersticas de
lenguajes de bajo nivel y a la vez realizar operaciones de alto nivel.

LENGUAJE MQUINA
El lenguaje mquina es el nico que entiende directamente la computadora, utiliza el alfabeto binario que consta de los
dos nicos smbolos 0 y 1, denominados bits; fsicamente, se materializan con tensiones comprendidas entre 0 y 4.0
voltios y entre 4 y 5 voltios, respectivamente. Para representar datos que contengan una informacin se utilizan una serie
de unos y ceros cuyo conjunto indica dicha informacin.
COMPILADOR
Un compilador es un programa informtico que traduce un programa escrito en un lenguaje de programacin a otro
lenguaje de programacin, o tambin genera aplicaciones que sean directamente utilizables en un ordenador o
computadora. Un compilador lee el cdigo fuente creado en un determinado lenguaje de programacin, lo interpreta,

comprueba su sintaxis y traduce a lenguaje o cdigo mquina toda la serie de instrucciones, generando el archivo
ejecutable final.
Son programas que leen el cdigo fuente y lo traducen o convierten a otro lenguaje, estos programas muestran los
errores existentes en el cdigo fuente.

TIPOS DE COMPILADORES
Esta taxonoma de los tipos de compiladores no es excluyente, por lo que puede haber compiladores que se adscriban a
varias categoras:

Compiladores cruzados: generan cdigo para un sistema distinto del que estn funcionando.

Compiladores optimizadores: realizan cambios en el cdigo para mejorar su eficiencia, pero manteniendo la
funcionalidad del programa original.

Compiladores de una sola pasada: generan el cdigo mquina a partir de una nica lectura del cdigo fuente.

INTRPRETES
Un programa intrprete o traductor, analiza directamente la descripcin simblica del programa fuente y realiza las
instrucciones dadas.
Es un programa que traduce un lenguaje de alto nivel al lenguaje de mquina de una computadora, el programa siempre
permanece en su forma original y traduce cuando est en la fase de ejecucin instruccin por instruccin.
ENSAMBLADORES
Los ensambladores son programas que procesan los enunciados del programa origen en lenguaje ensamblador y los
traducen en archivos en lenguaje mquina que son ejecutados por un microprocesador o un microcontrolador. Los
ensambladores permiten que los programas origen se escriban y se editen en una computadora para generar un cdigo
ejecutable en otra computadora. El archivo en lenguaje objeto ejecutable resultante se carga y se ejecuta en el sistema
destino.
LENGUAJE INTERPRETADO
Un lenguaje de programacin es, por definicin, diferente al lenguaje mquina. Por lo tanto, debe traducirse para que el
procesador pueda comprenderlo. Un programa escrito en un lenguaje interpretado requiere de un programa auxiliar, que
traduce los comandos de los programas segn sea necesario.
LENGUAJE COMPILADO
Un programa escrito en un lenguaje "compilado" se traduce a travs de un programa anexo llamado compilador que, a su
vez, crea un nuevo archivo independiente que no necesita ningn otro programa para ejecutarse a s mismo. Este
archivo se llama ejecutable.
Un programa escrito en un lenguaje compilado posee la ventaja de no necesitar un programa anexo para ser ejecutado
una vez que ha sido compilado. Adems, como slo es necesaria una traduccin, la ejecucin se vuelve ms rpida.
Sin embargo, no es tan flexible como un programa escrito en lenguaje interpretado, ya que cada modificacin del archivo
fuente requiere de la compilacin del programa para aplicar los cambios.
LENGUAJES INTERMEDIARIOS

Algunos lenguajes pertenecen a ambas categoras (LISP, Java, Python...) dado que el programa escrito en estos
lenguajes puede, en ciertos casos, sufrir una fase de compilacin intermediaria, en un archivo escrito en un lenguaje
ininteligible y no ejecutable. Los applets Java, pequeos programas que a menudo se cargan en pginas Web, son
archivos compilados que slo pueden ejecutarse dentro de un navegador Web.
ALGORITMO
Se entiende como un conjunto finito de pasos a que especifican una secuencia de operaciones a realizar en orden para
resolver un problema especfico o clases de problemas, es decir, es un mtodo para la solucin del problema.
Es un conjunto de operaciones ordenadas de modo tal en que puedan resolver un problema, son pocos los saben que el
trmino deriva del matemtico astrnomo y gegrafo musulmn al Jwarizmi.
La ciencia que estudia los algoritmos se llama Algoritmia, siendo la famosa Mquina de Turing la que ha formalizado sus
conceptos en un modelo computacional.
ALGORITMO
Se entiende como un conjunto finito de pasos a que especifican una secuencia de operaciones a realizar en orden para
resolver un problema especfico o clases de problemas, es decir, es un mtodo para la solucin del problema.
Es un conjunto de operaciones ordenadas de modo tal en que puedan resolver un problema, son pocos los saben que el
trmino deriva del matemtico astrnomo y gegrafo musulmn al Jwarizmi.
La ciencia que estudia los algoritmos se llama Algoritmia, siendo la famosa Mquina de Turing la que ha formalizado sus
conceptos en un modelo computacional.
PROGRAMACIN ESTRUCTURADA
La programacin estructurada es un mtodo disciplinado de escribir programas que sean claros, que se demuestre que
sean correctos y fciles de modificar.
La programacin estructurada consiste en dividir los programas en mdulos y se basa en el desarrollo de programas que
van de lo general a lo particular, es decir, del conjunto al elemento, es decir de un todo a lo especfico.

2. Descripcin de archivos, registros y campos


CAMPO

Es un espacio de almacenamiento para un dato en particular. En las base de datos un


campo es la mnima unidad de informacin a la que se puede acceder; un campo o un
conjunto de ellos forman un registro, donde pueden existir campos en blanco, siendo
ste un error del sistema operativo. En las hojas de clculo (como los programas de
Excel) los campos son llamados celdas.

La mayora de los campos tienen atributos asociados a ellos. Por ejemplo, algunos
campos son numricos mientras otros almacenan texto, tambin vara el tamao de
estos.
Tipos de campo
Un campo puede ser:
Texto
En este tipo de campo se puede almacenar texto o combinaciones de texto y nmeros,
as como nmeros que no requieran clculos, como los nmeros de telfono o cdigos
postales.
Alfanumricos: Contiene cifras y letras.
Numrico: Se utiliza para almacenar datos numricos que pueden ser utilizados en todo
tipo de operaciones matemticas, excepto operaciones monetarias(para este tipo de
operaciones
se
utiliza
el
tipo
de
campo
Monetario).
Si/No: Son campos que solo contienen uno de dos valores, como: Si/No,
Verdadero/Falso, Activado/Desactivado
Registro
Un registro es un conjunto de campos que contienen datos que pertenecen a una misma
repeticin de una entidad.
Representa un objeto nico de datos implcitamente estructurado en una tabla.
La estructura implcita de un registro y el significado de los valores de sus campos exige
que dicho registro se a entendido como una sucesin de datos, uno en cada columna de
la tabla.
ARCHIVO
Un Archivo es identificado por un nombre y la descripcin de la carpeta o directorio que lo
contiene.
Son los equivalentes digitales de los archivos en tarjetas, papel o microfichas del entorno
de oficina tradicional, facilitan una manera de organizar los recursos usados para
almacenar permanentemente datos en un sistema informtico.

Gestores de archivos
Son utilidades que le permiten manipular archivos directamente. Le permite mover, crear
,borrar y renombrar archivos y carpetas.
Cada sistema informtico proporciona al menos un programa gestor de archivos, para su
sistema gestor de archivos nativo.

3. Etapas para la implementacin de un sistema

ETAPAS DEL CICLO DE VIDA


Fases del ciclo de vida del software
El software vara en complejidad dependiendo de lo que se requiere hacer. La mayora de los paquetes de software
consisten de muchos mdulos, cada mdulo es responsable de ciertas tareas especficas. Para simplificar el desarrollo
del software, se sigue una serie de pasos que se llaman el ciclo de vida del software, y que son seguidos durante el
tiempo que un software existe (figura 21).

Figura. 21. Fases del desarrollo del software. Una vez que se concluye, prcticamente se inicia nuevamente al entender

las modificaciones que deben hacerse.


1) Introduccin al ciclo de vida del software: Regularmente hay cinco pasos en el ciclo de vida del software:
a) Entender el problema. En la primera fase del ciclo de vida del software, se enlistan las tareas que el software debe
desarrollar, los problemas a ser resueltos, y en esta fase se estudian sus causas y efectos.
La tarea especfica que se requiere del programa se deriva del establecimiento del problema, que es una descripcin
concisa del problema en cuestin. El software debe proporcionar una solucin utilizable a este problema.
b) Disear el programa. En la fase de diseo, el objetivo es conocer las relaciones entre los mdulos del programa, y
garantizar que se cumplen cabalmente los requerimientos solicitados de una manera eficiente, lgica y completa.
Los diseadores de software consideran los recursos de hardware y software disponibles para poder alcanzar su
objetivo. Si se llega a la conclusin de que no es posible utilizar algn hardware o software, se planea utilizar una
estrategia diferente.
Primero se disea la estructura general del programa. Entonces el problema se divide en subproblemas en tareas ms y
ms pequeas hasta que tengan un tamao manejable.
c) Codificar el programa. Durante la fase de codificacin, el programa se escribe en un lenguaje de programacin. Hay
muchos lenguajes de programacin, cada uno de ellos es especialista en algn tipo de problemas. Por ejemplo,
FORTRAN es especialista en clculos numricos, mientras que LISP es especialista en problemas de inteligencia
artificial y procesamiento simblico. El cdigo del programa debe desarrollar la tarea solicitada, y debe ser legible de
modo que otros programadores lo puedan mantener. Los programas se escriben usualmente en mdulos separados,
cada mdulo desarrolla alguna tarea especfica y debe funcionar independientemente y en relacin con el resto del
programa.
d) Probar el programa. Durante la fase de pruebas, el programa se ejecuta y se revisa. Las tareas deben ejecutarse sin
errores en los resultados y tambin sin errores fatales. Los defectos en los programas se llaman bugs.
Se examinan primero los mdulos de manera individual, en forma independientemente, luego, se prueba todo el
programa para encontrar bugs que puedan ocurrir en la interaccin de los mdulos. Cuando se encuentra un bug, se
asla la causa y se resuelve. Este proceso se llama depuracin. El programador se debe asegurar de al resolver un bug,
no se crean otros ms en alguna otra parte del programa.
e) Mantener el programa. Durante la fase de mantenimiento, de determina cualquier error y deficiencia en el programa, y
se realizan cualquier accin para resolverla, mientras se preserva la integridad del programa. El uso de notas de diseo,
cdigo bien documentado y variables entendibles, pueden ayudar al mantenimiento futuro del programa.

Anlisis
Es necesario determinar que elementos intervienen en el sistema a desarrollar, as como su estructura, relaciones,
evolucin en el tiempo, detalle de sus funcionalidades, ... que van a dar una descripcin clara de qu sistema vamos a
construir, qu funcionalidades va a aportar y qu comportamiento va a tener. Para ello se enfocar el sistema desde tres
puntos de vista relacionados pero diferentes:

Funcional.

Esttico.

Dinmico.

Diseo
Tras la etapa anterior ya se tiene claro que debe hacer el sistema, ahora tenemos que determinar como va a hacerlo
(cmo debe ser construido el sistema?; aqu se definirn en detalle entidades y relaciones de las bases de datos, se
pasar de casos de uso esenciales a su definicin como casos expandidos reales, se seleccionar el lenguaje ms
adecuado, el Sistema Gestor de Bases de Datos a utilizar en su caso, libreras, configuraciones hardware, redes,
etc.). Observacin:Aunque todo debe ser tratado a su tiempo, y sera muy deseable que las decisiones correspondientes
en esta etapa fueran tomadas precisamente en esta etapa, muchas veces nos vamos a encontrar con unas decisiones
previamente impuestas sobre lenguaje, plataforma, etc. Unas veces se dirn justificadas en simple poltica de empresa y
por mantener "compatibilidad" en lo que respecta a los dems proyectos de la propia empresa, y en otras ocasiones por
rumores de que tal o cual herramienta mejorara la velocidad de desarrollo u otro aspecto de inters (en parte de los
casos no sern rumores con fundamento o estudios previos realizados al efecto, sino ms bien debidos a la propia
publicidad como consejera).

Implementacin
Llegado este punto se empieza a codificar algoritmos y estructuras de datos, definidos en las etapas anteriores, en el
correspondiente lenguaje de programacin y/o para un determinado sistema gestor de bases de
datos. Observacin:Lamentablemente en la actualidad, ao 2.000, quedan bastantes empresas en las que, tras una
reunin comercial en que tan solo se ha conseguido recabar una breve lista de requerimientos, a pesar de tener que
enfrentarse a proyectos grandes-medios, se pasa directamente a la etapa de implementacin; son proyectos guiados por
el riesgo que supone adoptar un modelo de ciclo de vida de codificar-corregir (code and fix) donde se eliminan las fases
de especificaciones, anlisis y diseo con la consiguiente prdida de control sobre la gestin del proyecto.

4. Organizacin de datos
Introduccin
Para procesar informacin en un computador es necesario hacer una abstraccin de los datos que
tomamos del mundo real -abstraccin en el sentido de que se ignoran algunas propiedades de los objetos
reales, es decir, se simplifican-. Se hace una seleccin de los datos ms representativos de la realidad a
partir de los cuales pueda trabajar el computador para obtener unos resultados.
Cualquier lenguaje suministra una serie de tipos de datos simples, como son los nmeros enteros,
caracteres, nmeros reales. En realidad suministra un subconjunto de stos, pues la memoria del
ordenador es finita. Los punteros (si los tiene) son tambin un tipo de datos. El tamao de todos los tipos
de datos depende de la mquina y del compilador sobre los que se trabaja.
En principio, conocer la representacin interna de estos tipos de datos no es necesaria para realizar un
programa, pero s puede afectar en algunos casos al rendimiento.

Qu es una estructura de datos?


Se trata de un conjunto de variables de un determinado tipo agrupadas y organizadas de alguna manera
para representar un comportamiento. Lo que se pretende con las estructuras de datos es facilitar un
esquema lgico para manipular los datos en funcin del problema que haya que tratar y el algoritmo para
resolverlo. En algunos casos la dificultad para resolver un problema radica en escoger la estructura de
datos adecuada. Y, en general, la eleccin del algoritmo y de las estructuras de datos que manipular
estarn muy relacionados.
Segn su comportamiento durante la ejecucin del programa distinguimos estructuras de datos:
- Estticas: su tamao en memoria es fijo. Ejemplo: arrays.
- Dinmicas: su tamao en memoria es variable. Ejemplo: listas enlazadas con punteros, ficheros, etc.
Las estructuras de datos que trataremos aqu son los arrays, las pilas y las colas, los rboles, y algunas
variantes de estas estructuras. La tabla que se encuentra al comienzo de esta pgina agrupa todas las
estructuras de datos que emplearn los algoritmos explicados en esta web.

Tipos Abstractos de Datos


Los tipos abstractos de datos (TAD) permiten describir una estructura de datos en funcin de las
operaciones que pueden efectuar, dejando a un lado su implementacin.
Los TAD mezclan estructuras de datos junto a una serie de operaciones de manipulacin. Incluyen una
especificacin, que es lo que ver el usuario, y una implementacin (algoritmos de operaciones sobre las
estructuras de datos y su representacin en un lenguaje de programacin), que el usuario no tiene
necesariamente que conocer para manipular correctamente los tipos abstractos de datos.
Se caracterizan por el encapsulamiento. Es como una caja negra que funciona simplemente conectndole
unos cables. Esto permite aumentar la complejidad de los programas pero manteniendo una claridad
suficiente que no desborde a los desarrolladores. Adems, en caso de que algo falle ser ms fcil
determinar si lo que falla es la caja negra o son los cables.
Por ltimo, indicar que un TAD puede definir a otro TAD. Por ejemplo, en prximos apartados se indicar
como construir pilas, colas y rboles a partir de arrays y listas enlazadas. De hecho, las listas enlazadas
tambin pueden construirse a partir de arrays y viceversa.

Como leer estos temas


Se recomienda tratar en profundidad los temas de estructuras de datos antes de entrar de lleno en los
algoritmos, si bien es muy recomendable al menos leer la introduccin a los algoritmos y algunos de los
temas que suelen ser ms conocidos, tales como la ordenacin y la bsqueda.
En primer lugar es fundamental el conocimiento de la recursividad, inherente a muchas estructuras de
datos y algoritmos. Es este por tanto el primer tema que hay tratar. Posteriormente conviene estudiar los
temas de arrays y listas enlazadas, puesto que son bsicos para implementar el resto de estructuras de
datos. Los temas de pilas y colas son fundamentales, y mucho ms sencillas de entender y aplicar que los
temas restantes. Como temas avanzados (y no por ello menos importantes, adems de que requieren el
conocimiento de los temas anteriores) figuran los grafos, si bien en la seccin de estructuras de datos se
estudiarn slo sus implementaciones y recorridos, los rboles y los montculos, una implementacin
especial de rbol. De manera independiente puede leerse el tema de conjuntos.

Asimismo, suele ocurrir que unos temas entran en el terreno de otros, y por lo tanto sern habituales las
referencias a otros temas, ya sean a estructuras de datos y a algoritmos.

Nota: Adems de las estructuras de datos que se estudian aqu existe otra que es el fichero; dicha
estructura no se estudiar en estas pginas. Slo se utilizarn ficheros en algunos casos -nicamente de
tipo ASCII-, y exclusivamente para facilitar la introduccin de los datos que se requieren para resolver un
problema y tambin para mostrar los datos producidos por el algoritmo que procesa esos datos.

Recursividad
Se dice que algo es recursivo si se define en funcin de s mismo o a s mismo. Tambin se dice que nunca
se debe incluir la misma palabra en la definicin de sta. El caso es que las definiciones recursivas
aparecen con frecuencia en matemticas, e incluso en la vida real. Un ejemplo: basta con apuntar una
cmara al monitor que muestra la imagen que muestra esa cmara. El efecto es verdaderamente curioso,
en especial cuando se mueve la cmara alrededor del monitor.
En matemticas, tenemos mltiples definiciones recursivas:
- Nmeros naturales:
(1)
1
es
(2) el siguiente nmero de un nmero natural es un nmero natural

nmero

natural.

- El factorial: n!, de un nmero natural (incluido el 0):


(1)
si
n
(2) si n > 0 entonces: n! = n (n-1)!

entonces:

0!

Asimismo, puede definirse un programa en trminos recursivos, como una serie de pasos bsicos, o paso
base (tambin conocido como condicin de parada), y un paso recursivo, donde vuelve a llamarse al
programa. En un computador, esta serie de pasos recursivos debe ser finita, terminando con un paso
base. Es decir, a cada paso recursivo se reduce el nmero de pasos que hay que dar para terminar,
llegando un momento en el que no se verifica la condicin de paso a la recursividad. Ni el paso base ni el
paso recursivo son necesariamente nicos.
Por otra parte, la recursividad tambin puede ser indirecta, si tenemos un procedimiento P que llama a
otro Q y ste a su vez llama a P. Tambin en estos casos debe haber una condicin de parada.
Existen ciertas estructuras cuya definicin es recursiva, tales como los rboles, y los algoritmos que
utilizan rboles suelen ser en general recursivos.
Un ejemplo de programa recursivo en C, el factorial:
int factorial(int n)
{
if (n == 0) return 1;
return n * factorial(n-1);
}
Como se observa, en cada llamada recursiva se reduce el valor de n, llegando el caso en el que n es 0 y
no efecta ms llamadas recursivas. Hay que apuntar que el factorial puede obtenerse con facilidad sin
necesidad de emplear funciones recursivas, es ms, el uso del programa anterior es muy ineficiente, pero
es un ejemplo muy claro.

A continuacin se expone un ejemplo de programa que utiliza recursin indirecta, y nos dice si un nmero
es par o impar. Al igual que el programa anterior, hay otro mtodo mucho ms sencillo de determinar si un
nmero es par o impar, basta con determinar el resto de la divisin entre dos. Por ejemplo: si hacemos
par(2) devuelve 1 (cierto). Si hacemos impar(4) devuelve 0 (falso).
/* declaracion de funciones, para evitar errores */
int par(int n);
int impar(int n);
int par(int n)
{
if (n == 0) return 1;
return impar(n-1);
}
int impar(int n)
{
if (n == 0) return 0;
return par(n-1);
}
En Pascal se hace as (notar el uso de forward):
function impar(n : Integer) : Boolean; forward;
function par(n : Integer) : Boolean; forward;
function par(n : Integer) : Boolean;
begin
if n = 0 then par := true
else par := impar(n-1)
end;
function impar(n : Integer) : Boolean;
begin
if n = 0 then impar := false
else impar := par(n-1)
end;

Ejemplo:
si
hacemos
par(2)
impar(1)
par(0) -> devuelve 1 (cierto)

la

llamada

impar(3)

hace

las

siguientes

llamadas:

Por lo tanto 3 es un nmero impar.

Qu pasa si se hace una llamada recursiva que no termina?


Cada llamada recursiva almacena los parmetros que se pasaron al procedimiento, y otras variables
necesarias para el correcto funcionamiento del programa. Por tanto si se produce una llamada recursiva
infinita, esto es, que no termina nunca, llega un momento en el que no quedar memoria para almacenar

ms datos, y en ese momento se abortar la ejecucin del programa. Para probar esto se puede intentar
hacer esta llamada en el programa factorial definido anteriormente:
factorial(-1);
Por supuesto no hay que pasar parmetros a una funcin que estn fuera de su dominio, pues el factorial
est definido solamente para nmeros naturales, pero es un ejemplo claro.

Cundo utilizar la recursin?


Para empezar, algunos lenguajes de programacin no admiten el uso de recursividad, como por ejemplo el
ensamblador o el FORTRAN. Es obvio que en ese caso se requerir una solucin no recursiva (iterativa).
Tampoco se debe utilizar cuando la solucin iterativa sea clara a simple vista. Sin embargo, en otros
casos, obtener una solucin iterativa es mucho ms complicado que una solucin recursiva, y es entonces
cuando se puede plantear la duda de si merece la pena transformar la solucin recursiva en otra iterativa.
Posteriormente se explicar como eliminar la recursin, y se basa en almacenar en una pila los valores de
las variables locales que haya para un procedimiento en cada llamada recursiva. Esto reduce la claridad
del programa. An as, hay que considerar que el compilador transformar la solucin recursiva en una
iterativa,
utilizando
una
pila,
para
cuando
compile
al
cdigo
del
computador.
Por otra parte, casi todos los algoritmos basados en los esquemas de vuelta atrs y divide y vencers son
recursivos, pues de alguna manera parece mucho ms natural una solucin recursiva.
Aunque parezca mentira, es en general mucho ms sencillo escribir un programa recursivo que su
equivalente iterativo. Si el lector no se lo cree, posiblemente se deba a que no domine todava la
recursividad. Se propondrn diversos ejemplos de programas recursivos de diversa complejidad para
acostumbrarse a la recursin.

Ejercicio
La famosa sucesin de Fibonacci puede definirse en trminos de recurrencia de la siguiente manera:
(1)
Fib(1)
=
(2) Fib(n) = Fib(n-1) + Fib(n-2) si n >= 2

Fib(0)

Cuantas llamadas recursivas se producen para Fib(6)?. Codificar un programa que calcule Fib(n) de forma
iterativa.
Nota: no utilizar estructuras de datos, puesto que no queremos almacenar los nmeros de Fibonacci
anteriores a n; s se permiten variables auxiliares.

Ejemplos de programas recursivos


- Dados dos nmeros a (nmero entero) y b (nmero natural mayor o igual que cero) determinar a^b.
int potencia(int a, int b)
{
if (b == 0) return 1;
else return a * potencia(a, b-1);
}

La condicin de parada se cumple cuando el exponente es cero. Por ejemplo, la evaluacin de potencia(2,
3) es:
potencia(-2,
3)
->
(-2)

potencia(-2,
2)
->
(-2)

(-2)

potencia(-2,
1)
->
(-2)

(-2)

(-2)

potencia(-2,
0)
->
(-2)

(-2)

(-2)

1
y

la

(-2)

(-2)
(-2)
<
(-2)
(-2)
< (-2)

< -8 /=/ potencia(-2,3)

vuelta

de
1 /=/ (-2)
(-2) /=/ (-2)
4 /=/ (-2)

la

recursin
(-2)
(-2)

(-2)

se

tiene:

potencia(-2,0)
potencia(-2,
1)
potencia(-2,2)

en negrita se ha resaltado la parte de la expresin que se evala en cada llamada recursiva.

- Dado un array constituido de nmeros enteros y que contiene N elementos siendo N >= 1, devolver la
suma de todos los elementos.
int sumarray(int numeros[], int posicion, int N)
{
if (posicion == N-1) return numeros[posicion];
else return numeros[posicion] + sumarray(numeros, posicion+1, N);
}
...
int numeros[5] = {2,0,-1,1,3};
int N = 5;
printf("%d\n",sumarray(numeros, 0, N));
Notar que la condicin de parada se cumple cuando se llega al final del array. Otra alternativa es recorrer
el array desde el final hasta el principio (de derecha a izquierda):
int sumarray(int numeros[], int posicion)
{
if (posicion == 0) return numeros[posicion];
else return numeros[posicion] + sumarray(numeros, posicion-1);
}
...
int numeros[5] = {2,0,-1,1,3};
int N = 5;
printf("%d\n",sumarray(numeros, N-1));

- Dado un array constituido de nmeros enteros, devolver la suma de todos los elementos. En este caso
se desconoce el nmero de elementos. En cualquier caso se garantiza que el ltimo elemento del array es
-1, nmero que no aparecer en ninguna otra posicin.
int sumarray(int numeros[], int posicion)
{
if (numeros[posicion] == -1) return 0;
else return numeros[posicion] + sumarray(numeros, posicion+1);
}
...

int numeros[5] = {2,4,1,-3,-1};


printf("%d\n",sumarray(numeros, 0));

La razn por la que se incluye este ejemplo se debe a que en general no se conocer el nmero de
elementos de la estructura de datos sobre la que se trabaja. En ese caso se introduce un centinela -como
la constante -1 de este ejemplo o la constante NULO para punteros, u otros valores como el mayor o
menor entero que la mquina pueda representar- para indicar el fin de la estructura.

- Dado un array constituido de nmeros enteros y que contiene N elementos siendo N >= 1, devolver el
elemento mayor.
int mayor(int numeros[], int posicion)
{
int aux;
if (posicion == 0)
return numeros[posicion];
else {
aux = mayor(numeros, posicion-1);
if (numeros[posicion] > aux)
return numeros[posicion];
else
return aux;
}
}
...
int numeros[5] = {2,4,1,-3,-1};
int N = 5;
printf("%d\n", mayor(numeros, 4));

- Ahora uno un poco ms complicado: dados dos arrays de nmeros enteros A y B de longitud n y m
respectivamente, siendo n >= m, determinar si B est contenido en A. Ejemplo:
A
=
{2,3,4,5,6,7,-3}
B = {7,-3} -> contenido; B = {5,7} -> no contenido; B = {3,2} -> no contenido
Para resolverlo, se parte del primer elemento de A y se compara a partir de ah con todos los elementos
de
B
hasta
llegar
al
final
de
B
o
encontrar
una
diferencia.
A
=
{2,3,4,5},
B
=
{3,4}
-2,3,4,5
3,4
^
En el caso de encontrar una diferencia se desplaza al segundo elemento de A y as sucesivamente hasta
demostrar que B es igual a un subarray de A o que B tiene una longitud mayor que el subarray de A.
3,4,5
3,4
Visto de forma grfica consiste en deslizar B a lo largo de A y comprobar que en alguna posicin B se
suporpone
sobre
A.
Se han escrito dos funciones para resolverlo, contenido y esSubarray. La primera devuelve cierto si el
subarray A y el array B son iguales; tiene dos condiciones de parada: o que se haya recorrido B completo
o que no coincidan dos elementos. La segunda funcin es la principal, y su cometido es ir 'deslizando' B a
lo largo de A, y en cada paso recursivo llamar una vez a la funcin contenido; tiene dos condiciones de
parada: que el array B sea mayor que el subarray A o que B est contenido en un subarray A.

int contenido(int A[], int B[], int m, int pos, int desp)
{
if (pos == m) return 1;
else if (A[desp+pos] == B[pos])
return contenido(A,B,m, pos+1, desp);
else
return 0;
}
int esSubarray(int A[], int B[], int n, int m, int desp)
{
if (desp+m > n)
return 0;
else if (contenido(A, B, m, 0, desp))
return 1;
else
return esSubarray(A, B, n, m, desp+1);
}
...
int A[4] = {2, 3, 4, 5};
int B[3] = {3, 4, 5};
if (esSubarray(A, B, 4, 5, 0)) printf("\nB esta contenido en A");
else printf("\nB no esta contenido en A");
Hay que observar que el requisito n >= m indicando en el enunciado es innecesario, si m > n entonces
devolver falso nada ms entrar en la ejecucin de esSubarray.
Este algoritmo permite hacer bsquedas de palabras en textos. Sin embargo existen algoritmos mejores
como el de Knuth-Morris-Prat, el de Rabin-Karp o mediante autmatas finitos; estos algoritmos som ms
complicados pero mucho ms efectivos.

- Dado un array constituido de nmeros enteros y que contiene N elementos siendo N >= 1, devolver el
elemento mayor. En este caso escribir un procedimiento, es decir, que el elemento mayor devuelto sea
una variable que se pasa por referencia.
void mayor(int numeros[], int posicion, int *m)
{
if (posicion == 0)
*m = numeros[posicion];
else {
mayor(numeros, posicion-1, m);
if (numeros[posicion] > *m)
*m = numeros[posicion];
}
}
...
int numeros[5] = {2,4,1,-3,-1};
int M;
mayor(numeros, 5-1, &M);
printf("%d\n", M);

Hay que tener cuidado con dos errores muy comunes: el primero es declarar la variable para que se pase
por valor y no por referencia, con lo cual no se obtiene nada. El otro error consiste en llamar a la funcin
pasando en lugar del parmetro por referencia una constante, por ejemplo: mayor(numeros, 5-1, 0); en
este caso adems se producir un error de compilacin.

- La funcin de Ackermann, siendo n y m nmeros naturales, se define de la siguiente manera:


Ackermann(0,
n)
Ackermann(m,
0)
Ackermann(m, n) = A(m-1, A(m, n-1))

A(m-1,

1
1)

Aunque parezca mentira, siempre se llega al caso base y la funcin termina. Probar a ejecutar esta funcin
con diversos valores de n y m... que no sean muy grandes!. En Internet pueden encontrarse algunas
cosas curiosas sobre esta funcin y sus aplicaciones.

Arrays

Cuestiones generales
Un array es un tipo de estructura de datos que consta de un nmero fijo de elementos del mismo tipo. En
una mquina, dichos elementos se almacenan en posiciones contiguas de memoria. Estos elementos
pueden ser variables o estructuras. Para definirlos se utiliza la expresin:
tipo_de_elemento nombre_del_array[nmero_de_elementos_del_array];
int mapa[100];
Cada uno de los elementos de los que consta el array tiene asignado un nmero (ndice). El primer
elemento tiene ndice 0 y el ltimo tiene ndice nmero_de_elementos_del_array-1. Para acceder a ese
elemento se pone el nombre del array con el ndice entre corchetes:
nombre_del_array[ndice]
mapa[5]
Los elementos no tienen por qu estar determinados por un solo ndice, pueden estarlo por dos (por
ejemplo, fila y columna), por tres (p.e. las tres coordenadas en el espacio), o incluso por ms. A estos
arrays definidos por ms de un ndice se le llaman arrays multidimensionales o matrices, y se definen:
tipo_de_elemento
int mapa[100][50][399];

nombre_del_array[nmero1]

[nmero2]...

[nmeroN];

Y para acceder a un elemento de ndices i1,i2,...,iN, la expresin es similar:


nombre_del_array[i1][i2]...[iN]
mapa[34][2][0]
Hay que tener cuidado con no utilizar un ndice fuera de los lmites, porque dar resultados inesperados
(tales como cambio del valor de otras variables o finalizacin del programa, con error "invalid memory
reference").

Manejo de arrays
Para manejar un array, siempre hay que manejar por separado sus elementos, esto es, NO se pueden
utilizar operaciones tales como dar valores a un array (esperando que todos los elementos tomen ese
valor). Tambin hay que tener en cuenta de que al definir un array, al igual que con una variable normal,
los elementos del array no estn inicializados. Para ello, hay que dar valores uno a uno a todos los
elementos. Para arrays unidimensionales, se utiliza un for:

for(i=0;i<N;i++)
array[i]=0;
y para arrays multidimensionales se hara as:
for(i1=0;i1<N1;i1++)
for(i2=0;i2<N2;i2++)
...
for(iN=0;iN<NN;iN++)
array1[i1][i2]...[iN]=0;
Hay una funcin que, en determinadas ocasiones, es bastante til para agilizar esta inicializacin, pero que
puede ser peligrosa si se usa sin cuidado: memset (incluida en string.h). Lo que hace esta funcin es dar,
byte a byte, un valor determinado a todos los elementos:
memset(array,num,tamao);
donde array es el nombre del array (o la direccin del primer elemento), num es el valor con el que se
quiere inicializar los elementos y tamao es el tamao del array, definido como el nmero de elementos
por el tamao en bytes de cada elemento. Si el tamao de cada elemento del array es 1 byte, no hay
problema, pero si son ms, la funcin da el valor num a cada byte de cada elemento, con lo que la salida
de un programa del tipo:
#include<stdio.h>
#include<string.h>
void
{
short
memset(mapa,1,10*sizeof(short));
printf("%d",mapa[0]);
}

main()
mapa[10];

no es 1 (0000000000000001 en base 2), como cabra esperar, sino 257 (0000000100000001 en base 2).
Tambin hay otra funcin que facilita el proceso de copiar un array en otro: memcpy (incluido tambin en
string.h). Esta funcin copia byte a byte un array en otro.

Arrays dinmicos
Si al iniciar un programa no se sabe el nmero de elementos del que va a constar el array, o no se quiere
poner un lmite predetermiado, lo que hay que hacer es definir el array dinmicamente. Para hacer esto,
primero se define un puntero, que sealar la direccin de memoria del primer elemento del array:
tipo_de_elemento *nombre_de_array;
y luego se utiliza la funcin malloc (contenida en stdlib.h) para reservar memoria:
nombre_de_array=(tipo_de_elemento *)malloc(tamao);
donde tamao es el nmero de elementos del array por el tamao en bytes de cada elemento. La funcin
malloc devuelve un puntero void, que indica la posicin del primer elemento. Antes de asignarlo a nuestro
puntero, hay que convertir el puntero que devuelve el malloc al tipo de nuestro puntero (ya que no se
pueden igualar punteros de distintos tipos).

Para arrays bidimensionales, hay que hacerlo dimensin a dimensin; primero se define un puntero de
punteros:
int **mapa;
Luego se reserva memoria para los punteros:
mapa=(int **)malloc(sizeof(int *)*N1);
y, por ltimo, para cada puntero se reserva memoria para los elementos:
for(i1=0;i1<N1;i1++)
mapa[i1]=(int *)malloc(sizeof(int)*N2);
Ya se puede utilizar el array normalmente. Para arrays de ms de dos dimensiones, se hace de forma
similar.

Listas

Definicin
Una lista es una estructura de datos secuencial.
Una manera de clasificarlas es por la forma de acceder al siguiente elemento:
- lista densa: la propia estructura determina cul es el siguiente elemento de la lista. Ejemplo: un array.
- lista enlazada: la posicin del siguiente elemento de la estructura la determina el elemento actual. Es
necesario almacenar al menos la posicin de memoria del primer elemento. Adems es dinmica, es decir,
su tamao cambia durante la ejecucin del programa.
Una lista enlazada se puede definir recursivamente de la siguiente manera:
- una lista enlazada es una estructura vaca o
- un elemento de informacin y un enlace hacia una lista (un nodo).
Grficamente se suele representar as:

Como se ha dicho anteriormente, pueden cambiar de tamao, pero su ventaja fundamental es que son
flexibles a la hora de reorganizar sus elementos; a cambio se ha de pagar una mayor lentitud a la hora de
acceder a cualquier elemento.
En la lista de la figura anterior se puede observar que hay dos elementos de informacin, x e y.
Supongamos que queremos aadir un nuevo nodo, con la informacin p, al comienzo de la lista. Para
hacerlo basta con crear ese nodo, introducir la informacin p, y hacer un enlace hacia el siguiente nodo,
que
en
este
caso
contiene
la
informacin
x.
Qu ocurre si quisiramos hacer lo mismo sobre un array?. En ese caso sera necesario desplazar todos
los elementos de informacin "hacia la derecha", para poder introducir el nuevo elemento, una operacin
muy engorrosa.

Implementacin
Para representar en lenguaje C esta estructura de datos se utilizarn punteros, un tipo de datos que
suministra el lenguaje. Se representar una lista vaca con la constante NULL. Se puede definir la lista
enlazada de la siguiente manera:
struct lista
{
int clave;
struct lista *sig;
};
Como se puede observar, en este caso el elemento de informacin es simplemente un nmero entero.
Adems se trata de una definicin autorreferencial. Pueden hacerse definiciones ms complejas. Ejemplo:
struct cl
{
char nombre[20];
int edad;
};
struct lista
{
struct cl datos;
int clave;
struct lista *sig;
};
Cuando se crea una lista debe estar vaca. Por tanto para crearla se hace lo siguiente:
struct lista *L;
L = NULL;

Operaciones bsicas sobre listas


- Insercin al comienzo de una lista:
Es necesario utilizar una variable auxiliar, que se utiliza para crear el nuevo nodo mediante la reserva de
memoria y asignacin de la clave. Posteriormente es necesario reorganizar los enlaces, es decir, el nuevo
nodo debe apuntar al que era el primer elemento de la lista y a su vez debe pasar a ser el primer
elemento.
En el siguiente ejemplo se muestra un programa que crea una lista con cuatro nmeros. Notar que al
introducir al comienzo de la lista, los elementos quedan ordenados en sentido inverso al de su llegada.
Notar tambin que se ha utilizado un puntero auxiliar p para mantener correctamente los enlaces dentro
de la lista.
#include <stdlib.h>
struct lista
{
int clave;
struct lista *sig;
};
int main(void)
{

struct lista *L;


struct lista *p;
int i;
L = NULL; /* Crea una lista vacia */
for (i = 4; i >= 1; i--)
{
/* Reserva memoria para un nodo */
p = (struct lista *) malloc(sizeof(struct lista));
p->clave = i; /* Introduce la informacion */
p->sig = L; /* reorganiza */
L = p;
/* los enlaces */

}
return 0;

- Recorrido de una lista.


La idea es ir avanzando desde el primer elemento hasta encontrar la lista vaca. Antes de acceder a la
estructura lista es fundamental saber si esa estructura existe, es decir, que no est vaca. En el caso de
estarlo o de no estar inicializada es posible que el programa falle y sea difcil detectar donde, y en algunos
casos puede abortarse inmediatamente la ejecucin del programa, lo cual suele ser de gran ayuda para la
depuracin.
Como se ha dicho antes, la lista enlazada es una estructura recursiva, y una posibilidad para su recorrido
es hacerlo de forma recursiva. A continuacin se expone el cdigo de un programa que muestra el valor de
la clave y almacena la suma de todos los valores en una variable pasada por referencia (un puntero a
entero). Por el hecho de ser un proceso recursivo se utiliza un procedimiento para hacer el recorrido.
Ntese como antes de hacer una operacin sobre el elemento se comprueba si existe.
int main(void)
{
struct lista *L;
struct lista *p;
int suma;
L = NULL;
/* crear la lista */
...

suma = 0;
recorrer(L, &suma);
return 0;

void recorrer(struct lista *L, int *suma)


{
if (L != NULL) {
printf("%d, ", L->clave);
*suma = *suma + L->clave;
recorrer(L->sig, suma);
}
}

Sin embargo, a la hora de hacer un programa, es ms eficaz si el recorrido se hace de forma iterativa. En
este caso se necesita una variable auxiliar que se desplace sobre la lista para no perder la referencia al
primer elemento. Se expone un programa que hace la misma operacin que el anterior, pero sin recursin.
int main(void)
{
struct lista *L;

struct lista *p;


int suma;
L = NULL;
/* crear la lista */
...
p = L;
suma = 0;
while (p != NULL) {
printf("%d, ", p->clave);
suma = suma + p->clave;
p = p->sig;
}
return 0;

A menudo resulta un poco difcil de entender la instruccin p = p->sig; Simplemente cambia la direccin
actual del puntero p por la direccin del siguiente enlace. Tambin es comn encontrar instrucciones del
estilo: p = p->sig->sig; Esto puede traducirse en dos instrucciones, de la siguiente manera:
p
=
p->sig;
p
=
p->sig;
Obviamente slo debe usarse cuando se sepa que p->sig es una estructura no vaca, puesto que si fuera
vaca, al hacer otra vez p = p->sig se producira una referencia a memoria no vlida.
Y si queremos insertar en una posicin arbitraria de la lista o queremos borrar un elemento? Como se
trata de operaciones algo ms complicadas (tampoco mucho) se expone su desarrollo y sus variantes en
los siguientes tipos de listas: las listas ordenadas y las listas reorganizables. Asimismo se estudiarn
despus las listas que incorporan cabecera y centinela. Tambin se estudiarn las listas con doble enlace.
Todas las implementaciones se harn de forma iterativa, y se deja propuesta por ser ms sencilla su
implementacin recursiva, aunque es recomendable utilizar la versin iterativa.

Listas ordenadas
Las listas ordenadas son aquellas en las que la posicin de cada elemento depende de su contenido. Por
ejemplo, podemos tener una lista enlazada que contenga el nombre y apellidos de un alumno y queremos
que los elementos -los alumnos- estn en la lista en orden alfabtico.
La creacin de una lista ordenada es igual que antes:
struct lista *L;
L = NULL;
Cuando haya que insertar un nuevo elemento en la lista ordenada hay que hacerlo en el lugar que le
corresponda, y esto depende del orden y de la clave escogidos. Este proceso se realiza en tres pasos:
1.- Localizar el lugar correspondiente al elemento a insertar. Se utilizan dos punteros: anterior y actual,
que
garanticen
la
correcta
posicin
de
cada
enlace.
2.- Reservar memoria para l (puede hacerse como primer paso). Se usa un puntero auxiliar (nuevo) para
reservar
memoria.
3.- Enlazarlo. Esta es la parte ms complicada, porque hay que considerar la diferencia de insertar al
principio, no importa si la lista est vaca, o insertar en otra posicin. Se utilizan los tres punteros antes
definidos para actualizar los enlaces.
A continuacin se expone un programa que realiza la insercin de un elemento en una lista ordenada.
Suponemos claves de tipo entero ordenadas ascendentemente.
#include <stdio.h>
#include <stdlib.h>

struct lista
{
int clave;
struct lista *sig;
};
/* prototipo */
void insertar(struct lista **L, int elem);
int main(void)
{
struct lista *L;
L = NULL; /* Lista vacia */
/* para probar la insercion se han tomado 3 elementos */
insertar(&L, 0);
insertar(&L, 1);
insertar(&L, -1);
return 0;
}
void insertar(struct lista **L, int elem)
{
struct lista *actual, *anterior, *nuevo;
/* 1.- se busca su posicion */
anterior = actual = *L;
while (actual != NULL && actual->clave < elem) {
anterior = actual;
actual = actual->sig;
}
/* 2.- se crea el nodo */
nuevo = (struct lista *) malloc(sizeof(struct lista));
nuevo->clave = elem;

/* 3.- Se enlaza */
if (anterior == NULL || anterior == actual) { /* inserta al principio */
nuevo->sig = anterior;
*L = nuevo; /* importante: al insertar al principio actuliza la cabecera */
}
else {
/* inserta entre medias o al final */
nuevo->sig = actual;
anterior->sig = nuevo;
}

Se puede apreciar que se pasa la lista L con el parmetro **L . La razn para hacer esto es que cuando se
inserta al comienzo de la lista (porque est vaca o es donde corresponde) se cambia la cabecera.
Un ejemplo de prueba: suponer que se tiene esta lista enlazada: 1 -> 3 -> 5 -> NULL
Queremos insertar un 4. Al hacer la bsqueda el puntero actual apunta al 5. El puntero anterior apunta al
3. Y nuevo contiene el valor 4. Como no se inserta al principio se hace que el enlace siguiente
a nuevo sea actual, es decir, el 5, y el enlace siguiente a anterior ser nuevo, es decir, el 4.
La mejor manera de entender el funcionamiento es haciendo una serie de seguimientos a mano o con la
ayuda del depurador.
A continuacin se explica el borrado de un elemento. El procedimiento consiste en localizarlo y borrarlo si
existe. Aqu tambin se distingue el caso de borrar al principio o borrar en cualquier otra posicin. Se
puede observar que el algoritmo no tiene ningn problema si el elemento no existe o la lista est vaca.

void borrar(struct lista **L, int elem)


{
struct lista *actual, *anterior;
/* 1.- busca su posicion. Es casi igual que en la insercion, ojo al (<) */
anterior = actual = *L;
while (actual != NULL && actual->clave
< elem) {
anterior = actual;
actual = actual->sig;
}
/* 2.- Lo borra si existe */
if (actual != NULL && actual->clave == elem) {
if (anterior == actual)
/* borrar el primero */
*L = actual->sig;
/* o tambien (*L)->sig; */
else
/* borrar en otro sitio */
anterior->sig = actual->sig;
free(actual);
}
}

Ejemplo: para borrar la clave '1' se indica as: borrar(&L, 1);

Listas reorganizables
Las listas reorganizables son aquellas en las que cada vez que se accede a un elemento ste se coloca al
comienzo de la lista. Si el elemento al que se accede no est en la lista entonces se aade al comienzo de
la misma. Cuando se trata de borrar un elemento se procede de la misma manera que en la operacin de
borrado de la lista ordenada. Notar que el orden en una lista reorganizable depende del acceso a un
elemento, y no de los valores de las claves.
No se va a desarrollar el procedimiento de insercin / acceso en una lista, se deja como ejercicio. De todas
formas es sencillo. Primero se busca ese elemento, si existe se pone al comienzo de la lista, con cuidado
de no perder los enlaces entre el elemento anterior y el siguiente. Y si no existe pues se aade al principio
y ya est. Por ltimo se actualiza la cabecera.

Cabecera ficticia y centinela


Como se ha observado anteriormente, a la hora de insertar o actualizar elementos en una lista ordenada o
reorganizable es fundamental actualizar el primer elemento de la lista cuando sea necesario. Esto lleva un
coste de tiempo, aunque sea pequeo salvo en el caso de numerosas inserciones y borrados. Para
subsanar
este
problema
se
utiliza
la
cabecera
ficticia.
La cabecera ficticia aade un elemento (sin clave, por eso es ficticia) a la estructura delante del primer
elemento. Evitar el caso especial de insertar delante del primer elemento. Grficamente se puede ver as:

Se declara una lista vaca con cabecera, reservando memoria para la cabecera, de la siguiente manera:

struct lista {
int clave;
struct lista *sig;
}
...
struct lista *L;
L = (struct lista *) malloc(sizeof(struct lista));
L->sig = NULL;
Antes de implementar el proceso de insercin en una lista con cabecera, se explicar el uso del centinela,
y se realizarn los procedimientos de insercin y borrado aprovechando ambas ideas.
El centinela es un elemento que se aade al final de la estructura, y sirve para acotar los elementos de
informacin que forman la lista. Pero tiene otra utilidad: el lector habr observado que a la hora de buscar
un elemento de informacin, ya sea en la insercin o en el borrado, es importante no dar un paso en falso,
y por eso se comprueba que no se est en una posicin de informacin vaca. Pues bien, el centinela evita
ese problema, al tiempo que acelera la bsqueda.
A la hora de la bsqueda primero se copia la clave que buscamos en el centinela, y a continuacin se hace
una bsqueda por toda la lista hasta encontrar el elemento que se busca. Dicho elemento se encontrar
en cualquier posicin de la lista, o bien en el centinela en el caso de que no estuviera en la lista. Como se
sabe que el elemento est en algn lugar de la lista (aunque sea en el centinela) no hay necesidad de
comprobar
si
estamos
en
una
posicin
vaca.
Cuando la lista est vaca la cabecera apunta al centinela. El centinela siempre se apunta a si mismo. Esto
se
hace
as
por
convenio.
Grficamente se puede representar as:

A continuacin se realiza una implementacin de lista enlazada ordenada, que incluye a la vez cabecera y
centinela.
struct lista
{
int clave;
struct lista *sig;
};
/* lista con cabecera y centinela */
struct listacc
{
struct lista *cabecera,
*centinela;
};

Procedimiento de inicializacin (ntese el *LCC):


void crearLCC(struct listacc *LCC)
{
LCC->cabecera = (struct lista *) malloc(sizeof(struct lista));
LCC->centinela = (struct lista *) malloc(sizeof(struct lista));
LCC->cabecera->sig = LCC->centinela;
LCC->centinela->sig = LCC->centinela; /* opcional, por convenio */

Procedimiento de insercin:
void insertarLCC(struct listacc LCC, int elem)
{
struct lista *anterior, *actual, *nuevo;
/* 1.- busca */
anterior = LCC.cabecera;
actual = LCC.cabecera->sig;
LCC.centinela->clave = elem;
while (actual->clave < elem) {
anterior = actual;
actual = actual->sig;
}
/* 2.- crea */
nuevo = (struct lista *) malloc(sizeof(struct lista));
nuevo->clave = elem;
/* 3.- enlaza */
nuevo->sig = actual;
anterior->sig = nuevo;
}
Procedimiento de borrado:
void borrarLCC(struct listacc LCC, int elem)
{
struct lista *anterior, *actual;
/* 1.- busca */
anterior = LCC.cabecera;
actual = LCC.cabecera->sig;
LCC.centinela->clave = elem;
while (actual->clave < elem) {
anterior = actual;
actual = actual->sig;
}
/* 2.- borra si existe */
if (actual != LCC.centinela && actual->clave == elem) {
anterior->sig = actual->sig;
free(actual);
}
}

Ejemplo de uso:
#include <stdio.h>
#include <stdlib.h>
struct lista
{
int clave;
struct lista *sig;
};
struct listacc
{
struct lista *cabecera,
*centinela;

};
void crearLCC(struct listacc *LCC);
void insertarLCC(struct listacc LCC, int elem);
void borrarLCC(struct listacc LCC, int elem);
int main(void)
{
struct listacc LCC;
crearLCC(&LCC);
insertarLCC(LCC, 3);
borrarLCC(LCC, 3);
return 0;
}
La realizacin de la lista reorganizable aprovechando la cabecera y el centinela se deja propuesta como
ejercicio.

Listas doblemente enlazadas


Son listas que tienen un enlace con el elemento siguiente y con el anterior. Una ventaja que tienen es que
pueden recorrerse en ambos sentidos, ya sea para efectuar una operacin con cada elemento o para
insertar/actualizar y borrar. La otra ventaja es que las bsquedas son algo ms rpidas puesto que no
hace falta hacer referencia al elemento anterior. Su inconveniente es que ocupan ms memoria por nodo
que una lista simple.
Se realizar una implementacin de lista ordenada con doble enlace que aproveche el uso de la cabecera
y el centinela. A continuacin se muestra un grfico que muestra una lista doblemente enlazada con
cabecera y centinela, para lo que se utiliza un nico nodo que haga las veces de cabecera y centinela.

- Declaracin:
struct listaDE
{
int clave;
struct listaDE *ant,
*sig;
};

- Procedimiento de creacin:
void crearDE(struct listaDE **LDE)
{

*LDE = (struct listaDE *) malloc(sizeof(struct listaDE));


(*LDE)->sig = (*LDE)->ant = *LDE;
}

- Procedimiento de insercin:
void insertarDE(struct listaDE *LDE, int elem)
{
struct listaDE *actual, *nuevo;
/* busca */
actual = LDE->sig;
LDE->clave = elem;
while (actual->clave < elem)
actual = actual->sig;
/* crea */
nuevo = (struct listaDE *) malloc(sizeof(struct listaDE));
nuevo->clave = elem;

/* enlaza */
actual->ant->sig = nuevo;
nuevo->ant = actual->ant;
nuevo->sig = actual;
actual->ant = nuevo;

- Procedimiento de borrado:
void borrarDE(struct listaDE *LDE, int elem)
{
struct listaDE *actual;
/* busca */
actual = LDE->sig;
LDE->clave = elem;
while (actual->clave < elem)
actual = actual->sig;

/* borra */
if (actual != LDE && actual->clave == elem) {
actual->sig->ant = actual->ant;
actual->ant->sig = actual->sig;
free(actual);
}

Para probarlo se pueden usar las siguientes instrucciones:


struct listaDE *LDE;
...
crearDE(&LDE);
insertarDE(LDE, 1);
borrarDE(LDE, 1);

Listas circulares
Las listas circulares son aquellas en las que el ltimo elemento tiene un enlace con el primero. Su uso
suele estar relacionado con las colas, y por tanto su desarrollo se realizar en el tema de colas. Por
supuesto, se invita al lector a desarrollarlo por su cuenta.

Algoritmos de ordenacin de listas

* Un algoritmo muy sencillo:


Se dispone de una lista enlazada de cualquier tipo cuyos elementos son todos comparables entre s, es
decir, que se puede establecer un orden, como por ejemplo nmeros enteros. Basta con crear una lista de
tipo ordenada e ir insertando en ella los elementos que se quieren ordenar al tiempo que se van borrando
de la lista original sus elementos. De esta manera se obtiene una lista ordenada con todos los elementos
de la lista original. Este algoritmo se llama Insercin Directa; ver Algoritmos de Ordenacin. La
complejidad para ordenar una lista de n elementos es: cuadrtica en el peor caso (n * n) -que se da
cuando la lista inicial ya est ordenada- y lineal en el mejor (n) -que se da cuanda la lista inicial est
ordenada
de
forma
inversa.
Para hacer algo ms rpido el algoritmo se puede implementar modificando los enlaces entre los
elementos de la lista en lugar de aplicar la idea propuesta anteriormente, que requiere crear una nueva
lista y borrar la lista no ordenada.
El algoritmo anterior es muy rpido y sencillo de implementar, pues ya estn creadas las estructuras de
listas ordenadas necesarias para su uso. Eso s, en general es ineficaz y no debe emplearse para ordenar
listas grandes. Para ello se emplea la ordenacin por fusin de listas.
* Un algoritmo muy eficiente: ordenacin por fusin o intercalacin (ver Ordenacin por fusin).

Problemas propuestos:
- La ordenacin por fusin no recursiva: consiste en desarrollar un algoritmo para fusionar dos listas pero
que no sea recursivo. No se trata de desarrollar una implementacin iterativa del programa anterior, sino
de
realizar
una
ordenacin
por
fusin ascendente.
Se
explica
mediante
un
ejemplo:
3
->
2
->
1
->
6
->
9
->
0
->
7
->
4
->
3
->
8
se fusiona el primer elemento con el segundo, el tercero con el cuarto, etctera:
[(3) -> (2)] -> [(1) -> (6)] -> [(9) -> (0)] -> [(7) -> (4)] -> [(3) -> (8)]
queda:
2
->
3
->
1
->
6
->
0
->
9
->
4
->
7
->
3
->
8
se fusionan los dos primeros (primera sublista) con los dos siguientes (segunda sublista), la tercera y
cuarta sublista, etctera. Observar que la quinta sublista se fusiona con una lista vaca, lo cual no supone
ningn
inconveniente
para
el
algoritmo
de
fusin.
[(2
->
3)
->
(1
->
6)]
->
[(0
->
9)
->
(4
->
7)]
->
[(3
->
8)]
queda:
1
->
2
->
3
->
6
->
0
->
4
->
7
->
9
->
3
->
8
se fusionan los cuatro primeros con los cuatro siguientes, y aparte quedan los dos ltimos:
[(1
->
2
->
3
->
6)
->
(0
->
4
->
7
->
9)]
->
[(3
->
8)]
queda:
0
->
1
->
2
->
3
->
4
->
6
->
7
->
9
->
3
->
8
se fusionan los ocho primeros con los dos ltimos, y el resultado final es una lista totalmente ordenada:
0 -> 1 -> 2 -> 3 -> 3 -> 4 -> 6 -> 7 -> 8 -> 9

Para una lista de N elementos, ordena en el mejor y en el peor caso en un tiempo proporcional a: NlogN.
Observar que para ordenar una lista de 2 elementos requiere un paso de ordenacin, una lista de 4
elementos requiere dos pasos de ordenacin, una lista de 8 elementos requiere tres pasos de ordenacin,
una
lista
de
16
requiere
cuatro
pasos,
etctera.
Es
decir:
log
2
=
1
log
4
=
2
log
8
=
3
log
16
=
4
log
32
=
5
De
ah
el
logaritmo
en
base
2.
N aparece porque en cada paso se requiere recorrer toda la lista, luego el tiempo es proporcional a
NlogN.
Se pide: codificar el algoritmo de ordenacin por fusin ascendente.

Conclusin
Las listas enlazadas son muy verstiles. Adems, pueden definirse estructuras ms complejas a partir de
las listas, como por ejemplo arrays de listas, etc. En algunas ocasiones los grafos se definen como listas
de adyacencia (ver seccin de grafos). Tambin se utilizan para las tablas de hash (dispersin) como
arrays de listas.
Son eficaces igualmente para disear colas de prioridad, pilas y colas sin prioridad, y en general cualquier
estructura cuyo acceso a sus elementos se realice de manera secuencial.

Pilas

Definicin
Una pila es una estructura de datos de acceso restrictivo a sus elementos. Se puede entender como una
pila de libros que se amontonan de abajo hacia arriba. En principio no hay libros; despus ponemos uno, y
otro encima de ste, y as sucesivamente. Posteriormente los solemos retirar empezando desde la cima de
la pila de libros, es decir, desde el ltimo que pusimos, y terminaramos por retirar el primero que
pusimos, posiblemente ya cubierto de polvo.
En los programas estas estructuras suelen ser fundamentales. La recursividad se simula en un computador
con la ayuda de una pila. Asimismo muchos algoritmos emplean las pilas como estructura de datos
fundamental, por ejemplo para mantener una lista de tareas pendientes que se van acumulando.
Las pilas ofrecen dos operaciones fundamentales, que son apilar y desapilar sobre la cima. El uso que se
les de a las pilas es independiente de su implementacin interna. Es decir, se hace un encapsulamiento.
Por eso se considera a la pila como un tipo abstracto de datos.
Es una estructra de tipo LIFO (Last In First Out), es decir, ltimo en entrar, primero en salir.
A continuacin se expone la implementacin de pilas mediante arrays y mediante listas enlazadas. En
ambos casos se cubren cuatro operaciones bsicas: Inicializar, Apilar, Desapilar, y Vaca (nos indica si la
pila est vaca). Las claves que contendrn sern simplemente nmeros enteros, aunque esto puede
cambiarse a voluntad y no supone ningn inconveniente.

Implementacin mediante array


Esta implementacin es esttica, es decir, da un tamao mximo fijo a la pila, y si se sobrepasa dicho
lmite se produce un error. La comprobacin de apilado en una pila llena o desapilado en una pila vaca no
se han hecho, pero s las funciones de comprobacin, que el lector puede modificar segn las necesidades
de su programa.
- Declaracin:
struct tpila
{
int cima;
int elementos[MAX_PILA];
};
Nota: MAX_PILA debe ser mayor o igual que 1.

- Procedimiento de Creacin:
void crear(struct tpila *pila)
{
pila->cima = -1;
}

- Funcin que devuelve verdadero si la pila est vaca:


int vacia(struct tpila *pila)
{
return (pila->cima == -1);
}

- Funcin que devuelve verdadero si la pila est llena:


int llena(struct tpila *pila)
{
return (pila->cima == MAX_PILA);
}

- Procedimiento de apilado:
void apilar(struct tpila *pila, int elem)
{
pila->elementos[++pila->cima] = elem;
}

- Procedimiento de desapilado:
void desapilar(struct tpila *pila, int *elem)
{
*elem = pila->elementos[pila->cima--];
}

Programa de prueba:
#include <stdio.h>
int main(void)
{
struct tpila pila;
int elem;

crear(&pila);
if (vacia(&pila)) printf("\nPila vacia.");
if (llena(&pila)) printf("\nPila llena.");
apilar(&pila, 1);
desapilar(&pila, &elem);
return 0;

Puesto que son muy sencillos, el usuario puede decidir implementar una pila 'inline', es decir, sin usar
procedimientos ni funciones, lo cual aumentar el rendimiento a costa de una cierta legibilidad. Es ms,
los problemas que aparecen resueltos en esta web en general utilizan las pilas con arrays de forma 'inline'.
Adems, esta implementacin es algo ms rpida que con listas enlazadas, pero tiene un tamao esttico.
En C y en algn otro lenguaje de programacin puede modificarse el tamao de un array si ste se define
como un puntero al que se le reserva una direccin de memoria de forma explcita (mediante malloc en
C). Sin embargo, a la hora de alterar dinmicamente esa regin de memoria, puede ocurrir que no haya
una regin en la que reubicar el nuevo array (mediante realloc en C) impidiendo su crecimiento.

Implementacin mediante lista enlazada


Para hacer la implementacin se utiliza una lista con cabecera ficticia (ver apartado de listas). Dado el
carcter dinmico de esta implementacin no existe una funcin que determine si la pila est llena. Si el
usuario lo desea puede implementar un anlisis del cdigo devuelto por la funcin de asignacin de
memoria.
- Declaracin:
struct tpila
{
int clave;
struct tpila *sig;
};
- Procedimiento de creacin:
void crear(struct tpila **pila)
{
*pila = (struct tpila *) malloc(sizeof(struct tpila));
(*pila)->sig = NULL;
}
- Funcin que devuelve verdadero si la pila est vaca:
int vacia(struct tpila *pila)

return (pila->sig == NULL);

- Procedimiento de apilado (apila al comienzo de la lista):


void apilar(struct tpila *pila, int elem)
{
struct tpila *nuevo;

nuevo = (struct tpila *) malloc(sizeof(struct tpila));


nuevo->clave = elem;
nuevo->sig = pila->sig;
pila->sig = nuevo;

- Procedimiento de desapilado (desapila del comienzo de la lista):


void desapilar(struct tpila *pila, int *elem)
{
struct tpila *aux;

aux = pila->sig;
*elem = aux->clave;
pila->sig = aux->sig;
free(aux);

Programa de prueba:
int main(void)
{
struct tpila *pila;
int elem;
crear(&pila);
if (vacia(pila)) printf("\nPila vacia!");
apilar(pila, 1);
desapilar(pila, &elem);
}

return 0;

Ver pilasle.c para tener una implementacin completa con lista enlazada.
En este caso, hacerlo 'inline' puede afectar seriamente la legibilidad del programa.
Si el usuario desea hacer un programa a prueba de balas puede probar el siguiente procedimiento de
apilado, que simplemente comprueba si hay memoria para una asignacin de memoria:
void apilar(struct tpila *pila, int elem)
{
struct tpila *nuevo;
if ((nuevo = (struct tpila *) malloc(sizeof(struct tpila))) == NULL)
generar_error();
else {

nuevo->clave = elem;
nuevo->sig = pila->sig;
pila->sig = nuevo;

}
Es obvio que si se llama al procedimiento generar_error es que el sistema se ha quedado sin memoria, o
al menos se ha agotado la regin de memoria que el sistema operativo y/o el compilador dedican para
almacenar los datos que la ejecucin del programa crea.

Otras consideraciones
- Cuantos elementos hay apilados?
En algunos casos puede ser interesante implementar una funcin para contar el nmero de elementos que
hay sobre la pila. En la implementacin con arrays esto es directo. Si se hace sobre listas enlazadas
entonces hay que hacer alguna pequea modificacin sobre la declaracin e implementacin:
struct nodo
{
int clave;
struct nodo *sig;
};
struct tpila
{
int numero_elems; /* mantiene el numero de elementos */
struct nodo *cima;
};
Los detalles de la implementacin no se incluyen, pues es sencilla.

- Cmo vaciar la pila?


En el caso de la implementacin con array es directo, basta con inicializar la cima al valor de vaco. Si es
una lista enlazada hay que ir borrando elemento a elemento (o desapilarlos todos). Los detalles se dejan
para el lector.

Elegir entre implementacin con listas o con arrays.


El uso del array es idneo cuando se conoce de antemano el nmero mximo de elementos que van a ser
apilados y el compilador admite una regin contigua de memoria para el array. En otro caso sera ms
recomendable usar la implementacin por listas enlazadas, tambin si el nmero de elementos llegase a
ser excesivamente grande.
La implementacin por array es ligeramente ms rpida. En especial, es mucho ms rpido a la hora de
eliminar los elementos que hayan quedado en la pila. Por lista enlazada esto no es tan rpido. Por
ejemplo, pinsese en un algoritmo que emplea una pila y que en algunos casos al terminar ste su
ejecucin deja algunos elementos sobre la pila. Si se implementa la pila mediante una lista enlazada
entonces quedaran en memoria una serie de elementos que es necesario borrar. La nica manera de
borrarlos es liberar todas las posiciones de memoria que le han sido asignadas a cada elemento, esto es,

desapilar todos los elementos. En el caso de una implementacin con array esto no es necesario, salvo
que quiera liberarse la regin de memoria ocupada por ste.

Colas

Definicin
Una cola es una estructura de datos de acceso restrictivo a sus elementos. Un ejemplo sencillo es la cola
del cine o del autobs, el primero que llegue ser el primero en entrar, y afortunadamente en un sistema
informtico no se cuela nadie salvo que el programador lo diga.
Las colas sern de ayuda fundamental para ciertos recorridos de rboles y grafos.
Las colas ofrecen dos operaciones fundamentales, que son encolar (al final de la cola) y desencolar (del
comienzo de la cola). Al igual que con las pilas, la implementacin de las colas suele encapsularse, es
decir, basta con conocer las operaciones de manipulacin de la cola para poder usarla, olvidando su
implementacin interna.
Es una estructra de tipo FIFO (First In First Out), es decir: primero en entrar, primero en salir.
A continuacin se expone la implementacin de colas, con arrays y con listas enlazadas circulares. En
ambos casos se cubren cuatro operaciones bsicas: Inicializar, Encolar, Desencolar, y Vaca. Las claves que
contendrn sern simplemente nmeros enteros.

Implementacin mediante array circular


Esta implementacin es esttica, es decir, da un tamao mximo fijo a la cola. No se incluye comprobacin
de errores dentro del encolado y el desencolado, pero se implementan como funciones aparte.
Por qu un array circular? Qu es eso? Como se aprecia en la implemetacin de las pilas, los elementos
se quitan y se ponen sobre la cima, pero en este caso se introducen por un sitio y se quitan por otro.
Podra hacerse con un array secuencial, como se muestra en las siguientes figuras. 'Entrada' es la posicin
de entrada a la cola, y 'Salida' por donde salen.
En esta primera figura se observa que se han introducido tres elementos: 3, 1 y 4 (en ese orden):

se desencola, obteniendo un 3:

se encola un 7:

Enseguida se aprecia que esto tiene un grave defecto, y es que llega un momento en el que se desborda
la capacidad del array. Una solucin nada efectiva es incrementar su tamao. Esta implementacin es
sencilla pero totalmente ineficaz.
Como alternativa se usa el array circular. Esta estructura nos permite volver al comienzo del array cuando
se llegue al final, ya sea el ndice de entrada o el ndice de salida.
Se implementarn dos versiones de arrays circulares, con lo que el programador podr escoger entre la
que ms le guste.

Primera versin:
Esta versin requiere el uso de la operacin mdulo de la divisin para determinar la siguiente posicin en
el array.
Por ejemplo, supngase un array de N = 2 elementos, contando desde 0 hasta 1. Suponer que entrada =
0, salida = 1; Para determinar la posicin siguiente del ndice i en el array se procede as:
i <- (i+1) Mod N
siendo
Mod
la
operacin
resto
de
la
sustituyendo
i
por
salida
se
determina
- sustituyendo i por entrada se determina que entrada = 1.

divisin
que

entera.
salida
=

Asi:
0.

Nota: si el array est indexado entre 1 y N -como suele ser habitual en Pascal- entonces la expresin que
determina
la
posicin
siguiente
es
esta:
i <- (i Mod N) + 1
si
entrada
=
1,
salida
=
sustituyendo
i
por
salida
se
determina
- sustituyendo i por entrada se determina que entrada = 2.

que

2,
salida

entonces:
=
1.

De esta manera se van dando vueltas sobre el array. La lgica es la siguiente:


Para encolar: se avanza el ndice entrada a la siguiente posicin, y se encola en la posicin que apunte
ste.
Para desencolar: el elemento desencolado es el que apunta el ndice salida, y posteriormente se
avanza salida a la siguiente posicin.
Cola vaca: la cola est vaca si el elemento siguiente a entrada es salida, como sucede en el ejemplo
anterior.
Cola llena: la cola est llena si el elemento que sigue al que sigue a entrada es salida.
Esto obliga a dejar un elemento vaco en el array, puesto que se reserva una posicin para separar los
ndices entrada y salida.
Para aclararlo, se muestran una serie de grficos explicativos, partiendo de un array de tres elementos,
es decir, una cola de DOS elementos.

Cola

vaca:

Se encola un 3.

Se desencola el 3; ahora se tiene una cola vaca.

Se

encolan

el

el

7.

Se

obtiene

una

cola

llena.

Si se desencola se obtiene el 5. Si en lugar de desencolar se encola un elemento cualquiera se obtiene


una cola vaca!.

- Declaracin:
struct tcola
{
int entrada, salida;
int elementos[MAX_COLA];
};
Una cola que tenga un elemento requiere que MAX_COLA = 2.

- Funcin que devuelve la posicin siguiente a i en el array circular.


int siguiente(int i)
{

return ((i+1) % MAX_COLA);

- Creacin:
void crear(struct tcola *cola)
{
cola->salida = 0;
cola->entrada = MAX_COLA - 1;
}

- Funcin que devuelve verdadero si la cola est vaca, cosa que ocurre cuando el siguiente
tras entrada es salida:
int vacia(struct tcola *cola)
{
return (siguiente(cola->entrada) == cola->salida);
}

- Funcin que devuelve verdadero si la cola est llena, caso que se da cuando el siguiente elemento
que sigue a entradaes salida:
int llena(struct tcola *cola)
{
return (siguiente(siguiente(cola->entrada)) == cola->salida);
}

- Encolado:
void encolar(struct tcola *cola, int elem)
{
cola->entrada = siguiente(cola->entrada);
cola->elementos[cola->entrada] = elem;
}

- Desencolado:
void desencolar(struct tcola *cola, int *elem)
{
*elem = cola->elementos[cola->salida];
cola->salida = siguiente(cola->salida);
}

- Programa de prueba:
#include <stdio.h>
#define MAX_COLA 50 /* cola de 49 elementos */
int main(void)
{
struct tcola cola;
int elem;

crear(&cola);
if (vacia(&cola)) printf("\nCola vacia.");
if (llena(&cola)) printf("\nCola llena.");
encolar(&cola, 1);
desencolar(&cola, &elem);
return 0;
}

Segunda versin:
En este caso se omite la funcin siguiente, y se aprovechan todos los elementos. Sin embargo se
contabiliza en una variable el nmero de elementos que hay en un momento dado en la cola. Esta
implementacin es parecida a la secuencial, pero vigilando que los ndices no se pasen de rosca.
Como se determina la siguiente posicin? Se avanza una posicin, y si llega al lmite del array el ndice se
actualiza
al
primer
elemento.
La
lgica
es
la
siguiente:
Para encolar: se encola en la posicin indicada por entrada, y se avanza una posicin.
Para desencolar: el elemento desencolado es el que apunta el ndice salida, y posteriormente se
avanza salida a la siguiente posicin.
Cola
vaca:
la
cola
est
vaca
si
el
nmero
de
elementos
Cola llena: la cola est llena si el nmero de elementos es el mximo admitido.
- Declaracin:
struct tcola
{
int elems;
int entrada, salida;
int elementos[MAX_COLA];
};
Una cola que tenga dos elementos requiere que MAX_COLA = 2.

- Creacin:
void crear(struct tcola *cola)
{
cola->elems = cola->salida = cola->entrada = 0;
}

- Funcin que devuelve verdadero si la cola est vaca:


int vacia(struct tcola *cola)
{
return (cola->elems == 0);
}

- Funcin que devuelve verdadero si la cola est llena:


int llena(struct tcola *cola)

es

cero.

return (cola->elems == MAX_COLA);

- Encolado:
void encolar(struct tcola *cola, int elem)
{
cola->elems++;
cola->elementos[cola->entrada++] = elem;
if (cola->entrada == MAX_COLA)
cola->entrada = 0;
}

- Desencolado:
void desencolar(struct tcola *cola, int *elem)
{
cola->elems--;
*elem = cola->elementos[cola->salida++];
if (cola->salida == MAX_COLA)
cola->salida = 0;
}

- Programa de prueba:
El mismo de antes sirve.

Implementacin mediante lista enlazada


Para
hacer
la
implementacin
se
utilizar
una lista
circular
sin
cabecera.
La cola estar inicialmente vaca. Cuando se aadan elementos el puntero que mantiene la cola apunta al
ltimo elemento introducido, y el siguiente elemento al que apunta es al primero que est esperando para
salir.
- Cmo encolar?. Se crea el nuevo elemento, se enlaza con el primero de la cola. Si no est vaca hay
que actualizar el enlace del, hasta el momento de la insercin, ltimo elemento introducido. Por ltimo se
actualiza
el
comienzo
de
la
cola,
est
vaca
o
no.
- Cmo desencolar?. Si tiene un slo elemento se borra y se actualiza el puntero a un valor nulo. Si
tiene dos o ms elementos entonces se elimina el primero y el ltimo apuntar al segundo.
Ejemplo grfico de encolado. Partiendo de una cola que tiene el elemento 3, se van aadiendo el 5 y
el 7 (observar de izquierda a derecha). A la hora de desencolar se extrae el siguiente al que apunta Cola.

Ejemplo grfico de desencolado. Partiendo de la cola formada anteriormente, se han quitado los dos
primeros
elementos
introducidos
(observar
de
izquierda
a
derecha).

- Declaracin:
struct tcola
{
int clave;
struct tcola *sig;
};

- Creacin:
void crear(struct tcola **cola)
{
*cola = NULL;
}

- Funcin que devuelve cierto si la cola est vaca:


int vacia(struct tcola *cola)
{
return (cola == NULL);
}
- Encolado:
void encolar(struct tcola **cola, int elem)
{
struct tcola *nuevo;
nuevo = (struct tcola *) malloc(sizeof(struct tcola));

nuevo->clave = elem;
if (*cola == NULL)
nuevo->sig = nuevo;
else {
nuevo->sig = (*cola)->sig;
(*cola)->sig = nuevo;
}
(*cola) = nuevo;
}
- Desencolado:
void desencolar(struct tcola **c1, int *elem)
{
struct tcola *aux;
*elem = (*c1)->sig->clave;
if ((*c1) == (*c1)->sig) {
free(*c1);
*c1 = NULL;
}
else {
aux = (*c1)->sig;
(*c1)->sig = aux->sig;
free(aux);
}
}

- Programa de prueba:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
struct tcola *cola;
int elem;
crear(&cola);
if (vacia(cola)) printf("\nCola vacia!");
encolar(&cola, 3);
desencolar(&cola, &elem);
}

return 0;

Ver colasle.c para tener una implementacin completa con listas enlazadas.
Al igual que en las pilas implementadas por listas enlazadas, es recomendable analizar el cdigo devuelto
por la funcin de asignacin de memoria para evitar posibles problemas en un futuro.

Otras consideraciones
En algunos casos puede ser interesante implementar una funcin para contar el nmero de elementos que
hay en la cola. Una manera de hacer esto con listas enlazadas es empleando la siguiente declaracin:

struct
{
int
struct
};
struct
{
int
struct
};

nodo
clave;
*sig;

nodo

tcola
numero_elems;

/*

mantiene

el

numero

nodo

de

elementos

*/
*cola;

Los detalles de la implementacin no se incluyen, pues es sencilla.

Qu implementacin es mejor, arrays o listas?


Al igual que con las pilas, la mejor implementacin depende de la situacin particular. Si se conocen de
antemano el nmero de elementos entonces lo ideal es una implementacin por array. En otro caso se
recomienda el uso de lista enlazada circular.

Conjuntos

Introduccin
Los conjuntos son una de las estructuras bsicas de las matemticas, y por tanto de la informtica. No se
va a entrar en la definicin de conjuntos ni en sus propiedades. Se supondr que el lector conoce algo de
teora de conjuntos. Con lo ms bsico es suficiente.
En realidad las estructuras de datos que se han implementado hasta ahora no son ms que elementos
diferentes entre s (en general) en los que se ha definido una relacin. Por ejemplo, en las listas
ordenadas o los rboles binarios de bsqueda se tiene una serie de elementos que estn ordenados entre
s. Obviando las propiedades de las estructuras, se ve que forman un conjunto, y su cardinal es el nmero
de elementos que contenga la estructura. En los conjuntos no existen elementos repetidos, y esto se
respeta en las implementaciones que se ofrecen a continuacin.
Ahora bien, en esta seccin se van definir unas implementaciones que permitan aplicar el lgebra de
conjuntos, ya sea unin, interseccin, pertenencia, etc. Se realizan tres implementaciones: array de bits,
arrays y listas enlazadas.

Representacin mediante arrays de bits


Ciertamente, un bit no da ms que para representar dos estados diferentes. Por supuesto, pueden ser
atributos muy variados, por ejemplo, ser hombre o mujer, adulto o nio, Windows o Linux, etc. Tambin
sirve para indicar si un elemento est o no dentro de un conjunto.
El array se utiliza para representar un conjunto de nmeros naturales (u otro tipo de datos cuyos
elementos se identifiquen por un nmero natural nico mediante una correspondencia) entre 0 y N, siendo
N la capacidad del array unidimensional (es decir, un vector); almacenar valores booleanos, esto es, 1
0.

Por ejemplo, suponer el conjunto universal formado por los enteros entre 0 y 4: U = {0, 1, 2, 3, 4}, y el
conjunto C = {1, 2}. Se representar de esta manera:
0

1
:
indica
que
el
elemento
0 : indica que el elemento no pertenece al conjunto.

pertenece

al

conjunto.

Ahora bien, se ha dicho que se va a emplear un array de bits. Qu se quiere decir con esto? Que no se va
a emplear un array o vector como tal, sino un tipo de datos definido por el lenguaje de programacin, que
suele ocupar entre 8 y 64 bits, y por tanto podr incluir hasta 64 elementos en el conjunto. Por ejemplo,
en
C
o
Pascal
se
define
un
tipo
que
ocupa
8
bits:
unsigned
var

char
conjunto8

conjunto8;
byte;

Si todos los bits de conjunto8 estn a 1 entonces se tiene el conjunto: U = {0, 1, 2, 3, 4, 5, 6, 7}, y su
cardinal es 8. Si todos los bits estn a 0 se tiene el conjunto vaco. El bit ms significativo sealar al
elemento de mayor valor, el bit menos significativo al de menor valor. Ejemplos (bit ms significativo a la
izquierda):
11111111
->
U
11110001
->
01010101
->
00000000 -> U = vaco

{0,
=
U

1,

{0,

2,

3,
4,

4,

{0,

5,

2,

5,

6,
4,

6,

7}
7}
6}

La razn para emplear los arrays de bits es que las operaciones sobre los conjuntos se realizan de manera
muy rpida y sencilla, al menos con los computadores actuales, que tienen un tamao de palabra mltiplo
de
8.
Por
supuesto,
la
ocupacin
en
memoria
est
optimizada
al
mximo.
El inconveniente es que el rango de representacin es muy limitado. Por eso su aplicacin es muy
restringida, y depende fuertemente del compilador y el computador sobre el que se implementan, pero es
increblemente rpida.
Nota: Pascal cuenta con un juego de instrucciones para manipular conjuntos definidos mediante arrays de
bits,
dando
al
usuario
transparencia
total
sobre
su
manejo.
A continuacin se implementa un TAD sobre conjuntos en C mediante array de bits.

- Tipo de datos empleado:


typedef unsigned long tconjunto;
El

tipo

long

suele

ocupar

32

bits,

por

tanto

el

rango

ser:

[0..31].

Nota importante: en los ejemplos se muestran conjuntos que slo tienen un mximo de 8 elementos (8
bits). Esto est puesto simplemente por aumentar la claridad, y cmo no, por ahorrar ceros.

- Definicin de conjunto vaco y universal:


const
tconjunto
const tconjunto vacio = 0;

Universal

0xFFFFFFFF;

Es decir, 32 bits puestos a 1 para el conjunto universal, 32 bits puestos a 0 para el conjunto vaco.

- Unin:
Se realiza mediante la operacin de OR inclusivo. Ejemplo (con 8 bits en lugar de 32):

Or

11001100

10010100

->

->

{2,3,6,7}
{2,4,7}

{2,3,6,7}
{2,4,7}

--------11011100 -> C = {2,3,4,6,7}


y se codifica as:
tconjunto unircjto(tconjunto A, tconjunto B)
{ return (A | B); }

- Interseccin:
Se realiza mediante la operacin AND. Ejemplo:

And

11001100

10010100

->

->

--------10000100 -> C = {2,7}


y se codifica as:
tconjunto interseccion(tconjunto A, tconjunto B)
{ return (A & B); }

- Diferencia:
Para obtener C = A-B se invierten todos los bits de B y se hace un AND entre A y B negado. Ejemplo:
10011101
->
10110010
->
B negado: 01001101 -> B(negado) = {0,2,3,6}

A
B

=
=

{0,2,3,4,7}
{1,4,5,7}

10011101
And

--------00001101 -> C = {0,2,3}

y se codifica as:
tconjunto diferencia2(tconjunto A, tconjunto B)
{ return (A & ~B); }

- Diferencia simtrica:

01001101

C
=
(A-B)
Unin
(B-A)
Se realiza mediante la operacin de OR exclusivo (XOR) o aplicando las primitivas definidas anteriormente.
Ejemplo:
11110000
Xor

->

00011110

->

--------11101110 -> C = {1,2,3,5,6,7}

{4,5,6,7}
{1,2,3,4}

y se codifica as:
tconjunto difsim(tconjunto A, tconjunto B)
{ return (A ^ B); }

- Igualdad de conjuntos:
La implementacin es directa, si todos los bits de A y B se corresponden entonces son iguales:
int
iguales(tconjunto
{ return (A == B); }

A,

tconjunto

B)

- Subconjuntos:
Si un conjunto A es subconjunto (considerando que un conjunto cualquiera es subconjunto de si mismo)
de otro B entonces verifica esta relacin: A interseccin B = A. Notar que A es subconjunto de A, pues A
interseccin A = A.
Ejemplo:
A
=
{1,2,3,4},
C = A interseccin B = {1,2,3}; C es distinto de A.

{0,1,2,3}

Se codifica as:
int subconjunto(tconjunto A, tconjunto B)
{ return (iguales(interseccion(A,B),A)); }

- Pertenencia:
Determinar si un elemento pertenece a un conjunto requiere efectuar una operacin de desplazamiento a
nivel de bits y una posterior comprobacin del bit de signo resultante. Como siempre, un ejemplo o dos lo
aclaran:
Sea
x
=
0
y
A
=
{0,1,2,5}.
Determinar
si
x
pertecene
a
A.
00100111
->
A.
Primero se desplazan los bits de A tantas veces a la derecha como valga x, en el ejemplo no se desplazan;
se obtiene A'. A continuacin se aplica el test del bit de signo sobre A', que consiste en obtener el resto de
la divisin entera entre dos. Si el resto es uno, entonces x pertenece a A. En caso contrario no pertenece a
A. En el ejemplo: A' mod 2 = 1, luego x pertenece a A.
Otro
Se

ejemplo:
desplazan
los

x
bits

=
de

3,
tres

A
posiciones

=
a

la

{0,1,2,5}.
derecha:

00000100
->
Se hace el test de signo: A' mod 2 = 0. x no pertenece a A.

A'.

La codificacin es la siguiente:
int pertenece(tconjunto A, int x)
{ return ((A >> x) % 2); }

- Insercin y borrado:
Para insertar un elemento x es necesario poner a 1 el bit correspondiente. Una manera sencilla de hacerlo
es mediante una suma. Hay que sumar un valor que se corresponda con el bit que se quiere establecer a
1. Para hacerlo se volver a aplicar una operacin de desplazamiento, pero esta vez hacia la izquierda y
aplicada sobre el nmero 1. Se desplazan x bits hacia la izquierda, suponiendo que el compilador llena con
ceros por la derecha.
Por
ejemplo,
partir
de
A
=
conjunto
vaco:
{
Se
quieren
insertar
los
elementos
0,2,3
sobre
Insertar
x = 0. p = 1, (00000001 en binario). Se desplaza p x (0) bits a la izquierda, p' = 1, y se suma a
Queda: A <- A + p'. A = 1.

}.
A.
0:
A.

Insertar
2:
x = 2. p = 1. Se desplaza p x (2) bits a la izquierda, p' = 4 (000000100 en binario). A <- A (1) + p' (4), A
= 5 (000000101).
Insertar
x =3. p = 1. Se desplaza p x (3) bits a la izquierda, p' = 8. A <- A (5) + p' (8), A = 13 (00001101).

3:

El borrado es exactamente lo mismo, pero hay que restar p en vez de sumar. Ejemplo: borrar 3 de A.
A <- A (13) - p' (8), A = 5 (00000101)

Antes de la codificacin, hay que considerar otro detalle: es necesario comprobar previamente si el
elemento ya est en el conjunto para evitar problemas inesperados. Por tanto, la codificacin queda as
para la insercin:
tconjunto insertar(tconjunto A, int x)
{
if (pertenece(A,x)) return A;
else return (A + ((tconjunto)1 << x));
}
y para el borrado:
tconjunto borrar(tconjunto A, int x)
{
if (pertenece(A,x)) return A;
else return (A - ((tconjunto)1 << x));
}

Conclusiones

Sin duda alguna, la gran ventaja de esta implementacin es la rapidez de ejecucin de todas las
operaciones, que se ejecutan en tiempo constante: O(1). Adems los elementos se encuentran
empaquetados ocupando el menor espacio posible, esto es, un nico bit.
La desventaja es que no admiten un rango muy amplio de representacin. Aun as, para incrementar el
rango basta con crear un array de tipo conjunto, por ejemplo: tconjunto superconjunto[10], y aplicar las
operaciones sobre los bits en todos los elementos del array, excepto para la insercin y borrado, en cuyo
caso hay que encontrar el bit exacto a manipular.

Representacin mediante array


Los elementos del conjunto se guardan uno a continuacin de otro empleando una lista densa
representada mediante un array.
Ejemplo: Sea el conjunto C = {1, 2}. Se representar de esta manera:
0
1

y su cardinal es 2.
Esta representacin no limita el rango de representacin ms que al tipo de datos empleado. Por
supuesto, ya no puede definirse explcitamente el conjunto universal.
Por razones de eficiencia a la hora de implementar las primitivas, las estructuras se pasan por referencia.
Es un detalle importante, porque C garantiza que un array se pasa siempre por referencia, pero eso no es
cierto si el array se pasa como parte de una estructura.
No se implementan rutinas de control de errores ni su deteccin. Se produce un error cuando se tratan de
aadir elementos y estos desbordan la capacidad del array.
Nota importante: los elementos dentro del array no estn ordenados entre s.

- Tipo de datos empleado:


typedef int tTipo;
typedef struct
{
tTipo elems[MAXELEM];
int cardinal;
} tconjunto;

- Definicin de conjunto vaco:


Un conjunto est vaco si su cardinal es cero. Para inicializar un conjunto a vaco basta con una
instruccin:

A->cardinal = 0

- Pertenencia:
Para determinar si un elemento x pertenece al conjunto basta con recorrer el array hasta encontrarlo. Se
devuelve True si se encuentra. Codificacin:
int pertenece(tconjunto *A, tTipo x)
{
int i;
for (i = 0; i < A->cardinal; i++)
if (A->elems[i] == x) return 1;
return 0;
}

- Insercin y borrado:
Para insertar un elemento, primero debe comprobarse que no est, despus se inserta en la ltima
posicin, esto es, la que seale el cardinal, que se incrementa en una unidad. Codificacin:
void insertar(tconjunto *A, tTipo x)
{
if (!pertenece(A, x))
A->elems[A->cardinal++] = x;
}
Borrar es aparentemente ms complicado. No se puede eliminar el elemento y dejar un hueco, puesto que
en ese caso ya no se tiene una lista. Para eliminar este problema se sustituye el elemento borrado por el
ltimo de la lista. Codificacin:
void borrar(tconjunto *A, tTipo x)
{
int i;
for (i = 0; i < A->cardinal; i++)
if (A->elems[i] == x) {
A->elems[i] = A->elems[--A->cardinal]; return;
}
}

- Unin:
Para hacer C = A Unin B, se introducen en C todos los elementos de A y todos los elementos de B que no
pertenezcan a A. Codificacin:
void unircjto(tconjunto *A, tconjunto *B, tconjunto *C)
{
int i;
*C = *A;
for (i = 0; i < B->cardinal; i++)
if (!pertenece(A, B->elems[i]))
insertar(C, B->elems[i]);
}

- Interseccin:
Para hacer C = A interseccin B, se hace un recorrido sobre A (o B) y se insertan en C los elementos que
estn en B (o A).
El pseudocdigo es:
C
=
para
cada
x
si x pertenece a B entonces insertar x en C.

elemento

de

vaco
A

En C:
void interseccion(tconjunto *A, tconjunto *B, tconjunto *C)
{
int i;
C->cardinal = 0;
for (i = 0; i < A->cardinal; i++)
if (pertenece(B, A->elems[i]))
insertar(C, A->elems[i]);
}

- Diferencia:
Para hacer C = A-B, se hace un recorrido sobre A (o B) y se insertan en C los elementos que no estn en
B (o A).
El pseudocdigo es:
C
=
para
cada
x
elemento
si x no pertenece a B entonces insertar x en C.

de

vaco
A

En C:
void diferencia(tconjunto *A, tconjunto *B, tconjunto *C)
{
int i;
C->cardinal = 0;
for (i = 0; i < A->cardinal; i++)
if (!pertenece(B, A->elems[i]))
insertar(C, A->elems[i]);
}

- Diferencia simtrica:
Sea C = (A-B) Unin (B-A). Para obtener este resultado se puede aprovechar el cdigo estudiado
anteriormente.
El pseudocdigo es:

C
=
para
cada
x
elemento
si
x
no
pertenece
a
B
entonces
para
cada
x
elemento
si x no pertenece a A entonces insertar x en C

de
insertar

de

vaco
A
en
C
B

En C:
void difsim(tconjunto *A, tconjunto *B, tconjunto *C)
{
int i;
C->cardinal = 0;
for (i = 0; i < A->cardinal; i++)
if (!pertenece(B, A->elems[i]))
insertar(C, A->elems[i]);
for (i = 0; i < B->cardinal; i++)
if (!pertenece(A, B->elems[i]))
insertar(C, B->elems[i]);
}

- Subconjuntos:
Determinar si un conjunto A es subconjunto de B se reduce a comprobar si todo elemento de A es
elemento de B. Se devuelve True si A es subconjunto de B. Codificacin:
int subconjunto(tconjunto *A, tconjunto *B) /
{
int i, esta;

esta = 1;
for (i = 0; i < A->cardinal; i++)
if (!pertenece(B, A->elems[i])) return 0;
return 1;

- Igualdad de conjuntos:
Un conjunto A es igual a otro B si A es subconjunto de B y ambos tienen los mismos elementos. Se
devuelve True si A es igual a B. Codificacin:
int iguales(tconjunto *A, tconjunto *B)
{
return (subconjunto(A,B) && A->cardinal == B->cardinal);
}

Conclusiones
La ventaja de esta implementacin es que no limita el rango de representacin de los elementos del
conjunto, y por supuesto tampoco limita el tipo de datos, siempre y cuando se pueda deducir cuando un
elemento es igual a otro o no.
La desventaja de esta implementacin con respecto a la de arrays de bits es su mala eficacia con respecto
al tiempo de ejecucin. El coste de la insercin y borrado es O(1). Siendo |A| el cardinal de un conjunto

cualquiera A las operaciones de pertenencia se ejecuta en un tiempo O(|A|). En las restantes operaciones,
que implican a dos conjuntos, la complejidad es O(|A||B|)
El espacio que ocupa un conjunto es de O(|MAXIMO|), siendo MAXIMO el tamao del array.

Representacin mediante lista enlazada


Esta representacin es muy parecida a la implementacin mediante un array, pero con alguna
particularidad que la hace ms interesante en algunos casos. Por supuesto, los tipos de datos de los
elementos que se insertan son igualmente admisibles con listas como lo eran con arrays.
Suponer que entre los elementos del conjunto se puede definir una relacin de orden, es decir, que se
puede determinar si un elemento es mayor que otro. En este caso se pueden insertar y borrar elementos
del conjunto de forma que la lista que los mantiene est ordenada. Esto puede resultar interesante en
algunas aplicaciones.
Sea |A| y |B| el cardinal de unos conjuntos cualesquiera A y B. Aplicando la suposicin anterior las
operacines
de
bsqueda,
insercin
y
borrado
se
ejecutan
en
un
tiempo
O(|A|).
Pero hay una gran ventaja, y es que las restantes operaciones se ejecutan en un tiempo O(|A|+|B|).
Cmo se consigue sto? Aprovechando las propiedades de tener listas ordenadas basta con hacer un
nico recorrido sobre cada lista. Esto es posible implementando un algoritmo basado en el algoritmo de
fusin de dos listas ordenadas, que obtiene una lista ordenada a partir de dos o ms listas ordenadas con
un nico recorrido de cada lista. (Es recomendable ver primero los Algoritmos de ordenacin de listas y
entender el proceso de intercalacin o fusin, pero NO es necesario estudiar el proceso recursivo ya que
no tiene inters aqu). Las operaciones de unin, interseccin, etctera, e incluiso el determinar si un
conjunto es subconjunto de otro se efectan haciendo pequeas variaciones sobre el algoritmo de
intercalacin.
Nota sobre la implementacin: Al estudiar la codificacin se podr notar que los conjuntos A y B sobre
los que se hacen los recorridos no se modifican sino que quedan como estn. Para ganar en eficiencia se
puede hacer que el nuevo conjunto C no cree su propia lista de elementos sino que simplemente
aproveche los enlaces de las listas que mantienen los conjuntos A y B, deshaciendo estos. Esto es algo
opcional y no se ha implementado, pero es til si dichos conjuntos no se van a emplear ms. Los punteros
c1 y c2 recorren las listas que representan a los conjuntos A y B respectivamente. El puntero c3 y aux
sirven para crear el nuevo conjunto C.

Definicin y tipo de datos empleado:


Se emplear una lista enlazada con cabecera ficticia y centinela. La razn es que se realizarn inserciones
y bsquedas sobre la lista que contiene los elementos del conjunto. Como se ha comentado
anteriormente, los elementos de la lista estarn ordenados. Por tanto, para emplear esta representacin
los elementos deben ser ordenables. En el cdigo propuesto, se tratarn conjuntos de nmeros enteros.
Los tipos de datos se declaran as:
typedef struct lista
{
int elem;
struct lista *sig;
} lista;
typedef struct tconjunto

lista *cabecera,
*centinela;
int cardinal;
} tconjunto;

- Creacin del conjunto vaco:


La creacin de un nuevo conjunto (vaco) se realiza estableciendo a cero el nmero de elementos y
reservando memoria para los elementos de cabecera y centinela de la lista.
void crearcjto(struct tconjunto *cjto)
{
cjto->cabecera = (lista *) malloc(sizeof(lista));
cjto->centinela = (lista *) malloc(sizeof(lista));
cjto->cabecera->sig = cjto->centinela;
cjto->centinela->sig = cjto->centinela; /* opcional, por convenio */
cjto->cardinal = 0;
}
- Pertenencia:
Para determinar si un elemento x pertenece al conjunto basta con recorrer la lista hasta encontrarlo o
llegar al final de sta. Se devuelve True si se encuentra antes del centinela.
int pertenece(tconjunto cjto, int x)
{
lista *actual;
actual = cjto.cabecera->sig;
cjto.centinela->elem = x;
while (actual->elem != x)
actual = actual->sig;
if (actual == cjto.centinela)
return 0;
else
return 1;
}
- Insercin y borrado:
Para insertar un elemento primero debe comprobarse que no est, despus se inserta ordenadamente en
la lista, y se incrementa el cardinal en una unidad.
void insertar(tconjunto *cjto, int x)
{
lista *anterior, *actual, *nuevo;
/* 1.- busca */
anterior = cjto->cabecera;
actual = cjto->cabecera->sig;
cjto->centinela->elem = x;
while (actual->elem < x) {
anterior = actual;
actual = actual->sig;
}
if (actual->elem != x || actual == cjto->centinela) {

/* 2.- crea */
nuevo = (lista *) malloc(sizeof(lista));
nuevo->elem = x;
/* 3.- enlaza */
nuevo->sig = actual;
anterior->sig = nuevo;
cjto->cardinal++;
}

Para borrar un elemento basta con localizarlo dentro de la lista y eliminarlo.


void borrar(tconjunto *cjto, int x)
{
lista *anterior, *actual;
/* 1.- busca */
anterior = cjto->cabecera;
actual = cjto->cabecera->sig;
cjto->centinela->elem = x;
while (actual->elem < x) {
anterior = actual;
actual = actual->sig;
}
/* 2.- borra si existe */
if (actual != cjto->centinela && actual->elem == x) {
anterior->sig = actual->sig;
free(actual);
}
}
- Unin:
A partir de los conjuntos A y B se crea un nuevo conjunto C. Se supone que el conjunto C no ha sido
inicializado antes. En cada paso se aade siempre un nuevo elemento. Por ltimo se comprueba que no
queden elementos sin copiar.
void unioncjto(tconjunto A, tconjunto B, tconjunto *C)
{
lista *c1, *c2, *c3, *aux;
crearcjto(C);
c3 = C->cabecera;
c1 = A.cabecera->sig; c2 = B.cabecera->sig;
while (c1 != A.centinela && c2 != B.centinela) {
aux = (lista *) malloc(sizeof(lista));
if (c1->elem < c2->elem) {
aux->elem = c1->elem;
c1 = c1->sig;
}
else if (c1->elem > c2->elem) {
aux->elem = c2->elem;
c2 = c2->sig;
}
else {
aux->elem = c1->elem; /* tambien vale: aux->elem = c2->elem */
c1 = c1->sig;
c2 = c2->sig;
}

aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;

}
/* copia los elementos restantes si los hubiera */
if (c1 != A.centinela) {
while (c1 != A.centinela) {
aux = (lista *) malloc(sizeof(lista));
aux->elem = c1->elem;
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c1 = c1->sig;
}
}
else if (c2 != B.centinela) {
while (c2 != B.centinela) {
aux = (lista *) malloc(sizeof(lista));
aux->elem = c2->elem;
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c2 = c2->sig;
}
}

- Interseccin:
C = A Interseccin B, es el nuevo conjunto que se crea. Se aade un elemento cuando coincide en ambas
listas a la vez (c1->elem == c2->elem).
void interseccion(tconjunto A, tconjunto B, tconjunto *C)
{
lista *c1, *c2, *c3, *aux;

crearcjto(C);
c3 = C->cabecera;
c1 = A.cabecera->sig; c2 = B.cabecera->sig;
while (c1 != A.centinela && c2 != B.centinela) {
if (c1->elem < c2->elem)
c1 = c1->sig;
else if (c1->elem > c2->elem)
c2 = c2->sig;
else {
aux = (lista *) malloc(sizeof(lista));
aux->elem = c1->elem; /* tambien vale: aux->elem = c2->elem */
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c1 = c1->sig;
c2 = c2->sig;
}
}

- Diferencia:
C = A-B. Se aade un nuevo elemento slo cuando (c1->elem < c2->elem).
void diferencia(tconjunto A, tconjunto B, tconjunto *C)

lista *c1, *c2, *c3, *aux;


crearcjto(C);
c3 = C->cabecera;
c1 = A.cabecera->sig; c2 = B.cabecera->sig;
while (c1 != A.centinela && c2 != B.centinela) {
if (c1->elem < c2->elem) {
aux = (lista *) malloc(sizeof(lista));
aux->elem = c1->elem;
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c1 = c1->sig;
}
else if (c1->elem > c2->elem)
c2 = c2->sig;
else
c1 = c1->sig,
c2 = c2->sig;
}
/* aniade lo que quede de A */
while (c1 != A.centinela) {
aux = (lista *) malloc(sizeof(lista));
aux->elem = c1->elem;
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c1 = c1->sig;
}

}
- Diferencia simtrica:
C = (A-B) Unin (B-A). Es decir, todos los elementos no comunes de ambos conjuntos. Se aaden
elementos si (c1->elem != c2->elem).
void difsim(tconjunto A, tconjunto B, tconjunto *C)
{
lista *c1, *c2, *c3, *aux;
crearcjto(C);
c3 = C->cabecera;
c1 = A.cabecera->sig; c2 = B.cabecera->sig;
while (c1 != A.centinela && c2 != B.centinela) {
if (c1->elem != c2->elem) {
aux = (lista *) malloc(sizeof(lista));
if (c1->elem < c2->elem) { aux->elem = c1->elem; c1 = c1->sig; }
else { aux->elem = c2->elem; c2 = c2->sig; }
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;

}
else {
c1 = c1->sig;
c2 = c2->sig;
}

}
/* copia los elementos restantes si los hubiera */
if (c1 != A.centinela) {
while (c1 != A.centinela) {

aux = (lista *) malloc(sizeof(lista));


aux->elem = c1->elem;
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c1 = c1->sig;
}
}
else if (c2 != B.centinela) {
while (c2 != B.centinela) {
aux = (lista *) malloc(sizeof(lista));
aux->elem = c2->elem;
aux->sig = C->centinela;
c3->sig = aux; c3 = aux;
C->cardinal++;
c2 = c2->sig;
}
}
}
- Subconjuntos:
Determinar si un conjunto A es subconjunto de B se reduce a comprobar si todo elemento de A es
elemento de B. Se devuelve True si A es subconjunto de B. Observar que si (c1->elem < c2->elem)
entonces A ya no puede ser subconjunto de B, pues implica que dicho elemento no est en B, ya que c2
representa al menor de los elementos restantes del conjunto. Por ltimo, observar la ltima condicin:
return (essub && c1 == A.centinela);. Es decir, quedan elementos de A que no han sido recorridos, pero B
ya est totalmente recorrido, luego A no es subconjunto de B. Si se da el caso de que essub = true y c1 !
= A.centinela entonces se puede devolver un tercer valor que indique que B es subconjunto de A.
int subconjunto(tconjunto A, tconjunto B)
{
int essub = 1;
lista *c1, *c2;

c1 = A.cabecera->sig; c2 = B.cabecera->sig;
while (c1 != A.centinela && c2 != B.centinela && essub) {
if (c1->elem < c2->elem)
essub = 0;
else if (c1->elem > c2->elem)
c2 = c2->sig;
else {
c1 = c1->sig;
c2 = c2->sig;
}
}
return (essub && c1 == A.centinela);

- Igualdad de conjuntos:
Un conjunto A es igual a otro B si ambos tienen los mismos elementos. Se devuelve True si A es igual a B.
Se comprueba primero el cardinal de ambos conjuntos.
int iguales(tconjunto A, tconjunto B)
{
int igual;
lista *c1, *c2;
igual = A.cardinal == B.cardinal;

c1 = A.cabecera->sig; c2 = B.cabecera->sig;
while (c1 != A.centinela && c2 != B.centinela && igual) {
if (c1->elem != c2->elem)
igual = 0;
c1 = c1->sig;
c2 = c2->sig;
}
return (igual);
}

Programa de prueba:
int main(void)
{
tconjunto A, B, C, p1, p2, p3, p4, p5, p6;
crearcjto(&A);
crearcjto(&B);
crearcjto(&C);
/* A = {2,3,5}
B = {1,2,3,4,5}
C= {3,4,6}
*/
insertar(&A, 2); insertar(&A, 3); insertar(&A, 5);
insertar(&B, 1); insertar(&B, 2); insertar(&B, 3); insertar(&B, 4); insertar(&B, 5);
insertar(&C, 3); insertar(&C, 4); insertar(&C, 6);
if (pertenece(A, 5)) printf("5 pertenece a A");
if (!pertenece(B, 6)) printf("\n6 no pertenece a B");
/* p1 = {2,3,4,5,6} */
unioncjto(A,C,&p1);
/* p2 = {3} */
interseccion(A,C,&p2);
/* p3 = {1,4} */
diferencia(B,A,&p3);
/* p4 = p6 = {2,4,5,6}, p5 = vaco */
difsim(C,A,&p4);
difsim(A,C,&p6);
difsim(B,B,&p5);
if
if
if
if
if

(iguales(p4,p6)) printf("\np4 = p6");


(subconjunto(B,B)) printf("\nB es subconjunto de B");
(!subconjunto(B,C)) printf("\nB no es subconjunto de C");
(subconjunto(A,B)) printf("\nA es subconjunto de B");
(subconjunto(p5, A)) printf("\np5 es subconjunto de A");

return 0;
}

Conclusiones

Esta implementacin tampoco limita el rango de representacin de los elementos del conjunto, y por
supuesto tampoco limita el tipo de datos, siempre y cuando se pueda deducir cuando un elemento es igual
a otro o no.
Dado un conjunto A y B, las operaciones de insercin, borrado y pertenencia se ejecutan en un tiempo de
O(|A|). Las operaciones de unin, interseccin, diferencia, diferencia simtrica, subconjunto e igualdad se
ejecutan en un tiempo de O(|A|+|B|).
El espacio que ocupa un conjunto es de O(|A|), siendo |A| el cardinal del conjunto A. Por supuesto es
proporcional al tamao del conjunto implementado mediante array, multiplicado por una constante debido
al espacio ocupado por los punteros.

rboles

Definicin de rbol
Un rbol es una estructura de datos, que puede definirse de forma recursiva como:
- Una estructura vaca o
- Un elemento o clave de informacin (nodo) ms un nmero finito de estructuras tipo rbol, disjuntos,
llamados subrboles. Si dicho nmero de estructuras es inferior o igual a 2, se tiene un rbol binario.
Es, por tanto, una estructura no secuencial.
Otra definicin nos da el rbol como un tipo de grafo (ver grafos): un rbol es un grafo acclico, conexo y
no dirigido. Es decir, es un grafo no dirigido en el que existe exactamente un camino entre todo par de
nodos. Esta definicin permite implementar un rbol y sus operaciones empleando las representaciones
que se utilizan para los grafos. Sin embargo, en esta seccin no se tratar esta implementacin.

Formas de representacin
- Mediante un grafo:

Figura 1

- Mediante un diagrama encolumnado:

b
c

d
e
f

En la computacin se utiliza mucho una estructura de datos, que son los rboles binarios. Estos rboles
tienen 0, 1 2 descendientes como mximo. El rbol de la figura anterior es un ejemplo vlido de rbol
binario.

Nomenclatura sobre rboles


- Raz: es aquel elemento que no tiene antecesor; ejemplo: a.
- Rama: arista entre dos nodos.
- Antecesor: un nodo X es es antecesor de un nodo Y si por alguna de las ramas de X se puede llegar a Y.
- Sucesor: un nodo X es sucesor de un nodo Y si por alguna de las ramas de Y se puede llegar a X.
- Grado de un nodo: el nmero de descendientes directos que tiene. Ejemplo: c tiene grado 2, d tiene
grado 0, a tiene grado 2.
- Hoja: nodo que no tiene descendientes: grado 0. Ejemplo: d
- Nodo interno: aquel que tiene al menos un descendiente.
- Nivel: nmero de ramas que hay que recorrer para llegar de la raz a un nodo. Ejemplo: el nivel del
nodo a es 1 (es un convenio), el nivel del nodo e es 3.
- Altura: el nivel ms alto del rbol. En el ejemplo de la figura 1 la altura es 3.
- Anchura: es el mayor valor del nmero de nodos que hay en un nivel. En la figura, la anchura es 3.
Aclaraciones: se ha denominado a a la raz, pero se puede observar segn la figura que cualquier nodo
podra ser considerado raz, basta con girar el rbol. Podra determinarse por ejemplo que b fuera la raz,
y a y d los sucesores inmediatos de la raz b. Sin embargo, en las implementaciones sobre un computador
que se realizan a continuacin es necesaria una jerarqua, es decir, que haya una nica raz.

Declaracin de rbol binario


Se definir el rbol con una clave de tipo entero (puede ser cualquier otra tipo de datos) y dos hijos:
izquierdo (izq) y derecho (der). Para representar los enlaces con los hijos se utilizan punteros. El rbol
vaco se representar con un puntero nulo.
Un rbol binario puede declararse de la siguiente manera:
typedef struct tarbol
{
int clave;
struct tarbol *izq,*der;
} tarbol;
Otras declaraciones tambin aaden un enlace al nodo padre, pero no se estudiarn aqu.

Recorridos sobre rboles binarios


Se consideran dos tipos de recorrido: recorrido en profundidad y recorrido en anchura o a nivel. Puesto
que los rboles no son secuenciales como las listas, hay que buscar estrategias alternativas para visitar
todos los nodos.
- Recorridos en profundidad:
* Recorrido en preorden: consiste en visitar el nodo actual (visitar puede ser simplemente mostrar la
clave del nodo por pantalla), y despus visitar el subrbol izquierdo y una vez visitado, visitar el subrbol
derecho.
Es
un
proceso
recursivo
por
naturaleza.
Si se hace el recorrido en preorden del rbol de la figura 1 las visitas seran en el orden siguiente:
a,b,d,c,e,f.
void preorden(tarbol *a)
{
if (a != NULL) {
visitar(a);
preorden(a->izq);
preorden(a->der);
}
}

* Recorrido en inorden u orden central: se visita el subrbol izquierdo, el nodo actual, y despus se visita
el subrbol derecho. En el ejemplo de la figura 1 las visitas seran en este orden: b,d,a,e,c,f.
void inorden(tarbol *a)
{
if (a != NULL) {
inorden(a->izq);
visitar(a);
inorden(a->der);
}
}
* Recorrido en postorden: se visitan primero el subrbol izquierdo, despus el subrbol derecho, y por
ltimo el nodo actual. En el ejemplo de la figura 1 el recorrido quedara as: d,b,e,f,c,a.
void postorden(arbol *a)
{
if (a != NULL) {
postorden(a->izq);
postorden(a->der);
visitar(a);
}
}
La ventaja del recorrido en postorden es que permite borrar el rbol de forma consistente. Es decir, si
visitar se traduce por borrar el nodo actual, al ejecutar este recorrido se borrar el rbol o subrbol que se
pasa como parmetro. La razn para hacer esto es que no se debe borrar un nodo y despus sus
subrboles, porque al borrarlo se pueden perder los enlaces, y aunque no se perdieran se rompe con la
regla de manipular una estructura de datos inexistente. Una alternativa es utilizar una variable auxiliar,
pero es innecesario aplicando este recorrido.

- Recorrido en amplitud:

Consiste en ir visitando el rbol por niveles. Primero se visitan los nodos de nivel 1 (como mucho hay uno,
la
raz),
despus
los
nodos
de
nivel
2,
as
hasta
que
ya
no
queden
ms.
Si se hace el recorrido en amplitud del rbol de la figura una visitara los nodos en este orden: a,b,c,d,e,f
En este caso el recorrido no se realizar de forma recursiva sino iterativa, utilizando una cola (ver Colas)
como estructura de datos auxiliar. El procedimiento consiste en encolar (si no estn vacos) los subrboles
izquierdo y derecho del nodo extraido de la cola, y seguir desencolando y encolando hasta que la cola est
vaca.
En la codificacin que viene a continuacin no se implementan las operaciones sobre colas.
void amplitud(tarbol *a)
{
tCola cola;
/* las claves de la cola sern de tipo rbol binario */
arbol *aux;
if (a != NULL) {
CrearCola(cola);
encolar(cola, a);
while (!colavacia(cola)) {
desencolar(cola, aux);
visitar(aux);
if (aux->izq != NULL) encolar(cola, aux->izq);
if (aux->der != NULL) encolar(cola, aux->der);
}
}
}

Por ltimo, considrese la sustitucin de la cola por una pila en el recorrido en amplitud. Qu tipo de
recorrido se obtiene?

Construccin de un rbol binario


Hasta el momento se ha visto la declaracin y recorrido de un rbol binario. Sin embargo no se ha
estudiado ningn mtodo para crearlos. A continuacin se estudia un mtodo para crear un rbol binario
que no tenga claves repetidas partiendo de su recorrido en preorden e inorden, almacenados en sendos
arrays.
Antes de explicarlo se recomienda al lector que lo intente hacer por su cuenta, es sencillo cuando uno es
capaz de construir el rbol viendo sus recorridos pero sin haber visto el rbol terminado.
Partiendo de los recorridos preorden e inorden del rbol de la figura 1 puede determinarse que la raz es el
primer elemento del recorrido en preorden. Ese elemento se busca en el array inorden. Los elementos en
el array inorden entre izq y la raz forman el subrbol izquierdo. Asimismo los elementos entre der y la
raz forman el subrbol derecho. Por tanto se tiene este rbol:

A continuacin comienza un proceso recursivo. Se procede a crear el subrbol izquierdo, cuyo tamao est
limitado por los ndices izq y der. La siguiente posicin en el recorrido en preorden es la raz de este
subrbol. Queda esto:

El subrbol b tiene un subrbol derecho, que no tiene ningn descendiente, tal y como indican los
ndices izq y der. Se ha obtenido el subrbol izquierdo completo de la raz a, puesto que b no tiene
subrbol izquierdo:

Despus seguir construyndose el subrbol derecho a partir de la raz a.


La implementacin de la construccin de un rbol partiendo de los recorridos en preorden y en inorden
puede consultarse aqu(en C).

rbol binario de bsqueda


Un rbol binario de bsqueda es aquel que es:
- Una estructura vaca o
- Un elemento o clave de informacin (nodo) ms un nmero finito -a lo sumo dos- de estructuras tipo
rbol, disjuntos, llamados subrboles y adems cumplen lo siguiente:
* Todas las claves del subrbol izquierdo al nodo son menores que la
* Todas las claves del subrbol derecho al nodo son mayores que la
* Ambos subrboles son rboles binarios de bsqueda.

clave
clave

del
del

nodo.
nodo.

Un ejemplo de rbol binario de bsqueda:

Figura 5
Al definir el tipo de datos que representa la clave de un nodo dentro de un rbol binario de bsqueda es
necesario que en dicho tipo se pueda establecer una relacin de orden. Por ejemplo, suponer que el tipo
de datos de la clave es un puntero (da igual a lo que apunte). Si se codifica el rbol en Pascal no se puede
establecer una relacin de orden para las claves, puesto que Pascal no admite determinar si un puntero es
mayor o menor que otro.
En el ejemplo de la figura 5 las claves son nmeros enteros. Dada la raz 4, las claves del subrbol
izquierdo son menores que 4, y las claves del subrbol derecho son mayores que 4. Esto se cumple
tambin para todos los subrboles. Si se hace el recorrido de este rbol en orden central se obtiene una
lista
de
los
nmeros
ordenada
de
menor
a
mayor.
Cuestin: Qu hay que hacer para obtener una lista de los nmeros ordenada de mayor a menor?
Una ventaja fundamental de los rboles de bsqueda es que son en general mucho ms rpidos para
localizar un elemento que una lista enlazada. Por tanto, son ms rpidos para insertar y borrar elementos.
Si el rbol est perfectamente equilibrado -esto es, la diferencia entre el nmero de nodos del subrbol
izquierdo y el nmero de nodos del subrbol derecho es a lo sumo 1, para todos los nodos- entonces el
nmero de comparaciones necesarias para localizar una clave es aproximadamente de logN en el peor
caso. Adems, el algoritmo de insercin en un rbol binario de bsqueda tiene la ventaja -sobre los arrays
ordenados, donde se empleara bsqueda dicotmica para localizar un elemento- de que no necesita hacer
una reubicacin de los elementos de la estructura para que esta siga ordenada despus de la insercin.
Dicho algoritmo funciona avanzando por el rbol escogiendo la rama izquierda o derecha en funcin de la
clave que se inserta y la clave del nodo actual, hasta encontrar su ubicacin; por ejemplo, insertar la clave
7 en el rbol de la figura 5 requiere avanzar por el rbol hasta llegar a la clave 8, e introducir la nueva
clave
en
el
subrbol
izquierdo
a 8.
El algoritmo de borrado en rboles es algo ms complejo, pero ms eficiente que el de borrado en un
array ordenado.

Ahora bien, suponer que se tiene un rbol vaco, que admite claves de tipo entero. Suponer que se van a
ir
introduciendo
las
claves
de
forma
ascendente.
Ejemplo:
1,2,3,4,5,6
Se crea un rbol cuya raz tiene la clave 1. Se inserta la clave 2 en el subrbol derecho de 1. A
continuacin
se
inserta
la
clave
3
en
el
subrbol
derecho
de 2.
Continuando las inserciones se ve que el rbol degenera en una lista secuencial, reduciendo drsticamente
su eficacia para localizar un elemento. De todas formas es poco probable que se de un caso de este tipo
en la prctica. Si las claves a introducir llegan de forma ms o menos aleatoria entonces la
implementacin de operaciones sobre un rbol binario de bsqueda que vienen a continuacin son en
general suficientes.
Existen variaciones sobre estos rboles, como los AVL o Red-Black (no se tratan aqu), que sin llegar a
cumplir al 100% el criterio de rbol perfectamente equilibrado, evitan problemas como el de obtener una
lista degenerada.

Operaciones bsicas sobre rboles binarios de bsqueda


- Bsqueda
Si el rbol no es de bsqueda, es necesario emplear uno de los recorridos anteriores sobre el rbol para
localizarlo. El resultado es idntico al de una bsqueda secuencial. Aprovechando las propiedades del rbol
de bsqueda se puede acelerar la localizacin. Simplemente hay que descender a lo largo del rbol a
izquierda o derecha dependiendo del elemento que se busca.
boolean buscar(tarbol *a, int elem)
{
if (a == NULL) return FALSE;
else if (a->clave < elem) return buscar(a->der, elem);
else if (a->clave > elem) return buscar(a->izq, elem);
else return TRUE;
}

- Insercin
La insercin tampoco es complicada. Es ms, resulta practicamente idntica a la bsqueda. Cuando se
llega a un rbol vaco se crea el nodo en el puntero que se pasa como parmetro por referencia, de esta
manera los nuevos enlaces mantienen la coherencia. Si el elemento a insertar ya existe entonces no se
hace nada.
void insertar(tarbol **a, int elem)
{
if (*a == NULL) {
*a = (arbol *) malloc(sizeof(arbol));
(*a)->clave = elem;
(*a)->izq = (*a)->der = NULL;
}
else if ((*a)->clave < elem) insertar(&(*a)->der, elem);
else if ((*a)->clave > elem) insertar(&(*a)->izq, elem);
}

- Borrado
La operacin de borrado si resulta ser algo ms complicada. Se recuerda que el rbol debe seguir siendo
de bsqueda tras el borrado. Pueden darse tres casos, una vez encontrado el nodo a borrar:
1)
El
nodo
no
tiene
descendientes.
Simplemente
se
borra.

2) El nodo tiene al menos un descendiente por una sola rama. Se borra dicho nodo, y su primer
descendiente se asigna como hijo del padre del nodo borrado. Ejemplo: en el rbol de la figura 5 se borra
el nodo cuya clave es -1. El rbol resultante es:

3) El nodo tiene al menos un descendiente por cada rama. Al borrar dicho nodo es necesario mantener la
coherencia de los enlaces, adems de seguir manteniendo la estructura como un rbol binario de
bsqueda. La solucin consiste en sustituir la informacin del nodo que se borra por el de una de las
hojas, y borrar a continuacin dicha hoja. Puede ser cualquier hoja? No, debe ser la que contenga una de
estas
dos
claves:
la mayor de las claves menores al nodo que se borra. Suponer que se quiere borrar el nodo 4 del rbol
de
la
figura
5.
Se
sustituir
la
clave
4
por
la
clave
2.
la menor de las claves mayores al nodo que se borra. Suponer que se quiere borrar el nodo 4 del rbol
de la figura 5. Se sustituir la clave 4 por la clave 5.
El algoritmo de borrado que se implementa a continuacin realiza la sustitucin por la mayor de las claves
menores, (aunque se puede escoger la otra opcin sin prdida de generalidad). Para lograr esto es
necesario descender primero a la izquierda del nodo que se va a borrar, y despus avanzar siempre a la
derecha hasta encontrar un nodo hoja. A continuacin se muestra grficamente el proceso de borrar el
nodo de clave 4:

Codificacin: el procedimiento sustituir es el que desciende por el rbol cuando se da el caso del nodo con
descencientes por ambas ramas.
void borrar(tarbol **a, int elem)
{
void sustituir(tarbol **a, tarbol **aux);
tarbol *aux;

if (*a == NULL) /* no existe la clave */


return;
if ((*a)->clave < elem) borrar(&(*a)->der, elem);
else if ((*a)->clave > elem) borrar(&(*a)->izq, elem);
else if ((*a)->clave == elem) {
aux = *a;
if ((*a)->izq == NULL) *a = (*a)->der;
else if ((*a)->der == NULL) *a = (*a)->izq;
else sustituir(&(*a)->izq, &aux); /* se sustituye por
la mayor de las menores */
free(aux);
}

Ficheros relacionados
Implementacin de algunas de las operaciones sobre rboles binarios.

Ejercicio resuelto
Escribir una funcin que devuelva el numero de nodos de un rbol binario. Una solucin recursiva puede
ser la siguiente:
funcion
inicio
si
en
otro
fin

nodos(arbol
arbol
caso

=
devolver

:
(1

tipoArbol)
vacio
+

entonces
nodos(subarbol_izq)

devuelve
+

entero;

devolver
0;
nodos(subarbol_der));

Adaptarlo para que detecte si un rbol es perfectamente equilibrado o no.

Problemas propuestos
rboles binarios: OIE 98. (Enunciado)

Aplicacin prctica de un rbol


Se tiene un fichero de texto ASCII. Para este propsito puede servir cualquier libro electrnico de la
librera Gutenberg o Cervantes, que suelen tener varios cientos de miles de palabras. El objetivo es
clasificar todas las palabras, es decir, determinar que palabras aparecen, y cuantas veces aparece cada
una. Palabras como 'nio'-'nia', 'vengo'-'vienes' etc, se consideran diferentes por simplificar el problema.

Escribir un programa, que


anteriormente.
Ejemplo:
Texto:
"a
La
a
adios
b
c
hola 2

salida

recibiendo
b'a
que

como

entrada un texto, realice


c.

hola,

produce

es

la clasificacin descrita
adios,
la

hola"
siguiente:
2
1
1
1

Ntese que el empleo de una lista enlazada ordenada no es una buena solucin. Si se obtienen hasta
20.000 palabras diferentes, por decir un nmero, localizar una palabra cualquiera puede ser, y en general
lo ser, muy costoso en tiempo. Se puede hacer una implementacin por pura curiosidad para evaluar el
tiempo de ejecucin, pero no merece la pena.
La solucin pasa por emplear un rbol binario de bsqueda para insertar las claves. El valor de
log(20.000) es aproximadamente de 14. Eso quiere decir que localizar una palabra entre 20.000 llevara
en el peor caso unos 14 accesos. El contraste con el empleo de una lista es simplemente abismal. Por
supuesto, como se ha comentado anteriormente el rbol no va a estar perfectamente equilibrado, pero
nadie escribe novelas manteniendo el orden lexicogrfico (como un diccionario) entre las palabras, asi que
no se obtendr nunca un rbol muy degenerado. Lo que est claro es que cualquier evolucin del rbol
siempre ser mejor que el empleo de una lista.
Por ltimo, una vez realizada la lectura de los datos, slo queda hacer un recorrido en orden central del
rbol y se obtendr la solucin pedida en cuestin de segundos.
Una posible definicin de la estructura rbol es la siguiente:
typedef struct tarbol
{
char clave[MAXPALABRA];
int contador; /* numero de apariciones. Iniciar a 0 */
struct tarbol *izq,
*der;
} tarbol;

Grafos

Definicin
Un grafo es un objeto matemtico que se utiliza para representar circuitos, redes, etc. Los grafos son muy
utilizados en computacin, ya que permiten resolver problemas muy complejos.
Imaginemos que disponemos de una serie de ciudades y de carreteras que las unen. De cada ciudad
saldrn varias carreteras, por lo que para ir de una ciudad a otra se podrn tomar diversos caminos. Cada
carretera tendr un coste asociado (por ejemplo, la longitud de la misma). Gracias a la representacin por
grafos podremos elegir el camino ms corto que conecta dos ciudades, determinar si es posible llegar de
una ciudad a otra, si desde cualquier ciudad existe un camino que llegue a cualquier otra, etc.
El estudio de grafos es una rama de la algoritmia muy importante. Estudiaremos primero sus rasgos
generales y sus recorridos fundamentales, para tener una buena base que permita comprender los
algoritmos que se pueden aplicar.

Glosario
Un grafo consta de vrtices (o nodos) y aristas. Los vrtices son objetos que contienen informacin y las
aristas son conexiones entre vrtices. Para representarlos, se suelen utilizar puntos para los vrtices y
lneas para las conexiones, aunque hay que recordar siempre que la definicin de un grafo no depende de
su representacin.
Un camino entre dos vrtices es una lista de vrtices en la que dos elementos sucesivos estn conectados
por una arista del grafo. As, el camino AJLOE es un camino que comienza en el vrtice A y pasa por los
vrtices J,L y O (en ese orden) y al final va del O al E. El grafo ser conexo si existe un camino desde
cualquier nodo del grafo hasta cualquier otro. Si no es conexo constar de varias componentes conexas.
Un camino simple es un camino desde un nodo a otro en el que ningn nodo se repite (no se pasa dos
veces). Si el camino simple tiene como primer y ltimo elemento al mismo nodo se denomina ciclo.
Cuando el grafo no tiene ciclos tenemos un rbol(ver rboles). Varios rboles independientes forman
un bosque. Un rbol de expansin de un grafo es una reduccin del grafo en el que solo entran a formar
parte el nmero mnimo de aristas que forman un rbol y conectan a todos los nodos.
Segn el nmero de aristas que contiene, un grafo es completo si cuenta con todas las aristas posibles (es
decir, todos los nodos estn conectados con todos), disperso si tiene relativamente pocas aristas
y denso si le faltan pocas para ser completo.
Las aristas son la mayor parte de las veces bidireccionales, es decir, si una arista conecta dos nodos A y B
se puede recorrer tanto en sentido hacia B como en sentido hacia A: estos son llamados grafos no
dirigidos. Sin embargo, en ocasiones tenemos que las uniones son unidireccionales. Estas uniones se
suelen dibujar con una flecha y definen un grafo dirigido. Cuando las aristas llevan un coste asociado (un
entero al que se denomina peso) el grafo es ponderado. Una red es un grafo dirigido y ponderado.

Representacin de grafos
Una caracterstica especial en los grafos es que podemos representarlos utilizando dos estructuras de
datos distintas. En los algoritmos que se aplican sobre ellos veremos que adoptarn tiempos distintos
dependiendo de la forma de representacin elegida. En particular, los tiempos de ejecucin variarn en
funcin del nmero de vrtices y el de aristas, por lo que la utilizacin de una representacin u otra
depender en gran medida de si el grafo es denso o disperso.
Para nombrar los nodos utilizaremos letras maysculas, aunque en el cdigo deberemos hacer
corresponder cada nodo con un entero entre 1 y V (nmero de vrtices) de cara a la manipulacin de los
mismos.

Representacin por matriz de adyacencia


Es la forma ms comn de representacin y la ms directa. Consiste en una tabla de tamao V x V, en que
la que a[i][j] tendr como valor 1 si existe una arista del nodo i al nodo j. En caso contrario, el valor ser
0. Cuando se trata de grafos ponderados en lugar de 1 el valor que tomar ser el peso de la arista. Si el
grafo es no dirigido hay que asegurarse de que se marca con un 1 (o con el peso) tanto la entrada a[i][j]
como la entrada a[j][i], puesto que se puede recorrer en ambos sentidos.
int V,A;
int a[maxV][maxV];

void inicializar()
{
int i,x,y,p;
char v1,v2;
// Leer V y A
memset(a,0,sizeof(a));
for (i=1; i<=A; i++)
{
scanf("%c %c %d\n",&v1,&v2,&p);
x=v1-'A'; y=v2-'A';
a[x][y]=p; a[y][x]=p;
}
}
En esta implementacin se ha supuesto que los vrtices se nombran con una letra mayscula y no hay
errores en la entrada. Evidentemente, cada problema tendr una forma de entrada distinta y la
inicializacin ser conveniente adaptarla a cada situacin. En todo caso, esta operacin es sencilla si el
nmero de nodos es pequeo. Si, por el contrario, la entrada fuese muy grande se pueden almacenar los
nombres de nodos en un rbol binario de bsqueda o utilizar una tabla de dispersin, asignando un entero
a cada nodo, que ser el utilizado en la matriz de adyacencia.
Como se puede apreciar, la matriz de adyacencia siempre ocupa un espacio de V*V, es decir, depende
solamente del nmero de nodos y no del de aristas, por lo que ser til para representar grafos densos.

Representacin por lista de adyacencia


Otra forma de representar un grafo es por medio de listas que definen las aristas que conectan los nodos.
Lo que se hace es definir una lista enlazada para cada nodo, que contendr los nodos a los cuales es
posible acceder. Es decir, un nodo A tendr una lista enlazada asociada en la que aparecer un elemento
con una referencia al nodo B si A y B tienen una arista que los une. Obviamente, si el grafo es no dirigido,
en la lista enlazada de B aparecer la correspondiente referencia al nodo A.
Las listas de adyacencia sern estructuras que contendrn un valor entero (el nmero que identifica al
nodo destino), as como otro entero que indica el coste en el caso de que el grafo sea ponderado. En el
ejemplo se ha utilizado un nodo z ficticio en la cola (ver listas, apartado cabeceras y centinelas).
struct nodo
{
int v;
int p;
nodo *sig;
};
int V,A; // vrtices y aristas del grafo
struct nodo *a[maxV], *z;
void inicializar()
{
int i,x,y,peso;
char v1,v2;
struct nodo *t;
z=(struct nodo *)malloc(sizeof(struct nodo));
z->sig=z;
for (i=0; i<V; i++)
a[i]=z;
for (i=0; i<A; i++)
{

scanf("%c %c %d\n",&v1,&v2,&peso);
x=v1-'A'; y=v2-'A';
t=(struct nodo *)malloc(sizeof(struct nodo));
t->v=y; t->p=peso; t->sig=a[x]; a[x]=t;
t=(struct nodo *)malloc(sizeof(struct nodo));
t->v=x; t->p=peso; t->sig=a[y]; a[y]=t;
}

En este caso el espacio ocupado es O(V + A), muy distinto del necesario en la matriz de adyacencia, que
era de O(V2). La representacin por listas de adyacencia, por tanto, ser ms adecuada para grafos
dispersos.
Hay que tener en cuenta un aspecto importante y es que la implementacin con listas enlazadas
determina fuertemente el tratamiento del grafo posterior. Como se puede ver en el cdigo, los nodos se
van aadiendo a las listas segn se leen las aristas, por lo que nos encontramos que un mismo grafo con
un orden distinto de las aristas en la entrada producir listas de adyacencia diferentes y por ello el orden
en que los nodos se procesen variar. Una consecuencia de esto es que si un problema tiene varias
soluciones la primera que se encuentre depender de la entrada dada. Podra presentarse el caso de tener
varias soluciones y tener que mostrarlas siguiendo un determinado orden. Ante una situacin as podra
ser muy conveniente modificar la forma de meter los nodos en la lista (por ejemplo, hacerlo al final y no al
principio, o incluso insertarlo en una posicin adecuada), de manera que el algoritmo mismo diera las
soluciones ya ordenadas.

Exploracin de grafos
A la hora de explorar un grafo, nos encontramos con dos mtodos distintos. Ambos conducen al mismo
destino (la exploracin de todos los vrtices o hasta que se encuentra uno determinado), si bien el orden
en que stos son "visitados" decide radicalmente el tiempo de ejecucin de un algoritmo, como se ver
posteriormente.
En primer lugar, una forma sencilla de recorrer los vrtices es mediante una funcin recursiva, lo que se
denomina bsqueda en profundidad. La sustitucin de la recursin (cuya base es la estructura de
datos pila) por una cola nos proporciona el segundo mtodo de bsqueda o recorrido, la bsqueda en
amplitud o anchura.

Suponiendo que el orden en que estn almacenados los nodos en la estructura de datos correspondiente
es A-B-C-D-E-F... (el orden alfabtico), tenemos que el orden que seguira el recorrido en profundidad
sera el siguiente:

A-B-E-I-F-C-G-J-K-H-D
En un recorrido en anchura el orden sera, por contra:
A-B-C-D-E-G-H-I-J-K-F
Es decir, en el primer caso se exploran primero los verdes y luego los marrones, pasando primero por los
de mayor intensidad de color. En el segundo caso se exploran primero los verdes, despus los rojos, los
naranjas y, por ltimo, el rosa.
Es destacable que el nodo D es el ltimo en explorarse en la bsqueda en profundidad pese a ser
adyacente al nodo de origen (el A). Esto es debido a que primero se explora la rama del nodo C, que
tambin conduce al nodo D.
En estos ejemplos hay que tener en cuenta que es fundamental el orden en que los nodos estn
almacenados en las estructuras de datos. Si, por ejemplo, el nodo D estuviera antes que el C, en la
bsqueda en profundidad se tomara primero la rama del D (con lo que el ltimo en visitarse sera el C), y
en la bsqueda en anchura se explorara antes el H que el G.

Bsqueda en profundidad
Se implementa de forma recursiva, aunque tambin puede realizarse con una pila. Se utiliza un
array val para almacenar el orden en que fueron explorados los vrtices. Para ello se incrementa una
variable global id (inicializada a 0) cada vez que se visita un nuevo vrtice y se almacena id en la entrada
del array val correspondiente al vrtice que se est explorando.
La siguiente funcin realiza un mximo de V (el nmero total de vrtices) llamadas a la funcin visitar,
que implementamos aqu en sus dos variantes: representacin por matriz de adyacencia y por listas de
adyacencia.
int id=0;
int val[V];
void buscar()
{
int k;
for (k=1; k<=V; k++)
val[k]=0;
for (k=1; k<=V; k++)
if (val[k]==0) visitar(k);
}
void visitar(int k) // matriz de adyacencia
{
int t;
val[k]=++id;
for (t=1; t<=V; t++)
if (a[k][t] && val[t]==0) visitar(t);
}
void visitar(int k) // listas de adyacencia
{
struct nodo *t;
val[k]=++id;
for (t=a[k]; t!=z; t=t->sig)
if (val[t->v]==0) visitar(t->v);

El resultado es que el array val contendr en su i-sima entrada el orden en el que el vrtice i-simo fue
explorado. Es decir, si tenemos un grafo con cuatro nodos y fueron explorados en el orden 3-1-2-4, el
array val quedar como sigue:
val[1]=2; // el primer nodo fue visto en segundo lugar
val[2]=3; // el segundo nodo fue visto en tercer lugar
val[3]=1; // etc.
val[4]=4;
Una modificacin que puede resultar especialmente til es la creacin de un array "inverso" al
array val que contenga los datos anteriores "al revs". Esto es, un array en el que la entrada i-sima
contiene el vrtice que se explor en i-simo lugar. Basta modificar la lnea
val[k]=++id;
sustituyndola por
val[++id]=k;
Para el orden de exploracin de ejemplo anterior los valores seran los siguientes:
val[1]=3;
val[2]=1;
val[3]=2;
val[4]=4;

Bsqueda en amplitud o anchura


La diferencia fundamental respecto a la bsqueda en profundidad es el cambio de estructura de datos:
una cola en lugar de una pila. En esta implementacin, la funcin del array val y la variable id es la
misma que en el mtodo anterior.
struct tcola *cola;
void visitar(int k) // listas de adyacencia
{
struct nodo *t;
encolar(&cola,k);
while (!vacia(cola))
{
desencolar(&cola,&k);
val[k]=++id;
for (t=a[k]; t!=z; t=t->sig)
{
if (val[t->v]==0)
{
encolar(&cola,t->v);
val[t->v]=-1;
}
}
}
}

5. Manejo de algoritmos
Introduccin a los algoritmos
- Qu es un algoritmo?
Una definicin informal (no se considera aqu una definicin formal, aunque existe): conjunto finito de
reglas que dan una secuencia de operaciones para resolver todos los problemas de un tipo dado. De forma
ms sencilla, podemos decir que un algoritmo es un conjunto de pasos que nos permite obtener un dato.
Adems debe cumplir estas condiciones:
Finitud: el algoritmo debe acabar tras un nmero finito de pasos. Es ms, es casi fundamental que sea
en
un
nmero
razonable
de
pasos.
Definibilidad: el algoritmo debe definirse de forma precisa para cada paso, es decir, hay que evitar toda
ambigedad al definir cada paso. Puesto que el lenguaje humano es impreciso, los algoritmos se expresan
mediante un lenguaje formal, ya sea matemtico o de programacin para un computador.
Entrada: el algoritmo tendr cero o ms entradas, es decir, cantidades dadas antes de empezar el
algoritmo. Estas cantidades pertenecen adems a conjuntos especificados de objetos. Por ejemplo, pueden
ser cadenas de caracteres, enteros, naturales, fraccionarios, etc. Se trata siempre de cantidades
representativas del mundo real expresadas de tal forma que sean aptas para su interpretacin por el
computador.
Salida:
el
algoritmo
tiene
una
o
ms
salidas,
en
relacin
con
las
entradas.
Efectividad: se entiende por esto que una persona sea capaz de realizar el algoritmo de modo exacto y
sin
ayuda
de
una
mquina
en
un
lapso
de
tiempo
finito.
A menudo los algoritmos requieren una organizacin bastante compleja de los datos, y es por tanto
necesario un estudio previo de las estructuras de datos fundamentales. Dichas estructuras pueden
implementarse de diferentes maneras, y es ms, existen algoritmos para implementar dichas estructuras.
El uso de estructuras de datos adecuadas pueden hacer trivial el diseo de un algoritmo, o un algoritmo
muy complejo puede usar estructuras de datos muy simples.
Uno de los algoritmos ms antiguos conocidos es el algoritmo de Euclides. El trmino algoritmo proviene
del matemtico Muhammad ibn Musa al-Khwarizmi, que vivi aproximadamente entre los aos 780 y 850
d.C. en la actual nacin Iran. El describi la realizacin de operaciones elementales en el sistema de
numeracin decimal. De al-Khwarizmi se obtuvo la derivacin algoritmo.

- Clasificacin de algoritmos
* Algoritmo determinista: en cada paso del algoritmo se determina de forma nica el siguiente paso.
* Algoritmo no determinista: deben decidir en cada paso de la ejecucin entre varias alternativas y
agotarlas todas antes de encontrar la solucin.
Todo algoritmo tiene una serie de caractersticas, entre otras que requiere una serie de recursos, algo que
es fundamental considerar a la hora de implementarlos en una mquina. Estos recursos son
principalmente:

El
tiempo:
perodo
transcurrido
entre
el
inicio
y
la
finalizacin
del
algoritmo.
La memoria: la cantidad (la medida vara segn la mquina) que necesita el algoritmo para su
ejecucin.
Obviamente, la capacidad y el diseo de la mquina pueden afectar al diseo del algoritmo.

En general, la mayora de los problemas tienen un parmetro de entrada que es el nmero de datos que
hay que tratar, esto es, N. La cantidad de recursos del algoritmo es tratada como una funcin de N. De
esta manera puede establecerse un tiempo de ejecucin del algoritmo que suele ser proporcional a una de
las siguientes funciones:

1 : Tiempo de ejecucin constante. Significa que la mayora de las instrucciones se ejecutan una
vez o muy pocas.

logN : Tiempo de ejecucin logartmico. Se puede considerar como una gran constante. La base
del logaritmo (en informtica la ms comn es la base 2) cambia la constante, pero no demasiado.
El programa es ms lento cuanto ms crezca N, pero es inapreciable, pues logN no se duplica hasta
que N llegue a N2.

N : Tiempo de ejecucin lineal. Un caso en el que N valga 40, tardar el doble que otro en que N
valga 20. Un ejemplo sera un algoritmo que lee N nmeros enteros y devuelve la media
aritmtica.

NlogN : El tiempo de ejecucin es NlogN. Es comn encontrarlo en algoritmos como Quick Sort y
otros del estilo divide y vencers. Si N se duplica, el tiempo de ejecucin es ligeramente mayor del
doble.

N2 : Tiempo de ejecucin cuadrtico. Suele ser habitual cuando se tratan pares de elementos de
datos, como por ejemplo un bucle anidado doble. Si N se duplica, el tiempo de ejecucin aumenta
cuatro veces. El peor caso de entrada del algoritmo Quick Sort se ejecuta en este tiempo.

N3 : Tiempo de ejecucin cbico. Como ejemplo se puede dar el de un bucle anidado triple. Si N se
duplica, el tiempo de ejecucin se multiplica por ocho.

2N : Tiempo de ejecucin exponencial. No suelen ser muy tiles en la prctica por el elevadsimo
tiempo de ejecucin. El problema de la mochila resuelto por un algoritmo de fuerza bruta -simple
vuelta atrs- es un ejemplo. Si N se duplica, el tiempo de ejecucin se eleva al cuadrado.

* Algoritmos polinomiales: aquellos que son proporcionales a N k. Son en general factibles.


* Algoritmos exponenciales: aquellos que son proporcionales a k N. En general son infactibles salvo un
tamao de entrada muy reducido.
- Notacin O-grande
En general, el tiempo de ejecucin es proporcional, esto es, multiplica por una constante a alguno de los
tiempos de ejecucin anteriormente propuestos, adems de la suma de algunos trminos ms pequeos.
As, un algoritmo cuyo tiempo de ejecucin sea T = 3N 2 + 6N se puede considerar proporcional a N2. En
este
caso
se
dira
que
el algoritmo
es del
orden de
N 2,
y se
escribe
O(N2)
2
Los grafos definidos por matriz de adyacencia ocupan un espacio O(N ), siendo N el nmero de vrtices de
ste.
La notacin O-grande ignora los factores constantes, es decir, ignora si se hace una mejor o peor
implementacin del algoritmo, adems de ser independiente de los datos de entrada del algoritmo. Es
decir, la utilidad de aplicar esta notacin a un algoritmo es encontrar un lmite superior del tiempo de
ejecucin, es decir, el peor caso.
A veces ocurre que no hay que prestar demasiada atencin a esto. Conviene diferenciar entre el peor caso
y el esperado. Por ejemplo, el tiempo de ejecucin del algoritmo Quick Sort es de O(N 2). Sin embargo, en
la prctica este caso no se da casi nunca y la mayora de los casos son proporcionales a NlogN. Es por ello
que se utiliza esta ltima expresin para este mtodo de ordenacin.

Una
definicin
rigurosa
de
Una funcin g(N) pertenece a O(f(N)) si y
|g(N)| <= |c0f(N)| , para todo N >= N0.

esta
slo si

notacin
es
existen las constantes

la
c0 y

siguiente:
N0 tales que:

- Clasificacin de problemas
Los problemas matemticos se pueden dividir en primera instancia en dos grupos:
* Problemas indecidibles: aquellos que no se pueden resolver mediante un algoritmo.
* Problemas decidibles: aquellos que cuentan al menos con un algoritmo para su cmputo.
Sin embargo, que un problema sea decidible no implica que se pueda encontrar su solucin, pues muchos
problemas que disponen de algoritmos para su resolucin son inabordables para un computador por el
elevado nmero de operaciones que hay que realizar para resolverlos. Esto permite separar los problemas
decidibles
en
dos:
*
intratables:
aquellos
para
los
que
no
es
factible
obtener
su
solucin.
* tratables: aquellos para los que existe al menos un algoritmo capaz de resolverlo en un tiempo
razonable.
Los problemas pueden clasificarse tambin atendiendo a su complejidad. Aquellos problemas para los
que se conoce un algoritmo polinmico que los resuelve se denominan clase P. Los algoritmos que los
resuelven son deterministas. Para otros problemas, sus mejores algoritmos conocidos son no
deterministas. Esta clase de problemas se denomina clase NP. Por tanto, los problemas de la clase P son
un subconjunto de los de la clase NP, pues slo cuentan con una alternativa en cada paso.

Ordenacin

Cuestiones generales
Su finalidad es organizar ciertos datos (normalmente arrays o ficheros) en un orden creciente o
decreciente mediante una regla prefijada (numrica, alfabtica...). Atendiendo al tipo de elemento que se
quiera ordenar puede ser:
- Ordenacin interna: Los datos se encuentran en memoria (ya sean arrays, listas, etc) y son de acceso
aleatorio o directo (se puede acceder a un determinado campo sin pasar por los anteriores).
- Ordenacin externa: Los datos estn en un dispositivo de almacenamiento externo (ficheros) y su
ordenacin es ms lenta que la interna.

Ordenacin interna
Los mtodos de ordenacin interna se aplican principalmente a arrays unidimensionales. Los principales
algoritmos de ordenacin interna son:
Seleccin: Este mtodo consiste en buscar el elemento ms pequeo del array y ponerlo en primera
posicin; luego, entre los restantes, se busca el elemento ms pequeo y se coloca en segudo lugar, y as
sucesivamente hasta colocar el ltimo elemento. Por ejemplo, si tenemos el array {40,21,4,9,10,35}, los
pasos a seguir son:

{4,21,40,9,10,35}
por
{4,9,40,21,10,35}
{4,9,10,21,40,35}
{4,9,10,21,40,35}
{4,9,10,21,35,40}

<-- Se coloca el 4, el ms pequeo, en primera posicin : se cambia el 4


el
40.
<-- Se coloca el 9, en segunda posicin: se cambia el 9 por el 21.
<-- Se coloca el 10, en tercera posicin: se cambia el 10 por el 40.
<-- Se coloca el 21, en tercera posicin: ya est colocado.
<-- Se coloca el 35, en tercera posicin: se cambia el 35 por el 40.

Si el array tiene N elementos, el nmero de comprobaciones que hay que hacer es de N*(N-1)/2, luego el
tiempo de ejecucin est en O(n2)
int array[N];
int i,j,menor,aux;
// Dar valores a los elementos del array
for(i=0;i<N-1;i++)
{
for(j=i+1,menor=i;j<N;j++)
if(array[j]<array[menor]) // Si el elemento j es menor que el menor:
menor=j; // el menor pasa a ser el elemento j.
aux=array[i]; // Se intercambian los elementos
array[i]=array[menor]; // de las posiciones i y menor
array[menor]=aux; // usando una variable auxiliar.
}
Burbuja: Consiste en comparar pares de elementos adyacentes e intercambiarlos entre s hasta que estn
todos ordenados. Con el array anterior, {40,21,4,9,10,35}:
Primera
{21,40,4,9,10,35}
{21,4,40,9,10,35}
{21,4,9,40,10,35}
{21,4,9,10,40,35}
{21,4,9,10,35,40}

<-<-<-<-<--

Se
Se
Se
Se
Se

cambia
cambia
cambia
cambia
cambia

Segunda
{4,21,9,10,35,40}
<-Se
cambia
{4,9,21,10,35,40}
<-Se
cambia
{4,9,10,21,35,40} <-- Se cambia el 21 por el 10.

el
el
el
el
el

21
40
9
40
35

por
por
por
por
por

el
el
el
el
el

pasada:
40.
4.
40.
10.
40.

el
el

21
9

por
por

el
el

pasada:
4.
21.

Ya estn ordenados, pero para comprobarlo habra que acabar esta segunda comprobacin y hacer una
tercera.
Si el array tiene N elementos, para estar seguro de que el array est ordenado, hay que hacer N-1
pasadas, por lo que habra que hacer (N-1)*(N-i-1) comparaciones, para cada i desde 1 hasta N-1. El
nmero de comparaciones es, por tanto, N(N-1)/2, lo que nos deja un tiempo de ejecucin, al igual que
en la seleccin, en O(n2).
int array[N];
int i,j,aux;
// Dar valores a los elementos del array
for(i=0;i<N-1;i++) // Hacer N-1 pasadas.
{
for(j=0;j<N-i-1;j++) // Mirar los N-i-1 pares.
{
if(array[j+1]<array[j]) // Si el elemento j+1 es menor que el elemento j:
{
aux=array[j+1]; // Se intercambian los elementos
array[j+1]=array[j]; // de las posiciones j y j+1

}
}

array[j]=aux; // usando una variable auxiliar.

Insercin directa: En este mtodo lo que se hace es tener una sublista ordenada de elementos del array
e ir insertando el resto en el lugar adecuado para que la sublista no pierda el orden. La sublista ordenada
se va haciendo cada vez mayor, de modo que al final la lista entera queda ordenada. Para el ejemplo
{40,21,4,9,10,35}, se tiene:
{40,21,4,9,10,35}
Insertamos
{40,40,4,9,10,35}
{21,40,4,9,10,35}
Insertamos
{21,40,40,9,10,35}
{21,21,40,9,10,35}
{4,21,40,9,10,35}
Insertamos
{4,21,40,40,10,35}
{4,21,21,40,10,35}
{4,9,21,40,10,35}
Insertamos
{4,9,21,40,40,35}
{4,9,21,21,40,35}
{4,9,10,21,40,35}

<--

<--

<--

<--

<--

La

primera

Ahora

Ahora

Ahora

el
<-sublista

la

Ahora

el
<-<-sublista

la

el
<-<-sublista

Y
por
ltimo
{4,9,10,21,40,40}
{4,9,10,21,35,40} <-- El array est ordenado.

ordenada

es

ordenada

el
<-<-sublista

la

la

sublista

ordenada

ordenada

insertamos
<--

21:
aux=21;
{21,40}.

es

4:
aux=4;
aux=4;
{4,21,40}.

es

ordenada

es

{40}.

9:
aux=9;
aux=9;
{4,9,21,40}.
10:
aux=10;
aux=10;
{4,9,10,21,40}.

es
el

35:
aux=35;

En el peor de los casos, el nmero de comparaciones que hay que realizar es de N*(N+1)/2-1, lo que nos
deja un tiempo de ejecucin en O(n2). En el mejor caso (cuando la lista ya estaba ordenada), el nmero
de comparaciones es N-2. Todas ellas son falsas, con lo que no se produce ningn intercambio. El tiempo
de ejecucin est en O(n).
El caso medio depender de cmo estn inicialmente distribuidos los elementos. Vemos que cuanto ms
ordenada est inicialmente ms se acerca a O(n) y cuanto ms desordenada, ms se acerca a O(n2).
El peor caso es igual que en los mtodos de burbuja y seleccin, pero el mejor caso es lineal, algo que no
ocurra en stos, con lo que para ciertas entradas podemos tener ahorros en tiempo de ejecucin.
int array[N];
int i,j,aux;
// Dar valores a los elementos del array
for(i=1;i<N;i++) // i contiene el nmero de elementos de la sublista.
{
// Se intenta aadir el elemento i.
aux=array[i];
for(j=i-1;j>=0;j--) // Se recorre la sublista de atrs a adelante para buscar
{
// la nueva posicin del elemento i.
if(aux>array[j]) // Si se encuentra la posicin:
{
array[j+1]=aux; // Ponerlo
break;
// y colocar el siguiente nmero.

}
else
// si no, sigue buscndola.
array[j+1]=array[j];

}
if(j==-1)
// si se ha mirado todas las posiciones y no se ha encontrado la correcta
array[0]=aux; // es que la posicin es al principio del todo.
}
Insercin binaria: Es el mismo mtodo que la insercin directa, excepto que la bsqueda del orden de
un elemento en la sublista ordenada se realiza mediante una bsqueda binaria (ver algoritmos de
bsqueda), lo que en principio supone un ahorro de tiempo. No obstante, dado que para la insercin sigue
siendo necesario un desplazamiento de los elementos, el ahorro, en la mayora de los casos, no se
produce, si bien hay compiladores que realizan optimizaciones que lo hacen ligeramente ms rpido.
Shell: Es una mejora del mtodo de insercin directa, utilizado cuando el array tiene un gran nmero de
elementos. En este mtodo no se compara a cada elemento con el de su izquierda, como en el de
insercin, sino con el que est a un cierto nmero de lugares (llamado salto) a su izquierda. Este salto es
constante, y su valor inicial es N/2 (siendo N el nmero de elementos, y siendo divisin entera). Se van
dando pasadas hasta que en una pasada no se intercambie ningn elemento de sitio. Entonces el salto se
reduce a la mitad, y se vuelven a dar pasadas hasta que no se intercambie ningn elemento, y as
sucesivamente hasta que el salto vale 1.
Por ejemplo, lo pasos para ordenar el array {40,21,4,9,10,35} mediante el mtodo de Shell seran:
Salto=3:
Primera
{9,21,4,40,10,35}
{9,10,4,40,21,35}
Salto=1:
Primera
{9,4,10,40,21,35}
{9,4,10,21,40,35}
{9,4,10,21,35,40}
Segunda
{4,9,10,21,35,40} <--

<-<--

se
se

intercambian
intercambian

el
el

40
21

y
y

el
el

<-<-<--

se
se
se

intercambian
intercambian
intercambian

el
el
el

10
40
35

y
y
y

el
el
el

pasada:
9.
10.
pasada:
4.
21.
40.
pasada:

se intercambian el 4 y el 9.

Con slo 6 intercambios se ha ordenado el array, cuando por insercin se necesitaban muchos ms.
int array[N];
int salto,cambios,aux,i;
for(salto=N/2;salto!=0;salto/=2) // El salto va desde N/2 hasta 1.
{
for(cambios=1;cambios!=0; )
// Mientras se intercambie algn elemento:
{
cambios=0;
for(i=salto;i<N;i++)
// se da una pasada
if(array[i-salto]>array[i]) // y si estn desordenados
{
aux=array[i];
// se reordenan
array[i]=array[i-salto];
array[i-salto]=aux;
cambios++;
// y se cuenta como cambio.
}
}
}
Ordenacin rpida (quicksort): Este mtodo se basa en la tctica "divide y vencers" (ver seccin
divide y vencers), que consiste en ir subdividiendo el array en arrays ms pequeos, y ordenar stos.
Para hacer esta divisin, se toma un valor del array como pivote, y se mueven todos los elementos

menores que este pivote a su izquierda, y los mayores a su derecha. A continuacin se aplica el mismo
mtodo a cada una de las dos partes en las que queda dividido el array.
Normalmente se toma como pivote el primer elemento de array, y se realizan dos bsquedas: una de
izquierda a derecha, buscando un elemento mayor que el pivote, y otra de derecha a izquierda, buscando
un elemento menor que el pivote. Cuando se han encontrado los dos, se intercambian, y se sigue
realizando la bsqueda hasta que las dos bsquedas se encuentran.Por ejemplo, para dividir el array
{21,40,4,9,10,35}, los pasos seran:
{21,40,4,9,10,35} <-- se toma como pivote el 21. La bsqueda de izquierda a derecha
encuantra el valor 40, mayor que pivote, y la bsqueda de derecha a izquierda encuentra el
valor
10,
menor
que
el
pivote.
Se
intercambian:
{21,10,4,9,40,35} <-- Si seguimos la bsqueda, la primera encuentra el valor 40, y la
segunda el valor 9, pero ya se han cruzado, as que paramos. Para terminar la divisin, se
coloca el pivote en su lugar (en el nmero encontrado por la segunda bsqueda, el 9,
quedando:
{9,10,4,21,40,35} <-- Ahora tenemos dividido el array en dos arrays ms pequeos: el
{9,10,4} y el {40,35}, y se repetira el mismo proceso.
La implementacin es claramente recursiva (ver recursividad), y suponiendo el pivote el primer elemento
del array, el programa sera:
#include <stdio.h>
void ordenar(int *,int,int);
void main()
{
// Dar valores al array
}

ordenar(array,0,N-1); // Para llamar a la funcin

void ordenar(int *array,int desde,int hasta)


{
int i,d,aux; // i realiza la bsqueda de izquierda a derecha
// y j realiza la bsqueda de derecha a izquierda.
if(desde>=hasta)
return;
for(i=desde+1,d=hasta; ; ) // Valores iniciales de la bsqueda.
{
for( ;i<=hasta && array[i]<=array[desde];i++); // Primera bsqueda
for( ;d>=0 && array[d]>=array[desde];d--);
// segunda bsqueda
if(i<d)
// si no se han cruzado, intercambiar
{
aux=array[i];
array[i]=array[d];
array[d]=aux;
}
else
// si se han cruzado, salir del bucle
break;
}
if(d==desde-1) // Si la segunda bsqueda se sale del array es que el
d=desde;
// pivote es el elemento ms pequeo: se cambia con l mismo
aux=array[d]; // Colocar el pivote en su posicin
array[d]=array[desde];
array[desde]=aux;
ordenar(array,desde,d-1); // Ordenar el primer subarray.
ordenar(array,d+1,hasta); // Ordenar el segundo subarray.

}
En C hay una funcin que realiza esta ordenacin sin tener que implementarla, llamada qsort (incluida en
stdlib.h):
qsort(nombre_array,nmero,tamao,funcin);
donde nombre_array es el nombre del array a ordenar, nmero es el nmero de elementos del
array, tamao indica el tamao en bytes de cada elemento y funcin es un puntero a una funcin que hay
que implementar, que recibe dos elementos y devuelve 0 si son iguales, algo menor que 0 si el primero es
menor que el segundo, y algo mayor que 0 si el segundo es menor que el primero. Por ejemplo, el
programa de antes sera:
#include <stdio.h>
#include <stdlib.h>
int funcion(const void *,const void *);
void main()
{
// Dar valores al array
}

qsort(array,N,sizeof(array[0]),funcion);

int funcion(const void *a,const void *b)


{
if(*(int *)a<*(int *)b)
return(-1);
else if(*(int *)a>*(int *)b)
return(1);
else
return(0);
}
Claramente, es mucho ms cmodo usar qsort que implementar toda la funcin, pero hay que tener
mucho cuidado con el manejo de los punteros en la funcin, sobre todo si se est trabajando con
estructuras.
Intercalacin: no es propiamente un mtodo de ordenacin, consiste en la unin de dos arrays
ordenados de modo que la unin est tambin ordenada. Para ello, basta con recorrer los arrays de
izquierda a derecha e ir cogiendo el menor de los dos elementos, de forma que slo aumenta el contador
del array del que sale el elemento siguiente para el array-suma. Si quisiramos sumar los arrays {1,2,4} y
{3,5,6}, los pasos seran:
Inicialmente:
i1=0,
i2=0,
is=0.
Primer
elemento:
mnimo
entre
1
y
3
=
1.
Suma={1}.
i1=1,
i2=0,
is=1.
Segundo
elemento:
mnimo
entre
2
y
3
=
2.
Suma={1,2}.
i1=2,
i2=0,
is=2.
Tercer elemento: mnimo entre 4 y 3 = 3. Suma={1,2,3}. i1=2, i2=1, is=3.
Cuarto elemento: mnimo entre 4 y 5 = 4. Suma={1,2,3,4}. i1=3, i2=1, is=4.
Como no quedan elementos del primer array, basta con poner los elementos que quedan del
segundo
array
en
la
suma:
Suma={1,2,3,4}+{5,6}={1,2,3,4,5,6}
int i1,i2,is;
int array1[N1],array2[N2],suma[N1+N2];
for(i1=i2=is=0;i1<N1 && i2<N2;is++) // Mientras no se me acabe ni array1 ni array2:
{
if(array1[i1]<array2[i2]) // Si el elemento de array1 es menor:

suma[is]=array1[i1];
i1++;

// se utiliza el de array1.

}
else
// Pero si el elemento de array2 es menor:
{
suma[is]=array2[i2];
// se utiliza el de array2.
i2++;
}

}
for( ;i1<N1;i1++,is++)
suma[is]=array1[i1];
for( ;i2<N2;i2++,is++)
suma[is]=array2[i2];

// Aadir los elementos de array1 (si quedan).


// Aadir los elementos de array2 (si quedan).

Fusin: Consta de dos partes, una parte de intercalacin de listas y otra de divide y vencers.
- Primera parte: Cmo intercalar dos listas ordenadas en una sola lista ordenada de forma eficiente?
Suponemos que se tienen estas dos listas de enteros ordenadas ascendentemente:
lista 1:
1
->
3
lista 2: 0 -> 2 -> 6 -> 7 -> 10

->

->

->

->

Tras mezclarlas queda:


lista: 0 -> 1 -> 2 -> 3 -> 5 -> 6 -> 6 -> 7 -> 8 -> 9 -> 10
Esto se puede realizar mediante un nico recorrido de cada lista, mediante dos punteros que recorren
cada una. En el ejemplo anterior se insertan en este orden -salvo los dos 6 que puede variar segn la
implementacin-: 0 (lista 2), el 1 (lista 1), el 2 (lista 2), el 3, 5 y 6 (lista 1), el 6 y 7 (lista 2), el 8 y 9
(lista 1), y por llegar al final de la lista 1, se introduce directamente todo lo que quede de la lista 2, que es
el
10.
En la siguiente implementacin no se crea una nueva lista realmente, slo se modifican los enlaces
destruyendo las dos listas y fusionndolas en una sola. Se emplea un centinela que apunta a s mismo y
que contiene como clave el valor ms grande posible. El ltimo elemento de cada lista apuntar al
centinela, incluso si la lista est vaca.
struct lista
{
int clave;
struct lista *sig;
};
struct lista *centinela;
centinela = (struct lista *) malloc(sizeof(struct lista));
centinela->sig = centinela;
centinela->clave = INT_MAX;
...
struct lista *fusion(struct lista *l1, struct lista *l2)
{
struct lista *inic, *c;
if (l1->clave < l2->clave) { inic = l1; l1 = l1->sig; }
else { inic = l2; l2 = l2->sig; }
c = inic;
while (l1 != centinela && l2 != centinela) {

if (l1->clave < l2->clave) {


c->sig = l1; l1 = l1->sig;
}
else {
c->sig = l2; l2 = l2->sig;
}
c = c->sig;

}
if (l1 != centinela) c->sig = l1;
else if (l2 != centinela) c->sig = l2;
return inic;

- Segunda parte: divide y vencers. Se separa la lista original en dos trozos del mismo tamao (salvo
listas de longitud impar) que se ordenan recursivamente, y una vez ordenados se fusionan obteniendo una
lista ordenada. Como todo algoritmo basado en divide y vencers tiene un caso base y un caso recursivo.
* Caso base: cuando la lista tiene 1 0 elementos (0 se da si se trata de ordenar una lista vaca). Se
devuelve la lista tal cual est.
* Caso recursivo: cuando la longitud de la lista es de al menos 2 elementos. Se divide la lista en dos
trozos del mismo tamao que se ordenan recursivamente. Una vez ordenado cada trozo, se fusionan y se
devuelve la lista resultante.
El esquema es el siguiente:
Ordenar(lista L)
inicio
si tamao de L es 1 o 0 entonces
devolver L
si tamao de L es >= 2 entonces
separar L en dos trozos: L1 y L2.
L1 = Ordenar(L1)
L2 = Ordenar(L2)
L = Fusionar(L1, L2)
devolver L
fin
El algoritmo funciona y termina porque llega un momento en el que se obtienen listas de 2 3 elementos
que se dividen en dos listas de un elemento (1+1=2) y en dos listas de uno y dos elementos (1+2=3, la
lista de 2 elementos se volver a dividir), respectivamente. Por tanto se vuelve siempre de la recursin
con listas ordenadas (pues tienen a lo sumo un elemento) que hacen que el algoritmo de fusin reciba
siempre listas ordenadas.
Se incluye un ejemplo explicativo donde cada sublista lleva una etiqueta identificativa.

Dada: 3 -> 2 -> 1 -> 6 -> 9 -> 0 -> 7 -> 4 -> 3 -> 8 (lista original)
se divide en:
3 -> 2 -> 1 -> 6 -> 9 (lista 1)
0 -> 7 -> 4 -> 3 -> 8 (lista 2)
se ordena recursivamente cada lista:
3 -> 2 -> 1 -> 6 -> 9 (lista 1)

se divide en:

3 -> 2 -> 1 (lista 11)

6 -> 9 (lista 12)

se ordena recursivamente cada lista:

3 -> 2 -> 1 (lista 11)

se divide en:
3 -> 2 (lista 111)
1 (lista 112)
se ordena recursivamente cada lista:
3 -> 2 (lista 111)
se divide en:
3 (lista 1111, que no se divide, caso base). Se devuelve 3
2 (lista 1112, que no se divide, caso base). Se devuelve 2
se fusionan 1111-1112 y queda:
2 -> 3. Se devuelve 2 -> 3

1
(lista
112)

1 (lista 1121, que no se divide, caso base). Se devuelve 1

se fusionan 111-112 y queda:

1 -> 2 -> 3 (lista 11). Se devuelve 1 -> 2 -> 3


6 -> 9 (lista 12)

se divide en:

6 (lista 121, que no se divide, caso base). Se devuelve 6

9 (lista 122, que no se divide, caso base). Se devuelve 9

se fusionan 121-122 y queda:

6 -> 9 (lista 12). Se devuelve 6 -> 9


se fusionan 11-12 y queda:
1 -> 2 -> 3 -> 6 -> 9. Se devuelve 1 -> 2 -> 3 -> 6 -> 9
0 -> 7 -> 4 -> 3 -> 8 (lista 2)
... tras repetir el mismo procedimiento se devuelve 0 -> 3 -> 4 -> 7 -> 8
se fusionan 1-2 y queda:
0 -> 1 -> 2 -> 3 -> 3 -> 4 -> 6 -> 7 -> 8 -> 9, que se devuelve y se termina.

La implementacin propuesta emplea un centinela sobre la lista inicial que apunte hacia s mismo y que
adems contiene el mximo valor de un entero. La lista dispone de cabecera y centinela, pero obsrvese
como se elimina durante la ordenacin.
#include <stdlib.h>
#include <limits.h>
struct lista
{
int clave;
struct lista *sig;
};
/* lista con cabecera y centinela */
struct listacc
{
struct lista *cabecera,
*centinela;
};
/* centinela declarado como variable global */
struct lista *centinela;
/* fusiona dos listas */
struct lista *fusion(struct lista *l1, struct lista *l2)
{
struct lista *inic, *c;
if (l1->clave < l2->clave) { inic = l1; l1 = l1->sig; }
else { inic = l2; l2 = l2->sig; }
c = inic;
while (l1 != centinela && l2 != centinela) {

if (l1->clave < l2->clave) {


c->sig = l1; l1 = l1->sig;
}
else {
c->sig = l2; l2 = l2->sig;
}
c = c->sig;

}
if (l1 != centinela) c->sig = l1;
else if (l2 != centinela) c->sig = l2;
return inic;

/* algoritmo de ordenacin por fusin mediante divide y vencers */


struct lista *ordenfusion(struct lista *l)
{
struct lista *l1, *l2, *parte1, *parte2;
/* caso base: 1 0 elementos */
if (l->sig == centinela) return l;
/* caso recursivo */
/* avanza hasta la mitad de la lista */
l1 = l; l2 = l1->sig->sig;
while (l2 != centinela) {
l1 = l1->sig;
l2 = l2->sig->sig;
}
/* la parte en dos */
l2 = l1->sig;
l1->sig = centinela;
/* ordena recursivamente cada parte */
parte1 = ordenfusion(l);
parte2 = ordenfusion(l2);
/* mezcla y devuelve la lista mezclada */
l = fusion(parte1, parte2);
return l;
}
/* operaciones de lista */
void crearLCC(struct listacc *LCC)
{
LCC->cabecera = (struct lista *) malloc(sizeof(struct lista));
LCC->centinela = (struct lista *) malloc(sizeof(struct lista));
LCC->cabecera->sig = LCC->centinela;
LCC->centinela->sig = LCC->centinela;
}
/* inserta un elemento al comienzo de la lista */
void insertarPrimero(struct listacc LCC, int elem)
{
struct lista *nuevo;

nuevo = (struct lista *) malloc(sizeof(struct lista));


nuevo->clave = elem;
nuevo->sig = LCC.cabecera->sig;
LCC.cabecera->sig = nuevo;

int main(void)
{
struct listacc LCC;
crearLCC(&LCC);
centinela = LCC.centinela;
centinela->clave = INT_MAX;
insertarPrimero(LCC, 8);
insertarPrimero(LCC, 3);
insertarPrimero(LCC, 4);
insertarPrimero(LCC, 7);
insertarPrimero(LCC, 0);
insertarPrimero(LCC, 9);
insertarPrimero(LCC, 6);
insertarPrimero(LCC, 1);
insertarPrimero(LCC, 2);
insertarPrimero(LCC, 3);
LCC.cabecera = ordenfusion(LCC.cabecera->sig);
return 0;
}
Este es un buen algoritmo de ordenacin, pues no requiere espacio para una nueva lista y slo las
operaciones recursivas consumen algo de memoria. Es por tanto un algoritmo ideal para ordenar listas.
La complejidad es la misma en todos los casos, ya que no influye cmo est ordenada la lista inicial -esto
es, no existe ni mejor ni peor caso-, puesto que la intercalacin de dos listas ordenadas siempre se realiza
de una nica pasada. La complejidad es proporcional a NlogN, caracterstica de los algoritmos "Divide y
Vencers". Para hacer ms eficiente el algoritmo es mejor realizar un primer recorrido sobre toda la lista
para contar el nmero de elementos y aadir como parmetro a la funcin dicho nmero.

Bsqueda

Cuestiones generales
La bsqueda de un elemento dentro de un array es una de las operaciones ms importantes en el
procesamiento de la informacin, y permite la recuperacin de datos previamente almacenados. El tipo de
bsqueda se puede clasificar como interna o externa, segn el lugar en el que est almacenada la
informacin (en memoria o en dispositivos externos). Todos los algoritmos de bsqueda tienen dos
finalidades:
- Determinar si el elemento buscado se encuentra en el conjunto
- Si el elemento est en el conjunto, hallar la posicin en la que se encuentra.

en

el

que

se

busca.

En este apartado nos centramos en la bsqueda interna. Como principales algoritmos de bsqueda en
arrays tenemos la bsqueda secuencial, la binaria y la bsqueda utilizando tablas de hash.

Bsqueda secuencial
Consiste en recorrer y examinar cada uno de los elementos del array hasta encontrar el o los elementos
buscados, o hasta que se han mirado todos los elementos del array.

for(i=j=0;i<N;i++)
if(array[i]==elemento)
{
solucion[j]=i;
j++;
}
Este algoritmo se puede optimizar cuando el array est ordenado, en cuyo caso la condicin de salida
cambiara a:
for(i=j=0;array[i]<=elemento;i++)
o cuando slo interesa conocer la primera ocurrencia del elemento en el array:
for(i=0;i<N;i++)
if(array[i]==elemento)
break;
En este ltimo caso, cuando slo interesa la primera posicin, se puede utilizar un centinela, esto es, dar a
la posicin siguiente al ltimo elemento de array el valor del elemento, para estar seguro de que se
encuentra el elemento, y no tener que comprobar a cada paso si seguimos buscando dentro de los lmites
del array:
array[N]=elemento;
for(i=0;;i++)
if(array[i]==elemento)
break;
Si al acabar el bucle, i vale N es que no se encontraba el elemento. El nmero medio de comparaciones
que hay que hacer antes de encontrar el elemento buscado es de (N+1)/2.

Bsqueda binaria o dicotmica


Para utilizar este algoritmo, el array debe estar ordenado. La bsqueda binaria consiste en dividir el array
por su elemento medio en dos subarrays ms pequeos, y comparar el elemento con el del centro. Si
coinciden, la bsqueda se termina. Si el elemento es menor, debe estar (si est) en el primer subarray, y
si es mayor est en el segundo. Por ejemplo, para buscar el elemento 3 en el array {1,2,3,4,5,6,7,8,9} se
realizaran los siguientes pasos:
Se
toma
el
elemento
central
y
se
divide
el
array
en
dos:
{1,2,3,4}-5-{6,7,8,9}
Como el elemento buscado (3) es menor que el central (5), debe estar en el primer subarray: {1,2,3,4}
Se
vuelve
a
dividir
el
array
en
dos:
{1}-2-{3,4}
Como el elemento buscado es mayor que el central, debe estar en el segundo subarray: {3,4}
Se
vuelve
a
dividir
en
dos:
{}-3-{4}
Como el elemento buscado coincide con el central, lo hemos encontrado.
Si al final de la bsqueda todava no lo hemos encontrado, y el subarray a dividir est vacio {}, el
elemento no se encuentra en el array. La implementacin sera:
int desde,hasta,medio,elemento,posicion; // desde y
// hasta indican los lmites del array que se est mirando.
int array[N];

// Dar valor a elemento.


for(desde=0,hasta=N-1;desde<=hasta;)
{
if(desde==hasta) // si el array slo tiene un elemento:
{
if(array[desde]==elemento) // si es la solucin:
posicion=desde; // darle el valor.
else // si no es el valor:
posicion=-1; // no est en el array.
break; // Salir del bucle.
}
medio=(desde+hasta)/2; // Divide el array en dos.
if(array[medio]==elemento) // Si coincide con el central:
{
posicion=medio; // ese es la solucin
break; // y sale del bucle.
}
else if(array[medio]>elemento) // si es menor:
hasta=medio-1; // elige el array izquierda.
else // y si es mayor:
desde=medio+1; // elige el array de la derecha.
}
En general, este mtodo realiza log(2,N+1) comparaciones antes de encontrar el elemento, o antes de
descubrir que no est. Este nmero es muy inferior que el necesario para la bsqueda lineal para casos
grandes.
Este mtodo tambin se puede implementar de forma recursiva, siendo la funcin recursiva la que divide
al array en dos ms pequeos (ver recursividad).

Bsqueda mediante transformacin de claves (hashing)


Es un mtodo de bsqueda que aumenta la velocidad de bsqueda, pero que no requiere que los
elementos estn ordenados. Consiste en asignar a cada elemento un ndice mediante una transformacin
del elemento. Esta correspondencia se realiza mediante una funcin de conversin, llamada funcin hash.
La correspondencia ms sencilla es la identidad, esto es, al nmero 0 se le asigna el ndice 0, al elemento
1 el ndice 1, y as sucesivamente. Pero si los nmeros a almacenar son demasiado grandes esta funcin
es inservible. Por ejemplo, se quiere guardar en un array la informacin de los 1000 usuarios de una
empresa, y se elige el nemro de DNI como elemento identificativo. Es inviable hacer un array de
100.000.000 elementos, sobre todo porque se desaprovecha demasiado espacio. Por eso, se realiza una
transformacin al nmero de DNI para que nos de un nmero menor, por ejemplo coger las 3 ltimas
cifras para guardar a los empleados en un array de 1000 elementos. Para buscar a uno de ellos, bastara
con realizar la transformacin a su DNI y ver si est o no en el array.
La funcin de hash ideal debera ser biyectiva, esto es, que a cada elemento le corresponda un ndice, y
que a cada ndice le corresponda un elemento, pero no siempre es fcil encontrar esa funcin, e incluso a
veces es intil, ya que puedes no saber el nmero de elementos a almacenar. La funcin de hash depende
de cada problema y de cada finalidad, y se pueden utilizar con nmeros o cadenas, pero las ms utilizadas
son:

Restas sucesivas: esta funcin se emplea con claves numricas entre las que existen huecos de
tamao conocido, obtenindose direcciones consecutivas. Por ejemplo, si el nmero de expediente
de un alumno universitario est formado por el ao de entrada en la universidad, seguido de un
nmero identificativo de tres cifras, y suponiendo que entran un mximo de 400 alumnos al ao, se
le asignaran las claves:

1998-000
-->
0
1998-001
-->
1
1998-002
-->
2
...
1998-399
-->
399
1999-000
-->
400
...
yyyy-nnn --> N = yyyynnn-1998000+(400*(yyyy-1998))

1998399-1998000
1999000-1998000+400

0
7
1
6

Mitad del cuadrado: consiste en elevar al cuadrado la clave y coger las cifras centrales. Este
mtodo tambin presenta problemas de colisin:
-->
-->
-->
-->

51
84
29
06

Truncamiento: consiste en ignorar parte del nmero y utilizar los elementos restantes como ndice.
Tambin se produce colisin. Por ejemplo, si un nmero de 8 cifras se debe ordenar en un array de
1000 elementos, se pueden coger la primer, la tercer y la ltima cifras para formar un nuevo
nmero:
13000000
12345678
13602499
71140205
73162135 --> 715

-->
-->
-->
-->

123*123=15129
136*136=18496
730*730=532900
301*301=90601
625*625=390625 --> 06

1998000-1998000
1998001-1998000
1998002-1998000

Aritmtica modular: el ndice de un nmero es resto de la divisin de ese nmero entre un nmero
N prefijado, preferentemente primo. Los nmeros se guardarn en las direcciones de memoria de 0
a N-1. Este mtodo tiene el problema de que cuando hay N+1 elementos, al menos un ndice es
sealado por dos elementos (teorema del palomar). A este fenmeno se le llama colisin, y es
tratado ms adelante. Si el nmero N es el 13, los nmeros siguientes quedan transformados en:
13000000
12345678
13602499
71140205
73062138 --> 6

=
=
=

-->
-->
-->
-->

100
138
169
715

Plegamiento: consiste en dividir el nmero en diferentes partes, y operar con ellas (normalmente
con suma o multiplicacin). Tambin se produce colisin. Por ejemplo, si dividimos los nmero de 8
cifras en 3, 3 y 2 cifras y se suman, dar otro nmero de tres cifras (y si no, se cogen las tres
ltimas cifras):
13000000
12345678
71140205
-->
13602499
25000009 --> 259=250+000+09

-->
-->
118
-->

-->

130=130+000+00
657=123+456+78
1118=711+402+05
259=136+024+99

Tratamiento de colisiones: Pero ahora se nos presenta el problema de qu hacer con las colisiones, qu
pasa cuando a dos elementos diferentes les corresponde el mismo ndice. Pues bien, hay tres posibles
soluciones:

Cuando el ndice correspondiente a un elemento ya est ocupada, se le asigna el primer ndice libre a
partir de esa posicin. Este mtodo es poco eficaz, porque al nuevo elemento se le asigna un ndice que
podr estar ocupado por un elemento posterior a l, y la bsqueda se ralentiza, ya que no se sabe la
posicin exacta del elemento.
Tambin se pueden reservar unos cuantos lugares al final del array para alojar a las colisiones. Este
mtodo tambin tiene un problema: Cunto espacio se debe reservar? Adems, sigue la lentitud de
bsqueda si el elemento a buscar es una colisin.
Lo ms efectivo es, en vez de crear un array de nmero, crear un array de punteros, donde cada puntero
seala el principio de una lista enlazada. As, cada elemento que llega a un determinado ndice se pone en
el ltimo lugar de la lista de ese ndice. El tiempo de bsqueda se reduce considerablemente, y no hace
falta poner restricciones al tamao del array, ya que se pueden aadir nodos dinmicamente a la lista (ver
listas).

Backtracking

Introduccin
Los algoritmos de vuelta atrs se utilizan para encontrar soluciones a un problema. No siguen unas reglas
para la bsqueda de la solucin, simplemente una bsqueda sistemtica, que ms o menos viene a
significar que hay que probar todo lo posible hasta encontrar la solucin o encontrar que no existe solucin
al problema. Para conseguir este propsito, se separa la bsqueda en varias bsquedas parciales o
subtareas. Asimismo, estas subtareas suelen incluir ms subtareas, por lo que el tratamiento general de
estos algoritmos es de naturaleza recursiva.
Por qu se llaman algoritmos de vuelta atrs?. Porque en el caso de no encontrar una solucin en una
subtarea se retrocede a la subtarea original y se prueba otra cosa distinta (una nueva subtarea distinta a
las probadas anteriormente).
Puesto que a veces nos interesa conocer mltiples soluciones de un problema, estos algoritmos se pueden
modificar fcilmente para obtener una nica solucin (si existe) o todas las soluciones posibles (si existe
ms de una) al problema dado.
Estos algoritmos se asemejan al recorrido en profundidad dentro de un grafo (ver seccin de grafos,
estructuras de datos, y recorrido de grafos, algoritmos), siendo cada subtarea un nodo del grafo. El caso
es que el grafo no est definido de forma explcita (como lista o matriz de adyacencia), sino de forma
implcita, es decir, que se ir creando segn avance el recorrido. A menudo dicho grafo es un rbol, o no
contiene ciclos, es decir, al buscar una solucin es, en general, imposible llegar a una misma
solucin x partiendo de dos subtareas distintas a y b; o de la subtarea a es imposible llegar a la
subtara b y
viceversa.
Grficamente se puede ver as:

A menudo ocurre que el rbol o grafo que se genera es tan grande que encontrar una solucin o encontrar
la mejor solucin entre varias posibles es computacionalmente muy costoso. En estos casos suelen
aplicarse una serie de restricciones, de tal forma que se puedan podar algunas de las ramas, es decir, no
recorrer ciertas subtareas. Esto es posible si llegado a un punto se puede demostrar que la solucin que se
obtendr a partir de ese punto no ser mejor que la mejor solucin obtenida hasta el momento. Si se hace
correctamente, la poda no impide encontrar la mejor solucin.
A veces, es imposible demostrar que al hacer una poda no se est ocultando una buena solucin. Sin
embargo, el problema quizs no pida la mejor solucin, sino una que sea razonablemente buena y cuyo
coste computacional sea bastante reducido. Esa es una buena razn para aumentar las restricciones a la
hora de recorrer un nodo. Tal vez se pierda la mejor solucin, pero se encontrar una aceptable en un
tiempo reducido.
Los algoritmos de vuelta atrs tienen un esquema genrico, segn se busque una o todas las soluciones, y
puede adaptarse fcilmente segn las necesidades de cada problema. A continuacin se exponen estos
esquemas, extrados de Wirth (verBibliografa). Los bloques se agrupan con begin y end, equivalentes a
los corchetes de C, adems estn tabulados.
- esquema para una solucin:
procedimiento ensayar
repetir
| seleccionar_candidato
| if aceptable then
| begin
|
anotar_candidato
|
|
begin
|
ensayar(paso_siguiente)
|
if no
|
end
|
else
|
anotar_solucion

(paso

TipoPaso)

if solucion_incompleta then
acertado then borrar_candidato
begin

|
| end
hasta
fin procedimiento

acertado

<-

que (acertado

cierto;

cierto) o (candidatos_agotados)

- esquema para todas las soluciones:


procedimiento ensayar
para
| seleccionar
| if aceptable then
| begin
|
anotar_candidato
|
|
ensayar(paso_siguiente)
|
else
|
almacenar_solucion
|
borrar_candidato
| end
hasta
fin procedimiento

(paso

TipoPaso)
cada candidato hacer
candidato

if solucion_incompleta then

que candidatos_agotados

Por ltimo, se exponen una serie de problemas tpicos que se pueden resolver fcilmente con las tcnicas
de vuelta atrs. El primero que se expone es muy conocido. Se trata de la vuelta del caballo. Muchos
problemas de los pasatiempos de los peridicos pueden resolverse con la ayuda de un ordenador y en esta
web se muestran algunos de ellos.

La vuelta del caballo


Se dispone de un tablero rectangular, por ejemplo el tablero de ajedrez, y de un caballo, que se mueve
segn las reglas de este juego. El objetivo es encontrar una manera de recorrer todo el tablero partiendo
de una casilla determinada, de tal forma que el caballo pase una sola vez por cada casilla. Una variante es
obligar
al
caballo
a
volver
a
la
posicin
de
partida
en
el
ltimo
movimiento.
Por ltimo se estudiar como encontrar todas las soluciones posibles partiendo de una misma casilla.
Para resolver el problema hay que realizar todos los movimientos posibles hasta que ya no se pueda
avanzar, en cuyo caso hay que dar marcha atrs, o bien hasta que se cubra el tablero. Adems, es
necesario determinar la organizacin de los datos para implementar el algoritmo.
- Cmo se mueve un caballo?. Para aquellos que no sepan jugar al ajedrez se muestra un grfico con los
ocho movimientos que puede realizar. Estos movimientos sern los ocho candidatos.

Con las coordenadas en las que se encuentre el caballo y las ocho coordenadas relativas se determina el
siguiente
movimiento.
Las
coordenas
relativas
se
guardan
en
dos
arrays:
ejex
=
[2,
1,
-1,
-2,
-2,
-1,
1,
2]
ejey = [1, 2, 2, 1, -1, -2, -2, -1]
El tablero, del tamao que sea, se representar mediante un array bidimensional de nmeros enteros. A
continuacin se muestra un grfico con un tablero de tamao 5x5 con todo el recorrido partiendo de la
esquina superior izquierda.

Cuando se encuentra una solucin, una variable que se pasa por referencia es puesta a 1 (cierto). Puede
hacerse una variable de alcance global y simplificar un poco el cdigo, pero esto no siempre es
recomendable.
Para codificar el programa, es necesario considerar algunos aspectos ms, entre otras cosas no salirse de
los lmites del tablero y no pisar una casilla ya cubierta (seleccin del candidato). Se determina que hay
solucin cuando ya no hay ms casillas que recorrer.
A continuacin se expone un cdigo completo en C, que recubre un tablero cuadrado de lado N partiendo
de la posicin (0,0).
#include <stdio.h>
#define N 5
#define ncuad N*N
void mover(int tablero[][N], int i, int pos_x, int pos_y, int *q);
const int ejex[8] = { -1,-2,-2,-1, 1, 2, 2, 1 },
ejey[8] = { -2,-1, 1, 2, 2, 1,-1,-2 };
int main(void)
{
int tablero[N][N]; /* tablero del caballo. */

int i,j,q;
/* inicializa el tablero a cero */
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
tablero[i][j] = 0;
/* pone el primer movimiento */
tablero[0][0] = 1;
mover(tablero,2,0,0,&q);
if (q) { /* hay solucion: la muestra. */
for (i = 0; i < N; i++) {
for (j = 0; j < N; j++)
printf("%3d ", tablero[i][j]);
putchar('\n');
}
}
else
printf("\nNo existe solucion\n");
return 0;
}
void mover(int tablero[][N],int i, int pos_x, int pos_y, int *q)
{
int k, u, v;
k = 0;
*q = 0;
do {
u = pos_x + ejex[k]; v = pos_y + ejey[k]; /* seleccionar candidato */
if (u >= 0 && u < N && v >= 0 && v < N) { /* esta dentro de los limites? */
if (tablero[u][v] == 0) { /* es valido? */
tablero[u][v] = i; /* anota el candidato */
if (i < ncuad) { /* llega al final del recorrido? */
mover(tablero,i+1,u,v,q);
if (!*q) tablero[u][v] = 0; /* borra el candidato */
}
else *q = 1; /* hay solucion */
}
}
k++;
} while (!*q && k < 8);
}

Cambiando el valor de N puede obtenerse una solucin para un tablero cuadrado de tamao N.
A continuacin, se muestra una adaptacin del procedimiento que muestra todas las soluciones. Si se
ejecuta para N = 5 se encuentra que hay 304 soluciones partiendo de la esquina superior izquierda.
Cuando se encuentra una solucin se llama a un procedimiento (no se ha codificado aqu) que imprime
todo el tablero.
void
mover(int
{
int k, u, v;

tablero[][N],int

i,

int

pos_x,

int

pos_y)

for
(k
=
0;
k
<
8;
k++)
{
u
=
pos_x
+
ejex[k];
v
=
pos_y
+
ejey[k];
if (u >= 0 && u < N && v >= 0 && v < N) { /* esta dentro de los limites */
if
(tablero[u][v]
==
0)
{

}
}

tablero[u][v]
if
(i
mover(tablero,i+1,u,v);
else
tablero[u][v]

<

i;

ncuad)

imprimir_solucion(tablero);
0;

El problema de las ocho reinas


Continuamos con problemas relacionados con el ajedrez. El problema que ahora se plantea es claramente,
como se ver, de vuelta atrs. Se recomienda intentar resolverlo a mano.
Se trata de colocar ocho reinas sobre un tablero de ajedrez, de tal forma que ninguna amenace (pueda
comerse) a otra. Para los que no sepan ajedrez deben saber que una reina amenaza a otra pieza que est
en la misma columna, fila o cualquiera de las cuatro diagonales.
La dificultad que plantea este problema es la representacin de los datos. Se puede utilizar un array
bidimensional de tamao 8x8, pero las operaciones para encontrar una reina que amenace a otra son algo
engorrosas y hay un truco para evitarlas. La solucin aqu expuesta vuelve a ser tomada de Wirth (ver
Bibliografa).
Es lgico que cada reina debe ir en una fila distinta. Por tanto, en un array se guarda la posicin de cada
reina en la columna que se encuentre. Ejemplo: si en la tercera fila hay una reina situada en la quinta
columna, entonces la tercera posicin del array guardar un 5. A este array se le llamar col.
Hace falta otro array que determine si hay puesta una reina en la fila j-sima. A este array se le
llamar fila.
Por ltimo se utilizan dos arrays ms para determinar las diagonales libres, y se llamarn diagb y diagc.
Para
poner
una
reina
se
utiliza
col[i] = j ; fila[j] = diagb[i+j] = diagc[7+i-j] = FALSE;

esta

instruccin:

Para
quitar
una
fila[j] = diagb[i+j] = diagc[7+i-j] = TRUE;

esta

otra:

reina

Se
considera
vlida
la
posicin
para
if (fila[j] && diagb[i+j] && diagc[7+i-j]) entonces proceder ...

este

caso:

A continuacin se expone el cdigo completo en C. Se han utilizado tipos enumerados para representar los
valores booleanos.
#include <stdio.h>
enum bool {FALSE, TRUE};
typedef enum bool boolean;
void ensayar(int i, boolean *q, int col[], boolean fila[], boolean diagb[], boolean
diagc[]);
int main(void)
{
int i;
boolean q;
int col[8];

boolean fila[8],diagb[15], diagc[15];


for (i = 0; i < 8; i++) fila[i] = TRUE;
for (i = 0; i < 15; i++) diagb[i] = diagc[i] = TRUE;
ensayar(0,&q,col,fila,diagb,diagc);
if (q) {
printf("\nSolucion:");
for (i = 0; i < 8; i++) printf(" %d", col[i]);
} else printf("\nNo hay solucion");
return 0;
}
void ensayar(int i, boolean *q, int col[], boolean fila[], boolean diagb[], boolean
diagc[])
{
int j;
j = 0;
*q = FALSE;
do {
if (fila[j] && diagb[i+j] && diagc[7+i-j]) {
col[i] = j; fila[j] = diagb[i+j] = diagc[7+i-j] = FALSE;
if (i < 7) { /* encuentra solucion? */
ensayar(i+1,q,col,fila,diagb,diagc);
if (!*q)
fila[j] = diagb[i+j] = diagc[7+i-j] = TRUE;
} else *q = TRUE; /* encuentra la solucion */
}
j++;
} while (!*q && j < 8);
}

Por ltimo, se deja al lector que implemente un procedimiento que encuentre todas las soluciones. Si se
desea complicar ms entonces se puede pedir que encuentre todas las soluciones distintas, es decir,
aquellas que no sean rotaciones o inversiones de otras soluciones.
Ahora que se conoce el mtodo general, puede hacerse extensible a mltiples piezas simultneamente.

El Problema de la mochila (seleccin ptima)


Con anterioridad se ha estudiado la posibilidad de encontrar una nica solucin a un problema y la
posibilidad de encontrarlas todas. Pues bien, ahora se trata de encontrar la mejor solucin, la solucin
ptima,
de
entre
todas
las
soluciones.
Partiendo del esquema que genera todas las soluciones expuesto anteriormente se puede obtener la mejor
solucin (la solucin ptima, seleccionada entre todas las soluciones) si se modifica la
instruccin almacenar_solucion por
esta
otra:
si f(solucion)
>
f(optimo) entonces optimo
<solucion
siendo f(s) funcin positiva, optimo es la mejor solucion encontrada hasta el momento, y solucion es una
solucion que se est probando.
El problema de la mochila consiste en llenar una mochila con una serie de objetos que tienen una serie de
pesos con un valor asociado. Es decir, se dispone de n tipos de objetos y que no hay un nmero limitado
de cada tipo de objeto (si fuera limitado no cambia mucho el problema). Cada tipo i de objeto tiene un
peso wi positivo y un valor vi positivo asociados. La mochila tiene una capacidad de peso igual a W. Se

trata de llenar la mochila de tal manera que se maximice el valor de los objetos incluidos pero
respetando al mismo tiempo la restriccin de capacidad. Notar que no es obligatorio que una solucin
ptima llegue al lmite de capacidad de la mochila.
Ejemplo: se
supondr:
n
=
4
W
=
8
w()
=
2,
3,
4,
5
v()
=
3,
5,
6,
10
Es decir, hay 4 tipos de objetos y la mochila tiene una capacidad de 8. Los pesos varan entre 2 y 5, y los
valores
relacionados
varan
entre
3
y
10.
Una solucin no ptima de valor 12 se obtiene introduciendo cuatro objetos de peso 2, o 2 de peso 4. Otra
solucin no ptima de valor 13 se obtiene introduciendo 2 objetos de peso 3 y 1 objeto de peso 2. Cul
es la solucin ptima?.
A continuacin se muestra una solucin al problema, variante del esquema para obtener todas las
soluciones.
void mochila(int i, int r, int solucion, int *optimo)
{
int k;

for (k = i; k < n; k++) {


if (peso[k] <= r) {
mochila(k, r - peso[k], solucion + valor[k], optimo);
if (solucion + valor[k] > *optimo) *optimo = solucion+valor[k];
}
}

Dicho procedimiento puede ser ejecutado de esta manera, siendo n, W, peso y valor variables globales
para
simplificar
el
programa:
n
W
peso[]
valor[]
optimo
...
mochila(0, W, 0, &optimo);

=
=
=
=

4,
8,
{2,3,4,5},
{3,5,6,10},
0;

Observar que la solucin ptima se obtiene independientemente de la forma en que se ordenen los
objetos.

Divide y vencers

Cuestiones generales
La tcnica de diseo de algoritmos llamada "divide y vencers" (divide and conquer) consiste en
descomponer el problema original en varios sub-problemas ms sencillos, para luego resolver stos
mediante un clculo sencillo. Por ltimo, se combinan los resultados de cada sub-problema para obtener la
solucin del problema original. El pseudocdigo sera:

funcion divide_y_venceras_1(problema)
{
descomponer el problema en n subproblemas ms pequeos;
para i=1 hasta n hacer
resolver el subproblema k;
combinar las n soluciones;
}
Un ejemplo de "divide y vencers" es la ordenacin rpida, o quicksort, utilizada para ordenar arrays. En
ella, se divida el array en dos sub-arrays, para luego resolver cada uno por separado, y unirlos (ver
algoritmos de ordenacin). El ahorro de tiempo es grande: el tiempo necesario para ordenar un array de
elementos mediante el mtodo de la burbuja es cuadrtico: kN 2. Si dividimos el array en dos y ordenamos
cada uno de ellos, el tiempo necesario para resolverlo es ahora k(N/2) 2+k(N/2)2=(kN2)/2. El tiempo
necesario para ordenarlo es la mitad, pero sigue siendo cuadrtico.
Pero ahora, si los subproblemas son todava demasiado grandes, por qu no utilizar la misma tctica con
ellos, esto es, dividirlos a ellos tambin, utilizando un algoritmo recursivo (ver recursividad) que vaya
dividiendo ms el sub-problema hasta que su solucin sea trivial? Un algoritmo del tipo:
funcion divide_y_venceras(problema)
{
si el problema es trivial
entonces resolver el problema;
si no es trivial
{
descomponer el problema en n subproblemas ms pequeos;
para i=1 hasta n hacer
divide_y_venceras(subproblema_k);
combinar las n soluciones;
}
}
Si aplicamos este mtodo al quicksort, el tiempo disminuye hasta ser logartmico, con lo que el tiempo
ahorrado es mayor cuanto ms aumenta N.

Tiempo de ejecucin
El tiempo de ejecucin de un algoritmo de divide y vencers, T(n), viene dado por la suma de dos
elementos:
El tiempo que tarda en resolver los A subproblemas en los que se divide el original, AT(n/B), donde

n/B es el tamao de cada sub-problema.


El tiempo necesario para combinar las soluciones de los sub-problemas para hallar la solucin del
original; normalmente es O(nk)
Por tanto, el tiempo total es: T(n) = AT(n/B) + O(n k). La solucin de esta ecuacin, si A es mayor o igual
que 1 y B es mayor que 1, es:
si
A>Bk,
k
si
A=B ,
si A<Bk, T(n) = O(nk)

Determinacin del umbral

T(n)

T(n)

O(n log
k

O(nlogBA)
n)

Uno de los aspectos que hay que tener en cuenta en los algoritmos de divide y vencers es dnde colocar
el umbral, esto es, cundo se considera que un sub-problema es suficientemente pequeo como para no
tener que dividirlo para resolverlo. Normalmente esto es lo que hace que un algoritmo de divide y
vencers sea efectivo o no. Por ejemplo, en el algoritmo de ordenacin quicksort, cuando se tiene un array
de longitud 3, es mejor ordenarlo utilizando otro algoritmo de ordenacin (con 6 comparaciones se puede
ordenar), ya que el quicksort debe dividirlo en dos sub-arrays y ordenar cada uno de ellos, para lo que
utiliza ms de 6 comparaciones.

Problema de los puntos ms cercanos

El problema es: "dado un conjunto de puntos P, hallar el par de puntos ms cercanos". La distancia entre
dos puntos i y j es sqrt[(xi-xj)2+(yi-yj)2]. Una primera solucin podra ser mirar todos los pares de puntos y
quedarse con el ms pequeo; al haber n(n-1)/2 pares de puntos, el tiempo de ejecucin es de O(n 2). El
programa resultante es muy corto, y rpido para casos pequeos, pero al ser este procedimiento una
bsqueda exhaustiva, debera haber un algoritmo ms rpido.
Supongamos que ordenamos los puntos segn la coordenada x; esto supondra un tiempo de O(nlog n),
lo que es una cota inferior para el algoritmo completo. Ahora que se tiene el conjunto ordenado, se puede
trazar una lnea vertical, x = x m, que divida al conjunto de puntos en dos: P i y Pd. Ahora, o el par ms
cercano est en Pi, o est en Pd, o un punto est en P i y el otro en Pd. Si los dos estuvieran en P i o en Pd,
se hallara recursivamente, subdividiendo ms el problema, por lo que ahora el problema se reduce al
tercer caso, un punto en cada zona.

Llamemos di, dd y dc a las mnimas distancias en el primer caso, en el segundo, y en el tercero,


respectivamente, y dmin al menor de di y dd. Para resolver el tercer caso, slo hace falta mirar los puntos
cuya coordenada x est entre x m-dmin y xm+dmin. Para grandes conjuntos de puntos distribuidos
uniformemente, el nmero de puntos que caen en esa franja es sqrt(n), as que con una bsqueda
exhaustiva el tiempo de ejecucin sera de O(n), y tendramos el problema resuelto. El tiempo de
ejecucin sera, segn lo dicho en el otro apartado, O(nlog n).

Pero si los puntos no estn uniformemente distribuidos, la cosa cambia. En el peor de los casos, todos los
puntos estn en la franja, as que la fuerza bruta no siempre funciona en tiempo lineal. Para ello, se puede
recurrir a ordenar los puntos de la franja segn la coordenada y, lo que supone un tiempo de ejecucin de
O(nlog n). Si la coordenada y difiere en ms de d min, se puede pasar al siguiente punto. El tiempo de
ejecucin es O(max(nlog n,nlog n))=O(nlog n), con lo que mantenemos el tiempo de ejecucin anterior.
El programa sera:
#include
#include
#include
#include

<stdio.h>
<stdlib.h>
<string.h>
<math.h>

#define MAX 110L // Nmero mximo de puntos.


struct punto // Estructura de un punto.
{
double x;
double y;
};
void busca(struct punto *,int);
int ordenax(const void *,const void *);
double dist(struct punto,struct punto);
struct punto c1,c2; // Puntos ms cercanos.
double mindist=1e10; // Mnima distancia.
int main()
{
int a,num;
double x,y;
struct punto p[MAX];
for(scanf(" %d",&num),a=0;a<num;a++) // Coge la entrada
{
scanf(" %lf %lf",&x,&y);
p[a].x=x;
p[a].y=y;
}
busca(p,num); // Hacer la primera bsqueda.
printf("Distancia minima: %.3lf\n",mindist);
printf("Puntos: (%.3lf,%.3lf) y (%.3lf,%.3lf)\n",c1.x,c1.y,c2.x,c2.y);
}

return(0);

void busca(struct punto *p,int num)


{
double d;
int desde,hasta,a,b;
if(num<=1) // Si no hay pares de puntos:
return; // salir.
// Ordenar los puntos por la coordenada x.
qsort(p,num,sizeof(struct punto),ordenax);
// Mirar en el subconjunto de la izquierda.
busca(p,num/2);
// Mirar en el subconjunto de la derecha.
busca(p+num/2,(num+1)/2);
// Hallar los lmites del conjunto central.
for(desde=num/2; desde>0 && p[num/2].x-p[desde].x<mindist; desde--);
for(hasta=num/2; hasta<num-1 && p[hasta].x-p[num/2].x<mindist; hasta++);
// Bsqueda exhaustiva en el conjunto central.
for(a=desde;a<=hasta;a++)
for(b=a+1;b<=hasta;b++)
if((d=dist(p[a],p[b]))<mindist)
{
mindist=d;
c1.x=p[0].x;
c1.y=p[0].y;
c2.x=p[1].x;
c2.y=p[1].y;
}
}
// Funcin auxiliar del qsort.
int ordenax(const void *a,const void *b)
{
return(((*(struct punto *)a).x<(*(struct punto *)b).x)?-1:1);
}
// Funcin que calcula la distancia entre dos puntos.
double dist(struct punto a,struct punto b)
{
return(sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)));
}

Hallar la mediana de un conjunto de puntos


La mediana es el elemento de un conjunto que cumple que en el conjunto hay el mismo nmero de
elemntos mayores que l y menores que l. Dicho de otra forma, si el conjunto est ordenado, la mediana
es el elemento central. Una forma de resolverlo es esa, ordenar el conjunto y localizar el elemnto central.
El tiempo de ejecucin sera de O(nlog n), pero, se podra resolver en tiempo lineal utilizando una
estrategia de divide y vencers?
Hay un mtodo llamado seleccin rpida que tiene un tiempo esperado lineal, pero que en algunos casos
puede tener tiempo O(n2). Este mtodo no slo puede hallar la mediana, sino el elemento que ocupa la
posicin k-sima. Para ello, se toma un elemento pivote (normalmente el primero), y se divide el
conjuntos en dos sub-conjuntos, segn los elementos sean mayores o menores que el pivote. El pivote
ahora ocupar una posicin p. Si p es igual a k, el nmero buscado es el pivote; si p es menor que k, el
nmero buscado est en el primer subconjunto, y si p es mayor que k, el nmero buscado est en el

segundo subconjunto. Este mtodo se continua recursivamente, hasta que se halle el elemento buscado.
El programa sera:
#include <stdio.h>
#include <stdlib.h>
int seleccionrapida(int *,int,int);
int k;
int main()
{
int num,a,m;
int *array;
scanf(" %d",&num); // Pedir el tamao del array
array=(int *)malloc(sizeof(int)*num);
for(a=0;a<num;a++) // Dar valores al array
scanf(" %d",&array[a]);
scanf(" %d",&k); // Pedir el valor k
k--; // Por ir de 0 a num-1
m=seleccionrapida(array,0,num-1); // Para llamar a la funcin
printf("%d\n",m);
return(0);
}
int seleccionrapida(int *array,int desde,int hasta)
{
int i,d,aux; // i realiza la bsqueda de izquierda a derecha
// y j realiza la bsqueda de derecha a izquierda.
for(i=desde+1,d=hasta; ; ) // Valores iniciales de la bsqueda.
{
for( ;i<=hasta && array[i]<=array[desde]; i++); // Primera bsqueda
for( ;d>=0 && array[d]>=array[desde]; d--); // Segunda bsqueda
if(i<d) // si no se han cruzado:
{
aux=array[i]; // Intercambiar.
array[i]=array[d];
array[d]=aux;
}
else // si se han cruzado:
break; // salir del bucle.
}
if(d==desde-1) // Si la segunda bsqueda se sale del array
d=desde; // es que el pivote es el elemento
// ms pequeo: se cambia con l mismo.
aux=array[d]; // Colocar el pivote
array[d]=array[desde]; // en su posicin.
array[desde]=aux;

if(d==k)
return(array[d]); // El pivote es el elemento buscado
else if(d>k)
return(seleccionrapida(array,desde,d-1)); // Buscar en el primer array.
else
return(seleccionrapida(array,d+1,hasta)); // Buscar en el segundo array.

En esta solucin en tiempo medio lineal se descartan slo unos cuantos elementos cada vez que se llama
a la funcin recursiva, dependiendo del pivote que elijamos. Para descartar una fraccin constante de
elementos (y mejorar el tiempo de ejecucin del peor caso) habra que elegir un mejor pivote, a ser
preferible la mediana (cosa que no puede ser, porque es justamente ella la que estamos buscando).
Tampoco se puede perder mucho tiempo en buscar un buen pivote, porque hara demasiado lento el
programa. Una opcin sera elegir tres elementos y utilizar como pivote la mediana de esos tres, pero en
el peor caso eso sigue siendo una mala opcin. Hay un algoritmo de eleccin del pivote llamado "particin
con base en la mediana de la mediana de cinco", que consiste en:

Dividir los n elementos en n/5 grupos de 5 elementos (ignorando los ltimos elementos).
Encontrar la mediana de cada grupo de 5 elementos, lo que da una lista de n/5 medianas M.
Hallar la mediana de M, o un buen pivote en M, lo que se puede hacer utilizando este algoritmo
recursivamente.
Se puede probar que, utilizando este mtodo, y en el peor de los casos, cada llamada a la funcin
recursiva desprecia a ms del 30% de los elementos. Esto hace que el algoritmo de seleccin rpida sea
lineal, a pesar de que utilizamos un algoritmo auxiliar para la bsqueda del pivote. El problema completo
sera:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int seleccionrapida(int *,int *,int,int,int);
int hallamediana(int *,int *,int);
int main()
{
int num,a,m,k;
int *array,*pos;
scanf(" %d",&num); // Pedir el tamao del array
array=(int *)malloc(sizeof(int)*num);
for(a=0;a<num;a++) // Dar valores al array
scanf(" %d",&array[a]);
scanf(" %d",&k); // Pedir el valor k
k--;
pos=(int *)malloc(sizeof(int)*num);
m=seleccionrapida(array,pos,0,num-1,k); // Para llamar a la funcin
printf("%d\n",m);
return(0);
}
int seleccionrapida(int *array,int *pos,int desde,int hasta,int k)
{
int i,d,aux; // i realiza la bsqueda de izquierda a derecha
// y j realiza la bsqueda de derecha a izquierda.
// Hallar la falsa mediana para tomarla como pivote
for(i=desde;i<=hasta;i++)
pos[i-desde]=i;
d=hallamediana(array+desde,pos,hasta-desde+1);
aux=array[d]; // Poner la falsa mediana al principio.
array[d]=array[desde];
array[desde]=aux;
for(i=desde+1,d=hasta;;) // Valores iniciales de la bsqueda.

for(;i<=hasta && array[i]<=array[desde];i++); // Primera bsqueda


for(;d>=0 && array[d]>=array[desde];d--); // segunda bsqueda
if(i<d) // si no se han cruzado:
{
aux=array[i]; // Intercambiar.
array[i]=array[d];
array[d]=aux;
}
else // si se han cruzado:
break; // salir del bucle.

}
if(d==desde-1) // Si la segunda bsqueda se sale del array
d=desde; // es que el pivote es el elemento
// ms pequeo: se cambia con l mismo.
aux=array[d]; // Colocar el pivote
array[d]=array[desde]; // en su posicin.
array[desde]=aux;

if(d==k)
return(array[d]); // El pivote es el elemento buscado
else if(d>k)
return(seleccionrapida(array,pos,desde,d-1,k)); // Buscar en el primer array.
else
return(seleccionrapida(array,pos,d+1,hasta,k)); // Buscar en el segundo array.

int hallamediana(int *array,int *pos,int num)


{
int a,*array2,b,c,d;
if(num<5) // Si hay menos de 5 elementos
return(pos[0]); // vale el primero (lo que devuelve esta
// funcin es el ndice de la mediana en la variable "array" de la funcin
// seleccionrapida que llama por primera vez a esta funcin.
// Hallar la mediana de cada cinco elementos.
array2=(int *)malloc(sizeof(int)*((num/5)+4));
for(a=0;a+4<num;a+=5)
{
memcpy(array2+(a/5),array,sizeof(int)*5);
for(b=0;b<5;b++)
for(c=b+1;c<5;c++)
if(array2[b]>array2[c])
{
d=array2[b];
array2[b]=array2[c];
array2[c]=d;
d=pos[b];
pos[b]=pos[c];
pos[c]=d;
}
array2[a/5]=array[a+2];
pos[a/5]=pos[a+2];
}
// Hallar la mediana de las medianas.
a=hallamediana(array2,pos,num/5);
free(array2);
return(a);
}

Multiplicacin de matrices
Dadas dos matrices A y B de tamao n x n, hallar C, el producto de las dos. Un primer algoritmo se saca
de la definicin de producto de matrices: C i j es el producto escalar de la i-sima fila por la j-sima
columna. Su tiempo de ejecucin sera O(n3):
int i,j,k;
int A[n][n],B[n][n],C[n][n];
// Dar valores a A y B.
for(i=0;i<n;i++)
for(i=0;i<n;i++)
{
C[i][j]=0;
for(i=0;i<n;i++)
C[i][j]+=A[i][k]*B[k][j];
}
Pero este tiempo de ejecucin tambin se puede mejorar. Si n es una potencia de 2, se pueden dividir A, B
y C en cuatro submatrices cada una, de la forma:

Ahora, para hallar las submatrices de C, basta con utilizar que:


C1,1
C1,2
C2,1
C2,2

=
=
=
= A2,1B1,2 + A2,2B2,2

A1,1B1,1 +
A1,1B1,2 +
A2,1B1,1 +

A1,2B2,1
A1,2B2,2
A2,2B2,1

Con este mtodo el tiempo de ejecucin es de T(n) = 8T(n/2) + O(n 2), o lo que es lo mismo, O(n 3), con
lo que no habra diferencia entre este mtodo y el primero propuesto. Hay que reducir el nmero de
multiplicaciones por debajo de 8. Esto se logra utilizando el algoritmo de Strassen. Definimos las
siguientes matrices:
M1
M2
M3
M4
M5
M6
M7

=
=
=
=
=
=
= (A2,1 + A2,2)B1,1

(A1,2 (A1,1 +
(A1,1 -

(A1,1 +
A1,1(B1,2 A2,2(B2,1 -

A2,2)(B2,1 +
A2,2)(B1,1 +
A2,1)(B1,1 +

B2,2)
B2,2)
B2,1)
A1,2)B2,2
B2,2)
B1,1)

para las que se necesitan 7 multiplicaciones diferentes, y ahora se calculan las submatrices de C utilizando
slo sumas:
C1,1
C1,2
C2,1
C2,2

=
=
=
= M2 - M3 + M5 - M7

M1 +

M2 M4 +
M6 +

M4 +

M6
M5
M7

El nuevo tiempo de ejecucin es de T(n) = 7T(n/2) + O(n 2), es decir, de T(n) = O(n log27) = O(n2.81). Este
algoritmo slo es mejor que el directo cuando n es muy grande, y es ms inestable cuando se utiliza con
nmeros reales, por lo que tiene una aplicabilidad limitada.

Para el caso general en el que n no es potencia de 2, se pueden rellenar las matrices con ceros hasta que
las dos matrices sean cuadradas y n sea una potencia de 2.
El programa queda as:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int max(int,int);
int **multiplicamatriz(int **,int **,int); // Funcin recursiva que multiplica matrices
int **suma(int **,int **,int); // suma de matrices
int **resta(int **,int **,int);// Resta de matrices
void libera(int **,int); // Libera memoria
int main()
{
int **mapa1,**mapa2,**sol;
int f1,c1,f2,c2,a,b,m,m1;
scanf(" %d %d",&f1,&c1); // Tamao de la primera matriz
scanf(" %d %d",&f2,&c2); // Tamao de la segunda matriz
m1=max(f1,max(c1,max(f2,c2)));
for(m=1;m<m1;m*=2); // El tamao de las matrices cuadradas a multiplicar
//debe ser de la forma 2k, si no se completan con ceros.
mapa1=(int **)malloc(sizeof(int *)*m); // Se crea la primera matriz
for(a=0;a<m;a++)
{
mapa1[a]=(int *)malloc(sizeof(int)*m);
memset(mapa1[a],0,sizeof(int)*m);
}
for(a=0;a<f1;a++) // Se cogen los datos de la primera matriz.
for(b=0;b<c1;b++)
scanf(" %d",&mapa1[a][b]);
mapa2=(int **)malloc(sizeof(int *)*m); // Se crea la sedunda matriz.
for(a=0;a<m;a++)
{
mapa2[a]=(int *)malloc(sizeof(int)*m);
memset(mapa2[a],0,sizeof(int)*m);
}
for(a=0;a<f2;a++) // Se cogen los datos de la segunda matriz.
for(b=0;b<c2;b++)
scanf(" %d",&mapa2[a][b]);
sol=multiplicamatriz(mapa1,mapa2,m); // Se multiplican.
for(a=0;a<f1;a++) // Se imprime el resultado.
{
for(b=0;b<c2;b++)
printf("%d ",sol[a][b]);
printf("\n");
}
}

return(0);

int max(int a,int b)


{
return((a>b)?a:b);

}
int **multiplicamatriz(int **mapa1,int **mapa2,int num)
{
int **sol,**M[8],**f1,**f2,**aux,**aux2;
int **A[2][2],**B[2][2],**C[2][2];
int a,q,w,r;
sol=(int **)malloc(sizeof(int *)*num);
for(a=0;a<num;a++)
sol[a]=(int *)malloc(sizeof(int)*num);
if(num==1)
{
sol[0][0]=mapa1[0][0]*mapa2[0][0];
return(sol);
}
// Crear las submatrices de A y B.
for(q=0;q<2;q++)
{
for(w=0;w<2;w++)
{
A[q][w]=(int **)malloc(sizeof(int *)*(num/2));
for(a=0;a<num/2;a++)
{
A[q][w][a]=(int *)malloc(sizeof(int)*(num/2));
for(r=0;r<num/2;r++)
A[q][w][a][r]=mapa1[a+(num/2)*q][r+(num/2)*w];
}
B[q][w]=(int **)malloc(sizeof(int *)*(num/2));
for(a=0;a<num/2;a++)
{
B[q][w][a]=(int *)malloc(sizeof(int)*(num/2));
for(r=0;r<num/2;r++)
B[q][w][a][r]=mapa2[a+(num/2)*q][r+(num/2)*w];
}

}
}
// Hallar las matrices M.
f1=resta(A[0][1],A[1][1],num/2);
f2=suma(B[1][0],B[1][1],num/2);
M[1]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
libera(f2,num/2);
f1=suma(A[0][0],A[1][1],num/2);
f2=suma(B[0][0],B[1][1],num/2);
M[2]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
libera(f2,num/2);
f1=resta(A[0][0],A[1][0],num/2);
f2=suma(B[0][0],B[0][1],num/2);
M[3]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
libera(f2,num/2);
f1=suma(A[0][0],A[0][1],num/2);
f2=B[1][1];
M[4]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);

f1=A[1][1];
f2=resta(B[0][1],B[1][1],num/2);
M[5]=multiplicamatriz(f1,f2,num/2);
libera(f2,num/2);
f1=A[1][1];
f2=resta(B[1][0],B[0][0],num/2);
M[6]=multiplicamatriz(f1,f2,num/2);
libera(f2,num/2);
f1=suma(A[1][0],A[1][1],num/2);
f2=B[0][0];
M[7]=multiplicamatriz(f1,f2,num/2);
libera(f1,num/2);
// Hallar las submatrices de C.
C[0][0]=suma(M[1],M[2],num/2);
aux=C[0][0];
C[0][0]=resta(C[0][0],M[4],num/2);
aux2=C[0][0];
C[0][0]=suma(C[0][0],M[6],num/2);
libera(aux,num/2);
libera(aux2,num/2);
C[0][1]=suma(M[4],M[5],num/2);
C[1][0]=suma(M[6],M[7],num/2);
C[1][1]=resta(M[2],M[3],num/2);
aux=C[1][1];
C[1][1]=suma(C[1][1],M[5],num/2);
aux2=C[1][1];
C[1][1]=resta(C[1][1],M[7],num/2);
libera(aux,num/2);
libera(aux2,num/2);
for(a=1;a<=7;a++)
libera(M[a],num/2);
// Unir las submatrices de matrices C en sol.
for(q=0;q<num;q++)
for(w=0;w<num;w++)
sol[q][w]=C[q/(num/2)][w/(num/2)][q%(num/2)][w%(num/2)];
// Liberar las submatrices de A, B y C.
for(q=0;q<2;q++)
for(w=0;w<2;w++)
{
libera(A[q][w],num/2);
libera(B[q][w],num/2);
libera(C[q][w],num/2);
}
}

return(sol);

int **suma(int **mapa1,int **mapa2,int num)


{ // sumar mapa1 y mapa2.
int a,b;
int **sol;
sol=(int **)malloc(sizeof(int *)*num);
for(a=0;a<num;a++)
{
sol[a]=(int *)malloc(sizeof(int)*num);

for(b=0;b<num;b++)
sol[a][b]=mapa1[a][b]+mapa2[a][b];
}
return(sol);
}
int **resta(int **mapa1,int **mapa2,int num)
{ // Restar mapa2 de mapa1.
int **sol;
int a,b;
sol=(int **)malloc(sizeof(int *)*num);
for(a=0;a<num;a++)
{
sol[a]=(int *)malloc(sizeof(int)*num);
for(b=0;b<num;b++)
sol[a][b]=mapa1[a][b]-mapa2[a][b];
}
return(sol);
}
void libera(int **mapa,int num)
{
int a;
for(a=0;a<num;a++) // Liberar la tabla dinmica de 2D.
free(mapa[a]);
free(mapa);
}

6. Manejo de Diagramas de flujo

Introduccin.
Los diagramas de flujo son una manera de representar visualmente el flujo de datos a travz de
sistemas de tratamiento de informacin. Los diagramas de flujo describen que operacines y en que
secuencia se requieren para solucionar un problema dado.
Un diagrama de flujo u organigrama es una representacin diagramtica que ilustra la secuencia de
las operaciones que se realizarn para conseguir la solucin de un problema. Los diagramas de flujo
se dibujan generalmente antes de comenzar a programar el cdigo frente a la computadora. Los
diagramas de flujo facilitan la comunicacin entre los programadores y la gente del negocio. Estos
diagramas de flujo desempean un papel vital en la programacin de un problema y facilitan la
comprensin de problemas complicados y sobre todo muy largos. Una vez que se dibuja el
diagrama de flujo, llega a ser fcil escribr el programa en cualquier idima de alto nivel. Vemos a
menudo cmo los diagramas de flujo nos dan ventaja al momento de explicar el programa a otros.
Por lo tanto, est correcto decir que un diagrama de flujo es una necesidad para la documentacin
mejor de un programa complejo.
Reglas para dibujar un diagramas de flujo.

Los Diagramas de flujo se dibujan generalmente usando algunos smbolos estndares; sin embargo,
algunos smbolos especiales pueden tambin ser desarrollados cuando san requeridos. Algunos
smbolos estndares, que se requieren con frecuencia para diagramar programas de computadora
se muestran a continuacin:

Inicio o fin del programa

Pasos, procesos o lneas de instruccion de programa de computo

Operaciones de entrada y salida

Toma de desicines y Ramificacin

Conector para unir el flujo a otra parte del diagrama

Cinta magntica

Disco magntico

Conector de pagina

Lneas de flujo

Anotacin

Display, para mostrar datos

Enva datos a la impresora

Observacin: Para obtener la correcta elaboracin de los smbolos, existen plantillas. Las puedes
conseguir en Papeleras.
Simbolos grficos
Dentro de los simbolos fundamentales para la creaacin de diagramas de flujo, los smbolos grficos
son utilizdos especificamente para para operacines aritmticas y relacines condicionales. La
siguiente es una lista de los smbolos ms comunmente utilizados:
+

Sumar

Menos

Multiplicacin

Divisin

Mas o menos

Equivalente a

>

Mayor que

<

Menor que

Mayor o igual que

Menor o igual que

o <>

Diferente de
Si
No
True

False

Reglas para la creacion de Diagramas


1.

Los Diagramas de flujo deben escribirse de arriba hacia abajo, y/o de izquierda a derecha.

2.

Los smbolos se unen con lneas, las cuales tienen en la punta una flecha que indica la direccin que
fluye la informacin procesos, se deben de utilizar solamente lneas de flujo horizontal o verticales (nunca
diagonales).

3.

Se debe evitar el cruce de lneas, para lo cual se quisiera separar el flujo del diagrama a un sitio
distinto, se pudiera realizar utilizando los conectores. Se debe tener en cuenta que solo se vana utilizar
conectores cuando sea estrictamente necesario.

4.

No deben quedar lneas de flujo sin conectar

5.

Todo texto escrito dentro de un smbolo debe ser legible, preciso, evitando el uso de muchas palabras.

6.

Todos los smbolos pueden tener ms de una lnea de entrada, a excepcin del smbolo final.

7.

Solo los smbolos de decisin pueden y deben tener mas de una lnea de flujo de salida.

Ejemplos de diagramas de flujo


Diagrama de flujo que encuentra la suma de los primeros 50 numeros naturales

Bueno, y ahora la descripcin del diagrama anterior


Suma, es la variable a la que se le va agregando la valor de cada nmero natural. N, es el contador. ste
recorrer lo nmeros hasta llegar al 50.

El primer bloque indica el inicio del Diagrama de flujo

El

segundo

bloque,

es

un Smbolo

de

procesos En

este

bloque

se

asume

que

las

variablessuma y N han sido declaradas previamente y las inicializa en 0 para comenzar a el conteo y la
suma de valores (Para declararlas existe el bloque Tarjeta perforada).

El tercer bloque, es tambin un Smbolo de procesos En ste paso se incrementa en 1 la


variable N (N = N + 1). Por lo que, en la primera pasada esta N valdr 1, ya que estaba inicializada en 0.

El cuarto bloque es exactamente lo mismo que el anterior Pero en ste, ya se le agrega el valor de N
a la variable que contendr la suma (En el primer caso contendr 1, ya que N = 1).

El quinto bloque es uno Smbolo de Toma de decisiones y Ramificacin Lo que hay dentro del
bloque es una pregunta que se le hace a los valores que actualmente influyen en el proceso (Por decir
algo, no se como decirlo, soy muy sope :D) Es N=50?, Obviamente la respuesta es no, ya que N
todava es 1. por lo que el flujo de nuestro programa se dirigir haca la parte en donde se observa la
palabra no: Tercer Bloque, ste le sumar 1 (N=N+1) y vuelve a llegar a ste bloque, donde
preguntar Es N=50?... No!, todava es 2. Ha pues, regresa al Tercer bloque y vuelve hacer lo mismo.
Y as hasta llegar a 50, obteniendo as la suma de los primeros 50 primeros nmeros naturales.

Por ltimo indicamos que el resultado ser mostrado en la impresora (Este lo puedes cambiarlo por el
display para mostrar datos).

Fin del programa (o diagrama)

7. Dispositivos perifricos
DISPOSITIVOS DE ENTRADA.
TECLADO
Un teclado alfanumrico se utiliza principalmente como un dispositivo para introducir texto. El teclado es un dispositivo
eficaz para introducir datos no grficos como rtulos de imgenes asociados con un despliegue de grficas. Los teclados
tambin pueden ofrecerse con caractersticas que facilitan la entrada de coordenadas de la pantalla, selecciones de
mens o funciones de grficas.
Las teclas de control del cursor y las teclas de funciones son caractersticas comunes que se encuentran en teclados de
uso general. Las teclas de funciones permiten a los usuarios introducir operaciones de uso comn con un solo golpe de
la llave y las teclas de control del cursor seleccionan posiciones coordenadas posicionando el cursor de la pantalla en un
monitor de video. Adems, a menudo se incluye un teclado numrico en el teclado de la computadora para agilizar la
entrada de datos numricos.
Los teclados se pueden clasificar en: 101, 105, Internet, ergonmico.

TECLADO 101:
El teclado pesa 1.1 Lb y mide 11.6 Pulgadas de ancho, 4.3 pulgadas de profundidad y 1.2 de altura. Entre los accesorios
disponibles se encuentran: cableado para Sun, PC(PS/2) y computadoras Macintosh.
Las dimensiones de este teclado son su caracterstica principal. Es pequeo. Sin embargo se siente como un teclado
normal.
TECLADO ERGONMICO:

Al igual que los teclados normales a travs de ste se pueden introducir datos a la computadora pero su caracterstica
principal es el diseo del teclado ya que ste evita lesiones y da mayor comodidad al usuario, ya que las teclas se
encuentran separadas de acuerdo al alcance de nuestras manos, lo que permite mayor confort al usuario.

TECLADO PARA INTERNET:


El nuevo Internet Keyboard incorpora 10 nuevos botones de acceso directo, integrados en un teclado estndar de
ergonmico diseo que incluye un apoya manos. Los nuevos botones permiten desde abrir nuestro explorador Internet
hasta ojear el correo electrnico. El software incluido, IntelliType Pro, posibilita la personalizacin de los botones para
que sea el teclado el que trabaje como nosotros queramos que lo haga.

Caractersticas:
a)10 NUEVOS BOTONES PROGRAMABLES.
Incluye un botn de acceso directo a Internet, otro de acceso directo al correo electrnico y posibilidades de
programacin en funcin de nuestras necesidades.
b)TECLA DE ACCESO AL E-MAIL
Abre tu programa de correo electrnico para ver tus mensajes con un solo golpe de tecla, eso es todo lo que tienes que
hacer para accesar a tu bandeja de correo.

MOUSE
Es un dispositivo electrnico que nos permite dar instrucciones a nuestra computadora a travs de un cursor que aparece
en la pantalla y haciendo clic para que se lleve a cabo una accin determinada. A medida que el mouse rueda sobre el
escritorio, en correspondencia, el cursor (puntero) en la pantalla hace lo mismo. Tal procedimiento permitir controlar,
apuntar, sostener y manipular varios objetos grficos(y de texto) en un programa.
Al igual que el teclado, el Mouse es el elemento perifrico que ms se utiliza en una PC(aunque en dado caso, se puede
prescindir de l). Los ratones han sido los elementos que ms variaciones han sufrido en su diseo. Es difcil ver dos
modelos y diseos de ratones iguales, incluso siendo del mismo fabricante.
Es una unidad de ingreso de informacin. Funciona acoplado a la pantalla del operador permitiendo dar movilidad al
cursor (seal apuntadora en pantalla).
Tipos de Mouse: Existen diferentes tecnologas con las que funciona el Mouse:
Mecnica
ptica
Optomecnica

De estas tecnologas, la ltima es la ms utilizada en los ratones que se fabrican ahora. La primera era poco precisa y
estaba basada en contactos fsicos elctricos a modo de escobillas que en poco tiempo comenzaban a fallar. Los pticos
son muy precisos, pero demasiado caros y fallan a menudo.
Existen ratones especiales, como por ejemplo los trackballs, que son dispositivos en los cuales se mueve una bola con
la mano, en lugar de estar abajo y arrastrarla por una superficie. Son los dispositivos ms utilizados en las porttiles,
aunque no tanto en la actualidad ya que lo esta reemplazando una superficie del tamao de una tarjeta de visita por la
que se desliza el dedo para manejar el cursor. Pero en los dos casos, son estticos e ideales para cuando no se dispone
de mucho espacio.

Hay otro tipo de ratones especficos para algunas aplicaciones, como por ejemplo las presentaciones en PC. Estos

ratones suelen ser inalmbricos y su manejo es como el del tipo trackball o mediante botones de direccin. Y por ltimo,
podemos ver modelos con ruedas de arrastre que permiten visualizar ms rpidamente las pginas de Internet.
Eso, por no hablar de dispositivos con diseo de ciencia-ficcin, ergonmicos, diseados para navegar por la red con el
mnimo esfuerzo posible, o ratones que incluyen un teclado numrico en su parte superior y tambin superficies
sensibles que poseen una especie de bolgrafo que permite pulsar en ella (como en las porttiles, Palms, etc.).

SCANNERS
Es una unidad de ingreso de informacin. Permite la introduccin de imgenes grficas al computador mediante un
sistema de matrices de puntos, como resultado de un barrido ptico del documento. La informacin se almacena en
archivos en forma de mapas de bits (bit maps), o en otros formatos ms eficientes como JPEG o GIF.
Existen scanners que codifican la informacin grfica en blanco y negro, y a colores. As mismo existen scanners de
plataforma plana fija(cama plana) con apariencia muy similar a una fotocopiadora, y scanners de barrido manual.
Los scanners de cama plana pueden verificar una pgina entera a la vez, mientras que los porttiles solo pueden revisar
franjas de alrededor de 4 pulgadas. Reconocen imgenes, textos y cdigos de barras, convirtindolos en cdigo digital
(ASCII o EBCDIC).
los exploradores grficos convierten una imagen impresa en una de video (grficos por trama) sin reconocer el contenido
real del texto o las figuras.
DISPOSITIVOS DE SALIDA
MONITOR:
Dispositivos de salida ms comunes de las computadoras con el que los usuarios ven la informacin en pantalla. Recibe
tambin los nombres de CRT, pantalla o terminal. En computacin se distingue entre el monitor, que incluye todo el
aparato que produce las imgenes, y la pantalla, que es slo el rea donde vemos las imgenes. As, el dispositivo de
salida es todo el monitor, no solamente la pantalla.
Toda la informacin (letras, grficas y colores) de una pantalla est formada por pequeos puntos llamados pixels
(PICture Elements). La unidad del sistema manda la informacin al monitor acerca de los pixels que deben estar
apagados(color negro) y los que deben de estar prendidos (iluminados) con un determinado color o intesidad. As, punto
por punto, se van formando las letras y las reas iluminadas de una imagen.
Los primeros monitores de computadoras eran monocromticos, es decir, desplegaban un solo color, generalmente verde
o amarillo. Adems, las imgenes tenan muy poca resolucin, ya que cada pxel era muy grande. Actualmente estos
monitores se pueden ver en algunas terminales de aeropuertos.
Los monitores ms recientes no tienen problema en presentar grficas, lneas y reas de colores, adems de letras de
diferentes tipos y tamaos. Por esto tambin se les conoce como monitores grficos.
Tipo del monitor Resolucin en pixels Nmero de colores
CGA 320 x 200 4
EGA 640 x 350 16
VGA 640 x 480
320 x 200 16
256
Sper VGA 800 x 600
1024 x 768 256
256
XGA 1024 x 768 65 536
En la actualidad hay monitores que pueden presentar 256 colores o tonos a la vez en una pantalla.

ESTNDARES DE MONITORES
TUBOS DE RAYOS CATDICOS
Prcticamente todas las computadoras de escritorio utilizan tubos de rayos catdicos o CRT. Esta tecnologa consiste en
un aparato colocado en la parte trasera del monitor, que proyecta imgenes sobre la pantalla. Por eso, los monitores con
CRT son aparatos grandes , que necesitan de determinado espacio interno para proyectar imgenes. Sin embargo, esta
tecnologa es mucho ms barata y eficaz que la tecnologa utilizada en los monitores de computadoras porttiles.

MONITORES PLANOS
Este tipo de monitores son usados en la mayora de los casos en las computadoras porttiles. Se requiere que sean
aparatos ligeros y que consuman poca energa.
A continuacin se mencionan los tipos de monitores planos ms comunes:
Liquid- Cristal Display (LCD): Trabajan mediante una placa de cristal lquido de cuarzo, como la de algunos relojes. Al
aplicar una carga de energa elctrica a partes de esta placa, cambian sus propiedades pticas y es posible ver
caracteres que se estn desplegando. Debido a que estos dispositivos no emiten luz, es difcil ver la informacin, y su
resolucin es poca, por lo que estn orientados a desplegar slo textos y nmeros.

IMPRESORA:
Como indica su nombre, la impresora es el perifrico que el ordenador utiliza para presentar informacin impresa en
papel. Las primeras impresoras nacieron muchos aos antes que el PC e incluso antes que los monitores, siendo
durante aos el mtodo ms usual para presentar los resultados de los clculos en aquellos primitivos ordenadores, todo
un avance respecto a las tarjetas y cintas perforadas que se usaban hasta entonces.
Aunque en nada se parecen las modernas impresoras a sus antepasadas de aquellos tiempos, no hay duda de que igual
que hubo impresoras antes que PCs, las habr despus de stos, aunque se basen en tecnologas que an no han sido
siquiera inventadas. Resulta muy improbable que los seres humanos abandonemos totalmente el papel por una fra
pantalla de ordenador.

Fuentes de informacin
http://www.algoritmia.net/
http://mis-algoritmos.com/aprenda-a-crear-diagramas-de-flujo
http://www.educa.una.py/politecnica/mod/page/view.php?id=2767

Você também pode gostar