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