Sei sulla pagina 1di 132

Colas en C++

Rodrigo Paszniuk | 2013-04-27 | 3 Comments


16

Colas
Definición: ¿Qué es una Cola?
El diccionario de la Real Academia Española define una acepción de cola como “hilera de
personas que esperan turno para alguna cosa”; una hilera de vehículos esperando pasar
una frontera, una hilera de personas para entrar en un teatro, o una cola de trabajos en un
sistema de computadora que espera la disponibilidad de algún dispositivo de salida tal
como una impresora.
En cada uno de estos ejemplos, los elementos se atienden en el orden en que llegaron; es
decir, el primer elemento en entrar (primero de la cola) es el primero en ser
atendido (salir). La cola es una estructura FIFO (First In First Out)

Ejemplos de colas en Computación


 Cola en el Spooler de Impresión.
 Cola en la espera de instrucciones a seguir.
 Cola en la recepción de datos.
 Etc.

Evolución de la Cola
Operaciones Básicas de la Cola
 Inicializar la cola.
 Añadir un elemento al final de la cola.
 Eliminar el primer elemento de la cola.
 Vaciar la cola.
 Verificar el estado de la cola: Vacía / Llena.

Implementación de las funciones con Punteros


//Declaraciones generales
Estructura tipo_nombre
…………..
…………..
tipo_nombre siguiente
Fin de estructura
tipo_nombre à primero, aux, ultimo
tipo cantidad à tope

función inicializar
primero = nulo
ultimo = nulo
fin función

lógica función vacía


si ultimo = nulo
devolver (verdadero)
si no
devolver (falso)
fin función

lógica función llena


si primero = tope
devolver (verdadero)
si no
devolver (falso)
fin función

función eliminar
dato = primero.dato
aux = primero
primero = aux.siguiente
liberar(aux)
fin función

función añadir
reservar (aux)
aux.dato = dato
ultimo.siguiente = aux
ultimo = aux
fin función

Ejemplo de Cola:
1  
2
#include <iostream.h>
3 #include <conio.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7
8 struct datos {
int dato;
9 datos *s;
10 }*p,*aux,*u;
11
12 void insertar (int dat);
13 void borrar ();
void listar ();
14
15 main()
16 {
17 int opc,y;
18 do
19 {
cout<<"\n1. Insertar";
20 cout<<"\t2. Borrar";
21 cout<<"\t3. Listar";
22 cout<<"\t4. Salir";
23 cout<<"\n Ingrese opcion: ";cin>>opc;
switch(opc)
24 {
25 case 1: cout<<"Ingrese dato: ";
26 cin>>y;
27 insertar(y);
28 cout<<"\nDato insertado!!";
break;
29 case 2: borrar();
30 break;
31 case 3: listar(); break;
32 case 4: exit(1);
33 default: cout<<"\n Opcion no valida!!"; break;
}
34 }while(opc);
35 }
36
37 void insertar (int dat)
38 {
aux=new(datos);
39 aux->dato=dat;
40 if(u)
41 {
42 u->s=aux;
43 aux->s=NULL;
u=aux;
44 }
45 else
46 {
47 p=u=aux;
}
48 }
49 void borrar()
50 {
51 if(p)
52 {
53 aux=p;
54 cout<<"\nElimino a " <<p->dato;
p=aux->s;
55 delete(aux);
56 }
57 else
58 {
cout<<"\n No hay datos";
59 }
60 }
61
62 void listar()
63 {
64 int i;
if(!u)
65 {
66 cout<<"\n No hay datos en la cola";
67 return;
68 }
aux=p;
69 while(aux)
70 {
71 cout<<"\n"<<++i<<" - "<<aux->dato;
72 aux=aux->s;
73 }
}
74
75 &nbsp;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Hola amigos,
Resulta que tengo que hacer esto:

Cita:
Escriba un programa para ejecutar el experimento siguiente:
Genere 100 números aleatorios con valores en el rango entre 1 y 500. Conforme se
genera cada número, insértelo en una cola inicialmente vacía. Si el número es de dos
dígitos, tiene prioridad sobre números de tres dígitos.

Después de insertar los 100 números, imprima en orden secuencial las posiciones de la
cola donde se encuentra el número con mayor valor y el número con menor valor.

Nunca hemos trabajado usando Colas, no entiendo cómo uno puede aprender de esta
manera pero bueno, venga...

He investigado cómo crear una Cola y crear los números aleatorios.

Código:
Código:
#include <iostream>
#include <stdlib.h> // Librería para usar la función srand()
#include <time.h> // Librería para usar la función time()

using namespace std;

//Declaraciones de tipos para manejar colas en C++


typedef struct _nodo {
int dato;
struct _nodo *siguiente;
} tipoNodo;

typedef tipoNodo *pNodo;


typedef tipoNodo *Cola;

int main()
{
//Declaración de variables
int i, Numero;
srand(time(NULL));

//Procesamiento
for(i = 1; i <= 100; i++)
{
Numero = 1 + rand() % (501 - 1);
cout << Numero << endl;
}

return 0;
}
…………………………………….

Problemas Resueltos de Punteros

1.- ENCUENTRA LOS ERRORES EN LA SIGUIENTE DECLARACIÓN DE


PUNTEROS:

Int x, *p, &y;


Char* b= “cadena larga” ;
Char* c= ‘C’ ;
Float x;
Void* r= &x;

Es incorrecta sintácticamente al declaración int &y. no tiene ningún sentido en C.


Cuando un carácter esta rodeado por comillas simples es considerado como una
constante de tipo char no como una cadena, para lo cual debería estar rodeado de
dobles comillas.

Char* c= “C”;

No se puede declarar un puntero a tipo void.

2.- DADA LA SIGUENTE DECLARACIÓN ESCRIBIR UNA FUNCIÓN QUE


TENGA COMO ARGUMENTO UN PUNTERO AL TIPO DE DATO Y MUESTRE
POR PANTALLA LOS CAMPOS.
Struct botón

{
Char * rotulo;
Int código;
};

La función puede ser la siguiente


Void mostrarboton (struct botón *pb)
{
Printf (“rotulo del boton : %s \n” , pb ->rotulo ) ;
Printf (“Codigo asociado al botón : %d\n” , pb->código ) ;
}

3.- EN EL SIGUIENTE CÓDIGO SE ACCEDE A LOS ELEMENTOS DE UNA


MATRIZ. ACCEDER A LOS MISMOS ELEMENTOS CON ARITMÉTICA DE
PUNTEROS.

#define N 4
#define N 5
Int f . c ;
Double mt [N] [M] ;
For ( f = 0; f<N; f++)
{
For (c = 0 , c < M ; c++)
Pintf (“%l f “. Mt [f] [c] );
Printf (“\n”) ;
}

Análisis del problema

Se define un puntero la primera posición de la matriz y se calcula la posición de


memoria donde se van encontrando cada uno de los elementos de la matriz, base
de sumar la longitud de las filas desde el comienzo de la matriz y los elementos
desde el comienzo de la fila donde está situado el elemento al que desea acceder.
Si un elemento está en la fila 5, habrá que saltar 5 filas enteras de elementos del
tipo de la matriz, ´para situarse al comienzo de su fila en la memoria. Recordar
que en C los arrays siempre se enumeran desde 0. Si el elemento que se busca
está en la columna 3, hay que calcular 3 posiciones desde el comienzo de su fila
calculado antes. Así se llega a la dirección donde se encuentra el elemento
buscado. Para hacer estos cálculos es imprescindible conocer el número de
columnas de la matriz que es igual al tamaño de cada fila. Sin este dato sería
imposible reconstruir la estructura de la matriz, partiendo solo del espacio que
ocupa en la memoria, ya que este es puramente lineal. Una vez que se tiene dicha
dirección se accede a su contenido. Esta expresión es la misma que sustituye el
compilador de C cuando compila la indireccion que representan los operadores
corchetes de los arrays.

Codificación

#define N 4
#define M 5
Int f.c ;
Doublé mt [N] [M] , *pmt=mt ;
For ( f = 0 ; f<N ; f++)
{
For ( c = 0 ; c<M; c++ )
Printf (“\n”) ;
}

Otra opción podría haber sido hacer que el puntero recorra la matriz sabiendo que
la matriz esta almacenada en memoria fila a fila de forma lineal.

#define N 4
#define M 5
Int f.c ;
Doublé mt [N] [M] , *pmt=mt ;
For ( f = 0 ; f<N ; f++)
{
For (c = 0 ; c<M ; c++)
Printf (“%l f “ , *pmt++) ;
Printf (“\n”) ;
}

4.- ESCRIBA UNA FUNCIÓN CON UN ARGUMENTO DE TIPO PUNTERO A


DOUBLÉ Y OTRO ARGUMENTÓ DE TIPO INT. EL PRIMER ARGUMENTO SE
DEBE CORRESPONDER CON UN ARRAY Y EL SEGUNDO CON EL NUMERO
DE ELEMENTOS DEL ARRAY. LA FUNCIÓN A DE SER DE TIPO PUNTERO A
DOUBLÉ PARA DEVOLVER LA DIRECCIÓN DEL ELEMENTO MENOR.

Análisis del problema.

Al hacer la llamada función se pasa el nombre del array a recorrer o un puntero


con la direccione del comienzo del array. De la misma manera el puntero que
recibe la función puede ser tratado dentro de su código como si fuera un array,
usando el operador corchete, o como un simple puntero que se va a mover por la
memoria. En ambos casos el segundo parámetro de la función es imprescindible
para no acceder fuera de la región de la memoria donde están los datos validos
del array original almacenados.

Codificación

Doublé *menorArray (doublé *v, int n)


{
Int i , min = -1 ;
/ * suponer que los elementos del arrays son positivos */
Doublé *menor ;
For (i=0 ; i<n ; i++)
If (v[i] < min ) menor = &v[ I ] ;
/* o if (*v++ < min ) menor = v – 1 ; */
Return menor ;
}
5.- DADAS LAS SIGUIENTES DECLARACIONES DE ESTRUCTURAS,
ESCRIBE COMO ACCEDER AL CAMPO X DE LA VARIABLE ESTRUCTURA T.

Struct fecha
{
Int d, m, a;
Float x;
};
Struct dato
{
Char* mes;
Struct fecha* f;
} t;

Análisis del problema

La variable t es una variable de tipo estructura, por lo que se usa el operador


punto para acceder al miembro f. desde el dato miembro f es necesario usar el
operador flecha para acceder al campo x de la estructura fecha a la que apunta.

t.f -> x ;

que problema habría en la siguiente sentencia

gets (t. mes) ;

El campo mes de la estructura fecha no apunta a ningún sitio , por lo cual dará
problemas de asignación de memoria cuando la función gets ( ) intente colocar el
resultado en el puntero que se le pasa como argumento. Para evitar esto sería
necesario reservar memoria antes de llamar a gets ( ).

Problemas Resueltos de Listas

1.- Escriba una función que devuelva cierto si la lista esta vacía y falso en
otros caso, y otra que cree una lista vacía.

Codificación

Si se supone siguiente declaración:

Typedef int Item;


Typedef struct Registro
{
Item el;
Struct Registro* sig;
} Nodo;

La codificacion de la funcion Esvacia sera:

Int Esvacia (Nodo _* Primero)


{
Return (Primero == NULL);
}

La codificacion de la funcion VaciaL sera:

Void VaciaL (Nodo ** Primero)


{
*Primero == NULL;
}

2.- Escriba una función entera que devuelva el numero de nodos de una lista
enlazada.
Codificación

Si se supone la declaración de problema anterior se tiene:

Int NumerNodos (Nodo *Primero)


{
Int k = 0;
Nodo *p;
P = Primero;
While (p ¡= NULL)
{
K++;
P = p->sig;
}
Return(k);
}

3.- Escriba una función que elimine el nodo que ocupa la posición i de una
lista enlazada ordenada

Análisis del problema

Para resolver el problema se necesita recorrer la lista contando el número de


elementos que van pasando, y corte el recorrido, cuando la lista este vacía, o
cuando se haya llegado a la posición que se busca. Una vez terminado el primer
bucle de búsqueda en el caso de que haya que eliminar el elemento, se borra
teniendo en cuenta si es o no el primer elemento de acuerdo con lo indicado en la
teoría.
Codificación

Si se supone la declaración realizada en el problema se tiene:

Void EliminaPosicion (Nodo** Primero, Int i)


{
Int k = 0;
Nodo *ptr, *ant;

Ptr = *Primero;
Ant = NULL:
While ( (k < i) && (ptr != NULL))
{
K++;
Ant = ptr;
Ptr = ptr->sig;
}
If(k == i)
{
If( ant == NULL)
*Primero = ptr->sig;
Else
Ant->sig = ptr->sig;
Free(ptr);
}
}

4.- Escriba una función que reciba como parámetro una lista enlazada
apuntada por Primero, un dato cualquiera e inserte en la lista enlazada un
nuevo nodo con la información almacenada en dato y de tal forma que sea el
primer elemento de la lista.

Análisis del problema

Los pasos que se seguirán son: asignar memoria a un nuevo puntero nuevo; situar
el nuevo dato en el campo el; mover el campo sig de nuevo puntero nuevo al
puntero Primero y hacer que Primero apunte a nuevo. Esta función trabaja
correctamente, aun cuando la lista este vacía, siempre que previamente se haya
iniciado a NULL

Codificación

Si se supone la siguiente declaración:

Typedef int Item;


Typedef struct Registro
{
Item el;
Struct Registro * sig;
} Nodo;

La codificacion de la function sera:

Void InsertarprimeroLista (Nodo** Primero, Item dato)


{
Nodo *nuevo;
Nuevo = (Nodo*) malloc (sizeof(Nodo));
Nuevo -> el = dato;
Nuevo -> sig = *Primero;
*Primero = nuevo;
}

5.- Escriba una función que reciba como parámetro un puntero ant que
apunte a un nodo de una lista enlazada e inserte el valor recibido en el
parámetro dato como un nuevo nodo que este inmediatamente después de
ant (Inserción en el centro y final de una lista).

Análisis del problema

Se crea un nuevo nodo apuntado por nuevo, donde se almacena el dato, para
posteriormente poner como siguiente del nuevo nodo nuevo el siguiente de ant,
para por ultimo enlazar el siguiente de ant con nuevo.

Codificación

Si se supone la siguiente declaración:

Typedef int Item;


Typedef struct Registro
{
Item el;
Struct Registro* sig;
} Nodo;

La codificación de la función será:

Void InsertarLista (Nodo* ant, Item dato)


{
Nodo *nuevo;

Nuevo = (Nodo*) malloc (sizeof(Nodo));


Nuevo -> el = dato;
Nuevo -> sig = ant -> sig;
Ant -> sig = nuevo;
}

Problemas Resueltos de Pila

1.- Escriba las primitivas de gestión de una pila implementada con un array.
Análisis del problema

Se define en primer lugar una constante MaxTamaPila de valor 100 valor máximo
de los elementos que podrá contener la pila. Se define la pila como una estructura
cuyos campos (miembros) serán el puntero cima que apuntara siempre al ultimo
elemento añadido a la pila y un array A cuyos índices variaran entre 0 y
MaxTamaPila-1. Posteriormente se implementan las primitivas
• VaciaP. Crea la pila vacía poniendo la cima en el valor -1.
• EsvaciaP. Decide si la pila esta vacia. En este caso ocurrira cuando su cima
valga -1.
• EstallenaP. Si bien no es una primitiva basica de gestion de una pila; la
implementacion se realiza con un array conviene disponer de ella para prevenir
posibles errores. En este caso la pila estara llena cuando la cima apunte al valor
MazTamaPila-º.
• AnadeP. Añade un elemento a la pila. Para hacerlo comprueba en primer lugar
que la pila no este ellena, y en caso afirmativo. Incrementa la cima en unidad, para
posteriormente poner en el array A en la posicion cima el elemento.
• PrimeroP. Comprueba que la pila no este vacía, y en caso de que asi sea, dara
el elemento del array A almacenado en la posición apuntada por la cima.
• BorrarP. Se encarga de eliminar el último elemento que entro en la pila. En
primer lugar comprueba que la pila no este vacia cuyo caso, disminuye la cima en
una unidad.
• Pop. Esta operación extrae el primer elemento de la pila y lo borra. Puede ser
implementada directamente, o bien llamando a las primitivas PrimeroP y
posteriormente a BorrarP.
• Push. Esta primitiva coincide con AnadeP.

Codificación

Typedef int TipoDato;


/* archivo pilaarray.h */
#include <stdio.h>
#include <stdlib.h>
#define MaxTamaPila 100

Typedef struct
{
TipoDato A[MaxTamaPila];
Int cima;
}Pila;
Void VaciaP(Pila* P);
Void AnadeP(Pila* P,TipoDato elemento);
Void BorrarP(Pila* P);
TipoDato Primero(Pila P);
Int EsVaciaP(Pila P);
Int EstallenaP(Pila P);
Void Pop(Pila* P. TipoDato elemento);
TipoDato Push(Pila *P);

Void VaciaP(Pila* P)
{
P -> cima = -1;
}

Void AnadeP(Pila* P,TipoDato elemento)


{
If (EstallenaP(*P))
{
Puts(“Desbordamiento pila”);
Exit (1);
}
P->cima++;
p->A[P->cima] = elemento;
}
Void Pop(Pila* P,TipoDato elemento)
{
AnadeP(P, elemento);
}

TipoDato Push(Pila *P)


{
TipoDato Aux;
If (EsVaciaP(*P))
{
Puts(“Se intenta sacar un elemento en pila vacia”);
Exit (1);
}
Aux = P->A[P->cima];
P->cima-;
Return Aux;
}

TipoDato PrimeroP(Pila P)
{
TipoDato Aux;
If (EsVaciaP(P))
{
Puts(“Se intenta sacar un elemento en pila vacia”);
Exit (1);
}
P->cima --;
}

Int EsVaciaP(Pila P)
{
Return P,cima == -º;
}

Int EstallenaP(Pila P)
{
Return P,cima == MaxTamaPila-1;
}

2.- Escribir un programa que usando las primitivas de gestion de una pila,
lea datos de la entrada (-1 fin de datos) los almacene en una pila y
posteriormente visualice dicha la pila.
Analisis del problema

Si se supone que el archivo pilaarray.cpp contiene todas las primitivas de gestion


de una pila, para resolver el problema bastara declarar TipoDato como un entero
incluir el archivo pilaarray.cpp anterior, y mediante un programa principal en un
primer bucle while se leen dato y se almacenan en una pila, para posteriormente
en otro bucle extraer datos de la pila y presentarlos en pantalla.

Codificación

Typedef char TipoDato


#Include <pilaarray.cpp>
Void main()
{
Pila P;
Int X;

VaciaP(&P);
Do
{
Printf(“dame dato -1=fin \n”);
Scanf(“%d”,&x);
If (x != -1)
AnadeP(&P, x);
While (x != -1);
}
Printf(“escritura de la pila\n”);
While(¡EsVaciaP(P))
{
Printf(“%d \n”,PrimeroP(P));
BorrarP( &P);
}
}

3.- escriba las primitivas de gestión de una pila implementada con una lista
simple enlazada.
Análisis del problema

Se define como una lista simplemente enlazada. Posteriormente se implementan


las primitivas:
• VaciaP. Crea la pila vacia poniendo la pila P a NULL.
• EsvaciaP. Decide si pila vacia. Esto ocurrira cuando P valga NULL.
• AnadeP. Añade un elemento a la pila. Para hacerlo, lo unico que se debe hacer,
es añadir un nuevo nodo que contenga como informacion el elemento que se
quiera añadir y ponerlo como primero de la lista enlazada.
• PrimeroP. En primer lugar se comprobara que la pila (lista) no este vacia, y en
caso de que asi sea dara el campo el almacenado en el primer nodo de la lista
enlazada.
• BorrarP. Se encarga de eliminar el ultimo elemento que entro en la pila. En
primer lugar se comprueba que la pila no este vacia en cuyo caso, se borra el
primer nodo de la pila (lista enlazada).
• Pop. Esta operación extrae el primer elemento de la pila y lo borra. Puede ser
implementada directamente, o bien llamando a las primitivas PrimeroP y
posteriormente a BorrarP.
• Push. Esta primitiva coincide con AnadeP.
• NuevoNodo. Es una función auxiliar de la implementación que se encarga de
reserva de reservar memeoria para la operación AnadeP.
• EstallenaP. En esta implementación no tiene ningún sentido, ya que se supone
que la memoria dinámica es en principio inagotable.

Codificación

#include <stdio.h>
#include <stdlib.h>

Typedef int TipoDato;


Typedef struct unnodo
{
TipoDato el;
Struct unnodo *sig;
}Nodo;

Typedef Nodo Pila;


Nodo* NuevoNovo(TipoDato element);

Void VaciaP(Pila** P);


Void AnadeP(Pila** P,TipoDato element);
Void BorrarP(Pila** P);
TipoDato PrimeroP(Pila *P);
Int EsVaciaP(Pila *P);
Void Pop(Pila** P,TipoDato elemento);
TipoDato Push(Pila **P);

Nodo* NuevoNodo(TipoDato elemento)


{
Nodo *a;
A = (Nodo*)malloc(sizeof(Nodo));
A -> el = elemento;
A -> sig = NULL;
Return a;
}

Void VaciaP(Pila** P)
{
*P = nn;
}

Void AnadeP(Pila** P, TipoDato elemento)


{
Nodo * nn;
Nn = NuevoNodo(elemento);
nn->sig = (*P);
*P = nn;
}

TipoDato Pop(Pila** P, TipoDato elemento)


{
AnadeP(P, elemento);
}

TipoDato Push(Pila **P)


{
TipoDato Aux;
Pila *nn;

If (EsVaciaP(*P));
{
Puts(“Se intenta sacar un elemento en pila vacia”);
Exit (1);
}
Aux = (*P)->el;
Nn = *P;
*P = nn->sig;
Free(nn);
Return Aux;
}

TipoDato PrimeroP(Pila *P)


{
TipoDato Aux;

If (EsVaciaP(P))
{
Puts(“Se intenta sacar un elemento en pila vacia”);
Exit (1);
}
Nn =(*P);
(*P)= nn->sig;
Free(nn);
}
Int EsVaciaP(Pila *P)
{
Return P == NULL;
}

4.- Usando las primitivas de gestión de una pila de enteros escriba las
siguientes funciones: EscribePila que recibe como parámetro una pila y la
escribe. CopiadPila que coia una pila en otra. DaVueltaPila que da la vuelta a
una pila.

Análisis del problema

Usando el archivo pilalista.cpp en el que se tiene ya la implementacion de las


primitivas de una pila, lo unico que se debe hacer es implementar las siguientes
funciones:
• EscribePila que recibe como parametro por valor una pila y mediante un bucle
while, se van extrayendo, borrando y escribiendo los elementos de la pila.
• CopiaPila que recibe como parametro por valor una pila p y devuelve en Pcop
una copia exacta de la pila P. Par ello basta con volcar la pila P en una pila Paux
auxiliar, para posteriormente volcar la pila Paux en la pila Pcop.
• DaVueltaPila que recibe como parametro por valor la pila P y vuelva su contenido
en la pila Pcop.

Codificación

Void CopiaPila (Pila *P, Pila**Pcop)


{
Pila *Paux;
TipoDato e;

VaciaP(&Paux);
While (! EsVaciaP(P))
{
E = PrimeroP(P);
BorrarP(&P);
AnadeP(&Paux,e);
}
VaciaP(Pcop);
While (! EsVaciaP(Paux);
{
E = PrimeroP(Paux))
BorrarP(&Paux);
AnadeP(Pcop,e);
}
}

Void DaVueltaPila (Pila *P, Pila**Pcop)


{
TipoDato e;

VaciaP(Pcop);
While (!EsVaciaP(P))
{
E = PrimeroP(p);
BorrarP(&P);
AnadeP(Pcop,e);
}
}

Void EscribePila(Pila *P)


{
TipoDato e;
While (! EsVaciaP(P))
{
E = PrimeroP(P);
BorrarP(&P);
Printf(“%d\n”, e);
}
}

5.- Escriba las funciones LiberarPila y SonIgualesPilas que respectivamente


libera todos los nodos de una pila implementada con lista y decide si dos
pilas son iguales.
Análisis del problema

Al igual que en el ejercicio anterior se usa pilalista.cpp en el que se tiene ya la


implementacion de las primitivas de una pila, lo unico que resta por hacer es
implementar las siguientes funciones:
• LiberarPila que mediante un bucle mientras se encarga de ir extrayendo los
elementos de la pila y mediante la funcion BorrarP irlos eliminando.
• SonIgualesPilas. Dos pilas son iguales si tienen el mismo numero de elementos y
ademas coinciden en el orden de colocacion. Por lo tanto basta con un bucle
mientras, controlado por haber datos en las dos pilas y haber sido todos los
elementos extraido anteriormente iguale, extraer un elemento de cada una de las
pilas y seguir decidiendo sobre su igualdad. Al final del bucle debe ocurrir que las
dos pilas esten vacias y ademas que las variable logica que controla el bucle sea
verdadera.

Codificación

Void LiberarPila(Pila**P)
{
While (!EsVaciaP(*P))
BorrarP(P);
}
Int SonIgualesPilas(Pila *P, Pila* P1)
{
Int sw = 1;
TipoDato e,e1;

While (! EsVaciaP(P) && !EsVaciaP(P1) && sw)


{
E = PrimeroP(P);
BorrarP(&P);
El = PrimeroP(P1);
BorrarP(&P1);
Sw = (e == el);
}
Return (sw && EsVaciaP(P)&& EsVaciaP(P1));
}

Problemas Resueltos de Colas

1.- Escriba las declaraciones necesarias y las primitivas de gestión de una


cola implementada con listas enlazadas

Análisis del problema


Se declaran en primer lugar todos los tipos de datos necesarios para una lista
enlazada. Una cola será una estructura con dos punteros a la lista frente que
apuntara al primer elemento de la cola y final que apuntara al último elemento.
• VaciarC. Crea una cola vacia, para lo cual basta con poner en frente y el final a
null.
• EsvaciaC. Decide si la cola esta vacía. Es decir si frente y final valen null
• EstallenaC. Esta función no es ahora necesaria ya que teóricamente no hay
límite.
• PrimeroC. Extrae el primer elemento de la cola que se encuentra en el nodo
frente. Previamente a esta operación ha de comprobarse que la cola no este
vacia.
• AñadeC. Añade un elemento a la cola.este elemento se añade en un nuevo nodo
que será el siguiente de final en el caso de que la cola no este vacia. Si la cola
esta vacia el frente debe apuntar a este nuevo nodo. En todo caso el final siempre
debe moverse al nuevo nodo.
• BorrarC. Elimina el primer elemento de la cola. Para hacer esta operación la cola
no debe estar vacia. El borrado se realiza avanzando frente al nodo siguiente y
liberando la memoria correspondiente .
• EliminarC. Esta primitiva libera toda la memoria que tenga una cola ya creada.
Se realiza mediante un bucle controlado por el final de la lista. Liberando la
memoria ocupada por cada nodo en cada una de las interacciones del bucle.

Codificación

#include<stdio.h>
#include<stdlib.h>
Typedef int tipodato;
Struct nodo
{
Tipodato el;
Struct nodo* sig;
};
Typedef struct
}
Nodo* frente;
Nodo * final;
}cola;
Void vaciaC(cola* C);
Void añadeC(cola* C. tipodato el);
Void eliminarC(cola*C);
Void borrarC(cola* C);
Tipodato primeroC (cola C);
Int esvaciaC(cola C);
Nodo* crearnodo(tipodato el);
Void vaciarC(cola* C)
{
C->frente = null;
C->final = null;
}
Nodo*crearnodo(tipodato el)
{
Nodo* nn;
Nn = (nodo*) malloc(sizeof(nodo));
nn->el = el;
nn->sig = null;
return nn;}
int esvaciaC(Cola C)
return (C.frente == null);
}
Voyd añadeC(cola* C,tipodato el)
{
Nodo* a;
A = crearnodo(el);
If (esvaciaC(*C))
C->frente = a;
Else
C->final->sig = a;
C->final = a;
}
Void borrarC(cola*C)
{
Nodo *a;
If (esvaciaC(*C))
{
A = C->frente;
C->frente = C->frente->sig;
If(C->frente == null)
C->final ==null;
Free(a);
}
Else
{
Puts(“error eliminacion de una cola vacia”);
Exit(-1);
}
}
Topodato primeroC(cola C)
{
If (esvaciaC(C))
{
Puts(“error: cola vacia”);
Exit(-1);
}
Return (C.frente->el);
}
Void eliminaC(Cola* C)
{
For (;C->frente;)
{
Nodo* n;
N =C->frente;
C->frente = C->frente->sig;
Free(n);
}
}

2.- Escriba una función que tenga como argumento dos colas del mismo
tipo. Devuelva cierto si las dos colas son idénticas

Análisis del problema

Se usan para resolver el problema las primitivas de gestión de colas


implementando una función son igualescolas que dará el valor verdadero cuando
las dos colas tengan igual numero de elementos y además estén colocada en el
mismo orden.

Codificación
Int sonigualescolas(cola* C. cola* C1)
{
Int sw=1;
Tipodato e,el;
While(esvaciaC(C)&& esvaciaC(C1)&& sw)
{
E =primeroC(C);
borrarC(&C);
el =primeroC(C1);
borrarP(&C1);
sw =(e ==el);
}
Return (sw && esvaciaC(C)&& esvaciaC(C1));
}

3.- Escriba una función que reciba como parámetro una cola de números
enteros y nos devuelva el mayor y el menor de la cola.

Análisis del problema

Se usan las primitivas de gestión de colas implementadas con listas, lo único que
hay que hacer es inicializar mayor y menor al primer elemento de la cola, y
mediante en bucle voraz controlado por si se vacía la cola, ir actualizando las
variables mayor y menor.

Codificación

Void mayormenor(cola*C.tipodato *mayor. Tipodato *menor)


{
Tipodato M.m.e;
M=-32767;
m =32367;
while(esvadciaC(*C))
{
E=primeroC(*C);
borrarC(C);
if(m<e)
m=e;
if(m>e)
M=e;
}
*Mayor=M;
*mrnor=m;
}

4.- Problema de colas

• Estallenac. Decide si la cola esta llena. Es decir, si (final+1)%maxtamc==frente.


• Primeroc. Extrae el primer elemento de la cola que se encuentra en (frente+1)
maxtamc. Previamente a esta operación a de comprobarse que la cola no este
vacia.
• Añadec. Añade un elemento a la cola. Este elemento se añade a la posición del
array (final+1) %maxtamc. Final también debe ponerse en esa posición.
Previamente a esta operación a de comprobarse si la cola esta llena.
• Borrarc. Elimina el primer elemento de la cola. Para ello basta con hacer
frente=(frente+1) %maxtamc. Previamente a esta operación ha de comprobarse
que la cola no este vacia.

Codificación
#include<stdio.h>
#include<stdlib.h>
#definemaxtamc 100
Typedef int tipodato;
Typedef struct
{
Int frente. Final;
Tipodato A [maxtamc];
}cola;
Void vaciaC(cola* C);
Void anade C (cola* C, tipodato E);
Void borrarC(cola* C);
Tipodato primeroC(cola C);
Int esvaciaC(cola C);
Int estallenaC(cola C);
Void vaciaC (cola* C)
{
C->frente= 0;
C->final=0;
}
Void anadeC(cola* C, tipodato E)
{
If (estallenaC( *C))
{
Puts(“desbordamiento cola”);
Exit (1);
}
c->final = (C-> final + 1)% maxtamc;
C->A[C->final] =E;
}
Tipodato primeroC(cola C)
{
If (esvaciaC))
{
Puts(“elemento drente de una cola vacia”);
Exit (1);
Return (C.A[(C.frente+1) % maxtamC]);
}
Int esvaciaC (cola C)
{
Return (C.frente==C. final);
}
Int estallenaC(cola C)
{
Return ( C.frente==(==(C.final+1) % maxtamC);
}
Void borrarC (cola* C)
{
If (esvaciaC(*C))
{
Puts(“eliminacion de una cola vacia”);
Exit (1);
C->frente =(C->frente+1) % maxtamC;
}
Problema resuelto de árbol

1.- se dispone de un árbol binario de elementos de tipo entero. Escriba


funciones que calculen:

a) La suma se sus elementos.


b) La suma de sus elementos que son múltiplos de 3

Análisis del problema

Para resolver el problema basta con implementar las dos funciones efectuando al
hacer un recorrido del árbol las correspondientes operaciones.
Codificación
Int Suma (Nodo*a)
{
If(a)
Return (a->el + Suma(a->hi) + Suma(a-<>hd));
Else
Return(0);
}
Int SumaMultimpos (Nodo*a)
{
If(a)
If (a->el%3)
Return( a->el + SumaMultimpos(a->hi)+SumaMultimpos(a->hd);
Else
Return( sumaMultimpos(a->hi) + SumaMultimpos(a->hd)
Else
Return(0);
}

Colas en C++
octubre 30, 2012
Las colas se utilizan en sistemas informáticos, transportes y operaciones

de investigación (entre otros), dónde los objetos, personas o eventos son

tomados como datos que se almacenan y se guardan mediante colas para

su posterior procesamiento. Este tipo de estructura de datos abstracta se

implementa en lenguajes orientados a objetos mediante clases, en forma

de listas enlazadas.

Implementación

/*

* C++ - Colas/Queue

* Copyright 2014 Martin Cruz Otiniano

* Description : Encola elemento, Desesencola elemento, Mostrar cola, Vaciar cola

* Site : martincruz.me

*/

#include <iostream>
using namespace std;

/* Estructura de los nodos de la cola

------------------------------------------------------------------------*/

struct nodo

int nro;

struct nodo *sgte;

};

/* Estructura de la cola

------------------------------------------------------------------------*/

struct cola

nodo *delante;

nodo *atras ;

};

/* Encolar elemento

------------------------------------------------------------------------*/

void encolar( struct cola &q, int valor )

struct nodo *aux = new(struct nodo);

aux->nro = valor;
aux->sgte = NULL;

if( q.delante == NULL)

q.delante = aux; // encola el primero elemento

else

(q.atras)->sgte = aux;

q.atras = aux; // puntero que siempre apunta al ultimo elemento

/* Desencolar elemento

------------------------------------------------------------------------*/

int desencolar( struct cola &q )

int num ;

struct nodo *aux ;

aux = q.delante; // aux apunta al inicio de la cola

num = aux->nro;

q.delante = (q.delante)->sgte;

delete(aux); // libera memoria a donde apuntaba aux

return num;

/* Mostrar Cola

------------------------------------------------------------------------*/
void muestraCola( struct cola q )

struct nodo *aux;

aux = q.delante;

while( aux != NULL )

cout<<" "<< aux->nro ;

aux = aux->sgte;

/* Eliminar todos los elementos de la Cola

------------------------------------------------------------------------*/

void vaciaCola( struct cola &q)

struct nodo *aux;

while( q.delante != NULL)

aux = q.delante;

q.delante = aux->sgte;

delete(aux);

q.delante = NULL;

q.atras = NULL;
}

/* Menu de opciones

------------------------------------------------------------------------*/

void menu()

cout<<"\n\t IMPLEMENTACION DE COLAS EN C++\n\n";

cout<<" 1. ENCOLAR "<<endl;

cout<<" 2. DESENCOLAR "<<endl;

cout<<" 3. MOSTRAR COLA "<<endl;

cout<<" 4. VACIAR COLA "<<endl;

cout<<" 5. SALIR "<<endl;

cout<<"\n INGRESE OPCION: ";

/* Funcion Principal

------------------------------------------------------------------------*/

int main()

struct cola q;

q.delante = NULL;

q.atras = NULL;

int dato; // numero a encolar

int op; // opcion del menu


int x ; // numero que devuelve la funcon pop

system("color 0b");

do

menu(); cin>> op;

switch(op)

case 1:

cout<< "\n NUMERO A ENCOLAR: "; cin>> dato;

encolar( q, dato );

cout<<"\n\n\t\tNumero " << dato << " encolado...\n\n";

break;

case 2:

x = desencolar( q );

cout<<"\n\n\t\tNumero "<< x <<" desencolado...\n\n";

break;

case 3:

cout << "\n\n MOSTRANDO COLA\n\n";


if(q.delante!=NULL) muestraCola( q );

else cout<<"\n\n\tCola vacia...!"<<endl;

break;

case 4:

vaciaCola( q );

cout<<"\n\n\t\tHecho...\n\n";

break;

cout<<endl<<endl;

system("pause"); system("cls");

}while(op!=5);

return 0;

Una cola es un tipo especial de lista abierta en la que sólo se pueden insertar nodos en
uno de los extremos de la lista y sólo se pueden eliminar nodos en el otro. Además, como
sucede con las pilas, las escrituras de datos siempre son inserciones de nodos, y las
lecturas siempre eliminan el nodo leído.
Este tipo de lista es conocido como lista FIFO (First In First Out), el primero en entrar
es el primero en salir.
El símil cotidiano es una cola para comprar, por ejemplo, las entradas del cine. Los
nuevos compradores sólo pueden colocarse al final de la cola, y sólo el primero de la cola
puede comprar la entrada.
El nodo típico para construir pilas es el mismo que vimos en los capítulos anteriores para
la construcción de listas y pilas:

struct nodo {
int dato;
struct nodo *siguiente;
};

3.2 Declaraciones de tipos para manejar colas en C


^

Los tipos que definiremos normalmente para manejar colas serán casi los mismos que
para manejar listas y pilas, tan sólo cambiaremos algunos nombres:

typedef struct _nodo {


int dato;
struct _nodo *siguiente;
} tipoNodo;

typedef tipoNodo *pNodo;


typedef tipoNodo *Cola;

tipoNodo es el tipo para declarar nodos, evidentemente.


pNodo es el tipo para declarar punteros a un nodo.
Cola es el tipo para declarar colas.

Cola

Es evidente, a la vista del gráfico, que una cola es una lista abierta. Así que sigue siendo
muy importante que nuestro programa nunca pierda el valor del puntero al primer
elemento, igual que pasa con las listas abiertas. Además, debido al funcionamiento de
las colas, también deberemos mantener un puntero para el último elemento de la cola,
que será el punto donde insertemos nuevos nodos.
Teniendo en cuenta que las lecturas y escrituras en una cola se hacen siempre en
extremos distintos, lo más fácil será insertar nodos por el final, a continuación del nodo
que no tiene nodo siguiente, y leerlos desde el principio, hay que recordar que leer un
nodo implica eliminarlo de la cola.
3.3 Operaciones básicas con colas
^

De nuevo nos encontramos ante una estructura con muy pocas operaciones disponibles.
Las colas sólo permiten añadir y leer elementos:
 Añadir: Inserta un elemento al final de la cola.
 Leer: Lee y elimina un elemento del principio de la cola.
3.4 Añadir un elemento
^

Las operaciones con colas son muy sencillas, prácticamente no hay casos especiales,
salvo que la cola esté vacía.
Añadir elemento en una cola vacía

Cola vacía

Partiremos de que ya tenemos el nodo a insertar y, por supuesto un puntero que apunte
a él, además los punteros que definen la cola, primero y ultimo que valdrán NULL:

Elemento encolado

El proceso es muy simple, bastará con que:


1. Hacer que nodo->siguiente apunte a NULL.
2. Que el puntero primero apunte a nodo.
3. Y que el puntero último también apunte a nodo.
Añadir elemento en una cola no vacía

Cola no vacía

De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una
cola, en este caso, al no estar vacía, los punteros primero y ultimo no serán nulos:

Elemento encolado
El proceso sigue siendo muy sencillo:
1. Hacemos que nodo->siguiente apunte a NULL.
2. Después que ultimo->siguiente apunte a nodo.
3. Y actualizamos ultimo, haciendo que apunte a nodo.
Añadir elemento en una cola, caso general
Para generalizar el caso anterior, sólo necesitamos añadir una operación:
1. Hacemos que nodo->siguiente apunte a NULL.
2. Si ultimo no es NULL, hacemos que ultimo->siguiente apunte a nodo.
3. Y actualizamos ultimo, haciendo que apunte a nodo.
4. Si primero es NULL, significa que la cola estaba vacía, así que haremos
que primero apunte también a nodo.
3.5 Leer un elemento de una cola, implica eliminarlo
^

Ahora también existen dos casos, que la cola tenga un solo elemento o que tenga más de
uno.
Leer un elemento en una cola con más de un elemento
Usaremos un puntero a un nodo auxiliar:

Cola con más de un elemento

1. Hacemos que nodo apunte al primer elemento de la cola, es decir a


primero.
2. Asignamos a primero la dirección del segundo nodo de la pila: primero-
>siguiente.
3. Guardamos el contenido del nodo para devolverlo como retorno,
recuerda que la operación de lectura en colas implican también borrar.
4. Liberamos la memoria asignada al primer nodo, el que queremos
eliminar.

Elemento desencolado

Leer un elemento en una cola con un solo elemento


Cola con un elemento

También necesitamos un puntero a un nodo auxiliar:


1. Hacemos que nodo apunte al primer elemento de la pila, es decir a
primero.

Elemento desencolado

2. Asignamos NULL a primero, que es la dirección del segundo nodo teórico


de la cola: primero->siguiente.
3. Guardamos el contenido del nodo para devolverlo como retorno,
recuerda que la operación de lectura en colas implican también borrar.
4. Liberamos la memoria asignada al primer nodo, el que queremos
eliminar.
5. Hacemos que ultimo apunte a NULL, ya que la lectura ha dejado la cola
vacía.
Leer un elemento en una cola caso general
1. Hacemos que nodo apunte al primer elemento de la pila, es decir a
primero.
2. Asignamos a primero la dirección del segundo nodo de la pila: primero-
>siguiente.
3. Guardamos el contenido del nodo para devolverlo como retorno,
recuerda que la operación de lectura en colas implican también borrar.
4. Liberamos la memoria asignada al primer nodo, el que queremos
eliminar.
5. Si primero es NULL, hacemos que ultimo también apunte a NULL, ya que
la lectura ha dejado la cola vacía.
3.6 Ejemplo de cola en C
^

Construiremos una cola para almacenar números enteros. Haremos pruebas insertando
varios valores y leyéndolos alternativamente para comprobar el resultado.
Algoritmo de la función "Anadir"
1. Creamos un nodo para el valor que colocaremos en la cola.
2. Hacemos que nodo->siguiente apunte a NULL.
3. Si "ultimo" no es NULL, hacemos que ultimo->>siguiente apunte a nodo.
4. Actualizamos "ultimo" haciendo que apunte a nodo.
5. Si "primero" es NULL, hacemos que apunte a nodo.

void Anadir(pNodo *primero, pNodo *ultimo, int v) {


pNodo nuevo;

/* Crear un nodo nuevo */


nuevo = (pNodo)malloc(sizeof(tipoNodo));
nuevo->valor = v;
/* Este será el último nodo, no debe tener siguiente */
nuevo->siguiente = NULL;
/* Si la cola no estaba vacía, añadimos el nuevo a continuación de
ultimo */
if(*ultimo) (*ultimo)->siguiente = nuevo;
/* Ahora, el último elemento de la cola es el nuevo nodo */
*ultimo = nuevo;
/* Si primero es NULL, la cola estaba vacía, ahora primero apuntará
también al nuevo nodo */
if(!*primero) *primero = nuevo;
}

Algoritmo de la función "leer"


Algoritmo de la función "leer"
1. Hacemos que nodo apunte al primer elemento de la cola, es decir a
primero.
2. Asignamos a primero la dirección del segundo nodo de la cola: primero-
>siguiente.
3. Guardamos el contenido del nodo para devolverlo como retorno,
recuerda que la operación de lectura equivale a leer y borrar.
4. Liberamos la memoria asignada al primer nodo, el que queremos
eliminar.
5. Si primero es NULL, haremos que último también apunte a NULL, ya que
la cola habrá quedado vacía.

int Leer(pNodo *primero, pNodo *ultimo) {


pNodo nodo; /* variable auxiliar para manipular nodo */
int v; /* variable auxiliar para retorno */

/* Nodo apunta al primer elemento de la pila */


nodo = *primero;
if(!nodo) return 0; /* Si no hay nodos en la pila retornamos 0 */
/* Asignamos a primero la dirección del segundo nodo */
*primero = nodo->siguiente;
/* Guardamos el valor de retorno */
v = nodo->valor;
/* Borrar el nodo */
free(nodo);
/* Si la cola quedó vacía, ultimo debe ser NULL también*/
if(!*primero) *ultimo = NULL;
return v;
}

Código del ejemplo completo


Tan sólo nos queda escribir una pequeña prueba para verificar el funcionamiento de las
colas:

#include <stdio.h>

typedef struct _nodo {


int valor;
struct _nodo *siguiente;
} tipoNodo;

typedef tipoNodo *pNodo;

/* Funciones con colas: */


void Anadir(pNodo *primero, pNodo *ultimo, int v);
int Leer(pNodo *primero, pNodo *ultimo);

int main() {
pNodo primero = NULL, ultimo = NULL;

Anadir(&primero, &ultimo, 20);


printf("Añadir(20)\n");

C++ Estándar
Programación con el Estándar ISO y la Biblioteca de Plantillas (STL)
© Paraninfo Thomson Learning 2001

Ejercicios resueltos por capítulos.


Aquí encontrará los ejercicios resueltos por capítulos
(También puede descargarse un fichero comprimido con todos los fuentes de los ejercicios)
PARTE I
1. Introducción
No tiene ejercicios.

2. Conceptos básicos
2.1) Quedaría del siguiente modo:

hay otros tipos de comentarios como los de C++:


empezar un comentario tipo C y ahora lo acabo. */
/ * Que comentario más precioso * /
/ / Este es más precioso todavía.

main(), el programa no se podría enlazar. Si ponemos


2.2) Si en un programa no ponemos la función
dos funciones main() ocurre lo mismo, el compilador da un error de redefinición de una función.
2.3) El programa puede parecer a primera vista muy sencillo. En primer lugar vamos a leer y escribir una
cadena. La primera solución intuitiva:

#include <iostream> // USAMOS: cin, cout

void main() {

char s[20]; // Cadena de hasta 19 caracteres


cin >> s; // Leer la primera palabra
cout << endl << s // Escribir en nueva línea la cadena
<< endl; // Y pasar a la línea siguiente
}

El problema es que esto únicamente nos lee la primera palabra de una cadena (esto se explicará en el capítulo
de entrada/salida). Aunque no se comprenda de momento, la solución se encuentra en el fichero EJ02_03.CPP

2.4) Aquí está el programa:

#include <iostream> // USAMOS: cin, cout

void main() {
double d1, d2;
out << "Introduce dos reales: ";
cin >> d1 >> d2;
cout << "La suma es: " << d1 + d2 << endl
}

2.5) El programa correcto es éste:

#include <iostream.h> // USAMOS: cout


void main() {
cout << "Hola mundo";
}

2.6) Sí es correcta.

2.7) Este comentario es erróneo. Hemos dicho que los comentarios no se detectan en las cadenas. Pues no es
completamente cierto. No se detecta su apertura pero sí su clausura. Por ello, las sentencias se convertirían en:

";
*/

La solución sería definir de nuevo las sentencias como:

/*
cout << "/* Me pillaste *""/"; // Concatenación de cadenas
*/

3. Tipos de datos
3.1) La función es:

int Convierte(char c) {
return int(c - '0');
}
// Usando int() en vez de (int) se ahorra un par de paréntesis

3.2) Sí que es válido ya que en C++ todos los tipos integrales son compatibles. Aunque sería mucho mejor
explicitar las conversiones:

b= (byte)w;
w= (word)l;
d= (dword)w;

3.3) Su longitud es 9 tomando sizeof(int) == 2.


3.4) La primera dará error ya que 8 no es un dígito octal y por tanto 08 es un error. La segunda dará 24 porque
014 está en octal que es 12 en decimal.
4. Control de Flujo
4.1) El listado es funcionalmente correcto pero sintácticamente no. Faltan los puntos y comas de las cuatro
sentencias de asignación.

if (a < b)
if (a < c)
min= a;
else
min= c;
else
if (b > c)
min= c;
else
min= b;

4.2) Programa que cuenta el número de ocurrencias en una cadena de las 5 vocales en 5 variables diferentes:
a, e, i, o, u. Usaremos la función Leer_Cadena() del ejercicio 2.3. El programa está en EJ04_02.CPP

4.3) Una función que calcule el M.C.D. de dos números:

int Mcd(int a, int b) {


if (a <= 0 || b <= 0)
return -1; // Código de error
while (a != b)
if (a < b)
b= b - a;
// b-= a; // Igual que la anterior. Ya se verá
else
a= a - b;
// a-= b; // Igual que la anterior
return a; // Al final el mcd está en a y en b (a == b)
}

Un ejemplo de uso de la función está en EJ04_03.CPP

4.4) Función que compara dos cadenas:

int StrCmp(char *s1, char *s2) {


int i= 0;
while (s1[i] || s2[i]) { // Hasta terminar las dos
if (s1[i] < s2[i])
return -1; // La cadena 1 es menor que la 2
else if (s1[i] > s2[i])
return 1; // La cadena 1 es mayor que la 2
i++;
}
return 0; // Las cadenas son iguales
}

Esta función es similar a strcmp() de la librería estándar <cstring> del C++.


Un ejemplo de uso de la función está en EJ04_04.CPP

4.5) Sólo la b) es correcta. Recordemos que la sentencia:

for(a; b; c);

se convierte en:

{
a;
while(b) {
d;
c;
}
}

Por tanto, las sentencias anteriores son:

a) int i= 0, int j= 0; // Incorrecto


// Dos declaraciones separadas por una coma
while ..

b) int i= 0, j= 0; // Correcto
// Dos declaraciones de tipo entero
while ..

c) int i= 0, long j= 0; // Incorrecto


while ..

d) (int i = 0), (long j = 0) // Incorrecto


// Lástima porque era una buena idea

4.6) La solución se encuentra en el fichero EJ04_06.CPP

4.7) En este ejercicio se ha pretendido aumentar la atención del lector en este error común y sutil pero difícil
de detectar. La condición del bucle está formada por el operador de asignación (=) y no el operador de
comparación (==), con lo que el resultado del programa es que sólo muestra un 0, ya que el resultado de la
asignación i=10, además de asignar 10 a la variable i, es que devuelve el valor 10, que es un valor cierto, al
ser no nulo. Si después lo negamos con el operador ! obtenemos falso, con lo que el bucle sale después de la
primera iteración.
4.8) La solución se encuentra en el fichero EJ04_08.CPP

4.9) La solución se encuentra en el fichero EJ04_09.CPP

4.10) La solución se encuentra en el fichero EJ04_10.CPP

4.11) La solución se encuentra en el fichero EJ04_11.CPP

5. Operadores
5.1) Es simple:

x & (x - 1)

5.3) Se supone que tenemos dos valores enteros almacenados en dos variables reales. Yo lo haría así:

float Resto(float a, float b) {


return float((long)a % (long)b);
}

5.4) NO ES VÁLIDO PORQUE EL OPERADOR COMA NO SE PUEDE UTILIZAR EN ESE PARTE DEL
FOR. Si cogemos uno de los dos incementos y lo ponemos al final del bucle sí que funciona. En este caso
invierte el vector de caracteres s (no es una cadena porque no acaba en '\0'). El resultado en s será
ACABATOR.

5.5) Con algo parecido a esto sería suficiente para que pareciera aleatorio. Si además hacemos coincidir la
llamada a Rand() con un factor externo (tiempo, preferiblemente), esta función es casi impredecible. El
programa se encuentra en EJ05_05.CPP

6. Funciones
6.1) La solución se encuentra en el fichero EJ06_01.CPP

6.2) La solución se encuentra en el fichero EJ06_02.CPP

6.3) Es sintácticamente correcto. El compilador crea variables temporales para almacenar estas constantes y
así ya puede tomar la dirección. De todas formas no es un buen estilo de programación pasar constantes por
referencia porque aunque la función modifique su valor no nos podemos dar cuenta.

6.4) La llamada f(25) es ambigua. El compilador no sabe si llamar a la función con un argumento o
llamar a la segunda usando parámetros por defecto. La llamada f(17, 42) es completamente
correcta ya que no hay ambigüedad.

6.5) Sí que es correcto y sería equivalente a:

void f(int a= 1, int b= 2, int c= 3) {


// ..
}
6.6) La solución se encuentra en el fichero EJ06_06.CPP

7. Variables
7.1) Las variables estáticas se inicializan a 0. Las variables automáticas no. Por tanto a valdrá 0 y b tendrá un
valor indefinido dependiendo del compilador. No se recomienda usar la declaración de 'a' de ese modo.
Es mejor explicitar:

int a= 0;

7.2) Este sería un programa que volvería loco al propio Bjarne Stroustrup:

void Funcion(float f); // Decl1. Campo prototipo


float f;
// Decl 2. Campo global. Se almacena en el seg. de datos. f
vale 0
void Funcion(float f) {
// Decl. 2. Campo local automático. Se almacena en pila
float f;
// Error: parámetros y var. locales tienen el mismo campo

auto float a; // Este auto es opcional


// Decl.3.Campo local automático. Se almacena en pila. a vale
?

static float f;
// Error: mismo campo.

static float s;
// Decl.4.Campo local estático. Se almac. en el s. de datos.
s vale 0
{
float f;
// Decl. 5. Campo de bloque. Se almacena en la pila
f= 2; // Accedo a la 'f' de la decl. 5
::f= 3; // Accedo a la 'f' de la decl. 1
s= 4; // Accedo a la 's' de la decl. 4
a= 5.5; // Accedo a la 'a' de la decl. 3
// No hay forma de acceder al parámetro 'f' de la función
(Decl. 2)
}
}

float f; // Error! Redefinimos la variable global.


7.3) Como hemos visto en el caso anterior, no es correcto ya que los dos tienen el mismo campo local
automático.

7.4) Dará un error en la definición const int a ya que las constantes se deben inicializar en el
momento de la definición. Las otras dos también darían error.

7.5) No sería equivalente a:

const char * Var;

sino a:

char * const Var;

porque typedef no es una macro.


7.6) El programa A funciona correctamente. El programa B da error porque no sabemos cómo es la estructura,
por tanto, no podemos definir una variable de ella. El programa C funcionaría si no se tratara de una
estructura. Ya se vio que extern sólo es aplicable a variables de tipos no compuestos.

7.7) Este sería un programa que volvería loco al propio Bjarne Stroustrup:

void Funcion(float f); // Decl1. Campo prototipo

float f;
// Decl 2. Campo global. Se almacena en el seg. de datos. f
vale 0

void Funcion(float f) {
// Decl. 2. Campo local automático. Se almacena en pila
float f;
// Error: parámetros y var. locales tienen el mismo campo
auto float a; // Este auto es opcional
// Decl.3.Campo local automático. Se almacena en pila. a
vale ?
static float f;
// Error: mismo campo.
static float s;
// Decl.4.Campo local estático.Se almac. en el s. de
datos. s vale 0
{
float f;
// Decl. 5. Campo de bloque. Se almacena en la pila
f= 2; // Accedo a la 'f' de la decl. 5
::f= 3; // Accedo a la 'f' de la decl. 1
s= 4; // Accedo a la 's' de la decl. 4
a= 5.5; // Accedo a la 'a' de la decl. 3
// No hay forma de acceder al parámetro 'f' de la función
(Decl. 2)
}
}

float f; // Error! Redefinimos la variable global.

8. Sobrecarga y conversiones
8.1) En C++, las constantes tienen tipo por lo que el compilador asignará:

- la primera es un int . Coincidencia exacta con Print(int ).

- la segunda es un double. No hay coincidencia exacta ni trivial. No hay promoción. Hay conversión
estándar. Pero las conversiones estándar de un tipo aritmético puede ser a cualquier otro tipo aritmético. Por
tanto, fallará porque hay una ambigüedad. Podríamos haberlo solventado poniendo 2.2F.

char. No hay coincidencia exacta ni trivial. Pero hay promoción con int; por tanto,
- la tercera es un
se llama a Print(int ).
En general, las posibles soluciones a los problemas que aparecen (como el de la segunda llamada) son:

a) Declarar variables auxiliares del tipo que se desee.

b) Forzar que las constantes sean del tipo requerido.

c) Utilizar conversiones explícitas (cast)

8.2) Sí, no hay coincidencia exacta o trivial, no hay promociones, pero hay conversión estándar aritmética.
Por tanto, se llama sin ningún problema.

8.3) No porque tomará f() como float y no como una función. Concretamente, dará un error de
float como si
llamada a no-función ("call of non-function") ya que estamos intentando llamar a un
fuera una función.

8.4) La solución se encuentra en el fichero EJ08_04.CPP

8.5) La solución se encuentra en el fichero EJ08_05.CPP

8.6) Daría error al haber conversión trivial entre const T y T.


8.7) Como no hay conversión trivial, se podría definir perfectamente.

8.8) Las dos primeras llamadas invocan a sus funciones correspondientes sin ningún problema. La tercera
sigue estos pasos: Primero: no hay coincidencia exacta. Segundo: no hay promoción. Tercero: conversión
estándar, pero la hay a los dos, no le damos preferencia a la que no tiene signo. Por tanto daría error de
ambigüedad.
8.9) La solución se encuentra en el fichero EJ08_09.CPP

8.10) La solución se encuentra en el fichero EJ08_10.CPP

8.11) Son correctas. Se trata de conversiones de línea.

8.12) En C++, typedef no crea tipos nuevos distintos, sólo les da un nombre diferente.
8.13) Para las cinco llamadas, el proceso es bien diferente:

1.- El literal 0.0 es un double. Pasos: Primero: coincidencia exacta. Por tanto se
llama a f(double ).

0 es un int. Pasos: Primero: no hay coincidencia exacta. Segundo: no


2.- El literal
hay promoción posible. Tercero: hay conversión estándar de int a char y
de int a double. Además, dijimos que la constante 0 tiene conversión estándar
con cualquier puntero. Por tanto habrá error de ambigüedad al no poder elegir ninguna de
las tres funciones.

3.- El literal 0F da error de sintaxis, ya que F sólo se puede aplicar a constantes reales.

0.0F es un float. Pasos: Primero: no hay coincidencia exacta.


4.- El literal
Segundo: hay promoción de float a double. Por tanto se llama
a f(double ).

char *. Pasos: Primero: no hay coincidencia exacta.


5.- El literal cadena es un
Segundo: no hay promoción. Tercero: hay conversión estándar entre char
* y void *. Por tanto se llama a f(void *).

8.14) Para la primera combinación, la segunda llamda es correcta (mismo tipo), pero la primera no, porque no
hay conversión estándar desde int a enum. Como si está permitido lo contrario, la combinación dos
es perfectamente correcta. La combinación tercera también lo es, llamando cada una a su correspondiente
función.

8.15) El compilador da un error de ambigüedad, ya que no sabe si llamar a ff(fc) sin signo
o ff(fc) con signo. ¡Qué complicados son los complicadores!

9. Punteros
9.1) El primero carga en p la dirección de la variable a (p= &a), pero al cerrarse el bloque la
variable a se destruye con lo que el acceso posterior de (*p= 10) puede ser catastrófico.
El segundo programa, en cambio, funciona correctamente ya que el carácter a tratar se almacena en el 'heap' y
no en la pila, así al cerrar el bloque no destruimos ninguna variable ya que no hemos definido ninguna
tampoco. El acceso (*p= 10) será válido hasta que pongamos (delete p;).
Una mejor solución sería:

void main() {
char *p;
int a;
{
p= &a;
}
*p= 10;
}

9.2) Invierte una cadena. La solución se encuentra en el fichero EJ09_02.CPP

9.3) La solución se encuentra en el fichero EJ09_03.CPP

9.4) La solución se encuentra en el fichero EJ09_04.CPP

9.5) La solución se encuentra en el fichero EJ09_05.CPP

9.6) Ese programa es muy peligroso. Leemos una cadena en s, pero s apunta a una dirección indefinida;
por ello, podemos estar estropeando código, datos de nuestro o de otro programa. Además no se puede
asegurar que la salida sea igual que la entrada. En fin, que este es uno de los errores más graves y típicos del
C++. Además, puede que en un primer momento funcione. Más tarde el error aparecerá inesperadamente de
forma catastrófica. La solución es reservar la memoria que vamos a usar:

#include <iostream.h>

void main() {
char s[100];
// Suponemos que con 100 caracteres es suficiente
cin >> s;
cout << s;
}

También podríamos haber usado:

#include <iostream.h>

void main() {
char *s;
s= new int[100];
cin >> s;
cout << s;
delete []s;
}
9.7) No ocurre nada, al final del programa el compilador se encarga de hacer todos los delete que falten. De
todas formas, es muy recomendable no olvidarse de ponerlo porque si es en una función que se llama 1000
veces acabaremos con el 'heap' lleno!. Tampoco es muy recomendable hacer lo que se ha hecho en el
ejercicio 1, pero a veces como en ese ejercicio, es necesario.

9.8) Para hacer lo que se nos pide en el ejercicio habría que hacer uso de punteros:

float f;
int *pi= (int *)&f;
char *pc= (char *)&f;

Y con f, *pi, *pc accederíamos a lo mismo que con la unión: f, i, c. Claramente, usar una
unión anónima es más limpio aunque con punteros se ve físicamente que comparten la misma memoria. En
este caso, trabajar con punteros puede ser peligroso, ya que si tenemos:

char c;
int *pi= (int *)&c;
float *pf= (float *)&c;

un acceso a (*pi) a (*pf) excedería del tamaño del carácter, estropeando lo que hay después en
memoria, que en este caso es el puntero que accede. Aquí, se puede decir, que está casi asegurado que el
sistema se quede bloqueado o lo que en el argot se conoce como "colgado".

9.9) El programa compara los punteros, no donde apuntan. Si lo sustituyéramos por(*s == *t)
tampoco ya que sólo compararía el primer elemento. Queda como ejercicio hacer una función que compare
cadenas. En el siguiente capítulo también se verán algunas funciones de comparación.

9.10) No es correcto porque hemos definido p como un puntero a enteros constantes sobre los cuales nos
podemos hacer un delete. Además, delete p sólo borraría el primer elemento, en el caso de
que no fuera const.
9.11) Los dos son, obviamente, equivalentes y ninguno de ellos da error. El puntero retornado en p es
indefinido y la dirección a la que apunte no está reservada. No retorna NULL como podríamos imaginar en
un principio, del mismo modo que delete no modifica el puntero, sino simplemente libera la memoria.
9.12) Intentar borrar sólo una parte del vector reservado es una barbaridad, no porque sea ilógico pensarlo,
sino porque el C++ no lo detecta como error y dependiendo de la implementación, puede ser que no ocurra
nada o se convierta en un desastre. Lo único que sabemos con seguridad es que si hacemos lo correcto, no
tendremos ningún problema.

10. Eficiencia y Optimización


10.1) La solución se encuentra en el fichero EJ10_01.CPP

10.2) La solución se encuentra en el fichero EJ10_02.CPP

10.3) Tenemos un tipo reloj y tres funciones:


reloj Start_Timer();
// Crea un reloj y lo pone en marcha
double Get_Timer(reloj &);
// Retorna el tiempo en segundos desde que se creo este reloj
double Stop_Timer(reloj &);
// Igual que Get_Timer() pero además destruye el reloj

Además se ha implementado una función de retardo Delay(unsigned long ) que tarda


tantos milisegundos como se le pasen en su parámetro. Además tenemos una
función Calibrar(int ) para calibrar durante unos segundos la función Delay(). El
listado de la implementación es EJ10_03.CPP

10.4) Usando las funciones necesarias del ejercicio anterior veamos EJ10_04.CPP. En muchas máquinas
saldrá Fact3() la más rápida y Fact2() la más lenta, completamente al contrario de lo que
podríamos pensar en un principio. Esto depende de cómo estén orientados los procesadores, si tienen
antememorias (cachés), si son máquinas RISC o CISC, etc.

10.5) Se prueba en el siguiente ejercicio.

10.6) La solución se encuentra en el fichero EJ10_06.CPP

10.7) La función al ser inline haría que cualquier aparición de alias(i) fuera equivalente en
sentido y eficiencia a i.
10.8) La solución se encuentra en el fichero EJ10_08.CPP

10.9) Una forma de implementar el algoritmo quicksort() está en EJ10_09.CPP

10.10) La multiplicación por potencias de dos se puede realizar por medio de desplazamientos de bit.
Ejemplos para 2, 4 y 8 serían:

inline
int Mult2(int a) {
return a << 1;
}

inline
int Mult4(int a) {
return a << 2;
}

inline
int Mult8(int a) {
return a << 3
}
que por las pruebas que se han realizado son ligeramente más rápidas que la multiplicación normal. Esto
depende mucho de la máquina.
Las funciones para la divisiones son similares pero utilizando el operador >>.

10.11) Se trataría de lo siguiente:

inline
int Mult3(int a) {
return Mult2(a) + a;
}

inline
int Mult5(int a) {
return Mult4(a) + a;
}

inline
int Mult6(int a) {
return Mult4(a) + Mult2(a);
}

inline
int Mult7(int a) {
return Mult(6) + a;
}

inline
int Mult9(int a) {
return Mult8(a) + a;
}

Según vamos aumentando iremos perdiendo en eficiencia. La reutilización de unas funciones en otras no
ralentiza ya que son inline. En general:

int Mult(int a, int b) {


int r= 0;
while (b) {
if ((b | 1) == b) // bit es 1
r+= a;
b >>= 1;
a <<= 1;
}
return r;
}
que ya no es eficiente.
Esta solución y medidas de tiempo se encuentran en el fichero EJ10_11.CPP

10.12) Es correcto ya que en los macros los comentarios no son expandidos. Esto se verá mejor cuando se vea
preprocesamiento.

10.13) El algoritmo se encuentra en EJ10_13.CPP.

10.14) La solución se encuentra en el fichero EJ10_14.CPP

PARTE II
11. Clases
11.1) Al hacer delete this estamos liberando la memoria que ocupa el objeto actual por lo que el
siguiente this= Ultimo ya no es válido porque el Ultimo puede haber perdido su valor.
Para la gente que empieza puede quedar más claro poniendo:

delete this;
this= this->Ultimo;

que es lo mismo que antes pero ahora se ve que el puntero this al que hemos hecho un delete, lo
utilizamos como fuente en la siguiente sentencia. Por ello, se suele utilizar el puntero this para acceder a los
atributos de una clase cuando queda comprometida la claridad.

En segundo lugar como this es un puntero constante ni se puede hacer un delete sobre él ni se puede
poner como destino en una asignación.

11.2) El programa es completamente correcto. El primer objeto Obj1 llama al Pon de la


clase c1 con el parámetro 3. Por tanto Obj1.Valor se pone a 3. El segundo objeto Obj2 llama
al Pon de la clase c2 sin parámetros por lo que se toma el parámetro 1 por defecto que es lo que se
almacena en Obj2.Valor. Al hacer Obj3.Valor= 10 no modificamos ningún otro
objeto ni de c1 y mucho menos de c2 que no tiene nada que ver.

11.3) Las dos son inline.


11.5) La solución se encuentra en el fichero EJ11_05.CPP

11.6) La solución se encuentra en el fichero EJ11_06.CPP

11.7) La solución se encuentra en el fichero EJ11_07.CPP

11.9) En primer lugar no funcionaría porque hemos definido los métodos privados. Solventando este
problema no funcionaría tampoco porque cuando se llama a una función inline debe tener su implementación
ya definida. En este caso la solución sería cambiar de orden f1() y f2().
class clase {
..
public:
void f1();
void f2();
..
};

inline void clase::f2() {


..
}

void clase::f1() {
..
f2();
..
}

void main() {
clase o;
o.f1();
}

Una curiosa solución es poner las dos funciones inline, así las funciones no son evaluadas hasta que se
expanden, que en este caso ocurrirá cuando lleguemos a main(), pasadas ya las definiciones
de f1() y f2(). Otra solución, evidentemente, es no definir ninguna inline.
11.10) La solución se encuentra en el fichero EJ11_10.CPP

11.11) Si hacemos la implementación de los complejos en forma polar, no quita para que definamos
exactamente los mismos métodos, incluso los constructores. Por tanto, si sólo viésemos las declaraciones de
los métodos, no podemos saber si están implementados en forma rectangular o en forma polar.

11.12) La primera sentencia modifica el parámetro c1. La segunda modifica el miembro c2. La tercera
modifica la variable global c3. La cuarta sentencia, modifica el miembro c1 al usar this. La quinta
sentencia también al utilizar el operador de campo de clase.

11.13) No se puede. Deberemos hacer:

class clase {
static int Estatuto;
..
};
int clase::Estatuto= 23;

o definir un método estático para acceder a Estatuto. De todas formas la sentencia:


int clase::Estatuto;

se debe seguir poniendo.

11.14) Perfectamente. Aunque no tiene mucho sentido.

11.15) No podemos acceder a a porque f() es una función estática y por tanto no tenemos el parámetro
implícito this para poder acceder a los miembros.
11.16) La solución se encuentra en el fichero EJ11_16.CPP

12. Creación de objetos


12.1) Los métodos son:

a) constructor normal
b) constructor por defecto
c) exactamente igual a lo anterior
d) constructor copia
e) constructor por defecto (todos los argumentos por defecto) y
constructor de conversión de (int *) a c1
f) constructor de conversión de float a cl si se toma el último
argumento por defecto, si no, constructor normal
g) operador suma
h) operador de conversión de cl a int. No se pone retorno
i) operador de asignación
j) destructor
k) Error! Los destructores no tienen parámetros

punto(c.RE(), c.IM()) crea un objeto temporal en el cuerpo


12.2) No es correcta ya que
de la función punto(complejo c) y no modifica 'x' e 'y'. La solución sería:

class punto {
private:
double x, y;
public:
void Pon(double xx, double yy) {
x= xx; y= yy;
}
punto(double xx, double yy) {
Pon(xx, yy);
}
punto(const complejo & c) {
Pon(c.RE(), c.IM());
}
};

12.3) Tiene dos problemas, el primero es que como no hemos puesto ningún modificador de acceso y se trata
de 'class', el método f() será privado con lo que no lo podremos llamar. En segundo lugar, no
podemos llamar a funciones no constantes desde objetos constantes. En este caso, esta última restricción es
una garantía de seguridad de que el objeto si es constante no va a ser modificado.

12.4) Dependerá del orden en que están definidos dentro de una clase. Lo único que sabemos con seguridad es
que x pasará a valer a antes de que y pase a valer b. Esto es debido a que los dos se construyen en la
lista de inicialización, como'x' si que está en la lista se construye y toma su valor a la vez mientras
que 'y' tiene que esperar a valer 'b' al cuerpo de la función.
12.5) Para la primera no, ya que al haber otro constructor, ya no está definido el constructor por defecto. En la
segunda sí será posible (en la versión 2.0 no). Las dos últimas son perfectamente válidas.

12.6) Se creará un objeto temporal por lo que el objeto constante no puede ser modificado por mucha
referencia de que se trate.

12.8) Ver EJ12_08.CPP

12.10) Sí se pueden definir miembros (atributos y métodos) volatile.

12.11) El operador sizeof no puede sobrecargarse.


12.12) En primer lugar, falta el punto y coma final de la clase. En segundo lugar, nunca deberemos hacer una
asignación a this y mucho menos un delete.

12.13) Es correcto aunque es más recomendable definir la unión dentro de una clase.

12.14) Genera ambigüedad. No sabemos si llamar a

f(A(Objeto));

o a:

f(Objeto.operator A ());
12.15) No funcionaría porque en C++ no se buscan conversiones multinivel. Y no hay ninguna conversión en
un solo paso para hacer coincidir los parámetros.

12.16) Al llamarse a la función Nada() que parece que no hace nada, se crea un objeto temporal usando el
constructor copia. El constructor copia que tenemos definido sólo copia los atributos. Al llegar al final del
cuerpo de la función (en seguida porque no hace nada), se retornaría llamando al destructor del objeto
temporal, que de la forma que tenemos definida la cadena haría un delete s; liberando la memoria.
Cuando hiciéramos cout << c1; probablemente salga la cadena por pantalla, pero no se puede
asegurar, ya que hemos liberado la memoria que ocupa y puede ser utilizada por cualquier otro. Lo peor no es
esto, sino que al llegar al final de la función se destruiría c1 volviendo a llamar a delete s que ya
está borrado. Esto lo suele avisar el compilador por medio de un error al final del programa del tipo "Null
pointer assignment"

12.17) La solución se encuentra en el fichero EJ12_17.CPP

13. Herencia y Polimorfismo


13.1) La asignación e) es incorrecta ya que no podemos asignar un clase derivada con una clase base. La
asignación f) nos muestra que esto es imposible incluso utilizando casts. La asignación h) es incorrecta por el
mismo motivo que la e). Pero la i) es correcta porque siempre podemos pasar de un puntero de un tipo a un
puntero de otro tipo utilizando casts.

13.2) No, no tiene sentido heredar dos veces ya se virtual o no virtual. Si se quiere incluir dos veces una clase
se hace precisamente eso, incluir (composición).

c1 que es un puntero
13.3) La primera crea un objeto dinámico de la clase cuadrado y toma su dirección en
a cuadrilatero. La segunda sentencia es incorrecta ya que no se puede asignar un puntero a un
cuadrilatero a un puntero a un cuadrado. En el segundo lugar podríamos usar un cast pero si los
métodos no son virtuales puede ser peligroso.

cuadrado *c2= (cuadrado *)new cuadrilatero;

13.4) La longitud es 4 + 4 + 4 = 12 suponiendo 4 la longitud de int, 4 la longitud de float y 4 la longitud del


puntero a la tabla de métodos virtuales (suponiendo punteros de 32 bits).

13.5) Las funciones f() darán error ya que tienen los mismos parámetros y distinto tipo de retorno. En
cambio las funciones g() son funciones totalmente diferentes ya que tienen parámetros distintos. Por tanto
B heredará g(int, double) y tendrá además g(double, int).

13.6) Son los dos virtuales ya que si definimos un destructor como virtual en una clase base, los destructores
en las clases heredadas también serán virtuales. En estos casos se recomienda poner la palabra virtual para
dejarlo más claro. Se deja como ejercicio averiguar si teniendo dos clases A y B, una con destructor virtual y
la otra normal, si heredamos las dos en una clase C, ¿el destructor de C será virtual?

13.7) La solución se encuentra en el fichero EJ13_07.CPP

14. Plantillas
14.1) Sí que podemos compilar ese programa, pero en el momento que usemos la función f() dará error.
La solución es simplemente borrar la primera declaración ya que la segunda la incluye.

14.2) Porque el tipo A no está incluido en los parámetros de la función. Ya sabemos que el retorno no cuenta.

14.3) Ver EJ14_03.CPP. Se han definido algunos métodos internos y otros externos para mostrar el acceso a
vector. Sería preferible todos externos.

14.4) Que no se puede hacer coincidir (C *) con (int). Si hubiera sido (C) no habría problema, C
valdría int.
14.5) La solución vale para cualquier tipo:

template <class T>


int SizeOf_EnBits(T v) {
return sizeof(T) * 8;
//return sizeof v * 8; // También válido
}

12.6) No, pero la solución es simple:

typedef clase1<int> clase1_int;


clase2 <clase1_int> a;

15. Errores y Excepciones


16. Modularidad
16.1) Porque ya vienen provistas del método de protección contra redefiniciones que hemos explicado.

16.2) Funcionaría muy mal ya que no hemos definido el constructor copia y el operador de asignación. Al
tratarse de una estructura dinámica, cada vez que llamemos implícitamente al constructor copia (por ejemplo
con objetos temporales), deberíamos copiar toda la estructura y sólo copiamos la dirección. Pero cuando
destruimos los objetos temporales, sí que destruimos toda la estructura. En resumidas cuentas, que vamos a
destruir más veces que a construir.

16.3) Aquí viene la solución al ejercicio anterior. Se compone de tres


ficheros EJ16_03.H, EJ16_03.H1, EJ16_03.H2. Además tenemos un
fichero EJ16_03.CPP que nos lo prueba todo.
El fichero EJ16_03.H como vemos, queda limpio de toda implementación. En primer lugar incluye
EJ16_03.H1 en la parte privada de la clase y después incluye fuera a EJ16_03.H2. Como se observa
lo único que ve el usuario son los métodos de la lista. No se puede saber si está implementada dinámica o
estáticamente, no se sabe nada de sus miembros privados, ni estáticos, sólo lo imprescindible que debe
conocer el que usa esta clase. La parte privada de la clase (los atributos) está en el fichero EJ16_03.H1.

Aquí se definen los miembros privados. Es de resaltar la presencia interna de nodo. Se podría haber
definido como clase amiga de clista, pero como en este caso sólo la utilizamos aquí, la incluimos
dentro del campo de la función. Pasemos ahora a la implementación de los métodos. Están en el
fichero EJ16_03.H2.

El último método (Alias) se suele incluir para hacer referencias. Esto sirve para que tengamos varias listas
operando sobre los mismos datos. Esto suele ser peligroso por los objetos temporales y porque la destrucción
de uno implica que se ha liberado el espacio al que apuntan todos. Por eso no lo vamos a usar. Por último, hay
un fichero que lo prueba todo; este fichero sólo debe incluir la especificación. Es el
fichero EJ16_03.CPP.

COMENTARIOS: Los operadores de postincremento y postdecremento retornan por valor. Así, operaciones
como la siguiente, estarían permitidas pero no funcionarían de manera correcta:
++l1++;

Sólo incrementaría una vez l1, el otro operador actuaría sobre un objeto temporal retornado por el primero.

En resumen, este artificio de estructura modular es un poco largo de realizar y difícil de entender para el que
lo desarrolla. Pero nadie puede dudar que el fichero EJ16_03.H está claro como el agua.

16.5) Sí que compilaría y enlazaría. Al tener los dos el atributo const tienen acceso privado al módulo,
por lo que no son visibles externamente y no habría conflicto entre ellas. De todas formas, sería mucho más
conveniente, poner una sola declaración en una cabecera e incluirla en los dos módulos.

PARTE III
17. Introducción a las Librerías Estándar
No tiene ejercicios.

18. Entrada y salida


18.1) La solución se encuentra en el fichero EJ18_01.CPP

18.2) En cada caso saldría:

a) 00103
b) 10 // Todavía no había hecho efecto
c) a

18.3) El programa debe incluir algunos manipuladores y flags para que no se ignoren los caracteres blancos.

#include <iostream.h>
#include <iomanip.h>

void main() {
cin >> resetiosflags(ios::skipws);
while (1) {
char c;
cin >> c;
if (!cin)
break;
cout << c;
}
}
También se podía haber hecho así:

while (1) {
char c;
cin.get(c);
if (cin.eof())
break; // Fin copia
cout.put(c);
}

que no utiliza manipuladores.

18.4) No ya que el setw(100) actúa sobre un stream (cin) y el setw(5) actúa sobre otro
(cout). Además, la salida sería:

00069

18.5) No ya que ya está sobrecargado en <iostream.h> en las


clases istream y ostream.
18.6) Tenemos aquí un programa que produce un volcado hexadecimal de un fichero de entrada a otro de
salida. Si se omiten estos ficheros se cogerán por defecto la entrada y salida por pantalla estándar.
Ver EJ18_06.CPP

18.7) La solución es:

int i= 42;
const char *fn= "test.dat"
const int Largo = 7;
{
fstream f(fn, ios::out | ios::binary);
f.seekp(Largo, ios::beg);
f.write((const char *)&i, 2);
} // Al destruirse se cierra
{
fstream f(fn, ios::in | ios::binary);
f.seekg(Largo, ios::beg);
f.read((char *)&i, 2);
} // Al destruirse se cierra

También podíamos haber llamado a los destructores explícitamente. Ya que estamos con la programación
orientada a objetos, es más lógico utilizar constructores y destructores. Principalmente, lo que no hay que
hacer es mezclar los dos métodos.

18.8) Simplemente hay que saber el código del descriptor de la impresora (que suele ser 4). Utilizamos
entonces el constructor ofstream(int fh):
fstream Stream_de_la_impresora(4);

Si queremos utilizar buffer:

char *Buffer= new char [1024];


ofstream Str_de_la_impr(4, Buffer, 1024);

18.9) La función Leer_Cadena() la definimos así:


#include <iomanip.h> // resetiosflags

void Leer_Cadena(char *s) {


cin >> resetiosflags(ios::skipws);
// No pasar los caracteres blancos
for(int i= 0; cin >> s[i]; i++) // Leer hasta '\0'
if (s[i] == '\n') // Se ha pulsado INTRO
break;
s[i]= '\0'; // Poner caracter nulo de terminación de cadena
}

En primer lugar hacemos que no se quiten los caracteres blancos. Luego leemos hasta encontrar uno de estos
dos caracteres '\0', '\n'. Éste último se producirá cuando pulsemos la tecla de retorno. Al final deberemos
poner el carácter nulo ya que el stream no lo inserta.

19. Cadenas y Numéricos


19.1) La solución se encuentra en el fichero EJ19_01.CPP

19.2) La solución se encuentra en el fichero EJ19_02.CPP

19.4) La solución se encuentra en el fichero EJ19_04.CPP

19.5) La solución se encuentra en el fichero EJ19_05.CPP

20. Introducción a la STL


20.1-2) La solución se encuentra en el fichero EJ20_01.CPP

20.3) La solución se encuentra en los ficheros EJ20_03a.CPP, EJ20_03b.CPP y EJ20_03c.CPP.

20.4) La solución se encuentra en el fichero EJ20_04.CPP

20.5) La solución se encuentra en el fichero EJ20_05.CPP

21. Contenedores y adaptadores


21.1) La solución se encuentra en el fichero EJ21_01.CPP
21.2) La solución se encuentra en el fichero EJ21_02.CPP

21.3) La solución se encuentra en los ficheros EJ21_03a.CPP y EJ21_03b.CPP.

21.4) La solución se encuentra en los ficheros EJ21_04a.CPP y EJ21_04b.CPP.

21.5) La solución se encuentra en el fichero EJ21_05.CPP

21.6) La solución se encuentra en el fichero EJ21_06.CPP

21.7) La solución se encuentra en el fichero EJ21_07.CPP

21.8) La solución se encuentra en el fichero EJ21_08.CPP

21.9) La solución se encuentra en el fichero EJ21_09.CPP

22. Objetos-funciones y algoritmos


22.1) La solución se encuentra en el fichero EJ22_01.CPP

22.2) La solución se encuentra en el fichero EJ22_02.CPP

22.3) La solución se encuentra en el fichero EJ22_03.CPP

22.4) La solución se encuentra en el fichero EJ22_04.CPP

22.5) La solución se encuentra en el fichero EJ22_05.CPP

22.6) La solución se encuentra en el fichero EJ22_06.CPP

22.7) La solución se encuentra en el fichero EJ22_07.CPP

23. El Proceso de Desarrollo con C++

Bueno este estas son las operaciones basicas que se deben saber de Colas, las cuales son
ingresar datos, borrar dato e imprmir los datos.

Código:
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
void insertar_colas();
void imprimir_colas();
void eliminar_colas();

typedef struct nodoc


{
int dato_colas;//donde se guarda el telefono
int dura_colas;//donde se guarda el tiempo
struct nodoc *sgte;//puntero siguiente

}nodoc;

long int fono;


int tiempo;
nodoc *act_1,*fin,*inicio_1=NULL;

main()
{
int opcion;

do
{
system("color ");
system("CLS");
printf("\n\t\t\t\t***MENU***\n");
printf("\n\n Trabajo Colas\n");

printf(" ---Trabajar con COLAS---\n");


printf("\n 1.- Realizar llamada");
printf("\n 2.- Mostrar llamadas y su duracion:");
printf("\n 3.- SALIR");

printf("\n\n * Para Salir Presione 4: ");

printf("\n\n Ingrese una opcion: ");


scanf("%d",&opcion);
printf("\n");

if(opcion>3)
{
printf("\n Opcion NO VALIDA concentrese porfavor");
printf("\n\n ** PRESIONE CUALQUIER TECLA PARA VOLVER AL MENU **");
getch();
}

switch(opcion)
{
case 1:
insertar_colas();
break;

case 2:
imprimir_colas();
break;

case 3:
exit(0);
}
}
while(opcion!=0);
getch();
}

void insertar_colas()
{
printf("\n\n Ingrese numero: ");
scanf("%d",&fono);
printf("\n Ingrese duracion:");
scanf("%d",&tiempo);
act_1=(nodoc*)malloc(sizeof(nodoc));
act_1->dato_colas=fono;
act_1->dura_colas=tiempo;
act_1->sgte=NULL;

if(fin==NULL)
fin=inicio_1=act_1;
else
{
fin->sgte=act_1;
fin=act_1;
}
}

}
void imprimir_colas()
{
act_1=inicio_1;
while(act_1!=NULL)
{
printf(" La llamada %d duro : %d min\n",act_1->dato_colas,act_1-
>dura_colas);
act_1=act_1->sgte;
}
printf("\n\n ** PRESIONE CUALQUIER TECLA PARA VOLVER AL MENU **");
getch();
}

Saludos

_________________

Cambiando un poco el objetivo del sitio vamos a investigar un poco sobre las estructuras de datos y
algoritmos en C/C++. Para esto vamos a comenzar trabajando con estructuras de datos simples como listas y
colas, para luego pasar a estructuras como árboles, árboles binarios de búsqueda, AVLs, Hash y algoritmos
complejos.

Para poder seguir los ejemplos les recomiendo utilizar Cygwin si usan Windows para tener todas las
herramientas de compilado (g++, archivo make, librerias, etc). Si tienen Linux simplemente instalen los
paquetes devel.
Trabajaremos construyendo los tipos abstractos de datos (TADs) que nos permitirán abstraernos de la
estructura de punteros y trabajar con operaciones que se encargan de hacer el trabajo sucio =). Esto nos libera
de estar revisando detalles de la estructura para poder concentrarnos en como resolver problemas.

Comenzaremos trabajando con las dos estructuras más simples, listas y colas. En las dos estructuras de datos
encadenamos elementos en forma simple, la diferencia está en la forma en que una vez que ingresamos los
mismos, luego podemos sacarlos. En la lista insertamos en la cabeza y sacamos elementos desde la misma
cabeza, este orden es conocido como último en entrar, primero en salir (LIFO siglas en inglés). En el caso de
la cola utilizamos el otro orden, primero en entrar primero en salir (FIFO siglas en inglés).

Veamos la definición del tipo de datos para la lista:

typedef struct lista {


int valor;
lista * sig;
};

Con esta definición podemos ir enlazando los nodos de la lista, poniendo en el último el valor NULL en el
puntero a sig indicando que termina la lista.

El siguiente paso es definir la lista de operaciones con las cuales vamos a poder trabajar sobre este tipo de
datos. Definiremos las siguientes funciones:

lista * ListaCrearVacia();
// Crear una lista de nodos vacia.

bool ListaEsVacia(lista * l);


// Devuelve true si la lista es vacia y false en caso contrario.

void ListaAgregar (lista *& l, int n);


// Agrega el elemento n al comienzo de la lista.

int ListaPrimero(lista * l);


// Devuelve el primer elemento de la lista.

lista * ListaResto(lista * l);


// Devuelve un alias al resto de la lista.

void ListaDestruir(lista * l);


// Libera la memoria asociada a la lista.

Dadas estas operaciones podremos trabajar sobre el tipo de datos con comodidad, creando, insertando,
recorriendo y eliminando la estructura sin necesidad de preocuparnos por los punteros que la componen. Por
ejemplo si queremos recorrer una lista que tiene elementos podríamos hacer lo siguiente (debemos verificar
que no sea vacia primero):

while (!ListaEsVacia(listaDatos)) {
int valor = ListaPrimero(listaDatos);
printf(“\nDato: %d”,valor);
listaDatos= ListaResto(listaDatos);
}

Noten que deberíamos guardar una copia del puntero inicial de la lista para poder volver a recorrerla o bien
poder eliminar la memoria de la misma. Si no tenemos este puntero habremos perdido el comienzo de la lista
=P.
Entrando un poco en la construcción del TAD, en el caso de la lista tendremos que insertar el elemento al
principio de la misma, mientras que en la cola insertamos al final. Esto nos lleva a ver en el caso de la lista si
insertamos al comienzo podremos hacerlo rápidamente aunque la lista sea muy grande. Simplemente
modificaremos la cabeza de la lista y pondremos el nuevo dato como comienzo. Veamos un ejemplo:

void ListaAgregar (lista *& l, int n) {


lista* nuevoNodo = new lista;
nuevoNodo->valor = n;
nuevoNodo->sig = l;
l = nuevoNodo;
return;
}

Lo que hacemos es crear el nuevo nodo, asignar el valor de entero y luego encadenamos la lista “vieja” como
siguiente elemento del nuevo nodo. Esto nos deja la lista l con el nuevo nodo al comienzo.

La definición de las otras funciones es simple, veamos algunas:

lista * ListaCrearVacia() {
lista* l = NULL;
return l;
}

bool ListaEsVacia(lista * l) {
return l == NULL;
}

int ListaPrimero(lista * l) {
return l->valor;
}

lista * ListaResto(lista * l) {
return l->sig;
}

Pero pensemos por un momento en el caso de la cola, para insertar al final tendremos que ir hasta el final de la
misma, por lo cual en caso de que tengamos una gran cantidad de datos tendremos que recorrer muchos
nodos. Esto no es bueno, como veremos más adelante buscaremos que nuestras estructuras de datos sean
eficientes y sean “más lentas” cuantos más datos tengamos en las mismas. Si nuestra estructura se vuelve más
lenta a medida que tenemos más datos sufriremos consecuencias a largo plazo, nuestro programa será cada
vez más lento.

Para solucionar este problema lo que haremos será tener un puntero al comienzo y otro al final de la cola, lo
cual nos permitirá insertar al final de la misma y acceder al comienzo en forma directa, sin tener que recorrer
la misma.

typedef struct cola {


lista * inicio;
lista * fin;
};

Noten que utilizaremos la lista, pero con un nodo especial que nos permita acceder al comienzo y al fin de la
misma para remover e insertar respectivamente. Las funciones del TAD nos darán las operaciones que
podremos utilizar para trabajar sobre este tipo de datos sin preocuparnos de punteros y demás:

cola * ColaCrearVacia();
// Crea una cola vacia.
bool ColaEsVacia(cola * c);
// Devuelve true si la cola es vacia y false en caso contrario.

void ColaEncolar (cola * c, int n);


// Agrega el elemento n a la cola.

int ColaPrimero(cola * c);


// Devuelve el primer elemento de la cola.

void ColaDesencolar(cola * c);


// Elimina el primer elemento de la cola.

void ColaDestruir(cola * c);


// Libera la memoria asociada a la cola.

Para estas operaciones utilizaremos prácticamente el mismo código que para la lista, únicamente teniendo
cuidado de utilizar los “accesos directos” según el caso que corresponda, para evitar tener que recorrer toda la
cola.

En la próxima entrada veremos árboles binarios de búsqueda, los cuales permiten ordenar la información para
encontrar en forma más rapida la misma =)

2 Comments on “Estructuras de datos y algoritmos en


C/C++: Listas y Colas”

1.

1 Julito said at 12:59 am on julio 3rd, 2009:

Hola, muy xvr tu pag, pero

2.
2 marcela hernandez said at 9:56 am on octubre 26th, 2009:

hola m perdonaras pero esq necesito un favor….


bueno en fin esq tengo que hacer un inventario q utilice listas colas y pilas y pues necesito saber como pasar
un dato de una cola antiga a una nueva,,,,, me ayudarias… bueno hay va el codigo para ver q se puede
hacer… gracias
#include “iostream”
#include “stdlib.h”
#include “stdio.h”
#include “string”
#include “conio.h”
using namespace std;
int opc;
typedef struct _nodo{
int identificacion;
char nombre[40];
int cantidad;
int costo;
char fecha[40];
struct _nodo *siguiente;
} tipoNodo;
typedef tipoNodo *pNodo; /*prueba siguiente apunta a Null*/
typedef tipoNodo *Lista;
Lista lista=NULL; /*Lista a punta a prueba*/
pNodo p;
//funciones
int listavacia(Lista l);
void Insertar(Lista *l, int pidentificacion , char pnombre[40],int pcantidad, int pcosto, char pfecha[40]);
void Insertar2(Lista *l, int pidentificacion , char pnombre[40],int pcantidad, int pcosto, char pfecha[40]);
void insertarproducto();
void insertarproducto2();
void MostrarLista(Lista l);
void buscar(Lista *lista, int aux);
typedef struct nodo {
int identificacion;
char nombre[40];
int cantidad;
int costo;
char fecha[40];
struct nodo *siguiente;
} tipoNodop;
typedef tipoNodop *pNodop;
typedef tipoNodop *Pila;
Pila pila = NULL;
void Insertar(Pila *p, int pcantidad, int pcosto, char pfecha[40]);
void Insertar2(Pila *p, int pcantidad, int pcosto, char pfecha[40]);
int pilavacia(Pila p);
void Mostrarpila(Pila p);
int main()
{
int op,pcodigo,n,aux;
int dato,pago;
do{
op=0;
system(“cls”);
cout<<“\t\tMENU”<<endl;
cout<<“\t2.Comprar PRODUCTO”<<endl;
cout<<“\t3.mostrar informe PRODUCTO”<<endl;
cout<<“\t2.MOSTRAR INFORME”<<endl;
cout<<“\t4.mostrar pila”<<endl;
cout<>op;
system(“cls”);
switch(op){
case 1:
cout<<“\t\t____________________________________”<<endl;
cout<<“\t\t\tCREAR PRODUCTO”<<endl;
cout<<“\t\t____________________________________”<<endl;
insertarproducto();
break;
case 2:
cout<<“\t\t\tCOMPRAR PRODUCTO”<<endl;
cout<>aux;
buscar(&lista,aux);
insertarproducto2();
break;
break;
case 3:
cout<<“\t\t\t MOSTRAR INFORME”<<endl;
MostrarLista(lista);
break;
case 4:
cout<<“GRACIAS POR UTILIZAR NUESTROS SERVICIOS”<<endl;
Mostrarpila(pila);
system(“pause”);
break;
}
}while(op!=5);
system(“cls”);
getch();
}
void insertarproducto(){
int aidentificacion;
char anombre[40];
int acantidad;
char afecha[40];
int acosto;
cout<>aidentificacion;
cout<>anombre;
cout<>acantidad;
cout<>afecha;
cout<>acosto;
Insertar(&lista, aidentificacion,anombre,acantidad,acosto,afecha);
}
void Insertar(Lista *lista, int pidentificacion, char pnombre[40], int pcantidad,int pcosto, char pfecha[40]){
pNodo nuevo, anterior;
//crear un nuevo nodo
nuevo = (pNodo)malloc(sizeof(tipoNodo));
nuevo->identificacion = pidentificacion;
strcpy(nuevo->nombre,pnombre);
nuevo->cantidad=pcantidad;
strcpy(nuevo->fecha,pfecha);
nuevo->costo = pcosto;
/*si lista esta vacia*/
if(listavacia(*lista) || (*lista)->identificacion>pidentificacion){
/*añadimos el nuevo nodo*/
nuevo->siguiente = *lista;
/*ahora el comienzo de nuestra lista es un nuevo nodo*/
*lista = nuevo;
}
else {
anterior = *lista;
while(anterior->siguiente && anterior->siguiente->identificacion siguiente;
nuevo->siguiente = anterior->siguiente;
anterior->siguiente = nuevo;
}
}
void insertarproducto2(){
char anombre[40];
int aidentificacion;
int acantidad;
char afecha[40];
int acosto;
cout<>acantidad;
cout<>afecha;
cout<>acosto;
Insertar(&lista, aidentificacion,anombre,acantidad,acosto,afecha);
}
void Insertar2(Lista *lista, int pidentificacion, char pnombre[40], int pcantidad,int pcosto, char pfecha[40]){
pNodo nuevo, anterior;
//crear un nuevo nodo
nuevo = (pNodo)malloc(sizeof(tipoNodo));
nuevo->identificacion = pidentificacion;
strcpy(nuevo->nombre,pnombre);
nuevo->cantidad=pcantidad;
strcpy(nuevo->fecha,pfecha);
nuevo->costo = pcosto;
/*si lista esta vacia*/
if(listavacia(*lista) || (*lista)->identificacion>pidentificacion){
/*añadimos el nuevo nodo*/
nuevo->siguiente = *lista;
/*ahora el comienzo de nuestra lista es un nuevo nodo*/
*lista = nuevo;
}
else {
anterior = *lista;
while(anterior->siguiente && anterior->siguiente->identificacion siguiente;
nuevo->siguiente = anterior->siguiente;
anterior->siguiente = nuevo;
}
}
int listavacia(Lista lista) {
return (lista == NULL);
}
void MostrarLista(Lista lista) {
int pago;
pNodo nodo = lista;
if(listavacia(lista)) cout<<“Lista vacía \n”;
else {
while(nodo) {
cout<<“\tCodigo\tNombre\tfecha\tCantidad\tprecio”<<endl;
cout<<“\t “<identificacion<<“\t “<nombre<<“\t “<cantidad<<“\t “<fecha<<“\t\t “<costo<<endl;
cout<siguiente;
}
cout<identificacion siguiente;
}
if(!nodo || nodo->identificacion !=aux){
cout<<“\tNO HAY COINCIDENCIAS”<<endl;
cout<<“\tO ES POSIBLE QUE NO HAYA DATOS”<<endl;
}
else{
cout<<“\tNOMBRE DE EL PRODUCTO:”<nombre<cantidad=pcantidad;
nuevo->costo = pcosto;
strcpy(nuevo->fecha,pfecha);
/*si lista esta vacia*/
if(pilavacia(*p) || (*p)->cantidad>pcantidad){
/*añadimos el nuevo nodo*/
nuevo->siguiente = *p;
/*ahora el comienzo de nuestra lista es un nuevo nodo*/
*p = nuevo;
}
else {
anterior = *p;
while(anterior->siguiente && anterior->siguiente->cantidad siguiente;
nuevo->siguiente = anterior->siguiente;
anterior->siguiente = nuevo;
}
}
void Mostrarpila(Pila p) {
pNodop nodo = p;
if(pilavacia(p)) cout<<“Lista vacía \n”;
else {
while(nodo) {
cout<<“\tDIRRECCION DEL ESTUDIANTE: “<cantidad<<endl;
cout<<“\tTELEFONO DEL ESTUDIANTE: “<fecha<<endl;
cout<<“\tCARRERA DEL ESTUDIANTE: “<costo<<endl;
cout<siguiente;
system(“pause”);
}
cout<<“\n”;
system(“pause”);
}
}

https://cimec.org.ar/~mstorti/repositorio-cpp/

Una lista es una estructura de datos que nos permite agrupar elementos de una manera
organizada. Las listas al igual que los algoritmos son importantísimas en la computación y críticas
en muchos programas informáticos.

Las listas están compuestas por nodos, estos nodos tienen un dato o valor y un puntero a otro(s)
nodo(s).

Existen varios tipos de listas: Simplemente enlazada, doblemente enlazada, circular simplemente
enlazada, circular doblemente enlazada.

Vamos a revisar las listas enlazadas simples, por ser el punto de partida y fundamentales para
poder entender las otras.
Una lista enlazada tiene un conjunto de nodos, los cuales almacenan 2 tipos de información: El
dato que contienen y un puntero al siguiente nodo en la lista. El último nodo de la lista tiene como
siguiente nodo el valor NULL. Entonces las listas enlazadas simples solo pueden ser recorridas en
una dirección, apuntando al nodo siguiente, mas no a un nodo anterior.

Aquí una ejemplo de un lista enlazada simple.

1 En cristiano:
2 55-> 60-> 31-> 5-> 4-> 51-> 9-> 27-> 68-> 62-> NULL
3
4 Internamente:
5 Nodo-> Dato: 55 Direcion: 0x3d2c00 Siguiente: 0x3d2c80
6 Nodo-> Dato: 60 Direcion: 0x3d2c80 Siguiente: 0x3d2c90
Nodo-> Dato: 31 Direcion: 0x3d2c90 Siguiente: 0x3d2ca0
7 Nodo-> Dato: 5 Direcion: 0x3d2ca0 Siguiente: 0x3d2cb0
8 Nodo-> Dato: 4 Direcion: 0x3d2cb0 Siguiente: 0x3d2cc0
9 Nodo-> Dato: 51 Direcion: 0x3d2cc0 Siguiente: 0x3d3ab8
10 Nodo-> Dato: 9 Direcion: 0x3d3ab8 Siguiente: 0x3d3ac8
Nodo-> Dato: 27 Direcion: 0x3d3ac8 Siguiente: 0x3d3ad8
11 Nodo-> Dato: 68 Direcion: 0x3d3ad8 Siguiente: 0x3d3ae8
12 Nodo-> Dato: 62 Direcion: 0x3d3ae8 Siguiente: 0
13
14
Obviamente, internamente no existen las palabras nodo, dato,dirección y siguiente, es solo una
representación.

Como una lista es una estructura de datos dinámica, el tamaño de la misma puede cambiar
durante la ejecución del programa.

Como vimos en post anteriores, se puede generar memoria dinámicamente para un array, pero un
array es una estructura estática pues su tamaño tiene un limite y así creáramos array dinámicos
hay que redimensionar el tamaño si es necesario, lo cual ya implica un costo de volver a generar
memoria dinámica.

Entonces podemos ver una ventaja de la listas sobre los arrays: No tener que redimensionar la
estructura y poder agregar elemento tras elemento indefinidamente.

Cuando uno ya ha trabajado con arrays (vectores y matrices) y empieza a estudiar las listas, se da
cuenta que una restricción de las listas es el acceso a los elementos. En un vector podíamos hacer
algo como v[50] y nos estábamos refiriendo al índice 50 del vector v. A esto se le conoce como
acceso aleatorio.

En el caso de las listas el acceso es secuencial, es decir, para acceder a un elemento del conjunto
debemos de recorrer uno por uno los elementos hasta llegar al solicitado. Rápidamente se puede
concluir que el tiempo de acceso a los elementos de un array es muchísimo más rápido que en una
lista. Esta es una gran desventaja de las listas, por lo que buscar elementos por índice sería muy
costoso. Esto no quiere decir que trabajar con arrays sea mejor que con listas. Las listas son muy
flexibles y para muchos casos son imprescindibles.

Bueno, aquí va la primera práctica que hice sobre listas enlazadas. Implementación de una clase
Lista, clase Nodo y los siguientes métodos:

 Añadir un elemento al inicio.


 Añadir un elemento al final
 Añadir un elemento de manera ordenada
 Llenar la lista por teclado
 Llenar la lista aleatoriamente
 Imprimir la lista
 Buscar un elemento
 Eliminar un elemento por dato
 Eliminar un elemento por posicion o índice
 Eliminar toda la lista
 Invertir una lista
 Ordernar una lista
 Cargar una lista desde archivo
 Guardar la lista en un archivo
 Concatenar una lista a otra
 Intersección entre 2 listas
Podrán ver la variable *temp en casi todos los métodos , este es el puntero de tipo Nodo que me va
a permitir moverme a través de la lista y que inicialmente es igual a la cabeza (head). Mientras
exista algo en la lista, voy avanzado el puntero para que apunte al siguiente. Esto se consigue en
casi todos los casos con un while.

1 while (temp) {
2 temp = temp->next;
3 }
Otra operación común en los métodos es preguntar si inicialmente la lista está vacía, es decir, si la
cabeza no contiene algo o es igual a Null.

1 if (!m_head) {
2 ...
3 }
Apliqué mis limitados conocimientos de templates para tener una lista genérica y así pueda
funcionar con varios tipos de datos y de verdad funciona.

Ahí la definición e implementación de la clase, lista, clase nodo y el main para ver el
funcionamiento. Cualquier crítica, sugerencia o comentarios son bienvenidos siempre.

node.h
1 #ifndef NODE_H
2 #define NODE_H
3
#include <iostream>
4
5 using namespace std;
6
7 template <class T>
8
9 class Node
10 {
public:
11 Node();
12 Node(T);
13 ~Node();
14
15 Node *next;
16 T data;
17
void delete_all();
18 void print();
19 };
20
21 #endif // NODE_H
22
23
24
node.cpp
1
2
3 #include "node.h"
4
5 // Constructor por defecto
6 template<typename T>
7
8 Node<T>::Node()
{
9 data = NULL;
10 next = NULL;
11 }
12
13 // Constructor por parámetro
14 template<typename T>
Node<T>::Node(T data_)
15 {
16 data = data_;
17 next = NULL;
18 }
19
// Eliminar todos los Nodos
20 template<typename T>
21 void Node<T>::delete_all()
22 {
23 if (next)
24 next->delete_all();
delete this;
25 }
26
27 // Imprimir un Nodo
28 template<typename T>
29 void Node<T>::print()
{
30
//cout << "Node-> " << "Dato: " << dato << " Direcion: " << this << " Siguiente:
31 << endl;
32 cout << data << "-> ";
33 }
34
35 template<typename T>
Node<T>::~Node() {}
36
37
38
list.h
1 #ifndef LIST_H
#define LIST_H
2
3 #include <fstream>
4 #include <iostream>
5 #include <string>
6 #include <stdlib.h>
7
#include "node.h"
8 #include "node.cpp"
9
10 using namespace std;
11
12 template <class T>
13
14 class List
{
15 public:
16 List();
17 ~List();
18
19 void add_head(T);
20 void add_end(T);
void add_sort(T);
21 void concat(List);
22 void del_all();
23 void del_by_data(T);
24 void del_by_position(int);
void fill_by_user(int);
25 void fill_random(int);
26 void intersection(List);
27 void invert();
28 void load_file(string);
29 void print();
void save_file(string);
30 void search(T);
31 void sort();
32
33 private:
34 Node<T> *m_head;
35 int m_num_nodes;
};
36
37 #endif // LIST_H
38
39
40
41
42
43
44
list.cpp
1 #include "list.h"
2
using namespace std;
3
4 // Constructor por defecto
5 template<typename T>
6 List<T>::List()
7 {
8 m_num_nodes = 0;
m_head = NULL;
9 }
10
11 // Insertar al inicio
12 template<typename T>
13 void List<T>::add_head(T data_)
{
14 Node<T> *new_node = new Node<T> (data_);
15 Node<T> *temp = m_head;
16
17 if (!m_head) {
18 m_head = new_node;
19 } else {
new_node->next = m_head;
20 m_head = new_node;
21
22 while (temp) {
23 temp = temp->next;
24 }
}
25 m_num_nodes++;
26 }
27
28 // Insertar al final
29 template<typename T>
30 void List<T>::add_end(T data_)
{
31 Node<T> *new_node = new Node<T> (data_);
32 Node<T> *temp = m_head;
33
34 if (!m_head) {
35 m_head = new_node;
36 } else {
while (temp->next != NULL) {
37 temp = temp->next;
38 }
39 temp->next = new_node;
40 }
m_num_nodes++;
41 }
42
43 // Insertar de manera ordenada
44 template<typename T>
45 void List<T>::add_sort(T data_)
46 {
Node<T> *new_node = new Node<T> (data_);
47 Node<T> *temp = m_head;
48
49 if (!m_head) {
50 m_head = new_node;
51 } else {
if (m_head->data > data_) {
52 new_node->next = m_head;
53
54 m_head = new_node;
55 } else {
while ((temp->next != NULL) && (temp->next->data < data_)) {
56 temp = temp->next;
57 }
58 new_node->next = temp->next;
59 temp->next = new_node;
}
60 }
61 m_num_nodes++;
62 }
63
64 // Concatenar a otra List
65 template<typename T>
void List<T>::concat(List list)
66 {
67 Node<T> *temp2 = list.m_head;
68
69 while (temp2) {
70 add_end(temp2->data);
temp2 = temp2->next;
71 }
72 }
73
74 // Eliminar todos los nodos
75 template<typename T>
76 void List<T>::del_all()
{
77 m_head->delete_all();
78 m_head = 0;
79 }
80
81 // Eliminar por data del nodo
82 template<typename T>
void List<T>::del_by_data(T data_)
83 {
84 Node<T> *temp = m_head;
85 Node<T> *temp1 = m_head->next;
86
87 int cont = 0;
88
if (m_head->data == data_) {
89 m_head = temp->next;
90 } else {
91 while (temp1) {
92 if (temp1->data == data_) {
93 Node<T> *aux_node = temp1;
temp->next = temp1->next;
94 delete aux_node;
95 cont++;
96 m_num_nodes--;
97 }
temp = temp->next;
98 temp1 = temp1->next;
99 }
100 }
101
102 if (cont == 0) {
103 cout << "No existe el dato " << endl;
}
104 }
105
106 // Eliminar por posición del nodo
107 template<typename T>
108 void List<T>::del_by_position(int pos)
{
109 Node<T> *temp = m_head;
110 Node<T> *temp1 = temp->next;
111
112 if (pos < 1 || pos > m_num_nodes) {
113 cout << "Fuera de rango " << endl;
114 } else if (pos == 1) {
m_head = temp->next;
115 } else {
116 for (int i = 2; i <= pos; i++) {
117 if (i == pos) {
118 Node<T> *aux_node = temp1;
temp->next = temp1->next;
119 delete aux_node;
120 m_num_nodes--;
121 }
122 temp = temp->next;
123 temp1 = temp1->next;
}
124 }
125 }
126
127 // Llenar la Lista por teclado
128 template<typename T>
129 void List<T>::fill_by_user(int dim)
{
130 T ele;
131 for (int i = 0; i < dim; i++) {
132 cout << "Ingresa el elemento " << i + 1 << endl;
133 cin >> ele;
add_end(ele);
134 }
135 }
136
137 // Llenar la Lista aleatoriamente para enteros
138 template<typename T>
139 void List<T>::fill_random(int dim)
{
140 srand(time(NULL));
141 for (int i = 0; i < dim; i++) {
142 add_end(rand() % 100);
143 }
}
144
145 // Usado por el método intersección
146 template<typename T>
147 void insert_sort(T a[], int size)
148 {
149 T temp;
for (int i = 0; i < size; i++) {
150 for (int j = i-1; j>= 0 && a[j+1] < a[j]; j--) {
151 temp = a[j+1];
152 a[j+1] = a[j];
153 a[j] = temp;
}
154 }
155 }
156
157 // Números que coinciden en 2 Lists
158 template<typename T>
159 void List<T>::intersection(List list_2)
{
160 Node<T> *temp = m_head;
161 Node<T> *temp2 = list_2.m_head;
162
163 // Creo otra Lista
164 List intersection_list;
165
int num_nodes_2 = list_2.m_num_nodes;
166 int num_inter = 0;
167
168 // Creo 2 vectores dinámicos
169 T *v1 = new T[m_num_nodes];
170 T *v2 = new T[num_nodes_2];
171
172 // Lleno los vectores v1 y v2 con los datas de la lista original y segunda list
respectivamente
173 int i = 0;
174
175 while (temp) {
176 v1[i] = temp->data;
177 temp = temp->next;
178 i++;
}
179
180 int j = 0;
181
182 while (temp2) {
183 v2[j] = temp2->data;
184 temp2 = temp2->next;
j++;
185 }
186
187 // Ordeno los vectores
188 insert_sort(v1, m_num_nodes);
189 insert_sort(v2, num_nodes_2);
190
191 // Índice del 1er vector (v1)
int v1_i = 0;
192
193 // Índice del 2do vector (v2)
194 int v2_i = 0;
195
196 // Mientras no haya terminado de recorrer ambas Lists
197 while (v1_i < m_num_nodes && v2_i < num_nodes_2) {
if (v1[v1_i] == v2[v2_i]) {
198 intersection_list.add_end(v1[v1_i]);
199 v1_i++;
200 v2_i++;
201 num_inter++;
} else if (v1[v1_i] < v2[v2_i]) {
202 v1_i++;
203 } else {
204 v2_i++;
205 }
206 }
207
// Solo si hay alguna intersección imprimo la nueva lista creada
208 if (num_inter > 0) {
209 cout << "Existen " << num_inter << " intersecciones " << endl;
210 intersection_list.print();
211 } else {
cout << "No hay intersección en ambas listas" << endl;
212 }
213 }
214
215 // Invertir la lista
216 template<typename T>
217 void List<T>::invert()
{
218 Node<T> *prev = NULL;
219 Node<T> *next = NULL;
220 Node<T> *temp = m_head;
221
222 while (temp) {
223 next = temp->next;
temp->next = prev;
224 prev = temp;
225 temp = next;
226 }
227 m_head = prev;
}
228
229 // Cargar una lista desde un archivo
230 template<typename T>
231 void List<T>::load_file(string file)
232 {
233 T line;
ifstream in;
234 in.open(file.c_str());
235
236 if (!in.is_open()) {
237 cout << "No se puede abrir el archivo: " << file << endl << endl;
238 } else {
while (in >> line) {
239 add_end(line);
240 }
241 }
242 in.close();
243 }
244
// Imprimir la Lista
245 template<typename T>
246 void List<T>::print()
247 {
248 Node<T> *temp = m_head;
if (!m_head) {
249 cout << "La Lista está vacía " << endl;
250 } else {
251 while (temp) {
252 temp->print();
253 if (!temp->next) cout << "NULL";
temp = temp->next;
254 }
255 }
256 cout << endl << endl;
257 }
258
// Buscar el dato de un nodo
259 template<typename T>
260 void List<T>::search(T data_)
261 {
262 Node<T> *temp = m_head;
263 int cont = 1;
int cont2 = 0;
264
265 while (temp) {
266 if (temp->data == data_) {
267 cout << "El dato se encuentra en la posición: " << cont << endl;
268 cont2++;
269 }
temp = temp->next;
270 cont++;
271 }
272
273 if (cont2 == 0) {
274 cout << "No existe el dato " << endl;
}
275 cout << endl << endl;
276 }
277
278 // Ordenar de manera ascendente
279 template<typename T>
280 void List<T>::sort()
{
281 T temp_data;
282 Node<T> *aux_node = m_head;
283 Node<T> *temp = aux_node;
284
285 while (aux_node) {
temp = aux_node;
286
287 while (temp->next) {
288
289 temp = temp->next;
290
291 if (aux_node->data > temp->data) {
temp_data = aux_node->data;
292 aux_node->data = temp->data;
293 temp->data = temp_data;
294 }
295 }
296
aux_node = aux_node->next;
297 }
298 }
299
300 // Guardar una lista en un archivo
301 template<typename T>
302 void List<T>::save_file(string file)
{
303 Node<T> *temp = m_head;
304 ofstream out;
305 out.open(file.c_str());
306
307 if (!out.is_open()) {
cout << "No se puede guardar el archivo " << endl;
308 } else {
309 while (temp) {
310 out << temp->data;
311 out << " ";
312 temp = temp->next;
}
313 }
314 out.close();
315 }
316
317 template<typename T>
318 List<T>::~List() {}
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
main.cpp
1 #include <iostream>
2
#include "list.h"
3 #include "list.cpp"
4
5 using namespace std;
6
7 int main()
8 {
9 List<int> list_1;
List<int> list_2;
10 int ele;
11
12 int dim;
13 int pos;
14 string file_with_list;
15
cout << "Ingresa la dimensión de la lista: " << endl;
16 cin >> dim;
17
18 list_1.fill_random(dim);
19
20 cout << "Lista A al inicio " << endl;
21 list_1.print();
22
23 cout << "Agrega un elemento por la cabeza: " << endl;
cin >> ele;
24 list_1.add_head(ele);
25 list_1.print();
26
27 cout << "Lista invertida: " << endl;
28 list_1.invert();
list_1.print();
29
30 cout << "Lista ordenada: " << endl;
31 list_1.sort();
32 list_1.print();
33
34 cout << "Agrega un elemento. Será insertado ordenadamente: " << endl;
35 cin >> ele;
list_1.add_sort(ele);
36 list_1.print();
37
38 cout << "Busca un elemento: " << endl;
39 cin >> ele;
40 list_1.search(ele);
41
42 cout << "Elimina un elemento por dato: " << endl;
cin >> ele;
43 list_1.del_by_data(ele);
44 list_1.print();
45
46 cout << "Elimina un elemento por posición: " << endl;
47 cin >> pos;
list_1.del_by_position(pos);
48 list_1.print();
49
50 cout << "Cargar una lista desde archivo - Ingresa el nombre(Ex: list.txt): " <<
51 endl;
52 // El archivo debe estar en el mismo directorio que este programa
53 cin >> file_with_list;
list_2.load_file(file_with_list);
54
55 cout << "Lista B: " << endl;
56 list_2.print();
57
cout << "Guardar la lista en un archivo - Ingresa el nombre(Ex: list2.txt): " <<
58 endl;
59 cin >> file_with_list;
60 list_2.save_file(file_with_list);
61
62 cout << "Interseccion entre las listas A y B: " << endl;
list_1.intersection(list_2);
63
64 cout << "Listas A y B concatenadas: " << endl;
65 list_1.concat(list_2);
66 list_1.print();
67
68 list_1.del_all();
69 list_1.print();
70
return 0;
71 }
72
73
74
75
76
77
78
79
80

cola.h

#ifndef COLA_H

#define COLA_H
/*
* Autor: William David Parras Mendez
* Dependencias: nodo.h
*/

/* Plantilla de clase cola */


template <class T>
class Cola
{
private:
Nodo<T> * start,* end; /* Apuntadores al inicio y al final de la cola
*/
int length; /* Longitud actual de la cola */
public:
Cola(); /* Constructora */
~Cola(); /* Destructora */
void push(int); /* Encola un elemento */
int pop(void); /* Desencola un elemento */
int Length(void); /* Devuelve longitud de la cola */
void display(void); /* Muestra la cola */
bool isEmpty(void);
};

template <class T>


Cola<T>::Cola(){
start = end = NULL;
length = 0;
}

template <class T>


void Cola<T>::push(int value){
Nodo<T> * newNode = new Nodo<T>();
newNode->set_data(value);
if(start==NULL){
start = newNode;
end = newNode;
newNode->set_nxt(NULL);
length++;
}else{
end->set_nxt(newNode);
newNode->set_nxt(NULL);
end = newNode;
length++;
}
}

template <class T>


int Cola<T>::pop(){
int value = -1;
if(start==NULL)
return -1;
else{
value = start->get_data();
start = start->get_nxt();
length--;
}
return value;
}

template <class T>


int Cola<T>::Length(){
return length;
}

template <class T>


bool Cola<T>::isEmpty(){
if(start == NULL)
return true;
else
return false;
}

template <class T>


void Cola<T>::display(){
if(start==NULL)
std::cout << "NO DATA";
else{
for(Nodo<int> * seeker = start;seeker != NULL;seeker = seeker-
>get_nxt()){
std::cout << seeker->get_data() << ", ";
}
}
}
#endif
Raw

funciones.cpp

#include <iostream>

#include "nodo.h"
#include "cola.h"
#include <iomanip>
#include <stdio.h>
#include <exception>
using namespace std;

/* Prototipos */
/* vacio insert(<dato a insertar>,<prioridad del dato>, <cola donde se
insertata>) */
void insert(int,int,Cola<int> *);
/* vacio displayC(<Arreglo de colas a mostrar>,<tamaño de arreglo de colas>)
*/
void displayC(Cola<int> *,int);
/* entero deleteItem(<Arreglo de colas a vaciar>,<tamaño de arreglo de colas>)
*/
int deleteItem(Cola<int> *,int);

int main(void){
/* Crea un arreglo de colas, cada posicion es una prioridad */
/* Maxima prioridad igual a longitud de arreglo menos uno */
Cola<int> * cola = new Cola<int>[5];
// Prueba de la funcion de insercion
insert(3,4,cola);
insert(2,4,cola);
insert(3,0,cola);
insert(5,2,cola);
insert(7,1,cola);
insert(1,3,cola);
insert(8,2,cola);
insert(9,1,cola);
// Muestra la cola de prioridad actual
displayC(cola,5);
// extrae algunos elementos de la cola
cout << "Extrayendo elemento: "<<deleteItem(cola,5)<<endl;
cout << "Extrayendo elemento: "<<deleteItem(cola,5)<<endl;
cout << "Extrayendo elemento: "<<deleteItem(cola,5)<<endl;
cout << "Extrayendo elemento: "<<deleteItem(cola,5)<<endl;
getchar();
return 0;
}

void insert(int data,int priori, Cola<int> * a ){


// Si sale fuera de los limites genera una excepcion
try{
// Inserta el dato en la cola almacenada en la prioridad
indicada
a[priori].push(data);
}catch(exception& e){
cout << "OUT OF MEMORY: ";
}
}

void displayC(Cola<int> * a, int Maxpriori ){


// recorre toda el arreglo de colas
for(int i = 0; i < Maxpriori; i++ ){
// para cada cola almacenada llama a su respectivo metodo
display
a[i].display();
// da un salto, esto permite separar por prioridad
// y mostrar una prioridad por lina, si se elimina el salto
// se mostrara una sola cadena, esto es opcional
cout << endl;
}
}

int deleteItem(Cola<int> * a, int Maxpriori ){


int i = 0;
// Mientras no se llegue al final del arreglo
while(i < Maxpriori){
// verifica si la prioridad mas alta esta vacia
// si esta vacia entonces avanza a la siguiente
// prioridad mas alta
if(a[i].isEmpty()){
i++;
}else{
// cuando la prioridad tenga elementos encolados
// saca el elemento mas proximo de la cola y lo retorna
// por lo tanto se detiene la ejecucion de la funcion
return a[i].pop();
}
}
// en caso que no encuentra elementos retorna -1 esto puede
// servir para controlar excepciones
return -1;
}
Raw

nodo.h

#ifndef NODO_H

#define NODO_H
/*
* Autor: William David Parras Mendez
* Dependencias: Ninguna
*/

/* Clase Nodo De un camino*/


/* Pantilla */
template <class T>
class Nodo
{
/* Datos privados */
private:
Nodo * nxt; /* Apuntador a otro nodo */
T data; /* Dato que Almacena el nodo */
public:
/* Prototipos */
Nodo(); /* Constructora */
~Nodo(); /* Destructora */
void set_data(T); /* Inicializa dato del nodo */
T get_data(void); /* Obtiene Dato almacenado en el nodo */
Nodo * get_nxt(void); /* Enlaza el nodo */
void set_nxt(Nodo *); /* Obtiene el enlace del nodo */
};

/* Inicializa componentes */
template <class T>
Nodo<T>::Nodo(){
nxt = NULL;
data = 0;
}

/* Definicion de la insercion del dato */


template <class T>
void Nodo<T>::set_data(T value){
data = value;
}

/* Retorna el dato actual almacenado */


template <class T>
T Nodo<T>::get_data(){
return data;
}

/* Enlaza a otro nodo */


template <class T>
void Nodo<T>::set_nxt(Nodo * item){
nxt = item;
}

/* Obtiene el enlace del nodo */


template <class T>
Nodo<T>* Nodo<T>::get_nxt(){
return nxt;
}
#endif
3 lines (38 sloc) 1.25 KB
/*

* Copyright (C) 2009 Ronny Yabar Aizcorbe <ronnycontacto@gmail.com>


*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU Lesser General Public License,
* version 2.1, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <iostream>

using namespace std;

// Función factorial con tail recursion


int fact_tail_sum(int n, int sum) {
if (n == 1) {
return sum;
} else {
return fact_tail_sum(n - 1, sum * n);
}
}

// Para mantener la sintaxis original de como se llama a la función factorial


int fact_tail(int n) {
return fact_tail_sum(n, 1);
}

int main()
{
int num = 0;
cout << "Ingresa un nro " << endl;
cin >> num;
cout << "Su factorial es " << fact_tail(num) << endl;
return 0;
}

La recursividad es una técnica de programación que consiste en que una serie de instrucciones se
repiten como una subtarea de la tarea principal, es decir, las funciones, procesos o rutinas se llaman
a sí mismos cada vez que lo requieran y se ejecutan repetidas veces hasta que se satisface una
condición específica.

Sin embargo, la recursividad en algunos casos, tiene un costo computacional alto, debido a las
constantes llamadas a la misma función, rutina y muchas veces estas llamadas consumen
demasiada memoria.

En algunos algoritmos recursivos, se puede implementar un caso de recursividad especial


llamado Tail Recursion (recursividad por cola), la cual es una técnica para optimizar la recursividad
eliminando las constantes llamadas recursivas. Tail recursion es cuando la llamada recursiva es la
última instrucción de la función.
Sin embargo, las funciones tail recursive deben cumplir la condición que en la parte que realiza la
llamada a la función, no debe existir ninguna otra sentencia.
Una ventaja de la recursividad por cola es que podemos evitar la sobrecarga de cada llamada a la
función y nos evitamos el gasto de memoria de pila. Con una función tail recursive se puede evitar
lo que se conoce como stack overflow, que ocurre cuando la pila de llamadas (call stack) consume
mucha memoria.
Veamos esta técnica con un ejemplo muy conocido que es encontrar el factorial de un número.
Esta simple función obtiene el factorial de un número de la manera recursiva convencional: La
explicación es sencilla, llamamos a la misma función fact hasta que n sea igual a 1, en ese momento
la recursividad se detiene.

1
2 int fact_recursivo(int n)
3 {
if(n == 1)
4
return n;
5 else
6 return n * fact_recursivo(n-1);
7 }
8
9 int main() {
int num = 0;
10 cout << "Ingresa un nro " << endl; cin >> num;
11
12 cout << "Su factorial es " << fact_recursivo(num);
13 return 0;
14 }
15
Digamos que ingresé 15, si compilamos el programa la función me daría 2004310016, lo cual es
correcto, pero hay que tener en cuenta todas las llamadas recursivas que hizo la función
fact_recursivo, es decir, la función tiene que calcular el factorial de 14 y este el fact de 13, y este el
fact de 12… sucesivamente hasta 1. Tenemos un crecimiento del número de llamadas recursivas.

Ahora vamos a realizar la misma función usando tail recursion. En este caso no se necesita guardar
un marco de pila para cada llamada recursiva, y el algoritmo se comporta como si fuera iterativo.

Para conseguir esto usamos un parámetro adicional que actúa como acumulador.

Esta sería la función factorial con tail recursion:

1 // Función factorial con tail recursion


int fact_tail_sum(int n, int sum) {
2 if (n == 1) {
3 return sum;
4 } else {
5 return fact_tail_sum(n - 1, sum * n);
}
6 }
7
8 // Para mantener la sintaxis original de como se llama a la función factorial
9 int fact_tail(int n) {
10 return fact_tail_sum(n, 1);
11 }
12
int main()
13 {
14 int num = 0;
15 cout << "Ingresa un nro " << endl; cin >> num;
16 cout << "Su factorial es " << fact_tail(num) << endl;
return 0;
17 }
18
19
20
21
El resultado igualmente sería 2004310016, pero el costo computacional baja drásticamente debido
a que cuando se hace la llamada return fact_tail_sum(n – 1, sum*n) ambos parámetros son
inmediatamente resueltos. Con esta llamada podemos calcular sin esperar una llamada a una
función recursiva para que nos devuelva un valor.

En este caso el compilador reduce drásticamente el uso de la pila y la cantidad de información que
debe ser almacenada, el valor de n y sum es independiente del número de llamadas recursivas.

Una función recursiva normal se puede convertir a tail recursive usando en la función original un
parámetro adicional, usado para ir guardando un resultado de tal manera que la llamada recursiva
ya no tiene una operación pendiente.

También se usa una función adicional para mantener la sintaxis de como llamamos normalmente a
la función. En el caso del factorial, para seguir llamando a la función de la forma fact(n).
En conclusión:
• Una llamada es tail recursive (recursiva por cola) si no tiene que hacer nada más después de la
llamada de retorno.
• Tail recursion es cuando la llamada recursiva es la última instrucción en la función.
• Usar tail recursion es muy ventajoso, porque la cantidad de información que debe ser almacenada
durante el cálculo es independiente del número de llamadas recursivas.
• El compilador trata una función tail recursive como si fuera una función iterativa.
El código fuente de este y otros ejercicios de C++ está disponible en
Github:

https://github.com/ronnyml/C---Tutorial
Gracias por tu visita al blog. Puedes seguirme en Twitter haciendo click en el siguiente enlace:

Report this ad

Report this ad

Related
Quicksort en C++In "C++"
Listas en HaskellIn "Haskell"
Vectores, Matrices y Punteros en c++In "C++"
Written by Ronny Yabar
May 19, 2009 at 10:30 pm
Posted in C++
Tagged with C++, Recursividad, Tail Recursion, Tail Recursion en C++

« La senda del Líder del Dalai Lama


Vectores, Matrices y Punteros en c++ »

7 Responses
Subscribe to comments with RSS.

1. En que casos específicos es bueno utilizar la recursividad, ya que dijeron que consume mucha memoria.
#include <stdio.h>

#include <stdlib.h>
/* DECLARACIÓN DE TIPOS */

typedef char elemento;

struct NODE{

elemento info;

struct NODE *sgte;

};

typedef struct NODE nodo;

typedef struct NODE *posicion; /* La cola es un puntero a nodo */

typedef struct {

int longitud;

posicion prim, ult;

}COLA;

/* DECLARACIÓN DE FUNCIONES */

int vacia(COLA);

int queue(COLA *, elemento);

int dequeue(COLA *, elemento *);

int mostrar_cola(COLA);

int borrar_cola (COLA*);

void crear_cola(COLA *);

int llena(posicion *);

int tamagno(COLA);

main(){

int opcion;

COLA C;

elemento x;
C.longitud=-1;

printf("\n Este programa realiza las siguientes operaciones con


pilas:\n");

do{

printf ("\n 1: Crear cola.\n 2: Encolar.\n 3:


Desencolar.\n 4: Ver elementos.\n 5: Borrar cola. \n 6: Salir. \n
Seleccione la operacion que desea realizar: ");

scanf(" %d", &opcion);

switch(opcion){

case 1: crear_cola(&C); break;

case 2: printf("\n Dame el elemento a meter: ");do


scanf("%c",&x);while(x=='\n');queue(&C,x);break;

case 3: dequeue(&C,&x); break;

case 4: mostrar_cola(C);break;

case 5: borrar_cola(&C);break;

case 6: break;

default: printf("\n Error, ha de introducir un


numero del 1 al 6.\n");

printf("\n Pulsa [ENTER] para continuar...");

getchar();getchar();

}while (opcion!=6);

/* FUNCIONES */
/* Función que inicializa la cola */

void crear_cola(COLA *C){

(*C).prim=NULL;

(*C).ult=NULL;

(*C).longitud=0;

printf("\nSe ha creado una cola vacia\n");

/* Función que averigua si hay algún elemento en la cola */

int vacia (COLA C){

if (C.longitud<0) return -1;

else{

if (C.longitud==0) return 1;

else return 0;

/* Esta función devuelve 1 si no se puede reservar más memoria */

int llena(posicion *tmp){

*tmp=(nodo*)calloc(1,sizeof(nodo));

if ((*tmp)==NULL) return 1;

else return 0;

/* Función que devuelve el tamaño de la cola */

int tamagno(COLA C){

return C.longitud;

}
/* Función que inserta elementos en la cola */

int queue (COLA *C, elemento x){

posicion tmp;

if (vacia(*C)<0){

printf("\nError, debe crear una cola\n");

return 0;

else {

if (llena(&tmp)){

printf("\n Error: no se puede reservar mas


espacio");

return 0;

else{ /* Se ha reservado espacio y se procede con la


inserción */

(*tmp).info=x;

(*tmp).sgte=NULL; /* Al ser el último elemento de


la cola,no hay más después */

if(vacia(*C)==1)

(*C).prim=tmp; /* Si además la cola está


vacía, también será el primero */

else if(vacia(*C)==0)

(*C).ult->sgte=tmp; /* Se inserta a
continuación del último */

(*C).ult=tmp;/* Pues erá el último elemento de la


cola */

(*C).longitud=(*C).longitud+1; /* Ahora hay un


elemento más en la cola */

return 1;

}
}

/* Función que saca elementos de la cola */

int dequeue(COLA *C, elemento *x){

posicion tmp;

if (vacia(*C)<0){

printf("\nError, debe crear una cola\n");

return 0;

else{ /* Ya existe una cola */

if (vacia(*C)==0) { /* Hay algún elemento que sacar */

tmp=(*C).prim;

*x=tmp->info;

if(tamagno(*C)==1) /* Sólo hay un elemento */

(*C).ult=NULL; /* La cola queda vacía */

(*C).prim=tmp->sgte; /* El siguiente del primero


pasa a ser el nuevo primer elemento */

free(tmp); /* Se libera la memoria que ocupaba el


nodo */

(*C).longitud=(*C).longitud-1; /* Ahora hay un


elemento menos en la cola */

//printf("\n El elemento desencolado es:


'%c'\n",x);

//printf("\nLa longitud de la cola es: %d\n",


(*C).longitud);

return 1;

else{ /* No hay elementos que sacar */

printf("\nLa cola esta vacia\n");

return 0;
}

/* Función que muestra los elementos que hay en la cola */

int mostrar_cola (COLA C){

posicion aux;

int longitud;

longitud=tamagno(C); /* Es el número de elementos que hay que


mostrar */

aux=C.prim; /* aux apunta al primer elemento de la cola */

if (longitud<0){

printf("\nError, debe crear una cola\n");

return 0;

else{ /* Ya existe una cola */

if (longitud==0){ /* No hay elementos que mostrar */

printf("\n La cola esta vacia.\n");

return 0;

else{

printf("\n");

while(longitud>0){ /* Mientras haya elementos en


la cola,los muestra */

printf(" '%c' ", aux->info);

//printf(" '%s' ", aux->info);

longitud--;
aux=(*aux).sgte; /* aux avanza al siguiente
elemento */

return 1;

/* Función que borra los elementos de la cola */

int borrar_cola (COLA *C){

elemento x;

if (vacia(*C)<0){ /* No existe ninguna pila que borrar */

printf("\nError, debe crear una cola\n");

return 0;

else{ /* Existe una pila */

if(vacia(*C)==1) return 0; /* No hay nada que mostrar */

else {

while(vacia(*C)==0){ /* Mientras haya elementos,


los sacamos */

dequeue(C,&x);

return 1;

#include <iostream>

using namespace std;

class NodoEntero{//CLASE NODOENTERO//


private:
int valor;
NodoEntero *siguiente;

public:
NodoEntero(int valor){
this->valor=valor;
this->siguiente=NULL;
}
//METODOS SET Y GET//
void setValor(int valor){
this->valor=valor;
}
void setSiguiente(NodoEntero*siguiente){
this->siguiente=siguiente;
}
int getValor(){
return valor;
}
NodoEntero* getSiguiente(){
return siguiente;
}
};//FIN CLASE//

class Cola{
private:
NodoEntero *frente;
NodoEntero *final;
public:
Cola(){//CONSTRUCTOR//
this->frente=NULL;
this->final=NULL;
}

bool estaVacio(){
return frente=NULL;
}

int obtenerValor(){
return frente->getValor();
}

void meter(int valor){


NodoEntero *nuevoNodo = new NodoEntero(valor);

if(estaVacio()){
frente=nuevoNodo;
final=nuevoNodo;
}else{
final->setSiguiente(frente);
final=nuevoNodo;
}
}

int sacar(){
NodoEntero *apuntadorNodo= frente;
int valor=frente->getValor();
frente=apuntadorNodo->getSiguiente();
delete (apuntadorNodo);
return valor;
}

void mostrar(){
NodoEntero *apuntadorNodo=frente;
while(apuntadorNodo!=NULL){
cout<<" "<<apuntadorNodo->getValor();
apuntadorNodo=apuntadorNodo->getSiguiente();
}
}
};
El error que estoy teniendo al llamar a mostrar( ) es que entro en un bucle infinito, y no para
al llegar al último elemento de la lista.
c++

compartirmejorar esta pregunta


editada el 25 sep. 17 a las 17:26

Trauma

10.1k21243

formulada el 25 sep. 17 a las 15:26

José A. Solís

227

añade un comentario

1 respuesta
activasmás antiguas votos

voto a favor3votar en contra aceptada

Tienes 2 fallos:

bool estaVacio( ) {
return frente = NULL;
}
Eso que haces es una asignación. Para comparar, sería
return frente == NULL;
El otro fallo es:

void meter( int valor ) {


NodoEntero *nuevoNodo = new NodoEntero( valor );

if( estaVacio( ) ) {
frente = nuevoNodo;
final = nuevoNodo;
} else {
final->setSiguiente( frente );
final = nuevoNodo;
}
}
Lo que haces en el else es crear una lista circular. Los nodos se apuntan en bucle unos a otros,
haciendo imposible que se pueda recorrer.
Lo correcto sería

} else {
final->setSiguiente( nuevoNodo );
final = nuevoNodo;
}
compartirmejorar esta respuesta
editada el 25 sep. 17 a las 20:18

respondida el 25 sep. 17 a las 17:24

Trauma

10.1k21243

añade un comentario

Programación en C++/Librería Estándar de


Plantillas/Colas
< Programación en C++ | Librería Estándar de Plantillas

Editores:

Oscar E. Palacios
← Librería Estándar de Plantillas

Sumario
[ocultar]

 1C++ queue estándar


 2Colas con prioridad
 3Copia de contenedor
 4Métodos

C++ queue estándar[editar]

Una cola (queue) es una estructura en donde los elementos son insertados
en el inicio (front) de la misma, y retirados al final de la misma,
debido a ello el comportamiento de una cola se
conoce como FIFO ( primero en entrar, primero en salir ). Ver Estructuras
II

La Libreria estándar de plantillas soporta el uso de estructuras de cola a travez de la plantilla de


clase queue, la cual posee el mecanismo de operación necesario para manejar operaciones de
insertar (push), borrar(pop), entre otras. La clase queue posee únicamente seis métodos y dos
constructores.

En seguida se presenta un ejemplo sumamente básico, el cual consiste en crear una cola para
contener elementos de tipo char. Los caracteres se introducen en orden desde la 'A' hasta la 'Z'
y, tal como tiene que ser, al recuperarlos se obtienen en el orden ingresados, o sea, desde la
'A' hasta la 'Z'.
En el programa se debe observar que, se usa el método push para agregar componentes a la
lista; el método front regresa una referencia al elemento que se encuentra en el inicio de la cola
y este es usado para leer y desplegar el carácter; y se emplea el método pop para eliminar el
elemento que está en el frente de la cola.

// programa: cola01.cpp
// un simple ejemplo del uso de la plantilla queue

#include <cstdlib>
#include <iostream>
#include <queue>

using namespace std;

int main(int argc, char *argv[])


{
queue<char> s;
for (int i='A'; i <= 'Z'; i++)
s.push(i);

while (! s.empty() )
{
cout << s.front() << " " ;
s.pop();
}

cout << endl;


system("PAUSE");
return EXIT_SUCCESS;
}

Colas con prioridad[editar]


Las colas prioritarias ( priority_queue ) de la STL de C++ son parecidas a las colas, con la
diferencia de que en estas los elementos se ordenan mediante algun predicado.

Aunque se ha dicho que las colas prioritarias son parecidas a las colas, su comportamiento es
diferente, ya que en una priority_queue no se cumple el algoritmo FIFO. Por ejemplo, en el
siguiente programa se puede observar como se insertan de manera no ordenada elementos a
la lista por medio de push, los cuales al ser recuperados se presentan en orden.

#include <cstdlib>
#include <iostream>
#include <queue>

using namespace std;

void cola01()
{
priority_queue<int> p;

// insertar elementos
p.push(100);
p.push(35);
p.push(12);
p.push(200);

// mostrar elementos
while (! p.empty() )
{
cout << p.top() << endl;
p.pop();
}
cout << endl;
}

int main(int argc, char *argv[])


{
cola01();
system("PAUSE");
return EXIT_SUCCESS;
}

La salida del programa anterior es:

200
100
35
12

y se debe al hecho de que por defecto la función o predicado que compara los elementos de la
priority_queue es menor (less). Ahora bien, el predicado puede ser cambiado para que la
comparación se mayor (greater) y para lograrlo se debe de usar una plantilla basada en la
clase vector o en la clase deque. El programa que se mostrará en seguida es un ejemplo de
como usar la clase deque para declarar una priority_queue.

#include <cstdlib>
#include <iostream>
#include <queue>

using namespace std;

void cola02()
{
cout << "test 02" << endl;
priority_queue<int, deque<int>, greater<int> > p;

p.push(100);
p.push(35);
p.push(12);
p.push(200);

while (! p.empty() )
{
cout << p.top() << endl;
p.pop();
}
cout << endl;
}

int main(int argc, char *argv[])


{
cola02();
system("PAUSE");
return EXIT_SUCCESS;
}

La salida del programa anterior es:

12
35
100
200

Copia de contenedor[editar]
La magia de una priority_queue nos permite usar un constructor para obtener una
copia ordenada de un contenedor ( vector, deque ). Así, en el siguiente ejemplo se
muestra como crear una copia de un vector. El resultado es
una priority_queue conteniendo en orden a todos los elementos del vector
original. Veamos.

#include <cstdlib>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;


// declaración de tipo
typedef priority_queue<string, vector<string>, greater<string> > STRPQUE;

int main(int argc, char *argv[])


{
vector<string> v;

v.push_back("pera");
v.push_back("uva");
v.push_back("manzana");
v.push_back("banana");
v.push_back("coco");

vector<string>::iterator it = v.begin();
cout << "Contenido del vector" << endl;
while (it != v.end() )
cout << "\t" << *it++ << endl;

STRPQUE p( v.begin(), v.end() );

cout << "\nContenido de la priority_queue" << endl;


while (! p.empty() )
{
cout << "\t" << p.top() << endl;
p.pop();
}

cout << endl;

system("PAUSE");
return EXIT_SUCCESS;
}

Si se compila y ejecuta el programa anterior, la salida en pantalla resultante será:

Contenido de la priority_queue
banana
coco
manzana
pera
uva

esto, a pesar de que el predicado ( greater<string> ), usado en la declaración de tipo, indica


que los elementos se deben ordenar atendiendo al resultado de una comparación ( mayor que ).
El hecho de tales resultados se debe a la naturaleza misma de la priority_queue, es decir, los
elementos en esta están realmente ordenados de mayor a menor pero del inicio al fin, luego,
como la orden top() regresa una referencia al último elemento de la cola, se obtiene asi un
listado de los elementos ordenados de menor a mayor.

Introducción
Cada objeto del TDA Cola, modela una cola de elementos de la clase T,
una cola es un tipo particular de lista en la que los elementos se insertan por un
extremo (el final) y se consultan y suprimen por el otro (cabecera). Son listas del
tipo FIFO (First In, First Out).

Son objetos mutables, residen en memoria dinámica.

Primitivas de la cola

Dentro del TDA Cola hemos propuesto las siguientes primitivas,


aunque esto no quiere decir que sean las mejores ni las más adecuadas para
todos los casos:

 Cola () -> constructor primitivo, nos crea una cola vacía.


 Cola (const Cola<T> &c) -> constructor de copia, crea una cola que es copia
p.
PARÁMETROS : c -> cola que se copia.

 Bool vacia () const -> informa si la cola está vacía, devuelve true si la cola
está vacía, false en otro caso.

 T& cabecera () -> acceso al elemento al principio de la cola, devuelve una


referencia al elemento en la cabecera de la cola.

 Void poner (const T & elem) -> añade un elemento en la cola, inserta un
nuevo elemento al final de la cola.
PARÁMETROS : elem -> elemento que se inserta.

 Void quitar () -> quita un elemento de la cola, elimina el elemento en la


cabecera de la cola.
PRECONDICIÓN : el receptor no puede estar vacío.

 Int num_elementos () -> obtiene el número de elementos incluidos en la


cola.

 ~Cola () -> destructor, el receptor es destruido liberando todos los


recursos que usaba.
POSTCONDICIÓN : el receptor es modificado.
Ejemplos de uso

En este apartado vamos a mostrar un ejemplo para que comprendamos


el funcionamiento de las primitivas del TDA Cola:

Rotar una cola

La función rota una cola un número n de veces,si está vacía la función no hace
nada.

Cola<int> c;

void rotar (int n)


{
int nrotaciones, e;

if (!c.vacia ())
{
nrotaciones = n % c.num_elementos();
for (int i = 0; i < nrotaciones; i++)
{
e = c.cabecera ();
c.quitar ();
c.poner (e);
}
}
}
Implementación de las colas

Implementación enlazada

Igual que en el caso de las pilas, cualquier implementación de listas es


válida para las colas. No obstante, para aumentar la eficiencia de poner es
posible aprovechar el hecho de que las inserciones se efectúan sólo en el
extremo posterior de forma que en lugar de recorrer la lista de principio a fin
cada vez que desea hacer una inserción se puede mantener un apuntador al
último elemento, como en las listas de cualquier clase, tambien se mantiene un
puntero al frente de la lista, aquí ese puntero es útil para ejecutar mandatos del
tipo cabecera o quitar. Utilizaremos al igual que para las listas, un nodo de
cabecera con el puntero frontal apuntándola con lo que nos permitirá un
manejo más cómodo.

Puesto que esta implementación se basa en una lista la cola es una


lista de tipo T con todos sus componentes.

Veamos la especificación de la función de abstracción y el invariante de


la representación de este tipo de cola:
Función de Abstracción

Dado el objeto del tipo rep r, el objeto abstracto al que representa es:

c = l, con la cabecera de la cola en la posición l.primero () y el final en la


posición l.final ().

Invariante de Representación

true.

De manera que la clase Cola (con implementación enlazada) en C++


tendría el siguiente aspecto:

Class Cola
{

Public:
Cola ();
Cola (const Cola<T> &c);
bool vacia () const;
T& cabecera ();
void poner (const T& elem);
void quitar ();
int num_elementos () const;
~Cola ();

Private:
Lista<T> cola;

Pasemos ahora a ver la implementación de cada una de las primitivas


enunciadas con anterioridad en C++:

template <class T>

inline Cola<T>::Cola()

template <class T>

inline Cola<T>::Cola(const Cola<T> & c) : cola(c.cola)

template <class T>


inline bool Cola<T>::vacia() const

return cola.vacia();

template <class T>

inline T& Cola<T>::cabecera()

return cola.elemento(cola.primero());

template <class T>

inline void Cola<T>::poner(const T & elem)

cola.insertar(cola.final(), elem);

template <class T>

inline void Cola<T>::quitar()

cola.borrar(cola.primero());

}
template <class T>

inline int Cola<T>::num_elementos() const

return cola.num_elementos();

template <class T>

inline Cola<T>::~Cola()

Implementación mediante matrices circulares

La implementación matrical de las listas no es muy eficiente para las


colas, puesto que si bien con el uso de un apuntador al último elemento es
posible ejecutar poner en un tiempo constante, quitar que suprime le primer
elemento, requiere que la cola completa ascienda una posición en la matriz con
lo que tiene un orden de eficiencia lineal proporcional al tamaño de la cola. Para
evitarlo se puede adoptar un criterio diferente, imaginemos a la matriz como
un circulo en el que la primera posición sigue a la última, la cola se encuentra
en alguna parte de ese círculo ocupando posiciones consecutivas.

Tenemos pues un vector elementos con los elementos de la


cola, Lmax que denota el número de posiciones que tiene la cola y los
punteros post y ant.
Para insertar un elemento en la cola se mueve el puntero post una
posición en el sentido de las agujas del reloj y se escribe el elemento en esa
posición, veámoslo en la siguiente figura.

Para suprimir un elemento simplemente se mueve ant una posición en


el sentido de las agujas del reloj, de esta forma, la cola se mueve en ese sentido
conforme se insertan y suprimen elementos, veámoslo.
Obsérvese que utilizando este modelo los
procedimientos poner y quitar se pueden implementar de manera que su
ejecución se realice en tiempo constante.

Existe un problema que aparece en la representación y en cualquier


variación menor de esta estrategia (por ejemplo que post apunte a la última
posición en el sentido de las agujas del reloj), el problema es que no hay forma
de distinguir una cola vacia de una que llene el círculo completo salvo que
mantengamos un bit que sea verdad si y solo si la cola está vacia., si no
deseamos mantener este bit debemos prevenir que la cola llene alguna vez la
matriz.

Para ver por qué puede pasar esto, supongamos que la cola de la figura
anterior tuviera MAX_LONG elementos, entonces, post apuntaría a la posición
anterior en el sentido de las agujas del reloj de ant, ¿qué pasaria si la cola
estuviese vacia?, para ver como se representa una cola vacia, consideramos
primero una cola de un elemento, entonces post y ant apuntarian a la misma
posición, si extraemos un elemento, ant se mueve una posición en el sentido de
las agujas del reloj, formando una cola vacia., por tanto una cola vacia
tiene post a una posición de ant en el sentido de las agujas del reloj, que es
exactamente la misma posición relativa que cuando la cola tenia MAX_LONG
elementos, por tanto vemos que aún cuando la matriz tenga MAX_LONG
casillas, no podemos hacer crecer la cola más allá de MAX_LONG-1 casillas, a
menos que introduzcamos un mecanismo para distinguir si la cola está vacía o
llena.

Veamos la especificación de la función de abstracción y el invariante de


la representación de este tipo de cola:

Función de Abstracción

Dado el objeto del tipo rep r, r = {elementos, Lmax, ant, post}, el


objeto abstracto al que representa es:

Si r.ant == (r.post + 1) % r.Lmax, entonces c = <>

En otro caso : c = < r.elementos[r.ant], r.elementos[(r.ant+1) %


r.Lmax],..., r.elementos[r.post] >

Invariante de Representación

0 < r.Lmax.
0 <= r.ant < r.Lmax.

0 <= r.post < r.Lmax.

r.post != r.post.

r.elementos apunta a una dirección con espacio para al menos r.Lmax


componentes.

De manera que la clase Cola (con implementación basada en matrices


circulares) en C++ tendría el siguiente aspecto:

Class Cola
{

Public:
Cola (int LongMax = 100);
Cola (const Cola<T> &c);
bool vacia () const;
T& cabecera ();
void poner (const T& elem);
void quitar ();
int num_elementos () const;
~Cola ();
Private:
T *elementos;
const int Lmax;
int ant;
int post;

bool llena () const;


}

Pasemos ahora a ver la implementación de cada una de las primitivas


enunciadas con anterioridad en C++:

template <class T>

inline Cola<T>::Cola(int LongMax) : Lmax(LongMax + 1)

elementos = new T [LongMax + 1];

assert(elementos != 0);

ant = 0;

post = Lmax -1;

template <class T>

inline Cola<T>::Cola(const Cola<T> & c) : Lmax(c.Lmax)

{
ant = c.ant;

post = c.post;

elementos = new T [Lmax];

assert(elementos != 0);

for (int i = ant; i != post; i = (i + 1) % Lmax)

elementos[i] = c.elementos[i];

elementos[post] = c.elementos[post];

template <class T>

inline bool Cola<T>::llena() const

return ((post + 2) % Lmax) == ant;

template <class T>

inline bool Cola<T>::vacia() const

return ((post + 1) % Lmax) == ant;

template <class T>

inline T& Cola<T>::cabecera()

return elementos[ant];

}
template <class T>

inline void Cola<T>::poner(const T & elem)

assert(!llena());

post = (post + 1) % Lmax;

elementos[post] = elem;

template <class T>

inline void Cola<T>::quitar()

assert(!vacia());

ant = (ant + 1 ) % Lmax;

template <class T>

inline int Cola<T>::num_elementos() const

if (vacia())

return 0;

if (post > ant)

return post - ant + 1;

else
return (Lmax - ant + 1) + (post + 1);

template <class T>

inline Cola<T>::~Cola()

delete [] elementos;