Sei sulla pagina 1di 25

LISTAS

Hay dos desventajas serias con respecto a las estructuras estticas de pilas y colas usando arreglos. Estas desventajas son que tienen un espacio limitado de memoria y la otra desventaja es que es posible no ocupar toda la memoria disponible, haciendo que se desperdicie espacio. Una solucin es usar listas. Las listas son estructuras de datos que son dinmicas, esto significa que adquieren espacio y liberan espacio a medida que se necesita. sin embargo, hay una advertencia. Como regla general siempre hay que tener cuidado al manejar direcciones de espacios de memoria, porque es posible que accedamos a una localidad de memoria de la cual no deseabamos cambiar su contenido. Antes de estudiar las listas, daremos una breve introduccin a los grafos, pues las listas son un caso especial de los grafos.

Grafos
Los grafos son una manera visual de representar las relaciones. Definicin 7 Si y son dos conjuntos, decimos que est relacionado con

si es verdadera una sentencia que considere a ambos elementos. Esta sentencia puede ser cualquier predicado, por ejemplo: ``es padre de'', ``debe dinero a'', ``toma el curso de'' etc.; si el predicado es verdadero para ese par de elementos, lo escribimos como , y si el predicado es falso, lo escribimos como se puede leer: es el conjunto de materias y se lee `` es ``toma el .

As los ejemplos citados, si

Si

es el conjunto de alumnos,

curso'', entonces

. En la figura 17 se puede apreciar esto en forma de diagramas de Venn. Si es el conjunto de personas y es tambin el conjunto de personas, y es ``debe dinero a''; significa que ``marisol debe dinero a rafaelle'' y de ningn modo es al contrario, es decir ``rafaelle no debe dinero a marisol''.

Figura 17: Relacin ``toma el curso de'' para los conjuntos materias.

de personas y

de

Los elementos de la figura 17 definen un nuevo conjunto de elementos, el conjunto de pares de elementos que estan relacionados. As la relacin ``toma el curso de'' es el siguiente:

Grficamente podemos ilustrar el conjunto el que se muestra en la figura 18.

de ``toma el curso de'' con un grafo como

Figura 18: Grafo que ilustra la relacin ``toma el curso de''. De manera que podemos definir un grafo como una representacin grfica de una relacin. Definicin 8 Para definir formalmente un grafo debemos establecer la siguiente tupla:

Donde es un conjunto de aristas y un conjunto no vaco de nodos. En el caso de , el conjunto es el conjunto de nodos y el conjunto de flechas es el conjunto de aristas. Notemos que el conjunto de aristas puede ser un conjunto vaco, pero de ningn modo hay grafo sin nodos, es decir el conjunto debe ser diferente que el conjunto vaco.

Supongamos ahora

y la siguiente relacin en

Esta relacin luce como aparece en la figura 20.

Figura: Relacin y en forma de grafo es:

de A en A

Figura: Grafo de la relacin A esta clase de grafos, en las que cada nodo tiene a lo ms una arista dirigida que sale y a lo ms una arista dirigida que entra, se le llama lista.

Listas simplemente encadenadas


Como vimos en la seccin anterior, una lista es una relacin de elementos, tales que cada elemento est relacionado con nicamente un elemento del conjunto, diferente a s mismo. Como cada elemento puede tener a lo ms una arista dirigida que sale y una arista dirigida que entra, bien puede tener 0 aristas que salen, o cero aristas que entran. Si el nodo tiene 0 aristas que salen, entonces es el final de la lista. Si el nodo tiene 0 aristas que entran, entonces es el inicio de la lista. Por razones prcticas, se dibujan una flecha que sale de un identificador de la lista y entra al inicio de la lista y otra flecha que sale del final de la lista y apunta a un smbolo que se llama NULO.

Figura: Grafo de la relacin


listaLigada

con apuntadores del nombre de la lista y hacia NULL

En C/C++ el identificador de la lista contiene la direccin del primer elemento de la lista, as como sucede con los arreglos. El valor NULO es til para saber cundo termina la lista, es una constante estndar y no tiene valor. El contenido de los nodos, como ya hemos visto, son los elementos de un conjunto. Si ese conjunto tiene elementos estructurados, tambin es vlido usarlos. Normalmente cada nodo de la lista est estructurado con dos partes: 1. La parte de informacin. 2. La parte de direccin al siguiente nodo de la lista. El campo de informacin contiene el elemento real de la lista. El campo de direccin al siguiente contiene un apuntador al elemento con el cul est relacionado, es decir, al elemento siguiente de la lista. La lista completa se accesa mediante el identificador de lalista. El campo de la direccin del ltimo nodo apunta a una direccion nula. La lista que no tiene elementos (solamente tiene un identificador que apunta a nulo) se llama lista nula o lista vaca. Una lista se inicializa a una lista vaca haciendo lista=null, recordemos que lista es un apuntador a una direccin de memoria que puede albergar una variable del tipo que se hayan definido los nodos; null es una direccin de cualquier tipo, as que el compilador asigna la direccin null a lista.

Enseguida vamos a dar una lista de trminos usados para manejar los elementos de una lista simplemente encadenada, aunque no son los que usa C/C++ , pero s son bastante claros para hacer algoritmos. Si p es un apuntador a la direccin de una variable del tipo declarado para los nodos de una lista:
node(p):

hace referencia al noso al que se apunta mediante p. info(p): hace referencia a la informacin del nodo al que apunta p. next(p): hace referencia a la parte direccin siguiente y, por tanto, es un apuntador. As que la expresin info(next(p)) significa que se hace referencia a la seccin de informacin del nodo siguiente al que apunta p.

Listas en C/C++ con arreglos


Vamos a empezar una primera implementacin de listas usando arreglos, cada elemento del arreglo debe ser un elemento compuesto. Cada elemento debe contener una parte para la informacin y otra parte para apuntar al elemento siguiente:
#include <iostream> ( 1) #define numNodes 500 ( ( ( ( 2) struct nodeType{ 3) int info; 4) int next; 5) };

( 6) struct nodeType node[numNodes]; int main (int argc, char * const argv[]) { std::cout << "Hello, World!\n"; return 0; }

En el programa anterior, en las lneas (2) a (5) se crea un nuevo tipo de dato, el tipo nodo. Cada nodo tiene dos partes, su parte de informacin y su parte de apuntador al siguiente. Como solamente tenemos 500 nodos (declarados en la lnea (1), el tipo de siguiente es entero y hemos decidido almacenar nmeros enteros solamente. En la lnea (6) se ha declarado una variable global de tipo arreglo de estructura de nodos, es decir, se ha creado un arreglo de 500 nodos. En este esquema, el ltimo nodo apunta a NULL, que se representa con el valor entero -1. Tenemos tambin los siguientes elementos de cada nodo: node[p] corresponde a

next(p),

por la notacin propia del lenguaje; tambin node[p].info para info(p) y finalmente node[p].next hace referencia al nodo siguiente next(p). Al principio todos los nodos estn sin usar, porque solamente se ha creado el arreglo. As que todos los nodos van a formar parte de una lista de nodos disponibles. Si se usa la variable global avail para apuntar a la lista disponible, podramos organizar inicialmente esta lista como:
void inicializaAvail(void){ int i; avail = 0; for(i=0; i<numNodes-1; i++){ node[i].next = i+1; } node[numNodes-1].next = -1;

Cuando se requiere un nodo para usarlo en la lista, se obtiene de la lista disponible. Cuando ya no es necesario ese nodo, se devuelve a la lista disponible. Estas dos operaciones se implementan mediante las rutinas en C/C++ getnode y freenode:
int getNode(void){ int p; if (avail==-1){ std::cout<<"Overflow\n"; exit(1); } p=avail; avail=node[avail].next; return p; }

Si avail es igual a -1 significa que no hay nodos disponibles, es decir, que el arreglo est completamente lleno. Esto significa que las estructuras de lista de un programa particular han desbordado el espacio disponible. La funcin freeNode acepta un apuntador (nmero entero) a un nodo y devuelve ese nodo a la lista de disponibles:
void freeNode(int p){ node[p].next=avail; avail=p; }

Las operaciones primitivas para listas son versiones directas en C de los algoritmos correspondientes. La rutina insAfter acepta un apuntador p a un nodo y un elemento x como parmetros. Primero se asegura que p no sea nulo y despus se inserta x en el nodo siguiente al indicado por p.
void insAfter(int p, int x){ int q; if(p==-1){ std::cout<<"void insertion\n"; } else{ q=getNode();

node[q].info=x; node[q].next=node[p].next; node[p].next=q; } }

La rutina delAfter(p,px), llamada por el enunciado delAfter(p,&x), suprime el nodo despus de node(p) y almacena su contenido en x;
void delAfter(int p, int *px){ int q; if((p==-1)||(node[p].next==-1)){ std::cout<<"void detection\n"; } else{ q=node[p].next; *px = node[q].info; node[p].next=node[q].next; freeNode(q); } }

Antes de llamar insAfter debemos asegurarnos de que ni p ni node[p].next sean nulos.

Listas en C/C++ con arreglos


Vamos a empezar una primera implementacin de listas usando arreglos, cada elemento del arreglo debe ser un elemento compuesto. Cada elemento debe contener una parte para la informacin y otra parte para apuntar al elemento siguiente:
#include <iostream> ( 1) #define numNodes 500 ( ( ( ( 2) struct nodeType{ 3) int info; 4) int next; 5) };

( 6) struct nodeType node[numNodes]; int main (int argc, char * const argv[]) { std::cout << "Hello, World!\n"; return 0;

En el programa anterior, en las lneas (2) a (5) se crea un nuevo tipo de dato, el tipo nodo. Cada nodo tiene dos partes, su parte de informacin y su parte de apuntador al siguiente. Como solamente tenemos 500 nodos (declarados en la lnea (1), el tipo de siguiente es entero y hemos decidido almacenar nmeros enteros solamente. En la lnea (6) se ha declarado una variable global de tipo arreglo de estructura de nodos, es decir, se ha creado un arreglo de 500 nodos.

En este esquema, el ltimo nodo apunta a NULL, que se representa con el valor entero -1. Tenemos tambin los siguientes elementos de cada nodo: node[p] corresponde a next(p), por la notacin propia del lenguaje; tambin node[p].info para info(p) y finalmente node[p].next hace referencia al nodo siguiente next(p). Al principio todos los nodos estn sin usar, porque solamente se ha creado el arreglo. As que todos los nodos van a formar parte de una lista de nodos disponibles. Si se usa la variable global avail para apuntar a la lista disponible, podramos organizar inicialmente esta lista como:
void inicializaAvail(void){ int i; avail = 0; for(i=0; i<numNodes-1; i++){ node[i].next = i+1; } node[numNodes-1].next = -1;

Cuando se requiere un nodo para usarlo en la lista, se obtiene de la lista disponible. Cuando ya no es necesario ese nodo, se devuelve a la lista disponible. Estas dos operaciones se implementan mediante las rutinas en C/C++ getnode y freenode:
int getNode(void){ int p; if (avail==-1){ std::cout<<"Overflow\n"; exit(1); } p=avail; avail=node[avail].next; return p; }

Si avail es igual a -1 significa que no hay nodos disponibles, es decir, que el arreglo est completamente lleno. Esto significa que las estructuras de lista de un programa particular han desbordado el espacio disponible. La funcin freeNode acepta un apuntador (nmero entero) a un nodo y devuelve ese nodo a la lista de disponibles:
void freeNode(int p){ node[p].next=avail; avail=p; }

Las operaciones primitivas para listas son versiones directas en C de los algoritmos correspondientes. La rutina insAfter acepta un apuntador p a un nodo y un elemento x como parmetros. Primero se asegura que p no sea nulo y despus se inserta x en el nodo siguiente al indicado por p.
void insAfter(int p, int x){ int q; if(p==-1){ std::cout<<"void insertion\n"; }

else{ q=getNode(); node[q].info=x; node[q].next=node[p].next; node[p].next=q; }

La rutina delAfter(p,px), llamada por el enunciado delAfter(p,&x), suprime el nodo despus de node(p) y almacena su contenido en x;
void delAfter(int p, int *px){ int q; if((p==-1)||(node[p].next==-1)){ std::cout<<"void detection\n"; } else{ q=node[p].next; *px = node[q].info; node[p].next=node[q].next; freeNode(q); } }

Antes de llamar insAfter debemos asegurarnos de que ni p ni node[p].next sean nulos.

Insertar y eliminar nodos de una lista


En el uso de las listas ligadas se ven involucradas varias operaciones, entre ellas la de insertar un nuevo nodo a la lista y la operacin de eliminar un nodo de la lista. En ambos casos debemos recordar que se trata de manejo de la memoria, as que insertar un nodo en la lista significa obtener un espacio de memoria disponible y relacionarlo con los elementos de la lista; as mismo, eliminar un nodo de la lista significa liberar la memoria que ocupa ese nodo sin perder la relacin con el resto de los nodos de la lista. Insertar un elemento al inicio de la lista. La operacin p=getnode(); obtiene un nodo vaco y establece el contenido de una variable nombrada p en la direccin de este nodo, como se muestra en la figura 22.a. Este nodo an no pertenece a alguna lista, simplemente se ha logrado dedicar un especio de memoria que es apuntado por p, figura 22.b.

Figura 22: a) Creacin de un nuevo nodo. b) El nuevo nodo debe de ir insertado al

frente, atrs o en medio de la lista. Una vez que se ha creado un nuevo espacio para el nuevo nodo, se debe de establecer la parte de informacin de ese nodo con la operacin info(p), como se ilustra en el siguiente ejemplo con el dato 6.
info(p)=6;

Despus de esstablecer la parte de informacin es necesario establecer la parte siguiente de este nodo. Debido a que node(p) va a insertarse en la parte delantera de la lista, el nodo que sigue debe ser el primer nodo actual de la lista. Debido a que la variable lista (el identificador de la lista) contiene la direccin de ese primer nodo, node(p) se agrega a la lista ejecutando la operacin
next(p)=lista;

Esta operacin coloca el valor de lista (la direccin del primer nodo en la lista) en el campo siguiente de node(p). Estos pasos se ilustran en la figura 23

Figura 23: Operaciones involucradas en la insercin de un nuevo nodo al inicio de una lista: c) info(p). d) next(p)=list. e) list=p Hasta ahora, p apunta a la lista con el elemento adicional incluido. Sin embargo, debido a que list es el apuntador externo a la lista deseada, su valor debe modificarse en la direccin del nuevo primer nodo de la lista. Esto se hace ejecutando la operacin
list=p;

En resumen, ya tenemos un algoritmo para insertar un elemento al inicio de una lista simplemente ligada, al reunir todos los pasos tenemos:
p=getnode(); info(p)=6;

next(p)=list; list=p;

Eliminar un elemento de la lista. Para eliminar un elemento del inicio de la lista, se siguen los mismos pasos que se usan para insertar un elemento, pero en un orden diferente:
p=list; x=info(p); list=next(p);

Comentaremos cada una de estas tres lneas, que se pueden apreciar en la figura 24

Figura 24: Operaciones involucradas en la eliminacin de un nodo al inicio de una lista: c) p=list). d) x=info(p). e) list=next(p)

El uso de memoria dinmica en C/C++


Como sabemos, en lenguaje C/C++ , &x es la direccin donde se almacena en memoria la variable x. Si p es un apuntador en C/C++ , *p es el contenido de la localidad de memoria p. Si usamos C/C++ para implementar listas ligadas, podemos usar estos apuntadores. Sin embargo, primero analizaremos cmo asignar y liberar el almacenamiento en forma dinmica y cmo se accesa al almacenamiento dinmico en C/C++ . En C/C++ , una variable que debe contener la direccin en la memoria que almacena un nmero entero se crea mediante la declaracin
int *p;

Recordemos que esta declaracin se divide en dos partes: la parte de tipo int *, que indica que se trata de un apuntador a un entero; y la parte de identificador, en este caso p. Una vez declarada la variable p como un apuntador a un tipo especfico de dato, debe ser posible crear dinmicamente un objeto de este tipo especfico y asignar su direccin a p. Esto se hace en C/C++ mediante la funcin de la biblioteca estndar malloc(size). La fucnin malloc asigna de manera dinmica una parte de memoria de tamao especificado en size y devuelve un apuntador a un elemento de tipo char. Consideremos las siguientes declaraciones
extern char *malloc(); int *pi; float *pr;

La palabra clave extern especifica que una variable o funcin tiene un enlace externo. Esto significa que la variable o funcin a la que nos referimos est definida en algn otro archivo fuente, o ms adelante en el mismo archivo. Sin embargo, en C/C++ podemos usar esta palabra clave extern con una cadena. La cadena indica que se est usando el convenio de enlace de otro lenguaje para los identificadores que se estn definiendo. Para los programas C++ la cadena por defecto es ``C++''. Los enunciados
pi = (int *) malloc(sizeof(int)); pr = (float *) malloc(sizeof(float));

crean directamente la variable entera *pi y la variable real *pr. Estas se denominan variables dinmicas. Al ejecutar estos enunciados, el operador sizeof devuelve el tamao en bytes de su operando. Esto se usa para conservar la independencia de mquina. Despus, malloc crea un objeto de este tamao. Por tanto, malloc(sizeof(int)) asigna almacenamiento para un entero, en tanto que malloc(sizeof(float)) asigna espacio necesario para un real. De igual manera, malloc devuelve un apuntados al almacenamiento que asigna. Este apuntador es al primer byte de este almacenamiento y es de tipo char *. Para obligar al apuntador a que seale a un entero, usamos el operador de clculo (int *) (float *). El operador sizeof, devuelve un valor de tipo int, en tanto que la funcin malloc espera un parmetro de tipo unsigned. Para hacer que correspondan, debemos escribir
pi=(int *)malloc((unsigned)(sizeof(int)));

Como ejemplo, vamos a considerar este breve cdigo:


#include <iostream> int main (int argc, char * const argv[]) { ( 1) int *p, *q; ( 2) int x; ( 3) p = (int *)malloc(sizeof(int));

( 4) ( 5) ( 6) ( 7) ( 8) ( 9) (10) (11) (12) }

*p = 3; q = p; std::cout<< *p << " " << *q << "\n"; x = 7; *q = x; std::cout<< *p << " " << *q << "\n"; p = (int *)malloc(sizeof(int)); *p = 5; std::cout<< *p << " " << *q << "\n"; return 0;

En la l'inea (3), se crea una variable de tipo entero y su direccin se coloca en p. La lnea (4) establece el valor de esa variable en 3. La lnea (5) hace que la direccin q sea la misma direccin que p. El enunciado de la lnea (5) es perfectamente vlido, pues se asigna a una variable de tipo apuntador (q) el valor de otra variable del mismo tipo (p). En este momento *p y *q hacen referencia a la misma variable. Por tanto, la lnea (6) imprime el contenido de esa variable (que ahora es 3) dos veces. En la lnea (7), se almacena el valor 7 en la variable entera x. La lnea (8) cambia el valor de *q al valor de x. sin embargo, dado que p y q apuntan a la misma variable, *p y *q tienen el valor 7. Por tanto la lnea (9) imprime el nmero 7 dos veces. La lnea (10) crea una nueva variable entera y coloca su direccin en p. Ahora *p hace referencia a la variable entera recin creada que todava no ha recibido un valor. q no ha cambiado; por lo que el valor de *q sigue siendo 7. Observemos que *p no hace referencia a una variable especfica nica. Su valor cambia conforme se modifica el valor de p. La lnea (11) establece el valor de esta variable recin creada en 5 y la lnea 12 imprime los valores 5 y 7. Y as la salida del programa es:
3 7 5 3 7 7

mallocEjemplo has exited with status 0.

La funcin free se usa en C para liberar almacenamiento de una variable asignada dinmicamente. La orden
free(p);

invalida cualquier referencia futura a la variable *p (a menos que se asigne nuevo espacio de memoraia a esa variable). Llamar free(p) hace que quede disponible para reso el almacenamiento ocupado por *p, si es necesario. La funcin free espera un parmetro apuntador del tipo char *, para que no tengamos problemas de tipos, debemos hacer
free((char *)p);

Consideremos el siguiente ejemplo para ilustrar el uso de free:


#include <iostream>

int main (int argc, char * const argv[]) { int *p, *q; ( ( ( ( ( ( ( ( ( } 1) 2) 3) 4) 5) 6) 7) 8) 9) p=(int *)malloc(sizeof(int)); *p=5; q=(int *)malloc(sizeof(int)); *q=8; free(p); p=q; q=(int *)malloc(sizeof(int)); *q=6; std::cout<<*p<<" "<<*q<<"\n"; return 0;

Qu se imprime a la salida del programa? Observemos que si se llama malloc dos veces sucesivas y se asigna su valor a la misma variable como en:
p=(int *)malloc(sizeof(int)); *p=3; p=(int *)malloc(sizeof(int)); *p=7;

Se pierde la primera copia de *p, dado que su direccin no se guard.

Listas ligadas usando memoria dinmica


Para hacer las listas ligadas necesitamos un conjunto de nodos, cada uno de los cuales tiene dos campos: uno de informacin y un apuntador al siguiente nodo de la lista. Adems, un apuntador externo seala el primer nodo de la lista. Usamos variables de apuntador para implementar apuntadores de listas. As que definimos el tipo de un apuntador y un nodo mediante
struct node{ int info; struct node *next; };

typedef struct node *nodePtr;

Un nodo de este tipo es igual a los nodos de la implementacin con arreglos, excepto que el campo next es un apuntador y no un entero. En lugar de declarar un arreglo, para que represente un conjunto acumulado de nodos, stos se asignan y liberan segn es necesario. Se elimina la necesidad de un conjunto de nodos previamente declarado. Si declaramos
nodePtr p;

la ejecucin de la orden
p=getNode();

debe colocar la direccin de un nodo disponible en p:


nodePtr getNode(void){ nodePtr p; p=(nodePtr)malloc(sizeof(struct node)); return(p); }

Para liberar la memoria utilizada usamos freeNode.


void freeNode(nodePtr p){ free(p); }

Los procedimientos insAfter y delAfter usan la implementacin dinmica de una lista ligada. Supongamos que list es una variable apuntador que seala al primer nodo de una lista (si lo hay) y es igual a NULL en el caso de una lista vaca.
void insAfter(nodePtr p, int x){ nodePtr q; if(p==NULL){ std::cout<<"Insercion nula\n"; } else{ q=getNode(); q->info=x; q->next=p->next; p->next=q; } } void delAfter(nodePtr p, int *px){ nodePtr q; if((p==NULL)||(p->next==NULL)){ std::cout<<"Borrado prohibido\n"; } else{ q=p->next; *px=q->info; p->next=q->next; freeNode(q); }

COLAS
Definicin 5 Las colas son una estructura de datos similar a las pilas. Recordemos que las pilas funcionan en un depsito en donde se insertan y se retiran elementos por el mismo extremo. En las colas sucede algo diferente, se insertan elementos por un extremo y se retiran elementos por el otro extremo. De hecho a este tipo de dispositivos se les conoce como dispositivos ``fifo'' (first in, first out) porque funcionan como una tubera, lo que entra primero por un extremo, sale primero por el otro extremo. En una cola hay dos extremos, uno es llamado la parte delantera y el otro extremo se llama la parte trasera de la cola. En una cola, los elementos se retiran por la parte delantera y se agregan por la parte trasera.

a,b,c;

Figura 15: Dinmica de una cola. a) estado actual con una cola con tres elementos b) estado de la cola cuando se agrega el elemento d; c) estado de la cola cuando se elimina el elemento a del frente de la cola

En la figura 15 se muestra una actividad tpica de la cola, en donde se muestra que se agregan datos por la parte trasera de la cola y se eliminana datos por el frente de la cola. Si es una cola y colas: es un elemento, se pueden hacer tres operaciones bsicas con las

1. insert(Q,x), que inserta el elemento x en la parte trasera de la cola Q. 2. x=remove(Q), que almacena en x el valor del elemento retirado de la parte frontal de la cola Q. 3. empty(Q), que es un predicado de valor booleano, y es cuando la cola Q tiene 0 elementos, y es cuando la cola Q tiene al menos un elemento, en cuyo caso, ese nico elemento es la parte frontal y la parte trasera de la cola al mismo tiempo. Tericamente no hay lmite para el tamao de la cola, asi que siempre se debera poder insertar elementos a una cola, sin embargo, al igual que las pilas, normalmente se deja

un espacio de memoria para trabajar con esta estructura. Por el contrario, la operacin slamente se puede hacer si la cola no est vaca.

Estructura de las colas en C/C++


De manera similar a las pilas, las colas definen una estructura no estndar, de manera que se debe crear un nuevo tipo de dado, el tipo cola, que debe tener los siguientes elementos:

Un arreglo de elementos de algn tipo especfico, puede incluso ser un tipo estndar o no. Un nmero que indica el elemento que est en la posicin del frente de la cola. Un nmero que indica el elemento que est en la posicin trasera de la cola.

Suponiendo que los elementos son nmeros enteros, una idea para representar una cola en C/C++ es usar un arreglo para contener los elementos y emplear otras dos variables para representar la parte frontal y trasera de la cola.
#define maxQueue 100 struct cola{ int items[maxQueue]; int front; int rear; };

Esta representacin con arreglos es completamente vlida, pero debemos tener cuidado con los lmites del arreglo. Suponiendo que no existiera la posibilidad de caer en un desbordamiento del arreglo, es decir, que se insertaran ms elementos de lo que el arreglo puede almacenar, la operacin insert podra quedar como:
void insert(struct cola *C, int e){ C->items[++C->rear]=e; }

y al operacin x=remove(Q)
int remove(struct cola *C){ return C->items[C->front++]; }

y finalmente la operacin empty(Q):


bool empty(struct cola *C){ if(C->front>C->rear) return true; else return false; }

Colas con prioridad


Una cola con prioridad es una estructura de datos en la que se ordenan los datos almacenados de acuerdo a un criterio de prioridad. Hay dos tipos de colas de prioridad:

Las colas de prioridad con ordenamiento descendente. Las colas de prioridad con ordenamiento ascendente.

En las colas de prioridad ascendente se pueden insertar elementos en forma arbitraria y solamente se puede remover el elemento con menor prioridad. Si CPA es una cola de prioridad ascendente, la operacin insert(CPA,x) inserta el elemento x en la cola CPA; y la operacin x=minRemove(CPA) asigna a x el valor del elemento menor (de su prioridad) y lo remueve de la cola. En las colas de prioridad descendente es similar, pero slo permite la supresin del elemento ms grande. Las operaciones aplicables a la cola de prioridad descendente son insert(CPD,x) y x=maxRemove(CPD), cuando CPD es una cola de prioridad descendente y x es un elemento. La operacin empty(C) se aplica a cualquier tipo de cola y determina si una cola de prioridad est vaca. Las operaciones de insertar y borrar se aplican solamente si la pila no est vaca. Los elementos de la cola de prioridad no necesitan ser nmeros o caracteres para que puedan compararse directamente. Pueden ser estructuras complejas ordenadas en uno o varios campos. Por ejemplo, las agendas telefnicas constan de apellidos, nombres, direcciones y nmeros de telfono y estn ordenadas por apellido. A diferencia de las pilas y las colas, en las colas de prioridad se pueden sacar los elementos que no estn en el primer sitio del extremo donde salen los elementos. Esto es porque el elemento a retirar puede estar en cualquier parte del arreglo. Cuando se requiere eliminar un dato de una cola de prioridad se necesita verificar cada uno de los elementos almacenados para saber cul es el menor (o el mayor). Esto conlleva algunos problemas, el principal problema es que el tiempo necesario para eliminar un elemento puede crecer tanto como elementos tenga la cola. Para resolver este problema hay varias soluciones: 1. Se coloca una marca de ``vaco'' en la casilla de un elemento suprimido. Este enfoque realmente no es muy bueno, porque de cualquier modo se accesan los elementos para saber si es una localidad vaca o no lo es. Por otro lado, cuando se remueven elementos, se van creando lugares vacos y despus es necesario hacer una compactacin, reubicando los elementos en el frente de la cola. 2. Cada supresin puede compactar el arreglo, cambiando los elementos depus del elemento eliminado en una posicin y despus decrementando rear en 1. La insercin no cambia. En promedio, se cambian la mitad de los elementos de una cola de prioridad para cada supresin, por lo que esta operacin no es eficiente.

Recursin
Un tema fundamental para los prximos temas es el de recusrin. La recursin es muy importante tanto en mateticas como em computacin, pues se usa recursin para definir procedimientos autosimilares. Definicin 6 Decimos que un objeto es recursivo si en su definicin se nombra a s mismo. En programacin, una funcin es recursiva si en el mbito de esa funcin hay una llamada a s misma, C/C++ permite esta clase de acciones. Los algoritmos recursivos dan elegancia a las soluciones de los problemas. Un ejemplo clsico es el factorial de un nmero. Una manera de definir el factorial de un nmero es:

es decir, el producto de todos los nmeros enteros menores o guales que l, lo que se puede resolver fcilmente con una funcin iterativa, esto es, una funcin con un ciclo que itere suficientes veces, incrementando un valor y entonces ir almacenando en una variable el resultado de esas multiplicaciones. Una implementacin de esta definicin iterativa es:
(1) (2) (4) (5) (6) (7) (8) int i,n; long double valorAc; valorAc=1.0; std::cout << "Numero entero:"; std::cin>> n; for(i=1; i<=n; i++) valorAc = valorAc*i; std::cout<<"El factorial de "<<n<<" es:"<<valorAc;

El ciclo principal es en la lnea (7). No hay ningn truco hasta aqu. La nica observacin importante es en la lnea (2) en donde se declara el tipo long double para el valor del resultado, la razn para tal accin es que el nmero factorial crece muy rpido y an con entradas en el rango de los caracteres (hasta 255), el factorial es muy grande. Este procedimiento computacional no hace uso de tcnicas especiales empleadas para tratar nmeros grandes. Sin embargo una solucin ms elegante es usar la definicin recursiva, y esta es:

El programa en C/C++ es el que se muestra a continuacin:


( 1) double factorial(double a){

( 2) if (a<=1) return 1.0; ( 3) else return (a *factorial(a-1.0)); } ( 4) ( 5) int main (int argc, char * const argv[]) { ( 6) double n; ( 7) std::cout << "Numero entero:"; ( 8) std::cin>> n; ( 9) std::cout<<"El factorial de "<<n<<" es: "<< factorial(n); (10) return 0; }

Aqu hay varias cosas que sealar, en primer lugar se ha creado una nueva funcin, a diferencia de la definicin iterativa en donde era suficiente trabajar en el programa principal. Esta funcin se llama factorial (como era de suponerse), y empieza su encabezado en la lnea (1). All mismo en la misma lnea (1), es de notar que hemos emplado ahora el tipo double tanto para el tipo devuelto como para el tipo del argumento, a diferencia de la versin iterativa en donde emplebamos tipos diferentes. La razn es que al iniciar la recursin el argumento es del tipo devuelto, asi que deben ser del mismo tipo. Cada llamada recursiva genera una entrada a una pila, en donde se guardan (como elementos) los estados generales del sistema al momento de hacer la llamada, entonces, cuando se termina la funcin se recupera una entrada de la pila. En la figura 16 ilustra cmo funciona la recursividad cuando se intenta obtener el factorial(5).

Figura 16: Recursividad cuando se ejecuta factorial(5)

La serie Fibonacci
Una de las series ms famosas es sin duda alguna la serie de Fibonacci:

Un poco de observacin es sufucuente para encontrar que cualquier nmero (a partir del tercero de la serie, osea el segundo 1) es igual a la suma de los dos nmeros anteriores. Daremos en primer lugar la versin iterativa. En este algoritmo deseamos encontrar el -simo nmero de la serie Fibonacci. As si el resultado del algoritmo debe

ser ; si el resultado debe ser . La versin iterativa empieza desde los primeros 1's, sumndolos y encontrando el tercero, luego para encontrar el cuarto nmero se suman el tercero (recin encontrado) y el segundo, y as en adelante hasta encontrar el nmero buscado.
#include <iostream> int main (int argc, char * const argv[]) { int i,n,fib,fib1,fib2,fibx; std::cout<<"Un numero entero:"; std::cin>>n; fib1=2; fib2=1; i=3; if((n==1)||(n==2)) fib=1; else{ do{ fib = fib1 + fib2; fibx = fib1; i++; fib1 = fib; fib2 = fibx; }while(i<n); } std::cout << "\nEl "<<n<<"-esimo numero de la serie Fibonacci es: "<<fib; return 0; }

La definicin recursiva para encontrar todos los Fibonacci es:

primeros nmeros de la serie

En el siguiente cdigo, la solucin que propone la recursividad resulta en una programacin elegante, aunque costosa. El cdigo que hace esto es:
( 1) ( 2) ( 3) ( 4) ( 5) ( 6) ( 7) ( 8) ( 9) (10) (11) (12) (13) (14) (15) (16) #include <iostream> //==================== int fib(int val){ if ((val==1)||(val==2)) return 1; else return (fib(val-1)+fib(val-2)); } //==================== int main (int argc, char * const argv[]) { int n; std::cout<<"Numero entero:"; std::cin>>n; std::cout<<"\nEl "<< n <<"-esimo numero fibonacci es: "<< fib(n); return 0; }

Como regla general, cualquier algoritmo recursivo se puede reescribir en un algoritmo iterativo. La ventaja de tener un algoritmo iterativo es que no se usa una pila para guardar llamadas a la misma funcin de manera recursiva, esto es una ventaja porque el espacio de memoria destinado al uso de la pila es generalmente limitado, de manera que cuando se hacen demasiadas funciones push seguramente llegar el momento en que la pila ``se desborde'', que por cierto es un trmino usado en computacin para decir que ya no hay ms espacio disponible en la pila. UNIDAD 3: C++ ARREGLOS TEMA 3: C++ ARREGLO TIPO LISTA (1)

Un arreglo tipo lista se define como una variable que permite almacenar un conjunto de datos del mismo tipo organizados en una sola columna y uno o mas renglones. Tambin reciben el nombre de vectores en lgebra o arreglos unidimensionales en programacin. Los procesos normales con una lista o con sus elementos incluyen declarar toda la lista, capturar sus elementos, desplegarlos, realizar operaciones con ellos, desplegarlos, etc. Para declarar una lista se usa el siguiente formato; tipodato nomlista[cant de elementos o renglones]; ejemplos;

int edades[12]; float sueldos[10]; Char carreras[10][30];

Para crear e inicializar una lista usar el siguiente formato: Tipodato nomlista[cant reng]={elementos}; Ej Int edad[5] = { 12,18,20,23,30 }; Float sueldo[3] = { 1.36, 23.67, 77.90 }; Strcpy(carrers[0],informatica);strcpy(carreras[1],sistemas);etc;etc;;; Recordar que la primera posicin o rengln en una lista es la posicin o rengln 0 (cero).

Ejemplo de LISTAS(C++)
La forma ms simple de estructura dinmica es la lista enlazada (lista abierta o lista ligada). En esta forma los nodos se organizan de modo que cada uno apunta al siguiente, y el ltimo no apunta a nada, es decir, el puntero del nodo siguiente del ltimo nodo toma el valor nulo (NULL).En las listas abiertas existe un nodo especial: el primero, ese nodo deber poder ser referenciado directamente en cualquier momento (normalmente con una variable de tipo puntero). Esa referencia al primer nodo es muy importante ya que mediante ella podemos acceder a toda la lista.

Lista ligada con tres valores enterosMediante asignacin dinmica de memoria podemos hacer que la lista vare de tamao, aumentando o disminuyendo, segn las necesidades del programa que la utilice con la nica restriccin de que haya memoria disponible. Inicialmente, cuando una lista an no dispone de ningn nodo, o cuando se borran todos los nodos de los que dispone, diremos que la lista esta vaca, en ese caso la referencia al primer nodo tendr un valor nulo (puntero a NULL). Es muy importante que nuestro programa nunca pierda el valor de la referencia al primer elemento, ya que sino existe ninguna copia de ese valor, y se pierde, ser imposible acceder al primer nodo y por tanto al resto de la lista y no se podr liberar el espacio de memoria que ocupa.
/* Ejemplo de una lista dinamica de objetos mas ejemplos en http://www.tinchio.com.ar Autor: Tinchio */ #include "clase.h" #include <stdlib.h> #include <string> #include <iostream> using namespace std; //definimos cada uno de los metodos char *persona::get_nombre() { return nombre; } void persona::set_nombre() { fflush(stdin); cin.getline(nombre,30); } int persona::get_edad() { return edad; } void persona::set_edad() { cin >> edad; } char *persona::get_dni() { return dni; } void persona::set_dni()

{ fflush(stdin); cin.getline(dni,10); } persona *persona::get_sig() { return sig; } void persona::set_sig(persona *Aux) { sig=Aux; } //constructor de persona persona::persona() { cout << endl << "Nombre: "; set_nombre(); cout << endl << "Edad: "; set_edad(); cout << endl << "DNI: "; set_dni(); sig=NULL; } //destructor persona::~persona() { delete sig; } void persona::mostrar_persona() { cout << endl << "Nombre: " << get_nombre(); cout << endl << "Edad: " << get_edad(); cout << endl << "DNI: " << get_dni(); } /* LISTA */ //funcion para insertar un objeto persona en la lista void lista::insertar_lista() { persona *aux, *aux_pri; /* Creamos un objeto persona para agregar a la lista */ aux=new persona(); //si primero esta en NULL es porque esta vacio //asi que a primero directamente le asignamos aux if(primero==NULL) { primero = aux; return; } else { //aux_pri lo usamos para recorrer la lista sin alterar primero aux_pri=primero; while(aux_pri->sig!=NULL) { //le asignamos el siguiente aux_pri=aux_pri->sig; } //cuando ya recorrimos toda la lista y aux_pri quedo apuntando al

} } //esto es simple, recorrer la lista e ir mostrando void lista::mostrar_lista() { persona *aux; aux=primero; if(aux==NULL) { cout << endl << "La lista esta vacia" << endl; return; } else { while(aux!=NULL) { aux->mostrar_persona(); aux=aux->sig; } } } lista::~lista() { persona *aux; aux=primero; if(aux==NULL) { cout << endl << "La lista esta vacia" << endl; return; } else { while(aux!=NULL) { delete aux; aux=aux->sig; } }

//ultimo elemento, a aux_pri lo enlazamos con aux aux_pri->set_sig(aux);

} lista::lista() { primero=NULL; }