09.05.2013 Views

Cap. 5 Conjuntos Dinámicos. Listas, stacks, colas. - Inicio

Cap. 5 Conjuntos Dinámicos. Listas, stacks, colas. - Inicio

Cap. 5 Conjuntos Dinámicos. Listas, stacks, colas. - Inicio

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<strong>Cap</strong>ítulo 5.<br />

<strong>Conjuntos</strong> dinámicos.<br />

<strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>.<br />

Se estudian estructuras abstractas de datos para representar el concepto matemático de<br />

conjuntos, considerando que el número de los elementos del conjunto puede variar en el tiempo.<br />

5.1. Nodos.<br />

Cada elemento o nodo se representa por una estructura, cuyos campos pueden ser leídos y<br />

escritos a través de un puntero a la estructura.<br />

Suele existir un campo que se denomina clave, que identifica unívocamente al nodo; otros<br />

campos suelen contener punteros a otros nodos de la estructura. La clave puede ser numérica o<br />

alfanumérica.<br />

5.2. Operaciones.<br />

Las principales operaciones que suelen implementarse pueden clasificarse en consultas, y<br />

modificaciones.<br />

5.2.1. Consultas:<br />

Buscar un nodo de la estructura que tenga igual valor de clave, que un valor que se pasa como<br />

argumento; retornando un puntero al nodo encontrado o NULL si no está presente.<br />

Seleccionar un nodo de la estructura que tenga el menor o mayor valor de la clave.<br />

Hay otras consultas que pueden hacerse, como buscar el sucesor o antecesor de un nodo.<br />

5.2.2. Modificaciones.<br />

Insertar un nodo con determinados valores en la estructura.<br />

Debe establecerse la forma en que será insertado, de tal modo de preservar la organización de la<br />

estructura. Normalmente esto implica primero conseguir el espacio para el nuevo nodo, y la<br />

inicialización de sus campos; también es usual retornar un puntero al nodo recién creado.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

1


2 Estructuras de Datos y Algoritmos<br />

Descartar o remover un nodo de la estructura. Asumiendo que se pasa como argumento un<br />

puntero al nodo que será descartado, o al nodo anterior. La operación debe mantener la<br />

organización de la estructura.<br />

Algunos algoritmos no requieren implementar todas las operaciones. Por ejemplo los que tienen<br />

sólo las operaciones de buscar, insertar y descartar suelen denominarse diccionarios. Los<br />

algoritmos en que sólo se busque e inserte se denominan arreglos asociativos, o tablas de<br />

símbolos. En un diccionario puro sólo se implementa buscar.<br />

La complejidad de estas operaciones suele cuantificarse de acuerdo al número de nodos de la<br />

estructura.<br />

Los principales conjuntos dinámicos que estudiaremos son: listas, <strong>stacks</strong>, <strong>colas</strong>, árboles binarios<br />

de búsqueda, tablas de hash y <strong>colas</strong> de prioridad.<br />

5.3. <strong>Listas</strong>.<br />

Existe una gran variedad de estructuras denominas listas.<br />

5.3.1. Lista simplemente enlazada.<br />

La lista más básica es la simplemente enlazada, la que puede definirse como la secuencia de<br />

cero (lista vacía) o más elementos de un determinado tipo. Los elementos quedan ordenados<br />

linealmente por su posición en la secuencia. Se requiere sólo un enlace entre un elemento y su<br />

sucesor.<br />

Los elementos de un arreglo ocupan posiciones contiguas o adyacentes en la memoria.<br />

En las listas debe asumirse que el espacio de un nodo no es contiguo con otro; por esta razón, no<br />

basta incrementar en uno el puntero a un nodo, para obtener la dirección de inicio del nodo<br />

siguiente.<br />

Cada nodo está conectado con el siguiente mediante un puntero que es un campo del nodo.<br />

Los elementos del arreglo se direccionan en tiempo constante, O(1). Los elementos de las listas<br />

tienen un costo de acceso O(n), en peor caso.<br />

Las operaciones sobre listas deben considerar que ésta puede estar vacía, lo cual requiere un<br />

tratamiento especial; así también los elementos ubicados al inicio y al final de la lista deben<br />

considerarse especialmente.<br />

Los siguientes diagramas ilustran una lista vacía y una lista con tres elementos. Si los nodos se<br />

crean en el heap, la variable lista, de la Figura 5.1, debe estar definida en el stack, o en la zona<br />

estática, con el tipo puntero a nodo.<br />

Note que el programador no dispone de nombres de variables para los nodos, éstos sólo pueden<br />

ser accesados vía puntero (esto debido a que en el momento de la compilación no se conocen las<br />

direcciones de los nodos; estas direcciones serán retornadas por malloc en tiempo de ejecución).<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 3<br />

lista<br />

lista<br />

nodo1<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

1 2 3<br />

Figura 5.1. Lista vacía y con tres nodos.<br />

nodo2 nodo3<br />

Se denominan listas con cabecera (header) o centinela aquellas que tienen un primer nodo al<br />

inicio de la lista. Con esta definición algunas de las operaciones sobre listas resultan más<br />

simples, que el caso anterior.<br />

lista<br />

c<br />

lista<br />

c<br />

1 2 3<br />

nodo1<br />

nodo2 nodo3<br />

Figura 5.2. Lista con encabezado vacía y con tres nodos.<br />

El caso de lista vacía y las acciones con el primer o último elemento de la lista han intentado ser<br />

resueltas agregando un nodo de encabezado o un centinela al fin de la lista. Estos elementos<br />

facilitan que las funciones diseñadas traten en forma homogénea a todos los elementos de la<br />

lista; por ejemplo, la inserción al inicio se trata de igual forma que la inserción en otra posición;<br />

el costo del mayor tamaño es despreciable comparado con los beneficios.<br />

Se definen los tipos:<br />

typedef struct moldenodo<br />

{ int clave;<br />

struct moldenodo *proximo;<br />

} nodo, *pnodo;<br />

5.3.1.1. Crea Nodo<br />

La siguiente función retorna un puntero al nodo inicializado:<br />

pnodo CreaNodo(int dato)<br />

{ pnodo pn=NULL;<br />

}<br />

if ( (pn= (pnodo) malloc(sizeof(nodo))) ==NULL) exit(1);<br />

else<br />

{<br />

pn->clave=dato; pn->proximo=NULL;<br />

}<br />

return(pn);


4 Estructuras de Datos y Algoritmos<br />

dato<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

pn<br />

Figura 5.3. Espacio antes de salir de CreaNodo.<br />

El diagrama de la Figura 5.3, ilustra la situación justo antes de salir de la función. Después de<br />

salir no existe la variable pn, ya que es automática.<br />

Ejemplos de definición de listas:<br />

pnodo lista=NULL; //Creación de lista vacía sin centinela<br />

//Creación de lista vacía con encabezado.<br />

pnodo listaC = CreaNodo(0);<br />

lista<br />

Figura 5.4. Creación de lista vacía sin centinela.<br />

listaC<br />

Figura 5.5. Creación de lista vacía con encabezado.<br />

Se ha considerado valor de clave 0 en el encabezado, pero podría ser otro valor; por ejemplo,<br />

uno que no sea usado por los valores que se almacenarán en la lista.<br />

Debe liberarse, el espacio adquirido mediante malloc, cuando deje de usarse, y dentro del<br />

alcance de lista, y siempre que la lista no esté vacía. Esto se logra con:<br />

free(lista);<br />

Si lista está definida dentro de una función, debe liberarse el espacio, antes de salir de ésta, ya<br />

que luego será imposible liberar el espacio, debido a que las variables locales dejan de existir al<br />

salir de la función. El ejemplo anterior libera el espacio del nodo que está al inicio de la lista; el<br />

borrado de la lista completa requiere liberar el espacio de cada uno de los nodos.<br />

5.3.1.2. Operaciones de consultas en listas.<br />

a) Recorrer la lista.<br />

Recorrer una lista es un tipo de operación frecuente. Veamos por ejemplo una función que<br />

cuente los nodos de la lista.<br />

0


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 5<br />

/*<br />

Dada la dirección de un nodo de la lista<br />

Retornar el número de nodos desde el apuntado hasta el final de la lista.<br />

*/<br />

int LargoLista(pnodo p)<br />

{ int numeroelementos = 0;<br />

}<br />

while (p != NULL) {<br />

numeroelementos ++;<br />

p = p ->proximo; //recorre la lista<br />

}<br />

return (numeroelementos);<br />

lista<br />

1 2 3<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

p<br />

p->proximo<br />

numeroelementos<br />

Figura 5.6. Variables en LargoLista.<br />

Una alternativa de diseño es empleando un lazo for.<br />

int LargoLista(pnodo p)<br />

{ int numeroelementos = 0;<br />

for( ; p != NULL; p=p->proximo) numeroelementos ++;<br />

return (numeroelementos);<br />

}<br />

Otras operaciones que demandan recorrer la lista son el despliegue de los elementos de la lista o<br />

buscar un nodo que tenga un determinado valor de clave.<br />

b) Buscar elemento.<br />

Se da una lista y un valor de la clave: se retorna un puntero al nodo de la lista que tiene igual<br />

valor de clave, que el valor pasado como argumento; retorna NULL, si no encuentra dicho valor<br />

en la lista.


6 Estructuras de Datos y Algoritmos<br />

pnodo Buscar(pnodo p, int valor)<br />

{<br />

while (p != NULL) {<br />

if (p->clave== valor) return (p); //lo encontró<br />

else p = p ->proximo; //recorre la lista. O(n)<br />

}<br />

return (p); //retorna NULL si no lo encontró.<br />

}<br />

El costo de la operación es O(n).<br />

Ejemplo de uso.<br />

pnodo q;<br />

if ( (q= Buscar(lista, 5)) == NULL) { /* no encontró nodo con clave igual a 5*/ }<br />

else<br />

{ /* lo encontró. …..*/ }<br />

Si la lista es con centinela:<br />

if ( (q= Buscar(listaC->proximo, 5)) == NULL)<br />

{ /* no encontró nodo con clave igual a 5*/ }<br />

else<br />

{ /* lo encontró. …..*/ }<br />

c) Seleccionar un valor extremo.<br />

Se da una lista y se desea encontrar un puntero al nodo que cumple la propiedad de tener el<br />

mínimo valor de clave. Si la lista es vacía retorna NULL. Nótese que en seleccionar sólo se dan<br />

los datos de la lista; buscar requiere un argumento adicional.<br />

Debido a la organización de la estructura las operaciones de consulta tienen costo O(n).<br />

Veremos que existen estructuras y algoritmos más eficientes para buscar y seleccionar.<br />

pnodo SeleccionarMinimo(pnodo p)<br />

{ int min;<br />

pnodo t;<br />

if (p==NULL) return (NULL);<br />

else<br />

{min=p->clave; //Inicia min<br />

t=p;<br />

p=p->proximo;<br />

}<br />

while (p != NULL) {<br />

if (p->clave clave; t=p;}<br />

p = p ->proximo; //recorre la lista. O(n)<br />

}<br />

return (t);<br />

}<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 7<br />

Si se inicializa la variable min con el mayor valor de su tipo, se simplifica el tratamiento en el<br />

borde.<br />

pnodo SelMin(pnodo p)<br />

{ int min= INT_MAX; //requiere incluir limits.h<br />

pnodo t=NULL;<br />

while (p != NULL) {<br />

if (p->clave < min ) {min=p->clave; t=p;}<br />

p = p ->proximo; //recorre la lista. O(n)<br />

}<br />

return (t);<br />

}<br />

d) Buscar el último nodo.<br />

pnodo ApuntarAlFinal(pnodo p)<br />

{ pnodo t;<br />

if (p==NULL) return (NULL);<br />

else<br />

while (p != NULL) {<br />

t=p;<br />

p = p ->proximo; //recorre la lista. O(n)<br />

}<br />

return (t);<br />

}<br />

5.3.1.3. Operaciones de modificación de listas.<br />

a) Análisis de inserción.<br />

Si consideramos pasar como argumentos punteros a nodos, de tal forma de no efectuar copias de<br />

los nodos en el stack, en la inserción, se requiere escribir direcciones en los campos próximos de<br />

dos nodos, y en determinada secuencia. Esto se requiere para mantener la lista ligada.<br />

Supongamos que tenemos dos variables de tipo puntero a nodo: p apunta a un nodo de una lista<br />

y n apunta a un nodo correctamente inicializado (por ejemplo, el retorno de CreaNodo). La<br />

situación se ilustra en la Figura 5.7 a la izquierda, donde las variables n y p, se han diagramado<br />

por pequeños rectángulos. Los nodos se han representado por círculos, con una casilla para la<br />

clave, y otra para el puntero al nodo siguiente.<br />

El nodo n puede ser insertado después del nodo apuntado por p. La primera escritura en un<br />

campo de la estructura puede describirse por:<br />

n->proximo = p->proximo;<br />

Después de esta acción, la situación puede verse en el diagrama a la derecha de la Figura 5.7.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


8 Estructuras de Datos y Algoritmos<br />

p<br />

n<br />

1<br />

p->proximo<br />

3<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

2<br />

n->proximo<br />

p<br />

n<br />

1<br />

p->proximo<br />

Figura 5.7. Inserción en listas. Primer enlace.<br />

3<br />

2<br />

n->proximo<br />

La segunda escritura, que termina de encadenar la lista, y que necesariamente debe realizarse<br />

después de la primera, puede describirse por:<br />

p->proximo = n;<br />

La situación y el estado de las variables, después de la asignación, puede describirse según:<br />

p<br />

n<br />

1<br />

p->proximo<br />

3<br />

2<br />

n->proximo<br />

Figura 5.8. Inserción en listas. Segundo enlace.<br />

Los valores que toman las variables de tipo puntero son direcciones de memoria, y no son de<br />

interés para el programador. Es de fundamental importancia apoyarse en un diagrama para<br />

escribir correctamente expresiones en que estén involucrados punteros. Debe considerarse que si<br />

en el diseño se elige que las variables n y p sean los argumentos de la función que inserta un<br />

nodo, después de ejecutada la función, automáticamente ellas dejan de existir.


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 9<br />

Se puede emplear el siguiente código, si se desea insertar antes de la posición p; se requiere<br />

una variable entera, de igual tipo que la clave del nodo, para efectuar el intercambio. Si el nodo<br />

tiene más información periférica asociada, también debe ser intercambiada entre los nodos.<br />

int temp;<br />

n->proximo = p->proximo;<br />

p->proximo = n;<br />

temp=p->clave; p->clave=n->clave; n->clave=temp; //importa el orden de la secuencia.<br />

Después de ejecutado el segmento anterior, se ilustra el estado final de las variables y un<br />

esquema de la situación, en el diagrama siguiente.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

p<br />

temp<br />

1<br />

n<br />

3<br />

p->proximo<br />

Figura 5.9. Insertar antes.<br />

1<br />

2<br />

n->proximo<br />

Si la lista es sin cabecera, la inserción al inicio, debe codificarse en forma especial, ya que no<br />

existe en este caso la variable p->proximo. El inicio de la lista sin cabecera es una variable de<br />

tipo puntero a nodo, no es de tipo nodo, y por lo tanto no tiene el campo próximo.<br />

b) Análisis de la operación descarte.<br />

En el descarte de un nodo, si consideramos pasar como argumento un puntero a la posición del<br />

nodo anterior al que se desea descartar, se requiere escribir una dirección y mantener una<br />

referencia al nodo que se desea liberar a través de free.<br />

Entonces la variable p apunta al nodo anterior al que se desea descartar, y t apunta al nodo que<br />

se desea desligar de la lista. Se ilustra en la Figura 5.10, la situación de las variables, después de<br />

ejecutada la acción:<br />

t=p->proximo;


10 Estructuras de Datos y Algoritmos<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

p<br />

t<br />

1<br />

p->proximo<br />

Figura 5.10. Fijación de t.<br />

2<br />

3<br />

t->proximo<br />

Fijar la posición de t es necesario, ya que el siguiente paso es escribir en p->proximo, lo cual<br />

haría perder la referencia al nodo que se desea liberar.<br />

La variable t es necesaria, ya que tampoco se puede efectuar la liberación del nodo mediante:<br />

free(p->proximo) ya que esto haría perder la referencia al siguiente nodo de la lista (el nodo con<br />

clave 3 en el diagrama).<br />

La siguiente acción es la escritura en un campo, para mantener la lista ligada. Esto se logra con:<br />

p<br />

p->proximo = t->proximo;<br />

t<br />

1<br />

p->proximo<br />

2<br />

3<br />

t->proximo<br />

Figura 5.11. Mantención de lista ligada.<br />

Ahora puede liberarse el espacio, del nodo que será descartado, mediante:<br />

Lo cual se ilustra en la Figura 5.12.<br />

free(t);<br />

También puede descartarse el nodo apuntado por el argumento, pero se requiere copiar los<br />

valores del nodo siguiente, enlazar con el subsiguiente y liberar el espacio del nodo siguiente.<br />

También debe notarse que descartar el primer nodo requiere un tratamiento especial, ya que se<br />

requiere escribir en el puntero a un nodo, que define el inicio, y en éste no existe el campo<br />

próximo.


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 11<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

p<br />

t<br />

1<br />

p->proximo<br />

?<br />

3<br />

t->proximo<br />

Figura 5.12. Espacio después de liberar el nodo.<br />

Es un error serio, normalmente fatal, escribir expresiones formadas por:<br />

*t, t->clave, o t->proximo, ya que éstas dejaron de existir, después de la ejecución de free(t).<br />

Si no se libera el espacio, queda un fragmento de la memoria dinámica inutilizable.<br />

No siempre es necesario liberar el espacio, por ejemplo se desea sacar un elemento de una lista e<br />

insertarlo en otra, no debe invocarse a free.<br />

Aparentemente las operaciones de modificación de listas son sencillas, pero como veremos a<br />

continuación aún hay detalles que analizar.<br />

c) Análisis adicionales en operación Insertar después.<br />

Considerando lo analizado anteriormente un primer diseño de la función es el siguiente:<br />

pnodo InsertarDespues( pnodo posición, pnodo nuevo)<br />

{<br />

nuevo->proximo=posicion->proximo;<br />

posicion->proximo=nuevo;<br />

return(nuevo);<br />

}<br />

Se decide retornar la dirección del nodo recién incorporado a la lista.<br />

Pero el diseño puede originar problemas, si el nuevo nodo se obtiene invocando a la función<br />

CreaNodo2 y éste no pudo ser creado por malloc, ya que en este caso tendrá valor NULL.<br />

pnodo CreaNodo2(int dato)<br />

{ pnodo pn=NULL;<br />

if ( (pn= (pnodo) malloc(sizeof(nodo))) !=NULL) ;<br />

{<br />

pn->clave=dato; pn->proximo=NULL;<br />

}<br />

return(pn);<br />

}


12 Estructuras de Datos y Algoritmos<br />

En este caso, en la función InsertarDespues, no existe nuevo->proximo, lo cual produciría un<br />

error fatal en ejecución. Una forma de resolver lo anterior es agregando una línea para tratar la<br />

excepción.<br />

pnodo InsertarDespues( pnodo posición, pnodo nuevo)<br />

{<br />

if (nuevo == NULL) return (NULL);<br />

nuevo->proximo=posicion->proximo;<br />

posicion->proximo=nuevo;<br />

return(nuevo);<br />

}<br />

El diseño considera que si la función retorna NULL, implica que la inserción falló.<br />

La función funciona bien si la posición apunta al primer nodo, a uno intermedio o al último; ya<br />

que todos éstos tienen el campo próximo. Pero si el argumento posición toma valor NULL, se<br />

producirá un serio error, ya que posición->proximo apunta a cualquier parte, lo cual podría<br />

suceder si se intenta insertar en una lista vacía sin header. Esto lleva a agregar otra alternativa en<br />

el cuerpo de la función:<br />

pnodo InsertarDespues( pnodo posición, pnodo nuevo)<br />

{<br />

if (nuevo == NULL) return (NULL);<br />

if (posicion != NULL)<br />

{ nuevo->proximo=posicion->proximo;<br />

posicion->proximo=nuevo;<br />

}<br />

return(nuevo);<br />

}<br />

Se analiza a continuación la inserción en una lista vacía.<br />

pnodo listaS=NULL; //lista sin header<br />

pnodo listaC= CreaNodo(0); //lista con header<br />

listaS = InsertarDespues(listaS, CreaNodo(1));<br />

Es necesaria la asignación del retorno de la función a la variable listaS, para mantener vinculada<br />

la lista.<br />

En el caso de lista con header, el argumento listaC, no será NULL, en caso de lista vacía. El<br />

llamado: InsertarDespues(listaC, CreaNodo(1)); inserta correctamente el nuevo nodo al inicio<br />

de la lista. El valor de retorno apunta al recién agregado a la lista.<br />

5.3.2. <strong>Listas</strong> doblemente enlazadas.<br />

Una definición de tipos:<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 13<br />

typedef struct moldecelda<br />

{<br />

int clave;<br />

struct moldecelda *nx; //next<br />

struct modecelda *pr; // previo<br />

} nodo, *pnodo;<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

pr<br />

clave<br />

nx<br />

Figura 5.13. Lista doblemente enlazada.<br />

Los diagramas describen el estado de las variables, antes y después de la operación de insertar el<br />

nodo apuntado por q, después del nodo apuntado por p:<br />

p<br />

q<br />

Figura 5.14. Inserción de nodo en lista doblemente enlazada.<br />

La secuencia de asignaciones describe la inserción.<br />

q->nx = p->nx;<br />

q->pr = p;<br />

p->nx = q ;<br />

q->nx->pr = q ;<br />

Descartar el nodo apuntado por q:<br />

q->pr->nx = q->nx;<br />

q->nx->pr = q->pr ;<br />

free(q) ;<br />

Las operaciones de insertar, buscar y descartar deben considerar las condiciones en los bordes, y<br />

que la lista pueda estar vacía.<br />

p<br />

q


14 Estructuras de Datos y Algoritmos<br />

Una forma usual de tratar simplificadamente las condiciones de borde, es definir un nodo vacío,<br />

denominado cabecera o centinela. La Figura 5.15 superior muestra una lista doblemente<br />

enlazada vacía, la inferior una con dos elementos:<br />

Las listas circulares doblemente enlazadas con cabecera son más sencillas de implementar y<br />

manipular. Las listas circulares simplemente enlazadas ocupan menos espacio pero su<br />

codificación debe incluir varios casos especiales, lo cual aumenta el código necesario para<br />

implementarlas y el tiempo para ejecutar las acciones.<br />

lista<br />

lista<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

h<br />

Figura 5.15. Lista doblemente enlazada circular con centinela.<br />

Tarea: Desarrollar las operaciones: Insertar, descartar y buscar en una lista doblemente enlazada<br />

circular.<br />

5.3.3. Lista circular.<br />

En listas simplemente enlazadas, sin o con cabecera, puede escogerse que el último nodo apunte<br />

al primero, con esto se logra que el primer nodo pueda ser cualquier nodo de la lista.<br />

lista<br />

1 2 3 4<br />

Figura 5.16. Lista simplemente enlazada circular.<br />

La inserción al inicio, en el caso de la Figura 5.16, debe tratarse de manera especial, con costo<br />

O(n), para que el último nodo apunte al nuevo primero. Si la lista es con cabecera, y si el último<br />

apunta a la cabecera, no es necesario introducir código adicional.<br />

5.3.4. Lista auto organizada.<br />

La operación buscar mueve a la primera posición el elemento encontrado. De esta manera los<br />

elementos más buscados van quedando más cerca del inicio de la lista.<br />

h


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 15<br />

5.3.5. Lista ordenada.<br />

Se mantiene, según el orden de la lista, los valores ordenados de las claves. La inserción<br />

requiere primero buscar la posición para intercalar el nuevo nodo.<br />

5.3.6. <strong>Listas</strong> en base a cursores.<br />

En algunas aplicaciones se limita el número de nodos de la estructura por adelantado. En estos<br />

casos tiene ventajas tratar listas en base a arreglos. Pudiendo ser éstos: arreglos de nodos, en los<br />

cuales se emplean punteros; o bien arreglos que contienen la información de vínculos en base a<br />

cursores que almacenan índices.<br />

5.4. Ejemplos de operaciones en listas sin centinela.<br />

Ejemplo 5.1 Inserción de un nodo.<br />

a) Insertar antes.<br />

Para el diseño de la función suponemos que disponemos del valor nuevo, un puntero que apunta<br />

a un nodo inicializado.<br />

nuevo<br />

dato<br />

Figura 5.17. Nuevo nodo que será insertado.<br />

También disponemos del valor posición, un puntero que apunta al nodo sucesor del que será<br />

insertado. Se ilustran dos posibles escenarios, cuando existe lista y el caso de lista vacía.<br />

lista posición<br />

1 2 3<br />

Figura 5.18. Escenarios para inserción.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

lista<br />

posición<br />

En el diseño de la función consideramos que se retorne un puntero al nodo recién insertado.<br />

Para entender las operaciones sobre listas o estructuras que empleen punteros es recomendable<br />

emplear diagramas.<br />

Observamos que en caso de lista no vacía, debe escribirse en el campo nuevo->proximo el valor<br />

del argumento posición, y retornar el valor de nuevo. Si la lista, estaba originalmente vacía no<br />

es preciso escribir el puntero nulo en el campo nuevo->posición, si es que estaba correctamente<br />

inicializado.


16 Estructuras de Datos y Algoritmos<br />

lista<br />

nuevo<br />

1 2 3<br />

posición<br />

dato<br />

Figura 5.19. Variables en InsertaNodo.<br />

pnodo InsertaNodo(pnodo posicion, pnodo nuevo)<br />

{<br />

if (nuevo == NULL) return (NULL);<br />

if (posicion!=NULL) nuevo->proximo=posicion; //O(1)<br />

return nuevo;<br />

}<br />

Para una lista no vacía, un ejemplo de uso, se logra con:<br />

lista->proximo=InsertaNodo(lista->proximo, CreaNodo(8));<br />

lista<br />

1 8 2 3<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

nuevo<br />

Figura 5.20. Inserta nodo con valor 8 en Figura 5.18.<br />

Originalmente el primer argumento de InsertaNodo apuntaba al nodo dos. Dentro de la función<br />

se escribe en el campo próximo del nodo recién creado, de este modo se apunta al sucesor.<br />

Luego de la asignación, se escribe en el campo de enlace la dirección del nodo agregado.<br />

Un ejemplo de inserción al inicio:<br />

lista =InsertaNodo(lista, CreaNodo(7));<br />

lista<br />

7 1 2 3<br />

Figura 5.21. Inserción al inicio de nodo con valor 7 en Figura 5.18.<br />

La operación diseñada inserta antes de la posición indicada por el argumento.<br />

dato


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 17<br />

b) Insertar después.<br />

Una variante es insertar después de la posición.<br />

pnodo InsertaNodoDespues(pnodo posicion, pnodo nuevo)<br />

{<br />

if (nuevo == NULL) return (NULL);<br />

if (posicion!=NULL)<br />

{ nuevo->proximo=posicion->proximo; //enlaza con el resto de la lista<br />

posicion->proximo=nuevo; //termina de enlazar el nuevo nodo<br />

return (posicion);<br />

}<br />

return nuevo;<br />

}<br />

lista<br />

1 2 3<br />

nuevo<br />

posición<br />

Figura 5.22. Inserción del nodo con valor 4, después del nodo 2 en Figura 5.18.<br />

Es importante el orden de las asignaciones.<br />

c) Insertar al final.<br />

La siguiente función implementa la operación de insertar un nodo, con determinado valor, al<br />

final de la lista.<br />

pnodo InsertaNodoalFinal(pnodo posicion, int dato)<br />

{ pnodo temp=posicion;<br />

if (temp != NULL)<br />

{<br />

while (temp->proximo !=NULL) temp=temp->proximo; //O(n)<br />

temp->proximo=CreaNodo(dato);<br />

return (temp->proximo); //retorna NULL si no se pudo crear el nodo<br />

}<br />

else<br />

return (CreaNodo(dato));<br />

}<br />

Si frecuentemente se realizarán las operaciones de insertar al inicio o insertar al final, es<br />

preferible modificar la definición de la estructura de datos, agregando otra variable para apuntar<br />

al último de la lista, que suele denominarse centinela.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

4


18 Estructuras de Datos y Algoritmos<br />

d) Insertar al inicio y al final.<br />

Asumiendo variables globales, se simplifica el paso de argumentos. Sin embargo las<br />

operaciones sólo son válidas para la lista asociada a dichas variables globales:<br />

static pnodo cabeza=NULL;<br />

static pnodo cola=NULL;<br />

cabeza<br />

cola<br />

pnodo insertainicio(int clave)<br />

{ pnodo t=CreaNodo(clave);<br />

}<br />

if(cabeza==NULL) cola=t;<br />

t->proximo=cabeza; cabeza=t; //O(1)<br />

return(t);<br />

pnodo insertafinal(int clave)<br />

{ pnodo t =CreaNodo(clave);<br />

}<br />

if(cola==NULL) { cola=cabeza=t;}<br />

else { cola->proximo=t; cola=t;} //O(1)<br />

return(t);<br />

1 2 3 4<br />

Figura 5.23. Inserciones al inicio y al final.<br />

Tarea: Diseñar descartar al inicio y descartar al final.<br />

Cuando sólo se desea insertar y descartar en un extremo la estructura se denomina stack.<br />

Cuando se inserta en un extremo y se descarta en el otro se denomina cola (en inglés queue).<br />

Cuando la estructura posibilita insertar y descartar en ambos extremos se la denomina doble<br />

cola (dequeue o buffer de anillo).<br />

e) Procedimiento de inserción.<br />

Es posible diseñar una función que no tenga retorno, en este caso uno de los argumentos debe<br />

ser pasado por referencia, ya que para mantener la lista ligada debe escribirse en dos campos.<br />

La operación puede aplicarse a varias listas, a diferencia del diseño con globales visto<br />

anteriormente.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 19<br />

void insertanodo_ref(pnodo *p, pnodo t)<br />

{<br />

if (*p==NULL) *p=t; //inserta en lista vacía.<br />

else<br />

{<br />

t->proximo=*p; //lee variable externa.<br />

*p=t; //escribe en variable externa.<br />

}<br />

}<br />

Ejemplos de uso.<br />

Insertanodo_ref(&lista1, CreaNodo(5)); //Paso por referencia. Aparece &.<br />

Insertanodo_ref(&lista2, CreaNodo(3)); // Se inserta en lista2.<br />

p<br />

lista1<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

1 2 3 4<br />

t<br />

Figura 5.23a. Espacio luego de ingresar a la función Insertanodo_ref.<br />

En el diseño anterior, se pasa como argumento un puntero a un puntero a nodo. Lo cual permite<br />

pasar la dirección de la variable que define la lista.<br />

En caso de no emplear definición de tipos, en la definición de la función aparece más de un<br />

asterisco:<br />

void insertanodo_ref(struct moldenodo ** p, pnodo t)<br />

Complicando más aún la interpretación del código de la función.<br />

f) Error común en pasos por referencia.<br />

No es posible escribir fuera de la función sin emplear indirección.<br />

void Push(pnodo p, int valor)<br />

{<br />

pnodo NuevoNodo = malloc(sizeof(struct node));<br />

NuevoNodo->clave = valor;<br />

NuevoNodo->proximo = p;<br />

p = NuevoNodo; // No escribe en variable externa.<br />

}<br />

Push(lista, 1); //no se modifica la variable lista<br />

p pertenece al frame. Desaparece después de ejecutada la función.<br />

5


20 Estructuras de Datos y Algoritmos<br />

Ejemplo 5.2. Descartar o Borrar nodo.<br />

Debido a que descartar un nodo implica mantener la estructura de la lista, resulta sencilla la<br />

operación de borrar el siguiente a la posición pasada como argumento.<br />

Se tienen tres escenarios posibles:<br />

Que la lista esté vacía, que la posición dada apunte al último de la lista, y finalmente, que la<br />

posición apunte a un nodo que tiene sucesor.<br />

pnodo Descartar(pnodo p)<br />

{ pnodo t = p;<br />

if (p==NULL) return (p); // Lista vacía<br />

if ( p->proximo==NULL)<br />

{ free(p);<br />

return(NULL); // Último de la lista<br />

}<br />

else<br />

{ t=p->proximo;<br />

free(p);<br />

return (t); //Retorna enlace si borró el nodo.<br />

}<br />

}<br />

Los diagramas ilustran las variables luego de ingresar a la función.<br />

p<br />

p<br />

lista<br />

lista<br />

t<br />

lista<br />

t<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

5<br />

p<br />

1 2 3<br />

Figura 5.24. Tres escenarios en descarte de nodo.<br />

t<br />

p->proximo<br />

Es responsabilidad de la función que llama a Descarte mantener ligada la lista, mediante el<br />

retorno.<br />

Tarea: Confeccionar ejemplos de invocación a Descartar, manteniendo ligada la lista.<br />

Borrar el nodo apuntado por p, requiere recorrer la lista, para encontrar el nodo anterior al que<br />

se desea borrar; contemplando el caso que el nodo ha ser borrado sea el primero de la lista. Esta<br />

operación es O(n).


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 21<br />

Para lograr un algoritmo de costo constante, debe modificarse la estructura de datos de la lista,<br />

por ejemplo agregando un puntero al anterior.<br />

Similar situación se tiene si se desea implementar la operación predecesor.<br />

5.5. Stack. Pila. Estructura LIFO (last-in, first-out),<br />

5.5.1. Definición.<br />

La utilidad de esta estructura es muy amplia, y se la ha usado tradicionalmente incorporada al<br />

hardware de los procesadores: para organizar el retorno desde las subrutinas, para implementar<br />

el uso de variables automáticas, permitiendo el diseño de funciones recursivas, para salvar el<br />

estado de registros, en el paso de parámetros y argumentos. Generalmente los traductores de<br />

lenguajes, ensambladores y compiladores, emplean esta estructura para la evaluación y<br />

conversión de expresiones y para la determinación del balance de paréntesis; también existen<br />

arquitecturas virtuales denominadas máquinas de stack, para traducir a lenguajes de nivel<br />

intermedio las sentencias de lenguajes de alto nivel.<br />

Describiremos ahora lo que suele denominarse stack de usuario, como una estructura de datos<br />

que permite implementar el proceso de componentes con la política de atención: la última que<br />

entró, es la primera en ser atendida.<br />

El stack es una lista restringida, en cuanto a operaciones, ya que sólo permite inserciones y<br />

descartes en un extremo, el cual se denomina tope del stack.<br />

Debido a esta restricción suelen darse nombres especializados a las operaciones. Se denomina<br />

push (o empujar en la pila) a la inserción; y pop (o sacar de la pila) al descarte. No suele<br />

implementarse la operación buscar, ya que en esta estructura la complejidad de esta operación<br />

es O(n); en algunas aplicaciones se dispone de la operación leer el primer elemento del stack,<br />

sin extraerlo.<br />

En general la implementación de las operaciones generales de inserción y descarte usando<br />

arreglos son costosas, en comparación con nodos enlazados vía punteros, debido a que es<br />

necesario desplazar el resto de las componentes después de una inserción o descarte; además de<br />

que el tamaño del arreglo debe ser declarado en el código, no pudiendo crecer dinámicamente<br />

durante la ejecución. Sin embargo la primera dificultad no existe en un stack, la segunda se ve<br />

atenuada ya que no se requiere almacenar punteros lo cual disminuye el tamaño del espacio de<br />

almacenamiento; la única limitación es la declaración del tamaño del arreglo. Cuando es posible<br />

predecir por adelantado la profundidad máxima del stack, se suele implementar mediante<br />

arreglos.<br />

5.5.2. Diagrama de un stack. Variables.<br />

La representación gráfica siguiente, muestra el arreglo y dos variables para administrar el<br />

espacio del stack. La variable stack es un puntero al inicio del arreglo.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


22 Estructuras de Datos y Algoritmos<br />

stack<br />

NumeroDeElementos<br />

4<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

0<br />

1<br />

2<br />

3<br />

4<br />

5<br />

…<br />

MAXN-1<br />

Figura 5.25. Variables en un stack<br />

Base del stack<br />

Último ocupado<br />

Parte vacía del stack<br />

La variable NumeroDeElementos, contiene el número de elementos almacenados en el stack, el<br />

cual en la gráfica crece hacia abajo. Usualmente suele representarse al revés, para mostrar que<br />

es una estructura en que se van apilando las componentes; sólo se ve la primera componente, la<br />

del tope. El uso de la variable NumeroDeElementos, facilita el diseño de las funciones que<br />

prueban si el stack está lleno o vacío.<br />

5.5.3. Archivo de encabezado ( *.h).<br />

Si se desea utilizar en alguna implementación la estructura de datos stack, es una práctica usual<br />

definir un archivo con extensión h (por header o encabezado), en el que se describen los<br />

prototipos de las funciones asociadas al stack. Esto permite conocer las operaciones<br />

implementadas y sus argumentos, acompañando a este archivo está el del mismo nombre, pero<br />

con extensión .c, que contiene las definiciones de las operaciones; en éste, se suele incluir al<br />

principio el archivo con extensión h, de tal modo que si existen funciones que invoquen a otras<br />

del mismo paquete, no importe el orden en que son definidas, ya que se conocen los prototipos.<br />

En el archivo siguiente, con extensión h, se ha empleado la compilación condicional, mediante<br />

la detección de la definición de un identificador. En el caso que se analiza, si no está definido el<br />

símbolo __STACK_H__ (note los underscores, para evitar alcances de nombres) se lo define y<br />

se compila. En caso contrario, si ya está definido no se compila; esto permite compilar una sola<br />

vez este archivo, a pesar de que se lo puede incluir en diferentes archivos que usen el stack.<br />

En el texto se incluye un archivo datos.h que permite, usando la misma técnica, definir<br />

focalizadamente los tipos de datos que emplee la aplicación que use la herramienta stack. En<br />

este caso en particular debe definirse el tipo de datos ElementoStack, que describe la estructura<br />

de una componente del arreglo.


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 23<br />

/*stack.h> */<br />

#ifndef __STACK_H__<br />

#define __STACK_H__<br />

#include "datos.h"<br />

#define push2(A, B) StackPush((B)); StackPush((A));<br />

void StackInit(int);<br />

int StackEmpty(void);<br />

int StackFull(void);<br />

void StackPush(ElementoStack);<br />

ElementoStack StackPop(void);<br />

void StackDestroy(void);<br />

#endif /* __STACK_H__ */<br />

El ejemplo también ilustra la definición de una macro: push2, que se implementa mediante el<br />

reemplazo del macro por dos invocaciones a funciones del paquete. Note que los argumentos se<br />

definen entre paréntesis.<br />

5.5.4. Implementación de operaciones.<br />

El diseño de las funciones contempla tres variables globales asociadas al stack. Tope y<br />

NumeroDeElementos, que ya han sido definidas; además emplea la global MAXN, para<br />

almacenar el máximo número de elementos, ya que el tamaño del stack, se solicita<br />

dinámicamente, y no está restringido a ser una constante.<br />

Las variables globales simplifican el paso de argumentos de las operaciones; sin embargo<br />

restringen las operaciones a un solo stack. Si la aplicación empleara varios <strong>stacks</strong> diferentes, las<br />

funciones tendrían que ser redefinidas.<br />

/*stack.c Implementación basada en arreglos dinámicos. */<br />

#include <br />

#include <br />

#include "datos.h"<br />

#include "stack.h"<br />

static ElementoStack * stack; //puntero al inicio de la zona de la pila<br />

static int NumeroDeElementos; //elementos almacenados en el stack<br />

static int MAXN; //Máxima capacidad del stack<br />

void StackInit(int max)<br />

{stack = malloc(max*sizeof(ElementoStack) ); //se solicita el arreglo.<br />

if (stack == NULL) exit(1);<br />

NumeroDeElementos = 0; MAXN=max;<br />

}<br />

Profesor Leopoldo Silva Bijit 20-01-2010


24 Estructuras de Datos y Algoritmos<br />

int StackEmpty(void)<br />

{<br />

return(NumeroDeElementos == 0) ; //Retorna verdadero si stack vacío<br />

}<br />

int StackFull(void)<br />

{<br />

return(NumeroDeElementos == MAXN) ; //Retorna verdadero si stack lleno<br />

}<br />

//se puede empujar algo al stack si no está lleno.<br />

void StackPush(ElementoStack cursor)<br />

{<br />

if (!StackFull() ) stack[NumeroDeElementos ++]= cursor;<br />

}<br />

//se puede sacar algo del stack si no está vacío<br />

ElementoStack StackPop(void)<br />

{<br />

if( StackEmpty() ) {printf("error. Extracción de stack vacio\n"); exit(1); return; }<br />

else return ( stack[--NumeroDeElementos] ) ;<br />

}<br />

void StackDestroy(void)<br />

{<br />

free(stack);<br />

}<br />

Es buena práctica que las funciones StackInit y StackDestroy se invoquen en una misma<br />

función, para asegurar la liberación del espacio.<br />

Los programadores evitan la invocación de funciones innecesariamente, cuando las acciones de<br />

éstas sean simples; esto debido al costo de la creación del frame, de la copia de valores de<br />

argumentos y de la posterior destrucción del frame. En esta aplicación, podría haberse definido<br />

como macros los test de stack vacío o lleno, según:<br />

#define StackEmpty( ) (NumeroDeElementos == 0)<br />

#define StackFull( ) (NumeroDeElementos == MAXN)<br />

Ejemplo 5.3. Uso de stack. Balance de paréntesis.<br />

a) Especificación del algoritmo:<br />

Se dispone de un archivo de texto, que contiene expresiones que usan paréntesis. Se desea<br />

verificar que los paréntesis están balanceados.<br />

Es preciso identificar los pares que deben estar balanceados.<br />

Ejemplo: “(“, “)”, “[“, “]”, “{“, “}”, etc.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 25<br />

Se asume que se dispone de funciones para leer caracteres desde un archivo de texto, y para<br />

discriminar si el carácter es uno de los símbolos que deben ser balanceados o no.<br />

La secuencia siguiente no está balanceada: a+(b-c) * [(d+e])/f, al final están intercambiados dos<br />

tipos de paréntesis.<br />

b) Descripción inicial.<br />

Crear el stack.<br />

Mientras no se ha llegado al final del archivo de entrada:<br />

Descartar símbolos que no necesiten ser balanceados.<br />

Si es un paréntesis de apertura: empujar al stack.<br />

Si es un paréntesis de cierre, efectuar un pop y comparar.<br />

Si son de igual tipo continuar<br />

Si son de diferente tipo: avisar el error.<br />

Si se llega al fin de archivo, y el stack no esta vacío: avisar el error.<br />

Destruir el stack.<br />

El siguiente paso en el desarrollo es la descripción por seudo código, en la cual se establecen las<br />

variables y el nombre de las funciones.<br />

Ejemplo 5.4. Evaluación de expresiones en notación polaca inversa.<br />

Las expresiones aritméticas que generalmente escribimos están en notación “in situ” o fija. En<br />

esta notación los operadores se presentan entre dos operandos; por ejemplo: 2 + 3 * 4. Esta<br />

notación no explica el orden de precedencia de los operadores; debido a esto los lenguajes de<br />

programación tienen reglas de que establecen cuales operadores reciben primero sus operandos.<br />

En el lenguaje C, la multiplicación tiene mayor precedencia que el operador suma; entonces, en<br />

el caso del ejemplo, se realizará primero la multiplicación y luego la suma.<br />

La relación entre operadores y operandos puede hacerse explícita mediante el uso de paréntesis.<br />

La escritura de ( 2 + 3) *4 y 2 + (3 * 4) asocia operadores y operandos mediante paréntesis.<br />

En C, además existen reglas de asociatividad para especificar los operandos de un operador, en<br />

caso de que existan varios de igual precedencia, por ejemplo: 3*4*5.<br />

Si la asociatividad es de izquierda a derecha: se interpreta: ((3 * 4) * 5); si es de derecha a<br />

izquierda: (3* (4*5))<br />

La notación inversa desarrollada por Jan Lukasiewicz (1878 - 1956) y empleada por los<br />

ingenieros de Hewlett-Packard para simplificar el diseño electrónico de las primeras<br />

calculadoras, permite escribir expresiones sin emplear paréntesis y definiendo prioridades para<br />

los operadores. En esta notación el operador sigue a los operandos. La expresión infija 3 + 4<br />

tiene su equivalente en notación inversa como: 3 4 +. Y el ejemplo inicial: 2 + 3 * 4, se<br />

representa, en notación inversa, según: 2 3 4 * +.<br />

Una generalización es agregar el nombre de funciones a los operadores. Normalmente las<br />

funciones son operadores monádicos: sin[123 + 45 ln(27 - 6)]<br />

a) Ejemplo de evaluación.<br />

La expresión: (3 + 5) * (7 - 2) puede escribirse: 3 5 + 7 2 - *<br />

Profesor Leopoldo Silva Bijit 20-01-2010


26 Estructuras de Datos y Algoritmos<br />

Leyendo la expresión en notación inversa, de izquierda a derecha, se realizan las siguientes<br />

operaciones:<br />

Push 3 en el stack.<br />

Push 5 en el stack. Éste contiene ahora (3, 5). El 5 está en el tope, el último en entrar.<br />

Se aplica la operación + : la cual saca los dos números en el tope del stack, los suma y coloca el<br />

resultado en el tope del stack. Ahora el stack contiene el número 8.<br />

Push 7 en el stack.<br />

Push 2 en el stack. Éste contiene ahora (8, 7, 2). El 2 está en el tope.<br />

Se efectúa la operación – con los dos números ubicados en el tope.<br />

Éste contiene ahora (8, 5)<br />

Se efectúa la operación * con los dos números ubicados en el tope.<br />

Éste contiene ahora (40)<br />

La clave es entender que las operaciones se realizan sobre los dos primeros números<br />

almacenados en el stack, y que se empujan los operandos.<br />

b) Especificación.<br />

Se dispone de un archivo de texto que contiene expresiones aritméticas en notación inversa.<br />

Se dispone de funciones que permiten:<br />

leer un número como una secuencia de dígitos;<br />

reconocer los siguientes símbolos como operadores: +, -, * y /.<br />

descartar separadores, que pueden ser los símbolos: espacio, tab, nueva línea.<br />

reconocer el símbolo fin de archivo.<br />

c) Seudo código.<br />

While ( no se haya leído el símbolo fin de archivo EOF)<br />

{ leer un símbolo;<br />

Si es número: empujar el valor del símbolo en el stack<br />

Si es un operador:<br />

{ Efectuar dos pop en el stack;<br />

Operar los números, de acuerdo al operador;<br />

Empujar el resultado en el stack;<br />

}<br />

}<br />

Retornar el contenido del tope del stack, mediante pop.<br />

Ejemplo 5.5. Conversión de notación in situ a inversa.<br />

Se emplea para convertir las expresiones infijas y evaluarlas en un stack. Para especificar el<br />

algoritmo es preciso establecer las reglas de precedencia de operadores. La más alta prioridad<br />

está asociada a los paréntesis, los cuales se tratan como símbolos; prioridad media tienen la<br />

operaciones de multiplicación y división; la más baja la suma y resta.<br />

Se asume solamente la presencia de paréntesis redondos en expresiones.<br />

Como la notación polaca inversa no requiere de paréntesis, éstos no se sacarán hacia la salida.<br />

Notar que el orden en que aparecen los números son iguales en ambas representaciones, sólo<br />

difieren en el orden y el lugar en que aparecen los operadores.<br />

Se empleará el stack para almacenar los operadores y el símbolo de apertura de paréntesis.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 27<br />

Seudo código.<br />

While ( no se haya leído el símbolo fin de archivo EOF)<br />

{ leer un símbolo;<br />

Si es número: enviar hacia la salida;<br />

Si es el símbolo „)‟:<br />

sacar del stack hacia la salida, hasta encontrar „(„, el cual no debe copiarse hacia la<br />

salida.<br />

Si es operador o el símbolo „(„:<br />

Si la prioridad del recién leído es menor o igual que la prioridad del operado ubicado<br />

en el tope del stack:<br />

{ if( tope==‟(„ ) empujar el operador recién leído;<br />

else<br />

{ efectuar pop del operador y sacarlo hacia la salida hasta que la prioridad del<br />

operador recién leído sea mayor que la prioridad del operador del tope.<br />

Empujar el recién leído en el tope del stack.<br />

}<br />

}<br />

}<br />

Si se llega a fin de archivo: vaciar el stack, hacia la salida.<br />

Se trata un stack con el símbolo „(„ en el tope como un stack vacío.<br />

5.6. Cola. Buffer circular. Estructura FIFO (first-in, first-out).<br />

5.6.1. Definición de estructura.<br />

Una cola es una lista con restricciones. En ésta las inserciones ocurren en un extremo y los<br />

descartes en el otro. La atención a los clientes en un banco, el pago de peaje en autopistas, son<br />

ejemplos cotidianos de filas o <strong>colas</strong> de atención.<br />

Si se conoce el máximo número de componentes que tendrán que esperar en la cola, se suele<br />

implementar en base a arreglos.<br />

Se requieren ahora dos variables para administrar los índices de la posición del elemento que<br />

será insertado o encolado (cola, tail en inglés); y también el índice de la posición de la<br />

componente que será descartada o desencolada en la parte frontal (cabeza. head).<br />

- 1 2 3 4 - -<br />

cabeza cola<br />

Figura 5.26. Diagrama de una cola.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

out in<br />

cabeza cola


28 Estructuras de Datos y Algoritmos<br />

El diagrama ilustra la situación luego: de la inserción de los elementos: 0, 1, 2, 3, y 4 y del<br />

descarte del electo 0.<br />

La cabeza (head) apunta al elemento a desencolar.<br />

La cola (tail) apunta a la posición para encolar. Apunta a un elemento disponible.<br />

Se observa que a medida que se consumen o desencolan componentes, van quedando espacios<br />

disponibles en las primeras posiciones del arreglo. También a medida que se encolan elementos<br />

va disminuyendo el espacio para agregar nuevos elementos, en la zona alta del arreglo. Una<br />

mejor utilización del espacio se logra con un buffer circular, en el cual la posición siguiente a la<br />

última del arreglo es la primera del arreglo.<br />

5.6.2. Buffer circular.<br />

Esto es sencillo de implementar aplicando aritmética modular, si el anillo tiene N posiciones, la<br />

operación: cola = (cola+1) % N, mantiene el valor de la variable cola entre 0 y N-1. Operación<br />

similar puede efectuarse para la variable cabeza cuando deba ser incrementada en uno.<br />

La variable cola puede variar entre 0 y N-1. Si cola tiene valor N-1, al ser incrementada<br />

en uno (módulo N), tomará valor cero.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

N-1<br />

0<br />

5<br />

cola<br />

1<br />

4<br />

2<br />

3<br />

cabeza<br />

Figura 5.27. Buffer circular.<br />

Los números, del diagrama, muestran los valores del índice de cada casilla del arreglo circular.<br />

La gráfica anterior ilustra la misma situación planteada con un arreglo lineal.<br />

5.6.3. Cola vacía y llena.<br />

El diagrama a la izquierda ilustra una cola vacía; la de la derecha una cola con un espacio<br />

disponible. En esta última situación, el cursor cola (tail) dio la vuelta completa y está marcando<br />

como posición disponible para encolar la posición anterior a la que tocaría consumir. Si se<br />

encola un nuevo elemento, se producirá la condición de cola llena; pero esta situación es<br />

indistinguible de la de cola vacía.


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 29<br />

N - 1<br />

0 1<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

5<br />

cabeza<br />

2<br />

3<br />

4<br />

cola<br />

N - 1<br />

0 1<br />

Figura 5.28. Cola vacía y casi llena.<br />

2<br />

3 cola<br />

4<br />

5 cabeza<br />

De esta forma no es posible distinguir entre las dos situaciones: cola llena o vacía.<br />

Una de las múltiples soluciones a este problema, es registrar en una variable adicional la cuenta<br />

de los elementos encolados; esto además facilita el diseño de las funciones que determinan cola<br />

vacía o llena.<br />

Si la variable la denominamos encolados. Entonces con cola vacía, encolados toma valor cero.<br />

La cola llena se detecta cuando encolados toma valor N.<br />

El algoritmo se basa en las funciones que operan sobre una cola circular basada en arreglos. Con<br />

operaciones de colocar en la cola (put), sacar de la cola (get) y verificar si la cola está vacía o<br />

llena.<br />

5.6.4. Operaciones en <strong>colas</strong>.<br />

/* QUEUE.c en base a arreglo circular dinámico */<br />

#include <br />

#include "QUEUE.h"<br />

static Item *q; // Puntero al arreglo de Items<br />

static int N, cabeza, cola, encolados; //Administran el anillo<br />

Debe estar definido el tipo de datos Item.<br />

void QUEUEinit(int maxN) //maxN es el valor N-1 de la Figura 5.27.<br />

{ q = malloc((maxN+1)*sizeof(Item)); //Se pide espacio para N celdas.<br />

N = maxN+1; cabeza = 0; cola = 0; encolados=0;<br />

}<br />

La detección de cola vacía se logra con:<br />

int QUEUEempty()<br />

{ return encolados == 0; }


30 Estructuras de Datos y Algoritmos<br />

Si la cola no está vacía se puede consumir un elemento:<br />

Item QUEUEget()<br />

{ Item consumido= q[cabeza];<br />

cabeza = (cabeza + 1) % N ; encolados--;<br />

return (consumido); }<br />

Se emplea aritmética módulo N.<br />

La detección de cola llena se logra con:<br />

int QUEUEfull()<br />

{return( encolados == N); }<br />

Si la cola no está llena se puede encolar un elemento:<br />

void QUEUEput(Item item)<br />

{ q[cola] = item; cola = (cola +1) % N; encolados++;}<br />

Para recuperar el espacio:<br />

void QUEUEdestroy(void)<br />

{ free ( q ); }<br />

En un caso práctico las funciones cola llena y vacía se implementan con macros.<br />

#define QUEUEempty() (encolados == 0)<br />

#define QUEUEfull() (encolados == N)<br />

Las dos aplicaciones, el stack de usuario y la cola, se emplearán en algoritmos para construir<br />

árboles en grafos.<br />

Ejemplo 5.6. Diseño de buffer circular estático de caracteres.<br />

Para insensibilizarse de las diferentes velocidades que pueden tener un consumidor y un<br />

productor de caracteres, se suele emplear un buffer.<br />

En el caso de un computador alimentando a una impresora, la velocidad de producción de<br />

caracteres del procesador es mucho mayor que la que tiene la impresora para liberar los<br />

caracteres hacia el medio de impresión; el disponer de un buffer de impresora, permite al<br />

procesador escribir en el buffer y no tener que esperar que la impresora escriba un carácter. Lo<br />

mismo ocurre cuando un usuario escribe caracteres desde un teclado; su velocidad de digitación<br />

es bastante menor que la velocidad con que el procesador utiliza los caracteres.<br />

Se emplea la variable cnt para llevar la cuenta de los elementos almacenados en el buffer.<br />

#define SIZE 16<br />

#define LLENO (cnt==SIZE)<br />

#define VACIO (cnt==0)<br />

unsigned char Buffer[SIZE]; //buffer estático<br />

int rd=0, wr=0, cnt=0; //administran el espacio<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 31<br />

El cursor rd apunta al elemento a leer. El cursor wr al elemento que está disponible para ser<br />

escrito.<br />

La rutina put, coloca elementos en el buffer.<br />

void put(unsigned char c)<br />

{<br />

Buffer[wr]=c;<br />

wr=(wr+1)%SIZE; cnt++;<br />

}<br />

La rutina get consume elementos del buffer.<br />

unsigned char get(void)<br />

{ unsigned char ch;<br />

ch=Buffer[rd];<br />

rd=(rd+1)%SIZE; cnt--;<br />

return(ch);<br />

}<br />

cnt<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

2<br />

wr<br />

SIZE-1<br />

0<br />

1<br />

2<br />

rd<br />

Figura 5.29. Buffer de caracteres.<br />

Las siguientes sentencias ilustran el uso de las funciones:<br />

if ( !VACIO ) ch=get(); else printf("vacío\n");<br />

while( !LLENO ) put('1'); //lo llena<br />

if ( !LLENO ) put('2'); else printf("lleno\n");<br />

while( !VACIO ) putchar(get()); //lo vacia<br />

if ( !VACIO ) putchar(get()); else printf("\nvacio\n");<br />

Usualmente una de las rutinas opera por interrupciones. La rutina que no es de interrupción debe<br />

modificar la variable común cnt deshabilitando el tipo de interrupción.


32 Estructuras de Datos y Algoritmos<br />

Problemas resueltos.<br />

P5.1<br />

Se tienen los diagramas de una lista circular vacía, y luego de haber insertado uno, dos y tres<br />

elementos. Notar que el puntero a la lista referencia el último nodo insertado en la estructura.<br />

Profesor Leopoldo Silva Bijit 20-01-2010<br />

1 1 2 1 2 3<br />

Figura P5.1. Buffer de caracteres.<br />

Definir tipos de datos: nodo es el tipo de datos del nodo, y pnodo es el nombre del tipo puntero<br />

a nodo. El valor almacenado en el nodo es de tipo entero. En cada caso ilustrar un ejemplo de<br />

uso, mostrando las variables que sean necesarias, con diagramas que ilustren la relación entre<br />

los datos.<br />

a) Diseñar función insertar con prototipo: pnodo insertar(int);<br />

El argumento es el valor que debe almacenarse en el nodo que se inserta.<br />

Retorna puntero al recién insertado, nulo en caso que no se haya podido crear el nodo.<br />

Asumir que se tiene variable global de nombre lista, de tipo pnodo.<br />

b) Diseñar función sumar con prototipo: int sumar(pnodo);<br />

El argumento es un puntero a un nodo cualquiera de la lista.<br />

Retorna la suma de los valores almacenados en todos los nodos de la lista; 0 en caso de lista<br />

vacía.<br />

c) Asumir que se tienen varias listas circulares, cada una de ellas referenciadas por un puntero<br />

almacenado en una variable de tipo pnodo. Se tiene la siguiente función, en la cual el argumento<br />

sirve para referenciar a una de las listas.<br />

pnodo funcion(pnodo *p)<br />

{ pnodo t=*p;<br />

if(*p==NULL) return (NULL);<br />

*p = (*p)->proximo;<br />

return (t);<br />

}<br />

Determinar que realiza la función.<br />

Solución.<br />

typedef struct moldenodo<br />

{ int clave;<br />

struct moldenodo *proximo;


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 33<br />

} nodo, *pnodo;<br />

a)<br />

pnodo Insertar(int valor)<br />

{ pnodo pn=NULL;<br />

if ( (pn = (pnodo) malloc(sizeof(nodo))) == NULL) return NULL;<br />

pn->clave = valor;<br />

if (listac == NULL){pn->proximo = pn;}<br />

else {pn->proximo = listac->proximo; listac->proximo = pn;}<br />

listac = pn;<br />

return (pn);<br />

}<br />

La siguiente definición, debe estar fuera de las funciones, y ubicada antes de la definición de la<br />

función Insertar:<br />

pnodo listac=NULL;<br />

La sentencia siguiente forma la lista cuyo diagrama se muestra más a la izquierda, en la<br />

definición del problema.<br />

for(i=1; iclave;<br />

for(t = p->proximo; t != p; t = t->proximo)<br />

sum += t->clave;<br />

return (sum);<br />

}<br />

printf("La suma de los elementos de la lista circular es %d\n", Sumar(listac));<br />

c)<br />

La acción que realiza funcion(&Lista1), es apuntar al siguiente de la lista referenciada por la<br />

variable Lista1.<br />

Retorna puntero al que antes era el primero de la lista, nulo en caso de lista vacía.<br />

En el caso de la lista con tres elementos, dada al inicio, después de invocar a la función, en esa<br />

lista, debe retornar un puntero al nodo con valor 3, y la lista apunta al elemento con valor 1,<br />

según se ilustra en el siguiente diagrama.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


34 Estructuras de Datos y Algoritmos<br />

Lista1<br />

1 2 3<br />

Figura P5.2.<br />

Si antes de invocar se tiene la situación dada al inicio, el siguiente segmento:<br />

pnodo t=NULL;<br />

if( (t=avanzar(&Lista1))!=NULL) printf("el anterior era %d\n", t->clave);<br />

Imprime el valor 3.<br />

Ejercicios propuestos.<br />

E5.1. Verificar que para la siguiente entrada:<br />

a + b * c + ( d * e + f ) * g<br />

La salida, en notación polaca inversa, se genera en el siguiente orden:<br />

a b c<br />

a b c * +<br />

a b c * +<br />

a b c * + d<br />

a b c * + d e<br />

a b c * + d e *<br />

a b c * + d e * f<br />

a b c * + d e* f +<br />

a b c * + d e* f +<br />

a b c * + d e* f + g<br />

a b c * + d e* f + g * +<br />

Efectuar una traza del contenido del stack, a medida que se van procesando los símbolos de<br />

entrada.<br />

E5.2. Se tienen los siguientes tipos de datos:<br />

typedef struct moldenodo<br />

{ int clave;<br />

struct moldenodo *proximo;<br />

} nodo, *pnodo;<br />

Para la estructura de la Figura E5.1:<br />

a) Declarar las variables inicial y final.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 35<br />

b) Diseñar función que inserte nodo, con un valor pasado como argumento, al inicio.<br />

c) Diseñar función que inserte nodo, con valor pasado como argumento, al final.<br />

d) Diseñar función que intercambie el nodo inicial con el nodo final.<br />

Las funciones de inserción deben considerar la posibilidad de insertar en una cola vacía.<br />

inicial<br />

final<br />

E5.3. Búsqueda autoorganizada en listas.<br />

Figura E5.1. Cola.<br />

El proceso de reorganizar una lista por transposición, tiene por objetivo mejorar el tiempo<br />

promedio de acceso para futuras búsquedas, moviendo los nodos más accesados hacia el<br />

comienzo de la lista.<br />

Diseñar una rutina, en C, que busque un elemento en una lista en base a punteros. Y tal que<br />

cuando encuentre un elemento lo trasponga con el anterior, excepto cuando lo encuentre en la<br />

primera posición.<br />

E5.4. Insertar en lista ordenada.<br />

Comparar las dos funciones para insertar un nodo en una lista ordenada.<br />

pnodo inserteenorden (pnodo p, int k )<br />

{ pnodo p1, p2, p3;<br />

for( p2 = NULL, p1 = p; p1 != NULL && p1->clave < k; p2 = p1, p1 = p1->proximo );<br />

if (p1 != NULL && p1->clave == k) return p; //no acepta claves repetidas<br />

p3= (pnodo) malloc (sizeof (nodo)) ;<br />

if(p3!=NULL)<br />

{<br />

p3->clave = k;<br />

if (p2 == NULL) { /* inserta al inicio */<br />

p3->proximo = p1;<br />

return p3 ;<br />

}<br />

Profesor Leopoldo Silva Bijit 20-01-2010


36 Estructuras de Datos y Algoritmos<br />

p3->proximo = p2->proximo;<br />

p2->proximo = p3;<br />

}<br />

return p ;<br />

}<br />

pnodo inserteenordenHeader( pnodo p, int k )<br />

{ nodo header;<br />

pnodo p1,p2;<br />

header.proximo = p;<br />

for(p2 = &header; p != NULL && p->clave< k; p2 = p, p = p->proximo);<br />

if (p == NULL || p->clave !=k ){<br />

p1 = (pnodo) malloc(sizeof(nodo));<br />

if( p1!=NULL){<br />

p1->clave = k;<br />

p1->proximo = p;<br />

p2->proximo = p1;<br />

}<br />

}<br />

return header.proximo ;<br />

}<br />

Notar que se trata el encabezado como una variable local.<br />

Referencias.<br />

En el apéndice: Assemblers, Linkers, and the SPIM Simulator de James R. Larus, del libro de<br />

Patterson A. David y Hennessy L. John, Computer Organization and Design: The<br />

Hardware/software Interface, Morgan Kaufmann 2004, aparece una excelente descripción del<br />

proceso de compilación, de la creación de archivos objetos, del proceso de ligado y carga de un<br />

programa.<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 37<br />

Índice general.<br />

CAPÍTULO 5. ............................................................................................................................................ 1<br />

CONJUNTOS DINÁMICOS. ................................................................................................................... 1<br />

LISTAS, STACKS, COLAS. ..................................................................................................................... 1<br />

5.1. NODOS. .............................................................................................................................................. 1<br />

5.2. OPERACIONES. ................................................................................................................................... 1<br />

5.2.1. Consultas:.................................................................................................................................. 1<br />

5.2.2. Modificaciones. ......................................................................................................................... 1<br />

5.3. LISTAS. .............................................................................................................................................. 2<br />

5.3.1. Lista simplemente enlazada. ...................................................................................................... 2<br />

5.3.1.1. Crea Nodo ........................................................................................................................................... 3<br />

5.3.1.2. Operaciones de consultas en listas. ..................................................................................................... 4<br />

a) Recorrer la lista. ...................................................................................................................................... 4<br />

b) Buscar elemento. .................................................................................................................................... 5<br />

c) Seleccionar un valor extremo. ................................................................................................................. 6<br />

d) Buscar el último nodo. ............................................................................................................................ 7<br />

5.3.1.3. Operaciones de modificación de listas. ............................................................................................... 7<br />

a) Análisis de inserción. .............................................................................................................................. 7<br />

b) Análisis de la operación descarte. ........................................................................................................... 9<br />

c) Análisis adicionales en operación Insertar después. .............................................................................. 11<br />

5.3.2. <strong>Listas</strong> doblemente enlazadas. .................................................................................................. 12<br />

5.3.3. Lista circular. .......................................................................................................................... 14<br />

5.3.4. Lista auto organizada. ............................................................................................................. 14<br />

5.3.5. Lista ordenada. ........................................................................................................................ 15<br />

5.3.6. <strong>Listas</strong> en base a cursores. ........................................................................................................ 15<br />

5.4. EJEMPLOS DE OPERACIONES EN LISTAS SIN CENTINELA. .................................................................. 15<br />

Ejemplo 5.1 Inserción de un nodo. .................................................................................................... 15<br />

a) Insertar antes. ........................................................................................................................................ 15<br />

b) Insertar después. ........................................................................................................................................ 17<br />

c) Insertar al final. .......................................................................................................................................... 17<br />

d) Insertar al inicio y al final. ......................................................................................................................... 18<br />

e) Procedimiento de inserción. ....................................................................................................................... 18<br />

f) Error común en pasos por referencia. ......................................................................................................... 19<br />

Ejemplo 5.2. Descartar o Borrar nodo.............................................................................................. 20<br />

5.5. STACK. PILA. ESTRUCTURA LIFO (LAST-IN, FIRST-OUT), ................................................................ 21<br />

5.5.1. Definición. ............................................................................................................................... 21<br />

5.5.2. Diagrama de un stack. Variables. ........................................................................................... 21<br />

5.5.3. Archivo de encabezado ( *.h). ................................................................................................. 22<br />

5.5.4. Implementación de operaciones. ............................................................................................. 23<br />

Ejemplo 5.3. Uso de stack. Balance de paréntesis. ........................................................................... 24<br />

a) Especificación del algoritmo: .................................................................................................................... 24<br />

b) Descripción inicial. .................................................................................................................................... 25<br />

Ejemplo 5.4. Evaluación de expresiones en notación polaca inversa. .............................................. 25<br />

a) Ejemplo de evaluación. .............................................................................................................................. 25<br />

Profesor Leopoldo Silva Bijit 20-01-2010


38 Estructuras de Datos y Algoritmos<br />

b) Especificación. ........................................................................................................................................... 26<br />

c) Seudo código. ............................................................................................................................................. 26<br />

Ejemplo 5.5. Conversión de notación in situ a inversa. ..................................................................... 26<br />

Seudo código. ................................................................................................................................................. 27<br />

5.6. COLA. BUFFER CIRCULAR. ESTRUCTURA FIFO (FIRST-IN, FIRST-OUT). ............................................ 27<br />

5.6.1. Definición de estructura. .......................................................................................................... 27<br />

5.6.2. Buffer circular. ......................................................................................................................... 28<br />

5.6.3. Cola vacía y llena. ................................................................................................................... 28<br />

5.6.4. Operaciones en <strong>colas</strong>. .............................................................................................................. 29<br />

Ejemplo 5.6. Diseño de buffer circular estático de caracteres. ....................................................................... 30<br />

PROBLEMAS RESUELTOS. ........................................................................................................................ 32<br />

EJERCICIOS PROPUESTOS. ........................................................................................................................ 34<br />

E5.1. Verificar que para la siguiente entrada: .................................................................................. 34<br />

E5.2. Se tienen los siguientes tipos de datos: ..................................................................................... 34<br />

E5.3. Búsqueda autoorganizada en listas. ......................................................................................... 35<br />

E5.4. Insertar en lista ordenada. ....................................................................................................... 35<br />

REFERENCIAS. ......................................................................................................................................... 36<br />

ÍNDICE GENERAL. .................................................................................................................................... 37<br />

ÍNDICE DE FIGURAS. ................................................................................................................................ 39<br />

Profesor Leopoldo Silva Bijit 20-01-2010


<strong>Conjuntos</strong> dinámicos. <strong>Listas</strong>, <strong>stacks</strong>, <strong>colas</strong>. 39<br />

Índice de figuras.<br />

FIGURA 5.1. LISTA VACÍA Y CON TRES NODOS. ............................................................................................. 3<br />

FIGURA 5.2. LISTA CON ENCABEZADO VACÍA Y CON TRES NODOS. ................................................................ 3<br />

FIGURA 5.3. ESPACIO ANTES DE SALIR DE CREANODO. ................................................................................ 4<br />

FIGURA 5.4. CREACIÓN DE LISTA VACÍA SIN CENTINELA. .............................................................................. 4<br />

FIGURA 5.5. CREACIÓN DE LISTA VACÍA CON ENCABEZADO. ........................................................................ 4<br />

FIGURA 5.6. VARIABLES EN LARGOLISTA. ................................................................................................... 5<br />

FIGURA 5.7. INSERCIÓN EN LISTAS. PRIMER ENLACE. ................................................................................... 8<br />

FIGURA 5.8. INSERCIÓN EN LISTAS. SEGUNDO ENLACE. ................................................................................ 8<br />

FIGURA 5.9. INSERTAR ANTES. ...................................................................................................................... 9<br />

FIGURA 5.10. FIJACIÓN DE T. ...................................................................................................................... 10<br />

FIGURA 5.11. MANTENCIÓN DE LISTA LIGADA. ........................................................................................... 10<br />

FIGURA 5.12. ESPACIO DESPUÉS DE LIBERAR EL NODO. .............................................................................. 11<br />

FIGURA 5.13. LISTA DOBLEMENTE ENLAZADA. ........................................................................................... 13<br />

FIGURA 5.14. INSERCIÓN DE NODO EN LISTA DOBLEMENTE ENLAZADA. ..................................................... 13<br />

FIGURA 5.15. LISTA DOBLEMENTE ENLAZADA CIRCULAR CON CENTINELA. ................................................ 14<br />

FIGURA 5.16. LISTA SIMPLEMENTE ENLAZADA CIRCULAR. ......................................................................... 14<br />

FIGURA 5.17. NUEVO NODO QUE SERÁ INSERTADO. .................................................................................... 15<br />

FIGURA 5.18. ESCENARIOS PARA INSERCIÓN............................................................................................... 15<br />

FIGURA 5.19. VARIABLES EN INSERTANODO. ............................................................................................. 16<br />

FIGURA 5.20. INSERTA NODO CON VALOR 8 EN FIGURA 5.18. ..................................................................... 16<br />

FIGURA 5.21. INSERCIÓN AL INICIO DE NODO CON VALOR 7 EN FIGURA 5.18. ............................................. 16<br />

FIGURA 5.22. INSERCIÓN DEL NODO CON VALOR 4, DESPUÉS DEL NODO 2 EN FIGURA 5.18. ....................... 17<br />

FIGURA 5.23. INSERCIONES AL INICIO Y AL FINAL. ...................................................................................... 18<br />

FIGURA 5.23A. ESPACIO LUEGO DE INGRESAR A LA FUNCIÓN INSERTANODO_REF. ..................................... 19<br />

FIGURA 5.24. TRES ESCENARIOS EN DESCARTE DE NODO. ........................................................................... 20<br />

FIGURA 5.25. VARIABLES EN UN STACK ...................................................................................................... 22<br />

FIGURA 5.26. DIAGRAMA DE UNA COLA. .................................................................................................... 27<br />

FIGURA 5.27. BUFFER CIRCULAR. ............................................................................................................... 28<br />

FIGURA 5.28. COLA VACÍA Y CASI LLENA. .................................................................................................. 29<br />

FIGURA 5.29. BUFFER DE CARACTERES. ...................................................................................................... 31<br />

FIGURA P5.1. BUFFER DE CARACTERES. ..................................................................................................... 32<br />

FIGURA P5.2. .............................................................................................................................................. 34<br />

FIGURA E5.1. COLA. ................................................................................................................................... 35<br />

Profesor Leopoldo Silva Bijit 20-01-2010

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!