Documente Academic
Documente Profesional
Documente Cultură
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)
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.
función inicializar
primero = nulo
ultimo = nulo
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
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...
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()
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;
}
…………………………………….
Char* c= “C”;
{
Char * rotulo;
Int código;
};
#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”) ;
}
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”) ;
}
Codificación
Struct fecha
{
Int d, m, a;
Float x;
};
Struct dato
{
Char* mes;
Struct fecha* f;
} t;
t.f -> x ;
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 ( ).
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
2.- Escriba una función entera que devuelva el numero de nodos de una lista
enlazada.
Codificación
3.- Escriba una función que elimine el nodo que ocupa la posición i de una
lista enlazada ordenada
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.
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
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).
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
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 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;
}
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
Codificación
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
Codificación
#include <stdio.h>
#include <stdlib.h>
Void VaciaP(Pila** P)
{
*P = 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;
}
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.
Codificación
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);
}
}
VaciaP(Pcop);
While (!EsVaciaP(P))
{
E = PrimeroP(p);
BorrarP(&P);
AnadeP(Pcop,e);
}
}
Codificación
Void LiberarPila(Pila**P)
{
While (!EsVaciaP(*P))
BorrarP(P);
}
Int SonIgualesPilas(Pila *P, Pila* P1)
{
Int sw = 1;
TipoDato e,e1;
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
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.
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
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
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 listas enlazadas.
Implementación
/*
* C++ - Colas/Queue
* Site : martincruz.me
*/
#include <iostream>
using namespace std;
------------------------------------------------------------------------*/
struct nodo
int nro;
};
/* Estructura de la cola
------------------------------------------------------------------------*/
struct cola
nodo *delante;
nodo *atras ;
};
/* Encolar elemento
------------------------------------------------------------------------*/
aux->nro = valor;
aux->sgte = NULL;
else
(q.atras)->sgte = aux;
/* Desencolar elemento
------------------------------------------------------------------------*/
int num ;
num = aux->nro;
q.delante = (q.delante)->sgte;
return num;
/* Mostrar Cola
------------------------------------------------------------------------*/
void muestraCola( struct cola q )
aux = q.delante;
aux = aux->sgte;
------------------------------------------------------------------------*/
aux = q.delante;
q.delante = aux->sgte;
delete(aux);
q.delante = NULL;
q.atras = NULL;
}
/* Menu de opciones
------------------------------------------------------------------------*/
void menu()
/* Funcion Principal
------------------------------------------------------------------------*/
int main()
struct cola q;
q.delante = NULL;
q.atras = NULL;
system("color 0b");
do
switch(op)
case 1:
encolar( q, dato );
break;
case 2:
x = desencolar( q );
break;
case 3:
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;
};
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:
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
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:
Elemento desencolado
Elemento desencolado
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.
#include <stdio.h>
int main() {
pNodo primero = NULL, ultimo = NULL;
C++ Estándar
Programación con el Estándar ISO y la Biblioteca de Plantillas (STL)
© Paraninfo Thomson Learning 2001
2. Conceptos básicos
2.1) Quedaría del siguiente modo:
void main() {
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
void main() {
double d1, d2;
out << "Introduce dos reales: ";
cin >> d1 >> d2;
cout << "La suma es: " << d1 + d2 << endl
}
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:
";
*/
/*
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;
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
for(a; b; c);
se convierte en:
{
a;
while(b) {
d;
c;
}
}
b) int i= 0, j= 0; // Correcto
// Dos declaraciones de tipo entero
while ..
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
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í:
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.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.
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:
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)
}
}
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.
sino a:
7.7) Este sería un programa que volvería loco al propio Bjarne Stroustrup:
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)
}
}
8. Sobrecarga y conversiones
8.1) En C++, las constantes tienen tipo por lo que el compilador asignará:
- 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:
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.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.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 ).
3.- El literal 0F da error de sintaxis, ya que F sólo se puede aplicar a constantes reales.
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.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;
}
#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.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.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.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 >>.
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:
10.12) Es correcto ya que en los macros los comentarios no son expandidos. Esto se verá mejor cuando se vea
preprocesamiento.
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.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();
..
};
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.
class clase {
static int Estatuto;
..
};
int clase::Estatuto= 23;
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
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
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.13) Es correcto aunque es más recomendable definir la unión dentro de una clase.
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"
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.
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?
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:
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.
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.
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);
}
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
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);
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.
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();
}nodoc;
main()
{
int opcion;
do
{
system("color ");
system("CLS");
printf("\n\t\t\t\t***MENU***\n");
printf("\n\n Trabajo Colas\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).
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.
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:
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.
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.
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.
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 =)
1.
2.
2 marcela hernandez said at 9:56 am on octubre 26th, 2009:
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.
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:
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
*/
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;
}
nodo.h
#ifndef NODO_H
#define NODO_H
/*
* Autor: William David Parras Mendez
* Dependencias: Ninguna
*/
/* Inicializa componentes */
template <class T>
Nodo<T>::Nodo(){
nxt = NULL;
data = 0;
}
#include <iostream>
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.
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.
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++
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 */
struct NODE{
elemento info;
};
typedef struct {
int longitud;
}COLA;
/* DECLARACIÓN DE FUNCIONES */
int vacia(COLA);
int mostrar_cola(COLA);
int tamagno(COLA);
main(){
int opcion;
COLA C;
elemento x;
C.longitud=-1;
do{
switch(opcion){
case 4: mostrar_cola(C);break;
case 5: borrar_cola(&C);break;
case 6: break;
getchar();getchar();
}while (opcion!=6);
/* FUNCIONES */
/* Función que inicializa la cola */
(*C).prim=NULL;
(*C).ult=NULL;
(*C).longitud=0;
else{
if (C.longitud==0) return 1;
else return 0;
*tmp=(nodo*)calloc(1,sizeof(nodo));
if ((*tmp)==NULL) return 1;
else return 0;
return C.longitud;
}
/* Función que inserta elementos en la cola */
posicion tmp;
if (vacia(*C)<0){
return 0;
else {
if (llena(&tmp)){
return 0;
(*tmp).info=x;
if(vacia(*C)==1)
else if(vacia(*C)==0)
(*C).ult->sgte=tmp; /* Se inserta a
continuación del último */
return 1;
}
}
posicion tmp;
if (vacia(*C)<0){
return 0;
tmp=(*C).prim;
*x=tmp->info;
return 1;
return 0;
}
posicion aux;
int longitud;
if (longitud<0){
return 0;
return 0;
else{
printf("\n");
longitud--;
aux=(*aux).sgte; /* aux avanza al siguiente
elemento */
return 1;
elemento x;
return 0;
else {
dequeue(C,&x);
return 1;
#include <iostream>
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();
}
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++
Trauma
10.1k21243
José A. Solís
227
añade un comentario
1 respuesta
activasmás antiguas votos
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:
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
Trauma
10.1k21243
añade un comentario
Editores:
Oscar E. Palacios
← Librería Estándar de Plantillas
Sumario
[ocultar]
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
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>
while (! s.empty() )
{
cout << s.front() << " " ;
s.pop();
}
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>
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;
}
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>
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;
}
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>
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;
system("PAUSE");
return EXIT_SUCCESS;
}
Contenido de la priority_queue
banana
coco
manzana
pera
uva
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).
Primitivas de la cola
Bool vacia () const -> informa si la cola está vacía, devuelve true si la cola
está vacía, false en otro caso.
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.
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;
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
Dado el objeto del tipo rep r, el objeto abstracto al que representa es:
Invariante de Representación
true.
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;
inline Cola<T>::Cola()
return cola.vacia();
return cola.elemento(cola.primero());
cola.insertar(cola.final(), elem);
cola.borrar(cola.primero());
}
template <class T>
return cola.num_elementos();
inline Cola<T>::~Cola()
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.
Función de Abstracción
Invariante de Representación
0 < r.Lmax.
0 <= r.ant < r.Lmax.
r.post != r.post.
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;
assert(elementos != 0);
ant = 0;
{
ant = c.ant;
post = c.post;
assert(elementos != 0);
elementos[i] = c.elementos[i];
elementos[post] = c.elementos[post];
return elementos[ant];
}
template <class T>
assert(!llena());
elementos[post] = elem;
assert(!vacia());
if (vacia())
return 0;
else
return (Lmax - ant + 1) + (post + 1);
inline Cola<T>::~Cola()
delete [] elementos;