Anda di halaman 1dari 9

Maratones de programación para Dummies

Daniel Ampuero Anca


Junio 28, 2011

Esta guı́a básica se enfoca en resolver dudas concretas con respecto a la


solución de problemas en un maratón de programación.
Los temas a tratar en esta guı́a serán:
I/O
STL
Tips elementales para realizar pruebas

1. I/O
En C++, la biblioteca que maneja la entrada y salida es iostream. Sin em-
bargo, siempre es posible usar la biblioteca de C cstdio para realizar tales tareas.
Es importante saber en qué se diferencian y cuándo debemos usar una y no otra.
La biblioteca iostream de C++ nos permite realizar lecturas/escrituras de
forma más versátil, pues no es necesario espeficar el tipo de dato al momento
de hacer la lectura o escritura.
Por ejemplo, para leer cualquier variable de tipo T, se puede usar el siguiente
código:

T x;
c i n >> x ;

Para realizar escrituras, es muy similar:

T x = T( ) ;
c o u t << x ;

Tanto cin como cout, saben el tipo de dato de x y lo manejan de la forma


apropiada.
Sin embargo, si se usa cstdio para realizar I/O, se debe ser más concreto con
el tipo de dato que se lee.
El siguiente ejemplo lee un entero:

1
int x ;
s c a n f ( ” %d” , &x ) ;

Lo que encierran las comillas en el scanf determina el patrón de lo que se va


a leer. Para cada tipo de datos existe un patrón:

%d para int

%f para float
%s para char * o char[ ]
%lf para double
%c para char

De igual forma, estos patrones aplican para cuando se quiere escribir.


El siguiente ejemplo imprime dos números reales con dos y tres decimales
decimales respectivamente:

float x = 9.088494 , y = 7.4493;


s c a n f ( ” %.2 f %.3 f ” , x , y ) ;

Algo que se debe considerar al momento de escoger usar uno u otro método
para leer y escribir es la velocidad. En este sentido, la biblioteca de C otorga
mucho mejor rendimiento en tiempo que la de C++, siendo, en algunos casos,
la diferencia entre un problema aceptado y uno rechazado por exceso de tiempo.

2. STL
Un maratón donde no se use STL, es un maratón donde se pierde MUCHO
tiempo. Existen varias estructuras y algoritmos de la STL que son especialmente
útiles en un maratón. A continuación, algunas de ellas.

2.1. sort
Disponible a partir de la biblioteca algorithm, esta función realiza ordena-
ción sobre contenedores de acceso aleatorio (vectores, arreglos, strings) usando
quicksort con tiempo promedio de O(NlogN).
El siguiente es un ejemplo de su uso, tanto en estructuras con iteradores,
como en estructuras si ellos.

s t r i n g s t r = ” Navi e s t a en p e l i g r o ! ” ;
s o r t ( s t r . b e g i n ( ) , s t r . end ( ) ) ;

2
int a r r [ 6 ] = {8 , 2 , 4 , 1 , 3 , 6 } ;
s o r t ( a r r , a r r +6) ;

Por defecto, sort ordena de menor a mayor, pero se puede usar para que lo
haga de forma distinta. Por ejemplo:

s t r i n g s t r = ” Navi e s t a en p e l i g r o ! ” ;
s o r t ( s t r . r b e g i n ( ) , s t r . rend ( ) ) ; // ordena de mayor a menor

int a r r [ 6 ] = {8 , 2 , 4 , 1 , 3 , 6 } ;
s o r t ( a r r , a r r +6 , g r e a t e r <int >() ) ; // ordena de mayor a menor

2.2. vector
Pocas estructuras son tan útiles como vector<T>, pues ofrece las ventajas
del dinamismo de las listas y del acceso aleatorio y veloz de los arreglos estáti-
cos. Un vector<T> es un arreglo dinámico que pudiera crecer dependiendo de
las necesidades del programador. Al igual que todas las estructuras en C++,
vector<T> es una plantilla (template).
El siguiente es un ejemplo de cómo usarlo, tanto en el caso que no se conozca
el tamaño de antemano, como en caso que sı́.

v e c t o r <int> v1 ;
int next ;
// l e e r e n t e r o s h a s t a que n e x t s e a d i s t i n t o de 0
while ( s c a n f ( ” %d” , &n e x t ) !=EOF && n e x t ) {
v1 . p u s h b a c k ( n e x t ) ;
}

int n ;
c i n >> n ;
v e c t o r <s t r i n g > v2 ( n ) ; // c r e a un v e c t o r de n p o s i c i o n e s
f o r ( i n t i =0; i <v2 . s i z e ( ) ; ++i ) {
// l e o n v e c e s c o l o c a n d o e l r e s u l t a d o en e l a r r e g l o
c i n >> v2 [ i ] ;
}

2.3. map
map<K, T> es un arreglo asociativo implementado con árboles rojo-negro.
En pocas palabras: permite almacenar objetos cuyas claves no son enteras.
Un uso común de esta estructura es un diccionario. Por ejemplo:

map<s t r i n g , s t r i n g > z D i c t ;

3
z D i c t [ ”Deku Tree ” ] = ” V e n e r a b l e a r b o l l o c a l i z a d o en K o k i r i ” ;
z D i c t [ ”Ganon” ] = ” Bicho malo que hay que matar ” ;
z D i c t [ ” Z e l d a ” ] = ” P r i n c e s a que hay que r e s c a t a r ” ;
z D i c t [ ” Link ” ] = ” Heroe d e l tiempo ” ;

Hay que tener cuidado con el uso de la estructura map<K, T>, porque crea
las entradas aún cuando sólo estemos consultando su existencia.
El siguiente ejemplo ejemplificará este caso:

map<s t r i n g , s t r i n g > z D i c t ;
// l o l l e n a m o s con t e r m i n o s a p a r e n t e m e n t e a l e a t o r i o s . . .

// MAL
i f ( z D i c t [ ” Mickey Mouse” ] == ” ” ) { // e x i s t e Mickey en e l z D i c t ?
c o u t << ” El r a t o n no e s t a en c a s a ” << e n d l ;
}

// BIEN
i f ( z D i c t . count ( ” Mickey Mouse” ) == 0 ) {
c o u t << ” El r a t o n no e s t a en c a s a ” << e n d l ;
}

Por último, es importante saber que dado que map<K, T> es una estructura
que usa un árbol binario balanceado para realizar las asociaciones, las claves
(K) que se usen deben poder tener una relación de orden. En caso que no la
tengan, siempre es posible sobrecargar el operador menor que para que exista
una relación de orden.
Por ejemplo, supongamos que queremos indexar personas, y de ellas tenemos
su nombre y edad, la sobrecarga se realizarı́a de la siguiente forma:

class persona {
public :
s t r i n g nombre ;
i n t edad ;

p e r s o n a ( ) {}

// s o b r e c a r g a
bool operator <(const p e r s o n a& o t h e r ) const {
i f ( nombre == o t h e r . nombre )
return edad < o t h e r . edad ;
return nombre < o t h e r . nombre ;
}
};

Esta sobrecarga también es útil cuando deseamos hacer sort o usar la clase
set<T>.

4
2.4. Pilas y colas
Aún cuando las pilas y las colas pueden ser simuladas con listas enlazadas o
arreglos, la STL provee su propia implementación de ellas optimizada para las
situaciones tı́picas de éstas.

2.4.1. stack y queue


La clase stack<T> representa una pila. Tiene cuatro operaciones básicas:

Apilar
Ver el tope
Desapilar
¿Está vacı́a?

Por otro lado, queue<T> representa una cola. Tiene cuatro operaciones
básicas:

Encolar
Ver el frente
Desencolar
¿Está vacı́a?

El siguiente código muestra un ejemplo del uso de la pila de STL.

// V o l t e a r una p i l a s i n u s a r e s t r u c t u r a s a u x i l i a r e s

#i n c l u d e <i o s t r e a m >
#i n c l u d e <s t a c k >
#i n c l u d e < s t d l i b . h>

using namespace s t d ;

// L l e n a r l a p i l a l a p i l a con e n t e r o s a l e a t o r i o s
void l l e n a r ( s t a c k <int> &s t , i n t n ) {
f o r ( i n t i =0; i <n ; ++i )
s t . push ( rand ( ) ) ;
}

void m a n d a r a l f o n d o ( i n t e , s t a c k <int> &s t , i n t n ) ;

// V o l t e a r l a p i l a
void v o l t e a r ( s t a c k <int> &s t ) {
f o r ( i n t i=s t . s i z e ( ) −1; i >1; −−i ) {
i n t t o p e = s t . top ( ) ;
s t . pop ( ) ; // OJO, s i e m p r e h a c e r POP a l d e s a p i l a r !
m a n d a r a l f o n d o ( tope , s t , i ) ;
}

5
}

// Manda n p o s i c i o n e s a l f o n d o un e l e m e n t o
void m a n d a r a l f o n d o ( i n t e , s t a c k <int> &s t , i n t n ) {
i f ( n==0)
s t . push ( e ) ;
else {
i n t aux = s t . top ( ) ; s t . pop ( ) ;
m a n d a r a l f o n d o ( e , s t , n−1) ;
s t . push ( aux ) ;
}
}

void m o s t r a r ( s t a c k <int> s t ) {
while ( ! s t . empty ( ) ) {
c o u t << s t . top ( ) << e n d l ;
s t . pop ( ) ;
}
c o u t << ”−−−−” << e n d l ;
}

i n t main ( ) {
s t a c k <int> s t ;
l l e n a r ( st , 10) ;
mostrar ( s t ) ;
voltear ( st ) ;
mostrar ( s t ) ;
return 0 ;
}

El siguiente código ejemplifica el uso de una cola en un recorrido por anchura


en un grafo representado con una matriz de adyacencias.

const i n t MAXV = 5 0 0 ;
bool m a t r i z [MAXV] [MAXV] ;

typedef p a i r <int , int> p i i ;

// Encontrar un camino minimo e n t r e i n i c i o y f i n


void BFS( i n t i n i c i o , i n t f i n ) {
v e c t o r <bool> v i s i t a d o (MAXV, f a l s e ) ;
queue<p i i > q ;
q . push ( m a k e p a i r ( i n i c i o , 0 ) ) ; // nodo , p a s o s
v i s i t a d o [ i n i c i o ] = true ;
while ( ! q . empty ( ) ) {
p i i a c t u a l = q . f r o n t ( ) ; q . pop ( ) ;
const i n t nodo = a c t u a l . f i r s t , p a s o s = a c t u a l . s e c o n d ;
f o r ( i n t i =0; i <MAXV; ++i ) {
i f ( m a t r i z [ nodo ] [ i ] && ! v i s i t a d o [ i ] ) {
v i s i t a d o [ i ] = true ;
q . push ( m a k e p a i r ( i , p a s o s +1) ) ;
}
}
}

6
}

2.5. priority queue


La clase priority queue<T> está implementada como un montı́culo de máxi-
mos (max-heap), lo que significa que la operación de obtener el máximo siempre
es O(1), en tanto que la inserción y eliminación de elementos es O(logN).
Existen cuatro operaciones básicas:

Obtener el mayor elemento


Remover el mayor elemento
Insertar un elemento
¿Es vacı́a?

El siguiente código hace heapsort y ejemplifica el uso de la cola de prioridad.

// Declaramos y l l e n a m o s e l v e c t o r de e n t e r o s
v e c t o r <int> v ( 1 0 ) ;
f o r ( i n t i =0; i <v . s i z e ( ) ; ++i ) {
v [ i ] = rand ( ) ;
// . . . y l o imprimimos
c o u t << v [ i ] << e n d l ;
}

// Un s e p a r a d o r
c o u t << ”−−−−−” << e n d l ;

// Declraramos y l l e n a m o s l a c o l a de p r i o r i d a d
p r i o r i t y q u e u e <int> pq ;
f o r ( i n t i =0; i <v . s i z e ( ) ; ++i )
pq . push ( v [ i ] ) ;

// Ahora vaciamos l a c o l a de p r i o r i d a d
f o r ( i n t i =0; ! pq . empty ( ) ; ++i ) {
v [ i ] = pq . top ( ) ;
c o u t << v [ i ] << e n d l ;
pq . pop ( ) ;
}

2.6. Iteradores
A excepción de vector<T> y string, la mayorı́a de las estructuras contene-
doras en C++ deben ser recorridas con iteradores.
Usar iteradores es más sencillo de lo que parece. Tengamos por ejemplo, un
set de enteros:

7
s e t <int> s ; // p a r e c i d o a map , p e r o K = T

// l o l l e n a m o s con c o s a s a l e a t o r i a s
f o r ( i n t i =0; i <100; ++i )
s . i n s e r t ( rand ( ) ) ;

// ahora l o r e c o r r e m o s
f o r ( s e t <int > : : i t e r a t o r i t e r = s . b e g i n ( ) ; i t e r != s . end ( ) ; i t e r ++)
c o u t << ∗ i t e r << e n d l ;

Para usar iteradores en otras estructuras, sólo es necesario cambiar el tipo


de dato delante de la palabra ::iterator. Recuerda que siempre se debe colocar
el tipo de dato de los objetos de la plantilla.

2.7. ¡Importante!
Para usar cada una de estas estructuras, es necesario incluir la biblioteca
adecuada, i.e., el nombre de la estructura. Por ejemplo:

#include <v e c t o r >


#include <map>
#include <s t r i n g >
#include <a l g o r i t h m >

using namespace s t d ; \ b e g i n { f l u s h l e f t }\ end { f l u s h l e f t }

3. Tips elementales para realizar pruebas


3.1. Probar el programa
Una vez que la solución a un problema está escrita, es necesario probarla
para verificar que se obtiene la salida correcta.
Lo primero que se debe hacer, es compilar el programa en consola:

g++ programa.cpp -o programa -ggdb

Seguidamente, escribimos un archivo de texto con la entrada que queremos


probar y lo nombramos input.in.
Luego, se ejecuta:

./programa < input.in > output.out

El uso de ¡ y ¿ permite que se redirija la entrada y la salida respectivamente,


evitando la molestia de introducir los datos a mano cada vez que se quiera probar
un programa.

8
3.2. Depurar el programa
En caso que falle el programa, siempre se tiene la opción de depurarlo con
gdb. Para ello se escribe en consola:

gdb programa

Una vez dentro del shell de gdb, corremos el programa:

run input.in

Si el debugger se detiene en el main, escribimos:

continue

Para encontrar errores de segmentación o cualquier otro error sucedido du-


rante la ejecución del programa, escribimos:

where

También es posible colocar puntos de parada en el código, mediante el co-


mando break. Por ejemplo, si queremos que se detenga en la lı́nea 35, escribimos:

break 35

Podemos seguir paso a paso el curso del programa con el comando step e
imprimir el estado de las variables con print.

Typesed in LATEX

Anda mungkin juga menyukai