Qdoc - Tips Aaa Clases 2013

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 103

Apuntes de la clase de Programación Fecha: 1 de febrero de 2013

MSc. José Colbes

Bibliografía:
 "THE C PROGRAMING LANGUAGE" Kernighan y Ritchie - Pre ntice-Hall, Segunda Edición.
 “FUNDAMENTOS DE PROGRAMACIÓN: ALGORITMOS, ESTRUCTURAS DE DATOS Y
OBJETOS" Luis Joyanes Aguilar - McGraw-Hill  – Cuarta Edición
 “CÓMO PROGRAMAR EN C/C++ Y JAVA” Deitel – Prentice-Hall – Cuarta Edición.

Algoritmos, programas y lenguajes de programación

Algoritmo: (del griego y latín, dixit algorithmus y este a su vez del matemático persa Al-Juarismi,
siglo IX). Enunció paso a paso las reglas para ejecutar las operaciones básicas en matemáticas.

Algoritmo:   Conjunto de pasos para resolver un problema. “Un algoritmo es un procedimiento


computacional bien definido que toma un conjunto de valores como entrada y produce un
conjunto de valores como salida.”

Características
Características de un algoritmo
 Preciso: indicar el orden de realización en cada paso.
 Definido: si se sigue dos veces, obtiene el m ismo resultado cada vez (dudoso)
 Finito: tiene un fin. Tiene un número determinado de pasos.

Ejemplos de algoritmos de la vida cotidiana


 Suma de dos números enteros
 Recetas de cocina
 Cambiar la rueda de un auto

- Aflojar los tornillos de la rueda pinchada


- Levantar el auto hasta que la rueda pueda girar libremente
- Quitar los tornillos y la rueda pinchada
- Colocar la rueda de repuesto
re puesto y los tornillos
- Bajar el auto
- Apretar los tornillos

Formas de expresión de un algoritmo


 Lenguaje natural
Ejemplo: verificar si un número X es primo.
El algoritmo más sencillo que puede utilizarse para saber si un número  X  es  es primo es el siguiente:
se divide X  entre
 entre 2,3,4,5,..., X 
 X -1.
-1. Si X es divisible por alguno de e llos, no es un número primo.

 Fórmulas
Ejemplo: Hallar las raíces de         
(√  )
)
 

(√  )
)
 


 Diagramas de flujo
 Diagramas de flujo

 Pseudocódigo
Ejemplo: Suma de dos números

Entrada: a,b → números
Salida: s, la suma de a y b

inicio
leer (a,b)
s=a+b
escribir (s)
fin

 Programa
#include<stdio.h>
#include<conio.h>
void main()
{
int a, b, s;
printf("Ingrese el primer número ");
scanf("%d",&a);
printf("\n\nIngrese el segundo numero ");
scanf("%d",&b);
s=a+b;
printf("\n\nEl resultado de la adicion es: %d",s);
getch();
}

Lenguajes de programación

 Lenguaje de máquina: dependiente del procesador, usa 0 y 1.


 Lenguajes de bajo nivel: ensamblador. Difícil de aprender y dependiente de la máquina.
 Lenguajes de alto nivel: C, C++, C#, Java, Pascal, Visual Basic, Fortran, otros. Se asemeja
más al razonamiento humano.

Contadores y acumuladores
CON ← CON + 1
ACU ← ACU + X

Diseño de programas
 Programación modular (lógica fácil de seguir)
 Programación estructurada (escritura fácil de comprender)

Reglas de la programación
programación estructurada
 El programa tiene un diseño modular
Los módulos son diseñados de modo descendente.
 Diagramas de flujo

 Pseudocódigo
Ejemplo: Suma de dos números

Entrada: a,b → números
Salida: s, la suma de a y b

inicio
leer (a,b)
s=a+b
escribir (s)
fin

 Programa
#include<stdio.h>
#include<conio.h>
void main()
{
int a, b, s;
printf("Ingrese el primer número ");
scanf("%d",&a);
printf("\n\nIngrese el segundo numero ");
scanf("%d",&b);
s=a+b;
printf("\n\nEl resultado de la adicion es: %d",s);
getch();
}

Lenguajes de programación

 Lenguaje de máquina: dependiente del procesador, usa 0 y 1.


 Lenguajes de bajo nivel: ensamblador. Difícil de aprender y dependiente de la máquina.
 Lenguajes de alto nivel: C, C++, C#, Java, Pascal, Visual Basic, Fortran, otros. Se asemeja
más al razonamiento humano.

Contadores y acumuladores
CON ← CON + 1
ACU ← ACU + X

Diseño de programas
 Programación modular (lógica fácil de seguir)
 Programación estructurada (escritura fácil de comprender)

Reglas de la programación
programación estructurada
 El programa tiene un diseño modular
Los módulos son diseñados de modo descendente.
Contadores y acumuladores
CON ← CON + 1
ACU ← ACU + X

Diseño de programas
 Programación modular (lógica fácil de seguir)
 Programación estructurada (escritura fácil de comprender)

Reglas de la programación
programación estructurada
 El programa tiene un diseño modular
 Los módulos son diseñados de modo descendente.
 Cada módulo se codifica utilizando las tres estructuras de control básicas .

Obs: en la programación estructurada no se utiliza la sentencia GOTO.

Estructuras de control en la programación estructurada


 Secuenciales
 Selectivas
 Repetitivas

El término control de flujo (o flujo de control) se refiere al orden en que se ejecutan las sentencias
del programa. A menos que se especifique expresamente, el flujo normal de control de todos los
programas es el secuencial . Este término significa que las sentencias se ejecutan en secuencia, una
después de otra, en el orden
or den en que se sitúan dentro del programa.

Acción 1

Acción 2

Acción N

Las estructuras de selección, repetición


re petición e invocación permiten que el flujo secuencial del programa
sea modificado en un modo preciso y definido con anterioridad.

Estructuras selectivas
 Si (if )
 Según-sea (switch case)

Sentencia SI (Simple – Doble – Anidamiento)
Diagrama de Flujo
si ( condicion )
{
Instrucción 1
Instrucción 2
… Acciones Condición Acciones
sino
Instrucción 3

}
Sentencia SI (Simple – Doble – Anidamiento)
Diagrama de Flujo
si ( condicion )
{
Instrucción 1
Instrucción 2
… Acciones Condición Acciones
sino
Instrucción 3

}

Ejemplo
Determinar si un número es par o impar

Anidamiento  – Ejemplo: Mostrar la calificación de un alumno a partir del puntaje obte nido en el
examen.

inicio
cls()
imprimir ("Ingrese la nota del alumno: ")
leer (pnt)
si (pnt >= 90)
{
cal = 5
sino si (pnt >= 80)
cal = 4
sino si (pnt >= 70)
cal = 3
sino si (pnt >= 60)
cal = 2
sino
cal = 1
}
imprimir(cal)
fin

Estructuras repetitivas
Hay una gran variedad de situaciones que requieren que una o varias
instrucciones se repitan varias veces, ya sean cálculos u otro tipo de
instrucciones. Las estructuras repetitivas abren la posibilidad de realizar una
secuencia de instrucciones más de una vez. L as más conocidas son:

 Mientras (while)
 Repetir (do-while)
 Para ( for )

Conceptos de bucle (lazo) e iteración


Un bucle, es una sección de código que se repite. Es decir cuando se termina de ejecutar la última
instrucción del conjunto, el flujo de control retorna a la primera sentencia y comienza una nueva
repetición de las sentencias que forman esa sección de código. Se denomina iteración al hecho de
repetir la ejecución de una secuencia de acciones, la iteración se asocia a un número entero que
indica el número de veces que se repite un trozo de código.

Mientras – Sintaxis
mientras(condicion)
{
Conceptos de bucle (lazo) e iteración
Un bucle, es una sección de código que se repite. Es decir cuando se termina de ejecutar la última
instrucción del conjunto, el flujo de control retorna a la primera sentencia y comienza una nueva
repetición de las sentencias que forman esa sección de código. Se denomina iteración al hecho de
repetir la ejecución de una secuencia de acciones, la iteración se asocia a un número entero que
indica el número de veces que se repite un trozo de código.

Mientras – Sintaxis
mientras(condicion)
{
instrucciones
}

Repetir – Sintaxis
repetir
{
instrucciones
} hasta(condición)

Para (o Desde)  – Sintaxis


para var_control = valor_inicio hasta valor_fin [increment o paso]
{
instrucciones
}

Ejercicios

1. Escribir un algoritmo que obtenga el perímetro y área de un rectángulo, dados la base y la


altura del mismo.
2. Se desea obtener el salario de un trabajador conociendo la cantidad de horas trabajadas,
el salario por hora y la tasa de impuesto (%) que se aplica como deducción.
3. Desarrollar un algoritmo que permita leer dos valores distintos, determinar cual de los dos
valores es el mayor y escribirlo.
4. Desarrollar un algoritmo que permita leer tres valores y almacenarlos en las variables A, B
y C, respectivamente. El algoritmo debe imprimir el mayor de los valores.
5. Desarrollar un algoritmo que realice la sumatoria de los números enteros comprendidos
entre el 1 y el 10, es decir, 1 + 2 + 3 + …. + 10.
6. Describir un algoritmo que permita intercambiar los valores de las variables A y B, a través
de una variable AUX.
7. Supongamos que se proporciona una secuencia de N números, tales como:

Mediante el contador CON, contar e imprimir el número de ceros de la secuencia.


8. Escribir un algoritmo que calcule el promedio de N números.
9. Dado un número natural , desarrolle un algoritmo que calcule la sumatoria y el promedio
de los números menores a  y que sean múltiplos de 3.

10. Realizar la división entre 2 números naturales (  y ) por restas sucesivas, y mostrar el
cociente y el resto de la división.
11. Desarrolle un algoritmo que permita determinar a partir de un número de días (ingresado
por pantalla), los años, meses, semanas y días que constituyen el número de días
proporcionado.
12. Ingresar números por teclado hasta o btener uno que sea positivo e imprimirlo.
13. Calcular el factorial de un número N.

Estructuras de datos

Hasta ahora sólo hemos trabajado con variables simples, que únicamente pueden almacenar un
10. Realizar la división entre 2 números naturales (  y ) por restas sucesivas, y mostrar el
cociente y el resto de la división.
11. Desarrolle un algoritmo que permita determinar a partir de un número de días (ingresado
por pantalla), los años, meses, semanas y días que constituyen el número de días
proporcionado.
12. Ingresar números por teclado hasta o btener uno que sea positivo e imprimirlo.
13. Calcular el factorial de un número N.

Estructuras de datos

Hasta ahora sólo hemos trabajado con variables simples, que únicamente pueden almacenar un
dato. Por ejemplo, al trabajar con listas de números (calificaciones de alumnos de una misma
clase, trabajadores de una empresa, serie de números enteros, etc.), su manejo con variables
simples puede ser un poco impráctico, además de no considerar el almacenamiento de las
entradas.

Pregunta 1: ¿Cómo podríamos ordenar números utilizando variables simples?


Pregunta 2: ¿Cómo saber la cantidad de alumnos con notas mayor al promedio de la clase?

Para salvar estas situaciones, la mayoría de los lenguajes de programación incluyen características
de estructuras de datos. Una estructura de datos es una colección de datos que pueden ser
caracterizados por su organización y las operaciones que se definen en ella.

Las estructuras de datos básicas  que soportan la mayoría de los lenguajes son los arreglos (arrays)
(siendo el vector un arreglo de una dimensión, y la matriz uno de dos dimensiones). Se dividen en:

 Estáticas (tamaño definido de antemano): arreglos, registros, cadenas


 Dinámicas (no tiene limitaciones de tamaño): listas (pilas/colas), listas enlazadas, árboles,
grafos.

Un arreglo  es una secuencia de posiciones de la memoria central a las que se puede acceder
directamente, que contiene datos del mismo tipo y pueden ser seleccionados individualmente
mediante el uso de subíndices.

Podemos considerar al vector como un conjunto de variables simples con datos del mismo tipo.

Ejemplo de vector: Notas de una clase

Nota 1 Nota 2 Nota 3 …  Nota  …  Nota 

El vector nota tiene subíndices o índices de sus elementos (1, 2, …,  , …, ) que indican la posición
de un elemento particular dentro del arreglo. Por ejemplo, si se desea modificar el tercer
elemento de un vector de tipo numérico:
nota[3] = 80

Nombre del vector Posición del elemento

Matrices
Matrices

La matriz puede verse como un vector de vectores. Por ello, se necesita especificar dos subíndices
para poder identificar cada uno de sus e lementos.

  
Columna
Nombre de la matriz
Fila

Apuntes de la clase de PED 2 Fecha: 15 de febrero de 2012


MSc. José Colbes

Lenguaje C

C es un lenguaje de programación de propósito general. Se lo ha asociado estrechamente con el sistema


operativo UNIX (donde se desarrolló), ya que tanto el sistema y la mayoría de los programas que se ejecutan en
él están escritos en C. El lenguaje, sin embargo, no está ligado a ningún sistema operativo o máquina, y aunque
se lo ha llamado “lenguaje de  programación del sistema”, porque es útil para escribir compiladores y sistemas
operativos; se ha utilizado también para escribir programas importantes en muchos ámbitos diferentes.

Se ha creado en 1972 por Brian W. Kernighan y Dennis M. Ritchie en los Laboratorios Bell como evolución del
Apuntes de la clase de PED 2 Fecha: 15 de febrero de 2012
MSc. José Colbes

Lenguaje C

C es un lenguaje de programación de propósito general. Se lo ha asociado estrechamente con el sistema


operativo UNIX (donde se desarrolló), ya que tanto el sistema y la mayoría de los programas que se ejecutan en
él están escritos en C. El lenguaje, sin embargo, no está ligado a ningún sistema operativo o máquina, y aunque
se lo ha llamado “lenguaje de  programación del sistema”, porque es útil para escribir compiladores y sistemas
operativos; se ha utilizado también para escribir programas importantes en muchos ámbitos diferentes.

Se ha creado en 1972 por Brian W. Kernighan y Dennis M. Ritchie en los Laboratorios Bell como evolución del
anterior lenguaje B, a su vez basado en BCPL.

“Se trata de un lenguaje fuertemente tipificado de medio nivel pero con muchas características de bajo nivel.
Dispone de las estructuras típicas de los lenguajes de alto nivel pero, a su vez, dispone de construcciones del
lenguaje que permiten un control a muy bajo nivel. Los compiladores suelen ofrecer extensiones al lenguaje que
 posibilitan mezclar código en ensamblador con código C o acceder directamente a memoria o dispositivos
 periféricos.” (Wikipedia)

Empezando en C

#include <stdio.h>
main()
{
printf("hello, world\n");
}

Este programa imprimirá a la salida:

Esquema básico de un programa:


directivas
main(){
declaraciones
instrucciones
….
}
funciones(){


}

Algunas observaciones:
 stdio.h (Standard input/output) es la librería principal en C, con funciones básicas de entrada/salida.
 Cuando en un archivo .c se encuentra una línea con un #include seguido de un nombre de archivo, el
preprocesador la sustituye por el contenido de ese archivo.
 La función main es la principal (el programa se ejecuta al comienzo de la misma).
 Un método de comunicación de datos entre funciones es que la función que llama proporcione una
lista de valores, llamados argumentos , a la función llamada. Los paréntesis que van después del
nombre contienen la lista de argumentos. En este ejemplo, main se define como una función que no
espera argumentos, lo cual está indicado por la lista vacía ().
 Las sentencias de una función están encerradas por llaves {}.
 Una función es llamada por su nombre, seguido por una lista de argumentos entre ():
o Por ejemplo, se llama a printf con el argumento “hola mundo!!\n”.
 El fin de una instrucción lleva un punto y coma ;
 La secuencia \n indica un salto de línea. Representa un solo carácter y se denomina secuencia de
escape, que son caracteres invisibles o difíciles de escribir.
Función printf – Secuencias de escape

Tipos de datos

 char: un byte, capaz de almacenar un carácter.


 int: un entero, típicamente refleja el tamaño natural de los enteros de la máquina que ejecuta el programa.
 float: punto flotante de precisión simple.
 double: punto flotante de alta precisión.
Declaraciones y asignaciones

 int a=5;
 char b=’a’; ó char b=97;
 float f=2.45;
 int d=05; (un cero antes del número indica que es el valor octal)
 int e=0xD; (el 0x ó 0X indica que se tiene un valor hexadecimal)

Obs1: para operaciones con caracteres, se debe manejar el código de representación (por ejemplo, tabla
ASCII). Por ejemplo, no es lo mismo el entero 9 que el carácter ’9’.

Obs2: en el lenguaje C no existen las cadenas como tipo de dato.

Reglas para los identificadores (ANSI C):


1. Un identificador se forma con una secuencia de letras (minúsculas de la a a la z; mayúsculas de la A a la
Z; y dígitos del 0 al 9).
2. El carácter subrayado o guión bajo (_) se considera como una letra más.
3. Un identificador no puede contener espacios en blanco, ni otros caracteres distintos de los citados,
como por ejemplo (*,;.:-+, etc.).
4. El primer carácter de un identificador debe ser siempre una letra o un (_), es decir, no puede ser un
dígito.
5. Se hace distinción entre letras mayúsculas y minúsculas. Así, Masa es considerado como un
identificador distinto de masa y de MASA.
6. ANSI C permite definir identificadores de hasta 31 caracteres de longitud.
Ejemplos de identificadores válidos son los siguientes:
 tiempo, distancia1, caso_A, PI, velocidad_de_la_luz
Por el contrario, los siguientes nombres no son válidos (¿Por qué?)
 1_valor, tiempo-total, dolares$, %final

Ejemplo 2

#include<stdio.h>
main(){
int a;
a=3;
printf(“%d\n”);
}

Opciones para printf


 %d: despliega el valor entero
 %c: despliega el carácter correspondiente al valor.
 %f: despliega en formato de punto flotante.
 %lf: despliega en formato double.
 %.2f: despliega en formato de punto flotante con 2 decimales (el 2 puede ser cualquier número
contemplado en la precisión del tipo de dato)

Ejercicio: escribir un programa que permita asignar valores enteros a 2 variables y que calcule e imprima por
pantalla el producto de ambos.
#include<stdio.h> #include<stdio.h>
main(){ main(){
int a,b,c; int a,b,c;
a=3; a=3; b=4;
b=4; printf(“%d\n”,c=a*b);
c=a*b }
printf(“%d\n”,c);
}

Para tener en cuenta: supongamos que F es un entero que indica la temperatura en grados Fahrenheit, y
queremos transformarla en grados Celsius, almacenando el valor en el entero C. ¿Son equivalentes las
siguientes expresiones?:
 C=5*(F-32)/9;
 C=5/9*(F-32)
Funciones
Ejemplo: escribir un programa que sume dos números  x  e
 e y .

#include<stdio.h> #include<stdio.h>
int suma(int,int); int suma(int x, int y){
main(){ return (x+y);
int a,b,c; }
a=3; b=5; main(){
c=suma(a,b); int a,b,c;
printf(“%d\n”,c); a=3; b=5;
} c=suma(a,b);
int suma(int x, int y){ printf(“%d\n”,c);
return (x+y); }
}

Una función tiene el siguiente formato:


tipo_devuelto nombre_funcion(declaración
nombre_funcion(declaración de parámetros, si los hay)
{
Declaraciones
Sentencias

return expresion;
}

Introducción de datos por teclado


 getchar()
Permite la introducción de un carácter por teclado, está incluida en stdio.h. Un aspecto importante es que se
toma el enter como un carácter.
int a,b;
a=getchar();
b=getchar();
printf(“%c %c”,a,b);

 getch() y getche()
Están incluidos en conio.h ( Console input and output ). Introducen caracteres sin la necesidad de un “enter” al
final. La diferencia entre ambas es que con getch() lo ingresado por teclado no aparece en pantalla, pero con
getche() sí.

 scanf()
Es análogo a printf, y se encuentra en stdio.h. La forma general de esta función es la siguiente:
int scanf("%x1%x2...", &arg1, &arg2, ...);

Ejemplo:
int a;
scanf(“%d”,&a);
printf(“%d”,a);

Variables globales y locales

#include<stdio.h> #include<stdio.h>
int cuad(int); void cuad();
main(){ int a;
int a; main(){
scanf(“%d”,&a); scanf(“%d”,&a);
printf(“%d\n”,cuad(a)); cuad();
} printf(“%d\n”,a);
int cuad(int a){ }
return (a*a); void cuad(){
} a=a*a;
}
 El uso más o menos frecuente de las variables globales dependen de las necesidades del programa y
estilo de programación adoptado.

Pregunta:  este programa permite intercambiar los valores de las variables a y b?

#include<stdio.h>
void intercambiar (int,int);
int main(){
int a,b;
scanf("%d%d",&a,&b);
intercambiar(a,b);
printf("%d %d",a,b);
return 0;
}
void intercambiar(int a, int b){
int aux;
aux=b;
b=a;
a=aux;
}

Operadores en C
 Aritméticos:
o Suma: +
o Resta: -
o Multiplicación: *
o División: /
o Resto: %

 Asignación:
o Operador de igualdad: =
o Operadores +=, -=, *= y /= . Es lo mismo poner a+=1; que a=a+1; (y análogamente para los
demás casos).
o Operadores incrementales (++ y --)
 i = 2;
  j = 2;
 m = i++; // despues de ejecutarse esta sentencia m=2 e i=3
 n = ++j; // despues de ejecutarse esta sentencia n=3 y j=3

 Relacionales:
o Igual que: ==
o Menor que: <
o Mayor que: >
o Menor o igual que: <=
o Mayor o igual que: >=
o Distinto que: !=

 Lógicos:
o And: &&
o Or: ||
o Operador negación lógica (!): Este operador devuelve un cero (false) si se aplica a un valor
distinto de cero (true), y devuelve un 1 (true) si se aplica a un valor cero (false). Su forma
general es: !expresion
Reglas de precedencia y asociatividad

Ejercicios
 Luego de ingresar el radio por teclado, hallar la cía y el área del círculo.
 Realizar un programa que permita la introducción por teclado (mediante getchar) de un valor entero de
una cifra y que calcule e imprima por pantalla el cuadrado del mismo.
Apuntes de la clase de PED 2 Fecha: 22 de febrero de 2012
MSc. José Colbes

Páginas interesantes:
 Stackoverflow (http://stackoverflow.com/): comunidad donde se puede encontrar mucha información
sobre varios algoritmos y lenguajes.
 ACM-ICPC Live Archive (https://icpcarchive.ecs.baylor.edu/): Repositorio que contiene una variedad de
problemas que aparecieron en las olimpiadas de programación de la ACM (Association for Computing
Machinery) Tienen “jueces virtuales” para verificar las soluciones a lo s problemas. Las olimpiadas se
centran en el diseño de algoritmos para los problemas planteados en las competencias; pudiéndose
emplear C, C++ y Java.
 SPOJ (http://www.spoj.com/): lo mismo que lo anterior.
 Ahmed-Aly (http://ahmed-aly.com/): sitio donde se realizan competencias virtuales de programación
(o bien entrenamientos), usando los repositorios de problemas antes mencionados (y otros más).

Estructuras selectivas

En C las sentencias se ejecutan sucesivamente una tras otra. Esto define un camino que va desarrollado el
programa. Sin embargo, habrá momentos en que el programa deba ejecutar determinadas partes dependiendo
del estado en el que se halle el programa o de las variables externas. Para ello pueden utilizarse las sentencias
condicionales o incondicionales.

Una sentencia condicional es una instrucción o grupo de instrucciones que se pueden ejecutar o no, en función
del valor de una condición.

Las sentencias que se ejecutan de forma incondicional son: break, continue, goto y return.

Bifurcaciones condicionales (if)

La sentencia if admite dos tipos de sintaxis:

if(expresión)
sentencia1;

if(expresión)
sentencia1;
else
sentencia2;

Permite tomar decisiones al programa. En su primera forma, la sentencia1 sólo se ejecuta si el resultado de


evaluar la expresión es verdadero (distinto de cero). En la segunda forma, tenemos dos posibilidades: si al
evaluar la expresión el resultado es verdadero se ejecuta la sentencia1, pero si el resultado es falso se
ejecuta la sentencia2. En cualquier caso sólo una de las dos sentencias se ejecuta.

Por ejemplo:

if(numero1==1)
printf(“la variable numero1 vale 1” );
else
printf(“la variable numero1 no vale 1” );

Tras evaluarse la expresión if y ejecutarse la sentencia adecuada, el programa continúa con la línea siguiente
a la de la última sentencia del if. Como sentencia vale cualquier tipo de sentencia válida en C, entre ellas la
propia sentencia if.

En este caso hablaremos de sentencias if anidadas. Por ejemplo:

if(num>0)
if(num==1)
printf(“num es igual a 1” );
else
printf(“num es mayor que 1”);
else
printf(“num es menor que 1” );
Cuando hay dos if  anidados y a continuación hay un else, éste pertenece al último if. Así en el caso
anterior el primer else corresponde al segundo if. Si queremos que un else pertenezca al primer if de un
if anidado deberemos encerrar al segundo entre llaves. Por ejemplo:

if(num>=0)
{
if(num==1)
printf (“num es igual a 1” );
}
else
printf(“num es menor que 0” );

• Cuando necesitamos ejecutar varias sentencias que dependen de un if, utilizaremos los bloques de
sentencias. Un bloque de sentencias es un grupo de sentencias encerradas entre llaves { y }. Por ejemplo:

if (num 0){
printf(“num %d \n”);
if (num>=0)
printf(“num igual a 0” );
if (num>=1)
printf(“num mayor o igual a 1” );
}

Ejemplo
Determinar si un número es par o impar

/*Ejercicio que señala si


un numero ingresado por teclado es par o impar*/
#include<stdio.h>

main(){
int a,b;
printf("Ingrese el nro:\n");
scanf("%d",&a);
if((a%2)==0) //Condicion para que el numero sea par
printf("El numero es par\n");
else
printf("El numero es impar\n");
}

Obs: Los comentarios son partes del código ignorados por el compilador, sólo sirven para o rientar al
programador o a otra persona que lee el código. Puede señalarse mediante /* */ y // (este último sólo sirve
para una línea)

Anidamiento  – Ejemplo: Mostrar la calificación de un alumno a partir del puntaje obtenido en el examen.

#include<stdio.h>
main(){
int nota, cal;
printf("Ingrese la nota:\n");
scanf("%d",&nota);
if(nota>=90) cal=5;
else if(nota>=80) cal=4;
else if(nota>=70) cal=3;
else if(nota>=60) cal=2;
else cal=1;
printf("\nLa calificacion es: %d\n",cal);
}
Estructuras repetitivas

Hasta ahora se ha trabajado con instrucciones de entrada, salida, expresiones y operadores; asignaciones,
instrucciones secuenciales y de selección. Hay una gran variedad de situaciones que requieren que una o varias
instrucciones se repitan varias veces, ya sean cálculos u otro tipo de instrucciones. Las estructuras repetitivas
abren la posibilidad de realizar una secuencia de instrucciones más de una vez.

Estructura genérica Repetir - Formato


repetir
instrucción 1
instrucción 2 En algún punto debe haber un criterio de parada o finalización
instrucción n
fin_ repetir

Estructuras repetitivas
 Mientras (while)
 Repetir (do-while)
 Desde (for)

Conceptos de bucle (lazo) e iteración

Un bucle, es una sección de código que se repite. Es decir cuando se termina de ejecutar la última instrucción
del conjunto, el flujo de control retorna a la primera sentencia y comienza una nueva repetición de las
sentencias que forman esa sección de código. Se denomina iteración al hecho de repetir la ejecución de una
secuencia de acciones, la iteración se asocia a un número entero que indica el número de veces que se repite
un trozo de código.

El bucle while

El bucle while es el tipo de bucle más sencillo. Admite la siguiente sintaxis:

while(expresión)
sentencia;

El bucle while comienza por evaluar la expresión. Si es cierta, se ejecuta la sentencia. Entonces se vuelve a
evaluar la expresión. De nuevo, si es verdadera, se vuelve a ejecutar la sentencia. Este proceso continúa hasta
que el resultado de evaluar la expresión es falso. Por esto se le llama a esta expresión la condición de salida.
Normalmente, en las sentencias del bucle while se coloca alguna instrucción que modifique la expresión de
control. Lo más habitual es utilizar un bloque de sentencias en vez de una sentencia única. Por ejemplo:

int variable=10;
while(variable>1){
printf(“la variable vale %d \n“, variable);
variable=variable-1;
printf(“valor tras decrementar la variable %d\n”, variable);
}

Ejemplo 1

Dado un número natural , desarrolle un algoritmo que calcule la sumatoria y el promedio de los números

menores a  y que sean múltiplos de 3.

#include<stdio.h>
int main(){
int n,i=1,sum=0,cont=0;
float prom;
printf("\nIngrese el numero: ");
scanf("%d",&n);
while(i<n){
if((i%3)==0){sum+=i;cont++;}
i++;
}
if(cont){
prom=1.0*sum/cont;
printf("\nLa suma es: %d",sum);
printf("\nEl promedio es: %.3f",prom);
}
  else{
printf("\nEl nro ingresado es menor que 4");
}
return 0;
}

Ejemplo 2
Desarrolle un algoritmo que permita determinar a partir de un número de días (ingresado por pantalla), los
años, meses, semanas y días que constituyen el número de días proporcionado.

#include<stdio.h>
int main(){
int dias,meses=0,anhos=0,semanas=0;
printf("Ingrese el numero de dias: ");
scanf("%d",&dias);
while(dias>=365){
dias-=365;
anhos++;
}
while(dias>=30){
dias-=30;
meses++;
}
while(dias>=7){
dias-=7;
semanas++;
}
printf("\nLa cantidad de anhos es: %d",anhos);
printf("\nLa cantidad de meses es: %d",meses);
printf("\nLa cantidad de semanas es: %d",semanas);
printf("\nLa cantidad de dias es: %d",dias);
return 0;
}

Ejemplo 3 – Validación de datos


Ingresar un número positivo

#include<stdio.h>
int main(){
int n;
printf("\nIngrese un numero positivo: ");
scanf("%d",&n);
while(n<=0){
printf("\nEl numero debe ser positivo, ingreselo de nuevo: ");
scanf("%d",&n);
}
return 0;
}

El bucle do-while

La sintaxis de este bucle es:

do
sentencia;
while(expresión);

Su funcionamiento es análogo el del bucle while, salvo que la expresión de control se evalúa al final del bucle.
Esto nos garantiza que el bucle do-while  se ejecuta al menos una vez. Es menos habitual que el bucle
while.

Podemos incluir dentro del bucle un grupo de sentencias, en vez de sólo la sentencia. Por ejemplo:

int variable=10;
do{
printf(“numero actual %d \n”, variable);
variable=variable-1;
}while(variable>1);
Ejemplo 1 – Validación de datos
Ingresar un número positivo

#include<stdio.h>
int main(){
int n;
do{
printf("\nIngrese un numero positivo: ");
scanf("%d",&n);
}while(n<=0);
return 0;
}

Ejemplo 2

Calcular el factorial de un número .

#include<stdio.h>
int main(){
int n,fact=1,i=1;
printf("\nIngrese el nro n: ");
scanf("%d",&n);
do{
fact*=i;
i++;
}while(i<=n);
printf("\nEl factorial de %d es: %d",n,fact);
return 0;
}

El bucle for

La sintaxis del bucle for es:

for (inicio; control; incremento)


sentencia;

Este bucle se utiliza para realizar una acción un número determinado de veces. Está compuesto de tres
expresiones: la de inicio, la de control y la de incremento, y de una sentencia.

Un ejemplo sencillo de uso es:

for (i=0; i<10; i++)


printf( “i vale %d \n”, i);

Esta versión del bucle imprime un mensaje en la pantalla mientras que no se alcance la condición de salida,
i<10.

El funcionamiento del bucle for es el siguiente:


 primero se ejecuta la expresión de inicio. Normalmente esta es una expresión de asignación a una
variable, que le da un valor inicial.
 Luego se comprueba la expresión de control. Si esta expresión es verdadera se ejecuta la sentencia,
o el grupo de sentencias. Si la expresión es falsa el bucle finaliza.
 Tras ejecutarse la sentencia (o sentencias) se evalúa la expresión de incremento. Habitualmente lo
que hace esta expresión es incrementar la variable de control.
 A continuación se vuelve al segundo paso. El bucle finaliza cuando la expresión de control es falsa.

En un bucle for  podemos omitir la expresión de inicio; por ejemplo, si sabemos que la variable ya está
inicializada:

int i=0;
for ( ; i<10; i++)
printf(“i vale %d \n”, i);

También podemos omitir la expresión de incremento. Esto es habitual cuando la variable de control ya se
modifica dentro del bucle. Por ejemplo:
int i=0;
for( ; i<10; )
printf(“i vale %d \n”,i);

El bucle for es equivalente a un bucle while escrito del siguiente modo:

inicio;
while(control)
sentencia;
incremento;

El bucle for se sigue el orden: evaluación de control, ejecución de sentencia y evaluación de incremento.

Ejemplo 1 - Extraído de http://c2.com/cgi/wiki?FizzBuzzTest


Imprimir en pantalla los números comprendidos entre 1 y 100. Pero para los múltiplos de 3, imprimir “Fizz” en
lugar del número, mientras que para los múltiplos de 5 se imprime “Buzz” en lugar del número. Si el número es
múltiplo de 3 y de 5, mostrar “Fizzbuzz” en lugar del número.

#include<stdio.h>
int main(){
int i;
printf("\nNumeros del 1 al 100");
for(i=1;i<101;i++){
if((i%3==0)&&(i%5==0)) printf("\nFizzBuzz");
else if(i%3==0) printf("\nFizz");
else if(i%5==0) printf("\nBuzz");
else printf("\n%d",i);
}
return 0;
}

Anidamiento de ciclos repetitivos

Correcto Correcto Incorrecto Incorrecto

Ejemplo
Calcular el producto de todos los números primos comprendidos entre 1 y . 
#include<stdio.h>
int main(){
int i,k,n,primo,prod=1;
printf("\nIngrese el valor de n: ");
scanf("%d",&n);
for(i=1;i<=n;i++){
primo=1;
for(k=2;k<i;k++){
if(i%k==0){
primo=0;
k=i;
}
}
if(primo) prod*=i;
}
printf("\nEl producto de los numeros primos es: %d",prod);
return 0;
}
Uso de etiquetas.

Una etiqueta se define mediante su nombre (identificador) seguido del carácter dos puntos (:).

Uso de GOTO.

Es una sentencia de salto incondicional dentro del ámbito de una función. La sentencia goto nos permite dar
un salto a la parte del programa donde se encuentre la etiqueta respectiva. Un programa debidamente
estructurado debe evitar la utilización del goto, de hecho es la sentencia prohibida en la programación
estructurada.

La sentencia goto sirve para indicar al programa que continúe ejecutándose desde la línea de código indicada.
Su sintaxis es:

ETIQUETA:

Cuando se ejecute la sentencia goto, el programa saltará y continuará su ejecución a partir de la etiqueta
marcada.

Como se puede observar se puede usar para crear un bucle, salir de bucles anidados, o para ir a una parte del
código u otra si se combina con una sentencia if...else. Pero por lo general puede obtenerse el mismo
efecto utilizando los bucles anteriormente vistos.

Uso de BREAK.

En lenguaje C, para escribir una instrucción de salto break (interrumpir), se utiliza la sintaxis:

break;

La instrucción de salto break se usa para interrumpir (romper) la ejecución normal de un bucle, es decir, la
instrucción break finaliza (termina) la ejecución de un bucle y, por tanto, el control del programa se
transfiere (salta) a la primera instrucción después del bucle.

Uso de CONTINUE.

En lenguaje C, para escribir una instrucción de salto continue (continuar), se utiliza la sintaxis:

continue;

La instrucción de salto continue siempre se usa para interrumpir (romper) la ejecución normal de un bucle.
Sin embargo, el control del programa no se transfiere a la primera instrucción después del bucle (como sí hace
la instrucción break), es decir, el bucle no finaliza, sino que, finaliza la iteración en curso, transfiriéndose el
control del programa a la condición de salida del bucle, para decidir si se debe realizar una nueva iteración o
no.

Por tanto, la instrucción continue finaliza (termina) la ejecución de una iteración de un bucle, pero, no la
ejecución del bucle en sí. De forma que, la instrucción continue  salta (no ejecuta) las instrucciones que
existan después de ella, en la iteración de un bucle.

Uso de EXIT ().

La función exit() obliga a la terminación de un programa.

Puede tener como argumento a 0, exit(0), que significa terminación normal, en caso de que tenga como
argumento a un número diferente de 0, se supone que puede acceder a analizar el error con ese argumento.
Bifurcación múltiple: SWITCH

Esta sentencia sirve para agrupar varias sentencias if en una sola, en el caso particular en el que una variable
es comparada a diferentes valores, todos ellos constantes, y que realiza acciones si coincide con ellos. Su
sintaxis es:

switch(control){
case expresion1_const:
sentencia1;
break;
case expresion2_const:
sentencia2;
break;
default:
sentencia0;
break;
}

 Primero se evalúa la expresión de control.


 A continuación se compara con la expresión de la primera etiqueta case. Si son iguales, se ejecuta la
sentencia1.
 Luego se vuelve a comparar la expresión de control con la etiqueta del segundo case. De nuevo, si son
iguales, se ejecuta la sentencia2.
 Se repite el proceso hasta agotar todas las etiquetas case. Si al llegar a la etiqueta default no se ha
ejecutado ninguna otra sentencia ésta es la acción por defecto. La etiqueta default es opcional, si no
la ponemos el programa simplemente salta a la línea siguiente.

Ejemplo:
#include<stdio.h>
int main(){
int num;
printf("\nIngrese un numero: ");
scanf("%d",&num);
switch(num){
case 1:
printf("Es un 1\n");
break;
case 2:
printf("Es un 2\n");
break;
case 3:
printf("Es un 3\n");
break;
default:
printf("No es ni 1, ni 2, ni 3\n");
}
return 0;
}
Lista de ejercicios sobre estructuras de decisión

1) Indicar con un mensaje si un ángulo ingresado en grados es o no recto.


2) Escribir un programa que permita ingresar un carácter e indique si es una letra o no.
3) Escribir un programa que permita recibir un carácter y que devuelva su mayúscula en caso de tratarse
de un carácter alfabético, o que devuelva el mismo carácter en caso contrario.
4) Dados los valores a, b y c; indicar si pueden ser los lados de un triángulo.
5) Dados cinco números, calcular su promedio.
6) Dados dos valores a y b, indicar a través de un mensaje cuál de los 2 es el mayor (Ej: “El mayor es a” ).
En caso de que sean iguales, indicarlo a través de un mensaje.
7) Dados tres números, indicar cuál es el central.
8) Calcular la raíz cuadrada de un número y escribir su resultado.
9) Determinar el precio de un billete de ida y vuelta en tren, conociendo la distancia a recorrer y sabiendo
que si el número de días de estancia es superior a 7 y la distancia superior a 800 kilómetros, el billete
tiene una reducción del 30%. El precio por kilómetro es de 250 guaraníes.
10) Escribir un programa que, dados dos números a y b, indique si uno es divisor del otro.
11) Escribir un programa que, dado un ángulo en grados, indique si es recto, agudo u obtuso.
12) Escribir un programa que acepte dos números reales y un código de selección. Si el código de selección
es 1, entonces el programa suma los dos números introducidos previamente y se visualiza el resultado;
si el código de selección es 2, los números deben ser multiplicados y visualizado el resultado; y si el
código seleccionado es 3, el primer número se debe dividir por el segundo número y visualizarse el
resultado.
13) Si los días LUN-DOM se ingresan de forma numérica (del 1 al 7), devolver el nombre del día
correspondiente. Si el número es inválido, indicarlo con un mensaje.
14) Diseñar un programa que acepte las coordenadas x,y de un punto. Indicar con un mensaje si está
dentro o no de la circunferencia 
      (Obs: un punto en el borde se considera como interior).
También se debe indicar si está por encima, por debajo, o es un punto de la recta  .
15) Los empleados de una fábrica trabajan en dos turnos: diurno y nocturno. Se desea calcular el jornal
diario de acuerdo a los siguientes puntos:
 La tarifa de las horas diurnas es de 10.000 Gs
 La tarifa de las horas nocturnas es de 20.000 Gs
 Si es un domingo, el jornal aumenta en un 25%.
Para un trabajador considerado, se ingresan como entradas el día de la semana en la que trabajó (del 1
al 7, donde 7 es domingo), la cantidad de horas trabajadas en el turno diurno, y la cantidad de horas
trabajadas en el turno nocturno. Se debe retornar el jornal que le corresponde.
16) Escribir un programa que calcule las raíces de la ecuación 
        , sean éstas reales o
imaginarias.
17) Diseñar un algoritmo que resuelva el sistema de ecuaciones de la forma:
Indicar si existe una solución única, no tiene solución o tiene infinitas soluciones
18) Escribir un programa que indique si el año introducido es bisiesto o no.
19) Diseñar un programa en el que se ingresan tres variables: DIA, MES y AÑO (en forma númerica); y
devuelva la fecha del día siguiente (en formato DIA/MES/AÑO). Se deben considerar los años bisiestos,
cantidad de días de cada mes, etc. En caso de insertar números reales o fechas inválidas, indicar con un
mensaje.

Lista de ejercicios sobre estructuras de repetición


1) Dado un número natural , desarrolle un algoritmo que calcule la sumatoria y el promedio de los
números menores a n y que sean múltiplos de 3.
 
2) Realizar la división entre 2 números naturales ( y ) por restas sucesivas, y mostrar el cociente y el
resto de la división.
3) Desarrolle un algoritmo que permita determinar a partir de un número de días (ingresado por teclado),
los años, meses, semanas y días que constituyen el número de días proporcionado. Se supone que un
año siempre tiene 360 días, y que un mes tiene 30 días.
4) Asegurándose de que el número ingresado sea un número natural, calcular el factorial de dicho
número.
5) Extraído de http://en.wikipedia.org/wiki/Bizz_buzz: Imprimir en pantalla los números comprendidos
entre 1 y 100. Pero para los múltiplos de 3, imprimir “Fizz” en lugar del número, mientras que para los
múltiplos de 5 se imprime “Buzz” en lugar del número. Si el número es múltiplo de 3 y de 5, mostrar
“Fizzbuzz” en lugar del número.
6) Calcular el producto de todos los números primos comprendidos entre 2 y . 

7) Una empresa consta de  empleados. A fin de mes, se debe hacer una liquidación de los sueldos de los
mismos. Para ello, se debe diseñar un algoritmo que (en cada paso) lea los sueldos brutos de los
empleados, y devuelva el sueldo neto aplicando un descuento de 9% para IPS y 16% para jubilación.
Por disposición de la gerencia, también se desea conocer la diferencia entre el mayor y el menor sueldo
neto.
8) Calcule e imprima:


9) Calcular el máximo común divisor (MCD) de dos números A y B (usando el algoritmo de Euclides).
10) Diseñe un programa que acepte un número   y muestre los primeros   elementos de la sucesión:
  
11) Dado un número , imprimir en pantalla sus divisores.
12) Realice un programa para encontrar los 100 primeros números perfectos. Un número es perfecto
cuando la suma de sus divisores (sin contar el mismo número) es igual al mismo número. Por ejemplo:
6 = 1+2+3.

13) Dado un número , imprimir una tabla de multiplicar de la siguiente manera:
* 1 2 … 
1 1 2 … 
*1
2 2 4 … 
*2
… … … … …
 1* 2*  …  * 
Obs: Recordar que las tabulaciones se realizan con “ \t” y los saltos de línea con “ \n”.
14) Escribir un programa que, al ingresar un número entero (sea positivo o negativo), devuelva el número
en orden inverso. Ej: -375  -573

15) Dado un número entero con un número par de dígitos, escribir un programa que muestre el número

con cada par de dígitos intercambiado. Por ejemplo: si =654321 se debe mostrar 563412.

16) Dado un número , imprimir en pantalla sus factores primos de la siguiente manera (ejemplo, 150):
     
17) Calcular el enésimo término de la serie de Fibonacci (  ) definida por:
    
    
        
        
18) Dado un valor  como entrada, calcular el menor valor de  tal que se cumpla:
      
    
∑               


19) Leer un número  y diseñar un programa que calcule:
           
20) Una maestra desea obtener las estadísticas acerca del rendimiento de sus alumnos en la materia. Para
ello, se debe diseñar un algoritmo que lea las notas (números entre 1 y 100) y calcule:
 La mejor nota
 La peor nota
 La media de las notas.
 La cantidad de alumnos que aprueban y reprueban, considerando que se aprueba la materia
con una calificación de 60 o más.
El algoritmo finaliza cuando una nota ingresada sale fuera del rango.
21) Escribir un algoritmo que permita saber la cantidad de días entre 2 fechas cualesquiera.
22) Un comercio dispone de dos tipos de artículos en fichas correspondientes a diversas sucursales con los
siguientes campos:
 Código del artículo (A o B)
 Precio unitario del artículo
 Número de artículos
La última ficha del archivo de artículos tiene un código de artículo, una letra X. Se pide:
 El número de artículos existentes en cada categoría.
 El importe total de los artículos de cada categoría.
23) Una estación climática proporciona un par de temperaturas diarias (máxima, mínima). Ninguna de las
temperaturas debería ser igual a 9 grados. El algoritmo finaliza cuando se ingrese 0,0. Se pide
determinar el número de días cuyas temperaturas se han proporcionado; las medias máxima y mínima,
el número de errores (temperaturas de 9 grados) y el porcentaje que representan en relación al total
de valores de temperaturas consideradas.
24) Se desea conocer una serie de datos de una empresa con 50 empleados, conociendo su edad y salario:
 ¿Cuántos ganan más de 5.000.000 Gs?
 ¿Cuántos ganan entre 2.500.000 y 5.000.000 Gs?
 ¿Cuántos ganan menos que 2.500.000?
 ¿Cuántos empleados mayores a 50 años están en la empresa y cuál es la media de sus salarios?
25) Existe un juego llamado “Adivina mi número”, el cual consiste en que un niño trata de adivinar el
número (entero, y entre 1 y 100) pensado por el otro niño. Las reglas del juego son las siguientes:
 El niño pregunta al otro si un número que dice es el pensado por el segundo.
 Si el número es correcto, el primer niño gana el juego. Si no lo acierta, el segundo niño debe
indicarle si el número es mayor o menor del que pensó. El primer niño tiene 10 oportunidades
para adivinar el número, y si no lo hace, el juego es ganado por el segundo.
 El algoritmo debe indicar el ganador del juego.
Obs:  en este problema, el segundo niño es la computadora (el número puede ser generado
aleatoriamente o predeterminado por el programador); mientras que el usuario es el primer niño.

Algunos ejercicios de la primera parte.

 Problema 3
#include<stdio.h>
char mayusculas(char c);

int main(){
char c;
printf("Ingrese el caracter: \n");
scanf("%c",&c);
c=mayusculas(c);
printf("El nuevo caracter es: %c",c);
return 0;
}

char mayusculas(char c){


if((c>='a')&&(c<='z')) return (c+('A'-'a'));
else return c;
}

 Problema 12
#include<stdio.h>

int main(){
int sel;
double a,b,c=0.0;
printf("Ingrese el nro a: ");
scanf("%lf",&a);
printf("\nIngrese el nro b: ");
scanf("%lf",&b);
printf("\nIngrese el codigo de seleccion: ");
scanf("%d",&sel);
if(sel==1) c=a+b;
else if(sel==2) c=a*b;
else if(sel==3) c=a/b;
else printf("\nNo existe operacion asignada a ese codigo.");
printf("\n\nEl resultado es: %lf",c);
return 0;
}

Algunos ejercicios de la segunda parte.

 Problema 1
#include<stdio.h>

int main(){
int i,n,sum=0,con=0;
float prom;
printf("Ingrese el valor de n: ");
scanf("%d",&n);
for(i=1;i<=n;i++){
if((i%3)==0){
sum+=i;
con++;
}
}
if(con) prom=1.0*sum/con;
else prom=-1; //esto indica que no se puede hallar el promedio
printf("\nLa suma es: %d",sum);
printf("\nEl promedio es: %.2f\n",prom);
return 0;
}

 Problema 2
#include<stdio.h>

int main(){
int a,b,c=0;
printf("Ingrese el valor de a: ");
scanf("%d",&a);
printf("\nIngrese el valor de b: ");
scanf("%d",&b);
while(a>b){
c++;
a-=b;
}
printf("\nEl cociente es: %d",c);
printf("\nEl resto es: %d",a);
return 0;
}

 Problema 4
#include<stdio.h>
long long factorial(int n);

int main(){
double n;
long long m;
int c;
printf("\nIngrese el numero: ");
scanf("%lf",&n);
while(n!=((int) n)){
printf("\nIngrese un numero natural: ");
scanf("%lf",&n);
}
m=factorial(n);
printf("\nEl resultado es: %lld",m);
return 0;
}

long long factorial(int n){


int i;
long long prod=1;
for(i=2;i<=n;i++){
prod*=i;
}
return prod;
}

 Problema 5
#include<stdio.h>

int main(){
int i;
for(i=1;i<101;i++){
if((i%15)==0) printf("FizzBuzz\n");
else if((i%5)==0) printf("Buzz\n");
else if((i%3)==0) printf("Fizz\n");
else printf("%d\n",i);
}
return 0;
}
 Problema 6
#include<stdio.h>
int es_primo(int x);

int main(){
int n,i;
long long prod=1;
printf("Ingrese el numero: ");
scanf("%d",&n);
prod=1;
for(i=2;i<=n;i++){
if(es_primo(i)) prod*=i;
}
printf("\nEl producto de los numeros primos es: %lld",prod);
return 0;
}

int es_primo(int x){


int i;
int band=1;
for(i=2;i<x;i++){
if((x%i)==0) band=0;
}
return band;
}
Apuntes de la clase de PED 2 Fecha: 1 de marzo de 2013
MSc. José Colbes

Estructuras de datos
Hasta ahora sólo hemos trabajado con variables simples, que únicamente pueden almacenar un dato (sea éste
numérico o caracter). En ejercicios anteriores, hemos trabajado con listas de números (calificaciones de
alumnos de una misma clase, trabajadores de una empresa, serie de números enteros, etc.); y su manejo con
variables simples puede ser un poco impráctico, además de no co nsiderar el almacenamiento de las entradas.

Pregunta: ¿Cómo podríamos ordenar números utilizando variables simples?

Para salvar estas situaciones, la mayoría de los lenguajes de programación incluyen características de
estructuras de datos. Una estructura de datos es una colección de datos que pueden ser caracterizados por su
organización y las operaciones que se definen en e lla.

Las estructuras de datos básicas  que soportan la mayoría de los lenguajes son los arreglos (arrays) (siendo el
vector un arreglo de una dimensión, y la matriz uno de dos dimensiones). Se dividen en:

 Estáticas (tamaño definido de antemano): arreglos, registros, cadenas


 Dinámicas (no tiene limitaciones de tamaño): listas(pilas/colas), listas enlazadas, árboles, grafos.

Un arreglo es una secuencia de posiciones de la memoria central a las que se puede acceder directamente, que
contiene datos del mismo tipo y pueden ser seleccionados individualmente mediante el uso de subíndices.

Podemos considerar al vector como un conjunto de variables simples con datos del mismo tipo.

Ejemplo de vector: Notas de una clase

Nota 0 Nota 1 Nota 2 …  Nota  …  Nota   

El vector nota tiene subíndices o índices de sus elementos (0, 1, 2, …,  , …,   ) que indican la posición de un
elemento particular dentro del arreglo (de tamaño ). Por ejemplo, si se desea modificar el tercer elemento de
un vector de tipo numérico:
nota[2] = 80

Nombre del vector Posición del elemento

La declaración de un vector en C se hace de la siguiente manera:

int vector[tamaño];

Tipo de dato que almacena Nombre del vector

Tamaño máximo del vector

Observaciones importantes:  el primer elemento se representa por el índice 0. Si el tamaño del vector es ,
entonces el último elemento tiene un índice   .

Declaración de vectores

tipo nombre_vector[tam];

Es muy similar a la declaración de las variables simples, pero el tamaño del vector va en corchetes luego del
nombre del mismo. El tamaño tam debe tener valores asignados previamente (en el caso de que sean
variables), o bien ser constantes.

Lectura de un vector de tamaño 5


int a[5];
int i;
for(i=0;i<5;i++){
scanf("%d",&a[i]);
}
Si los elementos del vector están definidos, puede hacerse la siguiente declaración:

int a[5]={1,2,3,4,5};

Ejemplo 1: realizar un programa que permita leer un vector de  números enteros introducidos por teclado y
que calcule e imprima por pantalla la suma de todos sus elementos, el promedio, el mayor y el menor.

#include<stdio.h>

int main(){
int i,n,max,min,sum=0;
float p;
printf("Ingrese la cantidad de elementos del vector: ");
scanf("%d",&n);
int vec[n]; //Se dimensiona el vector
for(i=0;i<n;i++) scanf("%d",&vec[i]);
max=vec[0]; min=vec[0]; sum+=vec[0];
for(i=1;i<n;i++){
if(min>vec[i]) min=vec[i];
if(max<vec[i]) max=vec[i];
sum+=vec[i];
}
printf("\nEl menor es: %d",min);
printf("\nEl mayor es: %d",max);
printf("\nEl promedio es: %.2f",(1.0*sum/n));
return 0;
}

Algunas observaciones sobre los vectores:


 Si escribimos:
int a[10];
a[12]=8;
Esto no marcará un error, pero como se almacena el 8 en un lugar no reservado, puede sobre-escribirse otra
variable (o hasta dar un error en el sistema operativo).

 int[]; //da error de compilación


 int []={1,2,3} //Es correcto, y se reservan 3 espacios en la memoria
 Tener cuidado al dimensionar un vector con una variable (asegurarse de que esa variable tenga un
valor asignado anteriormente).

Cadenas en C

En C, no existe el tipo de dato “cadena” (ó string). Pero existe una convención en la forma de representar una
cadena, y es a través de un arreglo de caracteres. El fin de una cadena se indica con el carácter especial “\0”.

Esta cadena se declara de las siguientes m aneras:

 char a[7]={'h','e','l','l','o','\n','\0'};
 char b[7]="hello\n";

Formas de leer una cadena por teclado:


char cad[20];
 gets(cad); → se encuentra en stdio.h
 scanf(“%s”,cad); → fijarse que no se usa & (por tratarse de un vector), y se coloca %s

La diferencia entre ambas es que gets() lee los espacios en blanco, mientras que scanf() no.

Formas de imprimir una cadena en pantalla:


 printf(“%s”,cad); → imprime la cadena y el cursor queda al lado.
 puts(cad); → imprime la cadena y da un salto de línea.
Algunas funciones útiles de cadenas
 strcpy(cad1,cad2); → copia la cadena cad2 a la cad1.
 strcat(cad1,cad2); →concatena las cadenas cad1 y cad2 (el resultado queda en cad1).
 int a=strlen(cad1); →devuelve la cantidad de caracteres de la cadena cad1 (sin contar el \0).
 int a=strcmp(cad1,cad2); →devuelve el valor que representa la diferencia entre los
primeros caracteres distintos.

Paso por valor y paso por referencia (en forma superficial):


 Paso por valor: pasa una “copia” de la variable.
 Paso por referencia: pasa la “referencia” a la variable original (esto ocurre para los vectores).

Esbozo de las funciones anteriores

 strcpy(cad1,cad2)
void copia(char cad1[], char cad2[]){
int i=0;
while(cad2[i]!='\0'){
cad1[i]=cad2[i];
i++;
}
cad1[i]='\0';
}

 strcat(cad1,cad2)
void concatenar(char cad1[], char cad2[]){
int i=0;
while(cad1[i]!='\0') i++;
int j=0;
while(cad2[j]!='\0'){
cad1[i]=cad2[j];
i++;j++;
}
cad1[i]='\0';
}

 strlen(cad1)
int longitud(char cad1[]){
int i=0;
while(cad1[i]!='\0') i++;
return i;
}

 strcmp(cad1,cad2)
int comparar(char cad1[], char cad2[]){
int i=0,a;
while((cad1[i]!='\0')&&(cad1[i]==cad2[i])) i++;
a=cad1[i]-cad2[i];
return a;
}

Ejercicios de conversión de tipos de datos

 Cadena a entero (atoi):


#include<stdio.h>
int atoi(char s[]);

int main(){
char s[30];
printf("Ingrese la cadena:\n");
gets(s);
int num=atoi(s);
printf("%d\n",num);
return 0;
}
int atoi(char s[])
{
int i=0, n=0, signo=1;
while(s[i]==' ') i++; /* se eliminan los espacios en blanco */
if ((s[i]=='+')||(s[i]=='-')){ /* se toma el signo */
if(s[i] == '-') signo=-1;
i++;
}
while(s[i]!='\0'){
n=10*n +(s[i]-'0');
i++;
}
return (signo*n);
}

 Entero a cadena(itoa):

#include<stdio.h>
int itoa(char s[], int n);

int main(){
char s[30];
int num;
printf("Ingrese el numero: ");
scanf("%d",&num);
itoa(s,num);
puts(s);
return 0;
}

int itoa(char s[], int n){


int i=0,d=1,j;
if(n<0){
s[i]='-';
i++;
n=n*(-1);
}
int aux=n;
while(aux>=10){
d++;
aux/=10;
}
for(j=(d+i-1);j>=i;j--){
s[j]=(n%10)+'0';
n/=10;
}
s[i+d]='\0';
}

Tarea: convertir una cadena a un punto flotante (número decimal) y viceversa.


Lista de ejercicios sobre vectores

1) Calcular la cantidad de alumnos que obtuvieron nota inferior al promedio del curso en cierta materia. Hay
20 alumnos, y todos rindieron. Las notas van del 0 al 100 (se asume que todas las notas son correctas).
2) Se tienen las temperaturas (promedio) de todos los días del mes de febrero de 2012 almacenados en el
vector Temp (luego de cargar por teclado). Diseñar un algoritmo que obtenga las temperaturas máxima,
mínima (e indica los días correspondientes), y el promedio de las que se encuentran entre los días 21 y 27.
3) Diseñar un algoritmo que obtenga el producto escalar de dos vectores (de enteros) de tamaño .
4) Se tiene un vector de números binarios de tamaño  (siendo el mismo un múltiplo de 3). Un ejemplo es el
siguiente:

1 0 1 0 0 0 1 0 0

Diseñar un algoritmo que cree nuevo vector a partir del vector de entrada, donde después de cada 3
elementos del vector original, se agregue un elemento que indique la cantidad de 1’s en esos tres
elementos. En nuestro caso, la salida sería:

1 0 1 2 0 0 0 0 1 0 0 1

5) Escribir un algoritmo que muestre la cantidad de números positivos, negativos y los ceros de un vector de
100 elementos.
6) Calcular la suma y promedio de los valores positivos de un vector de 50 elementos.
7) Se dispone de un vector T de n elementos distintos de cero. Crear un nuevo vector en lo que todos sus
elementos resulten de dividir los elementos del vector T por el elemento T[k], siendo k un valor dado.
8) Dado un número x, verificar que exista ese valor dentro del vector A e indicar la posición del elemento. Si
no se encuentra, mostrar un mensaje de notificación.
9) Se tienen dos vectores A y B, donde se indican los promedios de los parciales y notas de los finales de 30
alumnos. Según la siguiente fórmula:
        

Imprimir las calificaciones de los alumnos según la siguiente escala:


 0/59: 1
 60/69: 2
 70/79: 3
 80/89: 4
 90/100: 5

10) Considere los 100 primeros números naturales. El programa debe devolver una matriz lógica (0=falso,
1=verdadero) donde se indique si el elemento i es o no primo.

11) Se cuenta con una lista L1 de números enteros en el cual existen numerosos valores re petidos.

A fin de economizar el espacio de almacenamiento, se desea crear una nueva lista L2 en la cual cada valor
diferente aparece una sola vez, sin repetición, pero indicando la cantidad de veces que se repite dicho valor en
la lista L1.

Ejemplo:
Lista original L1 (dato para el algoritmo)

23 27 8 14 23 23 8 23 27 23 27 8 27

Lista final L2 (resultado del algoritmo)

8 3 14 1 23 5 27 4

La interpretación de la lista L2 es como sigue: el valor 8 aparece 3 veces en L1, el valor 14 aparece 1 vez en L1,
el valor 23 aparece 5 veces en L1, y el valor 27 aparece 4 veces en L1.

Note que los valores de L1 deben aparecer ordenados ascendentemente en L2 (8 -14-23-27).


Escriba un algoritmo en C que rec iba la lista original L1 y obtenga la lista L2 c on las características indicadas.
12) Un palíndromo es un número o una frase de texto que se lee igual hacia delante y hacia atrás (un caso
conocido es un número capicúa). Ejemplo de palíndromos son: 41214, radar y 4ana4. Escribir un programa
que reciba una cadena  (de 50 caracteres como máximo) e indique si es o no un palíndromo.
13) Convertir un número binario ingresado por teclado a su equivalente decimal.

Algunos ejercicios resueltos


Ejercicio 1
#include<stdio.h>

int main(){
int i,sum=0,nota[20],con=0;
float prom;
for(i=0;i<20;i++){
printf("Ingrese la nota del alumno %d: ",(i+1));
scanf("%d",&nota[i]);
sum+=nota[i];
}
prom=1.0*sum/20;
for(i=0;i<20;i++){
if(nota[i]<prom) con++;
}
printf("\nLa cantidad de alumnos con nota inferior al promedio es: %d",con);
return 0;
}

Ejercicio 3
#include<stdio.h>

int main(){
int n,i;
//Lectura de datos
printf("Ingrese el tamanho de los vectores: ");
scanf("%d",&n);
int A[n],B[n];
printf("\nIngrese los elementos del vector A:\n");
for(i=0;i<n;i++){
printf("A[%d]: ",i);
scanf("%d",&A[i]);
}
printf("\nIngrese los elementos del vector B:\n");
for(i=0;i<n;i++){
printf("B[%d]: ",i);
scanf("%d",&B[i]);
}
//Proceso
int prod=0;
for(i=0;i<n;i++) prod+=(A[i]*B[i]);
//Impresion del resultado
printf("\nEl producto escalar es: %d\n",prod);
return 0;
}

Ejercicio 10
#include<stdio.h>

int main(){
int i,j,primo[101];
for(i=0;i<=100;i++) primo[i]=1;
//Se usará el metodo de Eratóstenes
for(i=2;(i*i)<=100;i++){
if(primo[i]){
for(j=(i*i);j<=100;j+=i) primo[j]=0;
}
}
//impresion de resultados
printf("\nLa tabla de numeros primos del 1 al 100 es:\n");
for(i=0;i<10;i++){
for(j=0;j<10;j++){
printf("%d ",primo[i*10+j+1]);
}
printf("\n");
}
return 0;
}
Ejercicio 11
#include<stdio.h>

int main(){
int i,n,aux,j;
printf("Ingrese el tamanho del vector: ");
scanf("%d",&n);
int vec[n];
printf("Ingrese los elementos del vector:\n");
for(i=0;i<n;i++){
printf("vec[%d]: ",i);
scanf("%d",&vec[i]);
}
//Ordenamiento del vector
int band=1;j=1;
while(band){
band=0;
for(i=0;i<(n-j);i++){
if(vec[i]>vec[i+1]){
band=1;
aux=vec[i];
vec[i]=vec[i+1];
vec[i+1]=aux;
}
}
j++;
}
//Proceso
int sol[(2*n)];
int cant=1,pos=0;
int act=vec[0];
for(i=1;i<n;i++){
if(vec[i]==act) cant++;
else{
sol[pos]=act;
sol[pos+1]=cant;
pos+=2;
act=vec[i];
cant=1;
}
}
sol[pos]=act;
sol[pos+1]=cant;
pos+=2;
printf("\nImpresion del resultado:\n");
for(i=0;i<pos;i++) printf("%d\t",sol[i]);
return 0;
}
Matrices y arreglos multidimensionales

Hasta el momento, hemos considerado sólo los arreglos unidimensionales (vectores), y en ellos cada elemento
se define o referencia por un índice o subíndice. Estos vectores son elementos de datos escritos en una
secuencia.

Sin embargo, existen grupos de datos que son mejor representados en forma de tabla o matriz con dos o más
subíndices. Por ejemplo: tablas kilométricas entre ciudades, informes de ventas periódicas, etc.

Se pueden definir tablas o matrices   como arreglos multidimensionales, cuyos elementos se pueden
referenciar por dos, tres o más subíndices. El término matriz  se asocia generalmente a un  arreglo
bidimensional. Como en el caso del vector o arreglo unidimensional, sus elementos son del mismo tipo (int,
char, float, etc).

Matrices

La matriz puede verse como un vector de vectores. Por ello, se necesita especificar dos subíndices para poder
identificar cada uno de sus elementos.

  
Columna
Nombre de la matriz
Fila

La declaración de una matriz o array bidimensional es:

tipo nombre_matriz[cant_filas][cant_columnas];

Se deben tener los mismos cuidados que los mencionados para el caso de los vectores en relación al
dimensionamiento de la matriz (c antidad de filas y columnas).

int matrix[3][6] = {{16, 21, 8, 3, -7, 9},{-3, 11, 0, 5, 9, 7},{13, 7, -64, 19, 14, 2}}

Las dimensiones de los arreglos van aumentando a medida que se incluyen más corchetes en la declaración de
los arreglos.

Por ejemplo:
int M3[3][5][2] //define un arreglo tridimensional

Matriz de tres
dimensiones
Lectura de datos de una matriz

for(i=0;i<cant_filas;i++){
for(j=0;j<cant_columnas;j++){
scanf("%d",&A[i][j]);
}
}

Impresión en pantalla de los datos de una matriz

for(i=0;i<cant_filas;i++){
for(j=0;j<cant_columnas;j++){
printf("%d\t",A[i][j]);
}
printf("\n");
}

Observación: un vector de cadenas puede verse como una matriz de caracteres, donde se tiene una palabra en
cada fila.

Ejemplo
Realizar un programa que permita leer una matriz de FxC elementos, de números enteros introducidos por
teclado. Calcular e imprimir el siguiente mensaje dependiendo de la suma de los elementos: “Filas mágicas”, si
todas las sumas de las filas dan el mismo resultado y “Columnas mágicas” si todas las  sumas de las columnas
dan el mismo resultado.

#include<stdio.h>

int main(){
int i,j,M,N,suma_ref_fil=0, suma_ref_col=0;
printf("Ingrese el numero de filas: ");scanf("%d",&M);
printf("Ingrese el numero de columnas: ");scanf("%d",&N);
int A[M][N];
for(i=0;i<M;i++){
for(j=0;j<N;j++){
printf("Ingrese A[%d][%d]: ",i,j);
scanf("%d",&A[i][j]);
}
}
for(i=0;i<N;i++) suma_ref_fil+=A[0][i];
for(i=0;i<M;i++) suma_ref_col+=A[i][0];
//Se revisan las filas
int fil=1,sum; //bandera que indica si es o no magica en cuanto a filas
for(i=1;i<M;i++){
sum=0;
for(j=0;j<N;j++) sum+=A[i][j];
if(sum!=suma_ref_fil){fil=0;break;}
}
int col=1; //bandera que indica si es o no magica en cuanto a columnas
for(i=1;i<N;i++){
sum=0;
for(j=0;j<M;j++) sum+=A[j][i];
if(sum!=suma_ref_col){col=0;break;}
}
if(fil) printf("\nFilas magicas!!");
else printf("\nLas filas no son magicas!!");
if(col) printf("\nColumnas magicas!!");
else printf("\nLas columnas no son magicas!!");
}
Lista de ejercicios sobre matrices

1) Dada una matriz A de tamaño m*n, obtener un vector B que contenga los me nores elementos de cada fila.
2) Multiplicar (si es posible) dos matrices A y B de tamaños m*n y p*q respectivamente (siendo estos últimos
números naturales). Guardar el resultado en una matriz C e imprimirla en pantalla.
3) Escribir un algoritmo que indique si una matriz es o no simétrica (condición de simetría: mat[i][j]=mat[j][i]).
4) Dadas la cantidad de filas y columnas maximas, generar una matriz "car acol"

5) Para transmitir mensajes de texto de hasta 100 caracteres de longitud se ha propuesto el siguiente método
de codificación:
a) Se genera una clave de 10 dígitos distintos (entre 0 y 9) (asumir que esa clave se introduce por
teclado y es válida).
b) Si el texto a codificar tiene menos de 100 caracteres, se completa al final con tantos asteriscos
como sea necesario para completar los 100 caracteres.
c) El texto resultante se coloca en una matriz de 10 filas y 10 columnas, de modo que cada carácter
ocupe un elemento de la matriz, fila por fila, de izquierda a derecha y de arriba hacia abajo.
d) Se toma el primer dígito de la clave y la columna correspondiente a ese dígito se pasa al texto
codificado. Se toma el segundo dígito de la clave y la columna correspondiente a ese dígito se
agrega al final del texto codificado. Se repite este procedimiento hasta utilizar todos los dígitos de
la clave.
e) Se agrega al final del texto codificado la clave utilizada para su codificación.
f) El resultado final es un mensaje codificado de 110 caracteres de longitud.

Se requiere:
a) Escribir un algoritmo para codificar un mensaje utilizando este método
b) Escribir otro algoritmo para decodificar un mensaje codificado con este método

Ejemplo
Mensaje original: “LA CRIPTOGRAFIA ES LA CIENCIA DE CIFRAR Y DESCIFRAR MENSAJES USANDO TECNICAS
MATEMATICAS”
Clave generada: 8204975613

La matriz es:
L A C R I P T O G
R A F I A E S L
A C I E N C I A
D E C I F R A R
Y D E S C I F R A
R M E N S A J E S
U S A N D O T E
C N I C A S M A T
E M A T I C A S * *
* * * * * * * * * *

Mensaje codificado final:


“O ARRETA* FC DMSIA*LRADYR CE*RAEISNNAI*GL ASET**TSIAFL MS*I NFCSDSC*PECRIAO A*AA E UNM*
CIICEEACT*8204975613 ”

6) Una empresa tiene cuatro vendedores (1 a 4), los cuales venden 5 productos distintos (1 a 5). Una vez al
día, cada vendedor introduce un registro para cada tipo de producto vendido. Cada registro contiene lo
siguiente:
a. El número de vendedor.
b. El número de producto.
c. El número total del producto vendido del día.
Suponga que están disponibles los registros del último mes (para nuestro ejercicio, suponemos que se
ingresan por teclado). Escriba un programa que lea toda la información hasta que se ingrese un número de
vendedor no válido, y sume el total de las ventas por vendedor y por producto. Todos los totales se deben
almacenar en la matriz VENTAS. Una vez procesada toda la información del último mes, despliegue los
resultados en forma tabular; en donde cada una de las columnas representa a un vendedor y cada una de
las filas representa un producto en particular. Obtenga la suma de cada fila para el total de ventas de cada
producto del último mes; obtenga la suma de cada columna para el total de ventas por vendedor del
último mes. Su salida tabular debe incluir esos totales a la derecha para las filas y en el fondo para las
columnas.

7) Teniendo un sistema de ecuaciones representado en una matriz, obtener la solución al sistema (suponer
que siempre hay una solución y es única) mediante el método de eliminación de Gauss:
d. Ir a la columna no cero extrema izquierda.
e. Si el primer renglón tiene un cero en esta columna, intercambiarlo con ot ro que no lo tenga.
f. Luego, obtener ceros debajo de este elemento delantero, sumando múltiplos adecuados del
renglón superior a los renglones debajo de él.
g. Cubrir el renglón superior y repetir el proceso anterior con la submatriz restante. Repetir con el
resto de los renglones (en este punto la matriz se encuentra en la forma de escalón).
h. Comenzando con el último renglón no cero, avanzar hacia arriba: para cada renglón obtener un 1
delantero e introducir ceros arriba de éste sumando múltiplos correspondientes a los renglones
correspondientes.

8) Ordenar alfabéticamente un conjunto de   palabras que se representan mediante una matriz de


caracteres. Cada palabra es una fila de la matriz y no supera los 20 caracteres.

E S T A \0
E S \0
U N A \0 \0
P R U E B A \0

9) Teniendo como entrada una matriz  de   filas y   columnas, escriba un programa que solicite los
elementos de esta matriz y luego la procese para ordenar sus valores según la regla que se muestra en el
siguiente ejemplo:

Entrada:
34 23 63 27 72
56 8 33 42 11
78 21 86 6 29
22 75 10 30 13
54 77 36 74 55
28 22 56 41 1

Salida:
1 6 8 10 11
23 22 22 21 13
27 28 29 30 33
54 42 41 36 34
55 56 56 63 72
86 78 77 75 74

10) Un mensaje de texto  ha sido codificado cambiando cada letra por la siguiente en el abecedario (en el
caso de tener la última letra del abecedario, cambiar por la primera) y modificando el orden de las mismas
en el texto en función a una clave ingresada por teclado. La longitud máxima de una palabra es de 30 y la
cantidad máxima de palabras es 10. Al final de cada palabra se agrega un número que indica cuál es su
posición en el mensaje.

Se hacen las siguientes suposiciones:


 La clave ingresada es siempre válida y t iene una cantidad de dígitos igual a la de palabras del mensaje .
 El mensaje  contiene sólo letras y sus palabras están separadas por espacios simples.
 No se tienen espacios al inicio ni al final del mensaje.
 Se tienen mayúsculas y minúsculas.
 Las vocales no llevan acento.
Se pide escribir un programa que r ealice la codificación.

Caso de prueba:
Mensaje original: “este es el examen de programacion”
Clave: 512403
Mensaje codificado: “qsphsbnbdjpo5ft1fm2ef4ftuf0fybnfo3”

Algunos ejercicios resueltos


Ejercicio 2

#include<stdio.h>

int main(){
int m,n,p,q,i,j,k;
printf("Ingrese la cantidad de filas y columnas de la matriz A:");
printf("\nFilas: "); scanf("%d",&m);
printf("Columnas: "); scanf("%d",&n);
printf("\n\nIngrese la cantidad de filas y columnas de la matriz B:");
printf("\nFilas: "); scanf("%d",&p);
printf("Columnas: "); scanf("%d",&q);
if(n!=p){printf("\n\nLas matrices no pueden multiplicarse\n"); return 0;}
int A[m][n],B[p][q],C[m][q];
//Lectura de datos
printf("\n\nIngrese los datos de la matriz A:\n");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("A[%d][%d]: ",i,j); scanf("%d",&A[i][j]);
}
}
printf("\n\nIngrese los datos de la matriz B:\n");
for(i=0;i<p;i++){
for(j=0;j<q;j++){
printf("B[%d][%d]: ",i,j); scanf("%d",&B[i][j]);
}
}
//Proceso
for(i=0;i<m;i++){
for(j=0;j<q;j++){
C[i][j]=0;
for(k=0;k<n;k++){
C[i][j]=C[i][j]+A[i][k]*B[k][j];
}
}
}
//Impresion de resultados
printf("\n\nMatriz C:\n");
for(i=0;i<m;i++){
for(j=0;j<q;j++){
printf("%d\t",C[i][j]);
}
printf("\n");
}
return 0;
}

Ejercicio 4

#include<stdio.h>

int main(){
int m,n,i,j,k,fil,col,filmin,filmax,colmin,colmax,dir,cont=0;
printf("Ingrese la cantidad de filas y columnas de la matriz C:");
printf("\nFilas: "); scanf("%d",&m);
printf("Columnas: "); scanf("%d",&n);
int C[m][n];
dir=1; filmin=0; filmax=m-1; colmin=0; colmax=n-1; fil=0; col=0;
for(k=1;k<=(m*n);k++){
C[fil][col]=k;
if(dir==1){
col++;
if(col==colmax){
dir=2;
filmin++;
}
}
else if(dir==2){
fil++;
if(fil==filmax){
  dir=3;
colmax--;
}
}
else if(dir==3){
col--;
if(col==colmin){
dir=4;
filmax--;
}
}
else{
fil--;
if(fil==filmin){
dir=1;
colmin++;
}
}
}

printf("\n\nMatriz C:\n");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("%d\t",C[i][j]);
}
printf("\n");
}
return 0;
}

Ejercicio 8

#include<stdio.h>

int comparar(char cad1[], char cad2[]){


int i=0,a;
while((cad1[i]!='\0')&&(cad1[i]==cad2[i])) i++;
a=cad1[i]-cad2[i];
return a;
}

void copia(char cad1[], char cad2[]){


int i=0;
while(cad2[i]!='\0'){
cad1[i]=cad2[i];
i++;
}
cad1[i]='\0';
}

int main(){
int i,n;
printf("Ingrese la cantidad de palabras: "); scanf("%d",&n);
char pal[n][21],cad[21];
printf("\nIngrese las palabras:\n");
for(i=0;i<n;i++){
printf("Palabra %d: ",(i+1));
scanf("%s",pal[i]);
}
//Proceso
int band=1,j=1,a;
while(band){
band=0;
for(i=0;i<(n-j);i++){
a=comparar(pal[i],pal[i+1]);
if(a>0){
band=1;
copia(cad,pal[i]);
copia(pal[i],pal[i+1]);
copia(pal[i+1],cad);
}
}
j++;
}

printf("\nPalabras ordenadas:\n");
for(i=0;i<n;i++){
puts(pal[i]);
}

return 0;
}
Apuntes de la clase de PED 2 Fecha: 8 de marzo de 2013
MSc. José Colbes

Recursiones o Recurrencias
Fuentes:
 Libro de Joyanes Aguilar – Capítulo 14: Recursividad
 Wikipedia: Recursión (ciencias de la computación), algoritmo recursivo, recursión

Recurrencia, recursión o recursividad  es la forma en la cual se especifica un proceso basado en su propia


definición. Un problema que pueda ser definido en función de su tamaño (sea este ), pueda ser dividido en
instancias más pequeñas (  ) del mismo problema y se conozca la solución explícita a las instancias más
simples (lo que se conoce como casos base); se puede aplicar inducción sobre las llamadas más pequeñas y
suponer que estas quedan resueltas.

Un ejemplo de recursión es el factorial de un número :

     (  )

Es decir,  puede conocerse solucionando el mismo problema, pero para un caso más pequeño (  ). Este
proceso podemos continuar hasta llegar al caso base de    . Podemos observar como un problema se
divide en varias (una o más) instancias del mismo problema, pero de tamaño menor gracias a lo cual se puede
aplicar inducción, llegando a un punto donde se conoce el resultado (el caso base).

Recursión es, en ciencias de computación, una forma de atacar y solucionar problemas. De hecho, la recursión
es una de las ideas centrales en ciencias de la computación. Resolver un problema mediante recursión significa
que la solución depende de las soluciones de casos más pequeños del mismo problema.

Dato interesante: La mayoría de los lenguajes de programación dan soporte a la recursión permitiendo a una
función llamarse a sí misma desde el texto del programa. Los lenguajes imperativos definen las estructuras de
loops como while y for  que son usadas para realizar tareas repetitivas. Algunos lenguajes de programación
funcionales  no definen estructuras de loops sino que posibilitan la recursión llamando código de forma
repetitiva. La teoría de la computabilidad ha demostrado que estos dos tipos de lenguajes son
matemáticamente equivalentes, es decir que pueden resolver los mismos tipos de problemas, aunque los
lenguajes funcionales carezcan de las típicas estructuras while y for.

Por ejemplo, se presentan dos soluciones para hallar el factor ial de un número n:

Método 1: Iteración
#include<stdio.h>
int main(){
int n,i;
long long facto=1;
printf("Ingrese un numero: ");
scanf("%d",&n);
for(i=1;i<=n;i++) facto*=i;
printf("\nEl factorial de %d es: %lld",n,facto);
}

Método 2: Recursión
#include<stdio.h>
long long factorial(int n){
long long facto;
if(n==0) return 1;
facto=n*factorial(n-1); //Aqui esta la recursion
return facto;
}
int main(){
int n;
printf("Ingrese un numero: ");
scanf("%d",&n);
printf("\nEl factorial de %d es: %lld",n,factorial(n));
return 0;
}

El factorial de un entero , mayor o igual a 0, se puede calcular del modo siguiente:


 factorial() = 1 si    (caso base o de salida)
 factorial() = factorial(  ) si    (llamada recursiva)
Algoritmos recursivos

Un algoritmo recursivo es un algoritmo que expresa la solución de un problema en términos de una llamada a
sí mismo. La llamada a sí mismo se conoce como llamada recursiva o recurrente.

Los programas examinados hasta ahora, generalmente estructurados, se componen de una serie de funciones
que llaman unas a otras de un modo “disciplinado”. En algunos problemas es útil disponer de funciones que se
llamen a sí mismas. Una función o subprograma recursivo es un subprograma que se llama a sí mismo ya sea
directa o indirectamente.

Se puede utilizar la recursividad como una alternativa a la iteración. De hecho, ciertas soluciones salen de
manera más “natural” ( por las características de los problemas; como ejemplos se tienen muchos problemas
matemáticos) utilizando la recursividad. Por esta causa, la re cursión es una herramienta poderosa e importante
en la resolución de problemas y en la programación.

Una solución recursiva es normalmente menos eficiente, en términos de tiempo de computadora, que una
solución iterativa; esto es debido a las operaciones auxiliares que llevan consigo las llamadas suplementarias a
las funciones. Además, se debe tener en cuenta el espacio de memoria para cada uno de los procesos que
quedan pendientes de resultado durante la ejecución del pro grama recursivo.

Un esquema de una función recursiva es el siguiente:

tipo_dato_retorno funcion_recursiva(argumentos){

funcion_recursiva(argumentos)

}

Ejemplo 1: A través de una función recursiva, calcular  , donde  es un número entero y  uno natural.

#include<stdio.h>
long long potencia(int a,int b){
if(b==0) return 1;
return (a*potencia(a,(b-1)));
}
int main(){
int a,b;
printf("Ingrese la base: "); scanf("%d",&a);
printf("Ingrese el exponente: "); scanf("%d",&b);
printf("\nEl valor de %d^%d es: %lld",a,b,potencia(a,b));
return 0;
}

Ejemplo 2: Calcular la suma de todos los números enteros entre 1 y 100.

#include<stdio.h>
int sumas(int i){
if(i==0) return 0;
return (i+sumas(i-1));
}
int main(){
printf("\nEl valor de la suma del 1 al 100 es: %d", sumas(100));
return 0;
}

Ejemplo 3:  Calcular el enésimo término de la sucesión de Fibonacci ( ()  (  )  (  )),
sabiendo que los dos primeros términos son 0 y 1.
#include<stdio.h>
int fibo(int i){
if(i==1) return 0;
if(i==2) return 1;
return (fibo(i-1)+fibo(i-2));
}
int main(){
int n;
printf("Ingrese el numero del termino cuyo valor se desea conocer: ");
scanf("%d",&n);
printf("\nEl valor del termino %d de la serie de Fibonacci es: %d",n,fibo(n));
return 0;
}
Recursividad directa e indirecta

Si una función se invoca a sí misma, el proceso se denomina recursión directa (los ejercicios vistos hasta aquí
son de este tipo). Si una función puede invocar a una segunda función que a su vez invoca a la primera, este
proceso se conoce como recursión indirecta o mutua.

Ejemplo: imprimir todo el alfabeto.

#include<stdio.h>
void A(char);
void B(char);
int main(){
printf("El alfabeto es:\n");
A('Z');
return 0;
}
void A(char c){
if(c>'A') B(c);
putchar(c);
}
void B(char c){
A(--c);
}

Explicación:  el programa principal llama a la función recursiva  ()  con el argumento   (la última letra del
alfabeto), y esta función examina su parámetro  . Si   está después que  (en orden alfabético), la función
llama a (), que inmediatamente llama a  (), pasándole un parámetro predecesor de  . Esta acción hace que
 () vuelva a examinar  , y nuevamente llame a (), hasta que  sea igual a . En este momento, la recursión
termina ejecutando putchar() 26 veces; y con esto se visualiza todo el alfabeto.

Recursión vs Iteración

Todos los ejercicios anteriores se pueden implementar de modo iterativo o de modo recursivo. A continuación
se compararán ambos enfoques, de manera a que el programador pueda elegir un enfoque u otro de acuerdo a
una determinada situación.

Tanto la iteración como la recursión se basan en una estructura de control: la iteración utiliza una estructura
repetitiva y la recursión utiliza una estructura de selección. La iteración y la recursión implican repetición: la
iteración utiliza explícitamente una estructura repetitiva, mientras que la recursión consigue la repetición
mediante llamadas repetidas. Ambas implican cada una condición de salida (test de terminación). La iteración
termina cuando la condición del bucle no se cumple, mientras que la recursión termina cuando se reconoce un
caso base o la condición de salida se alcanza.

La recursión tiene ciertas desventajas: se invoca repetidamente al mecanismo de recursividad y en


consecuencia se necesita tiempo suplementario para realizar las mencionadas llamadas.

Esta característica puede resultar cara en tiempo de procesador y espacio de memoria. Cada llamada de una
función recursiva produce que otra copia de la función (realmente sólo las variables de la función) sea creada;
esto puede consumir memoria considerable. Por el contrario, la iteración se produce dentro de una función, de
modo que las operaciones suplementarias de las llamadas a la función y asignación de memoria adicional son
omitidas.

En consecuencia, ¿cuáles son las razones para elegir la recursión? La razón fundamental es que existen
numerosos problemas complejos que poseen naturaleza recursiva y por ello son más fáciles de solucionar con
algoritmos de este tipo. Sin embargo, en condiciones típicas de tiempo y memoria, es decir, cuando el
consumo de tiempo y memoria sean decisivos o concluyentes para la resolución del problema, la solución a
elegir generalmente es la iterativa.
Resolución de ejercicios un poco más complejos con recursividad
Problema 1: Torres de Hanoi

El juego, en su forma más tradicional, consiste en tres varillas verticales. En una de las varillas se apila un
número indeterminado de discos (elaborados de madera) que determinará la complejidad de la solución, por
regla general se consideran ocho discos (pero pueden ser de cantidad ). Los discos se apilan sobre una varilla
en tamaño decreciente. No hay dos discos iguales, y todos ellos están apilados de mayor a menor radio en una
de las varillas, quedando las otras dos varillas vacantes. El juego consiste en pasar todos los discos de la varilla
ocupada (es decir la que posee la torre) a una de las otras varillas vacantes. Para realizar este objetivo, es
necesario seguir tres simples reglas:

 Sólo se puede mover un disco c ada vez.


 Un disco de mayor tamaño no puede descansar sobre uno más pequeño que él mismo.
 Sólo puedes desplazar el disco que se e ncuentre arriba en cada varilla.

Existen diversas formas de realizar la solución final, todas ellas siguiendo estrategias diversas. La que
emplearemos es la de recursión, y el programa se muestra a continuación:

#include<stdio.h>
void Hanoi(int inicio, int fin, int centro, int n){
if(n==1){
printf("\nMover disco 1 de la varilla %d a la varilla %d.",inicio,fin);
}
else{
Hanoi(inicio, centro, fin, (n-1));
printf("\nMover disco %d de la varilla %d a la varilla %d.",n,inicio,fin);
Hanoi(centro, fin, inicio, (n-1));
}
}
int main(){
int n;
printf("Ingrese el valor de n: ");
scanf("%d",&n);
Hanoi(1,3,2,n);// 1 es la varilla inicial, 2 la del centro y 3 la final.
return 0;
}

Problema 2: Ped-dólares (problema extraído de ICPC/ACM y adaptado para el curso)

Pedlandia tiene un sistema monetario bastante particular. Cada moneda tiene escrito un número entero que
indica la su valor en ped-dólares. Los bancos (aquí viene lo peculiar) permiten cambiar una moneda por otras
tres de valores ,  y ; redondeadas hacia abajo.

Además, se tiene que un ped-dólar equivale a un dólar americano. Entonces, teniendo una moneda de un
cierto valor x, ¿cuál es la mayor cantidad de dólares americanos que pueden obtenerse de él?

#include<stdio.h>
int cantidad(int);
int main(){
int n,m;
printf("\nIngrese el valor de la moneda: ");
scanf("%d",&n);
m=cantidad(n);
printf("\nLa cantidad de dolares es: %d",m);
return 0;
}
int cantidad(int x){
if(x==0) return 0;
int a,b,c,y;
a=x/2;
b=x/3;
c=x/4;
y=cantidad(a)+cantidad(b)+cantidad(c);
if(x>y) return x;
else return y;
}
Problema 3: Búsqueda binaria

En este problema, se tiene una lista de números (vamos a suponer que son distintos) ordenados de manera
ascendente. Se ingresa un número cualquiera (lo llamaremos clave) y se desea buscarlo dentro de la lista. Si
está, se debe indicar su posición en el arreglo; si no lo está, se indica con un mensaje.

Se puede realizar una búsqueda secuencial, pero el hecho de que los números estén ordenados nos permite
realizarla de manera más eficiente. Considerando los límites del arreglo, se considera el elemento central. Este
elemento se compara con la clave: si es igual a la clave, se termina y retorna la posición; si es mayor que la
clave, esta última podría encontrarse en el subarreglo izquierdo; si es menor, la clave podría encontrarse en el
subarreglo derecho. Este proceso se realiza e n forma recursiva.

//El vector ingresado ya debe estar ordenado


#include<stdio.h>
int busqueda_bin(int x, int inf, int sup, int lista[]){
if(inf>sup) return (-1);
int med=inf+(sup-inf)/2;
if(lista[med]==x) return med;
if(lista[med]>x) return busqueda_bin(x,inf,(med-1),lista);
else return busqueda_bin(x,(med+1),sup,lista);
}
int main(){
int n,i,clave;
printf("\nIngrese el tamanho de la lista: ");
scanf("%d",&n);
int lista[n];
for(i=0;i<n;i++){
printf("\nIngrese lista[%d]: ",i);
scanf("%d",&lista[i]);
}
printf("\nIngrese la clave a buscar en la lista: ");
scanf("%d",&clave);
int pos=busqueda_bin(clave,0,(n-1),lista);
if(pos==(-1)) printf("\nLa clave %d no se encuentra en la lista.",clave);
else printf("\nLa clave %d se encuentra en la posicion %d.",clave,pos);
return 0;
}

Problema 4: Juego sobre recoger dinero (adaptado de un problema de ICPC/ACM)

Se tiene una pista rectangular con      azulejos cuadrados. En cada uno de estos azulejos, se encuentra
una cierta cantidad de dólares (la pista se representa entonces como una matriz numérica, donde en cada
elemento se indica la cantidad de dólares correspondiente al azulejo en esa posición). El juego consiste en que
el participante obtenga la mayor cantidad de dólares posibles del suelo, siguiendo estas reglas:

 Se empieza eligiendo algún azulejo de la primera fila, y recogiendo los dólares en ella. Luego, se mueve
a un azulejo de la siguiente fila, recoge los dólares en él, y así sucesivamente hasta llegar a la última
fila.
 Cuando se mueve de un azulejo a otro de la siguiente fila, sólo puede moverse a un azulejo que se
encuentre directamente por debajo de él, o diagonalmente a la izquierda o derecha.

Dados  ,  y la matriz numérica; escribir un programa que calcule la cantidad máxima de dólares que se
puede ganar en un único viaje de la primera a la última fila.
#include<stdio.h>
int mat[200][200],fil,col;
maximo(int a, int b, int c){
if(a>=b){
if(a>=c) return a;
else return c;
}
else{
if(b>=c) return b;
else return c;
}
}
int recursion(int x, int y){
int a,b,c;
if(x==0) return mat[x][y];
b=recursion((x-1),y);
if(y==0){
a=-1;
c=recursion((x-1),(y+1));
}
else if(y==(col-1)){
a=recursion((x-1),(y-1));
c=-1;
}
else{
a=recursion((x-1),(y-1));
c=recursion((x-1),(y+1));
}
int d=maximo(a,b,c)+mat[x][y];
return d;
}
int main(){
int i,j,d;
printf("Ingrese la cantidad de filas: ");scanf("%d",&fil);
printf("Ingrese la cantidad de columnas: ");scanf("%d",&col);
printf("\nLa matriz es:\n");
for(i=0;i<fil;i++){
for(j=0;j<col;j++){
scanf("%d",&mat[i][j]);
printf("%d\t",mat[i][j]);
}
printf("\n");
}
int maxi=recursion((fil-1),0);
for(j=1;j<col;j++){
d=recursion((fil-1),j);
if(d>maxi) maxi=d;
}
printf("\nEl resultado es: %d",maxi);
return 0;
}
Apuntes de la clase de PED 2 Fecha: 15 de marzo de 2013
MSc. José Colbes

Punteros (Se recomienda leer el capítulo 5 del libro de Kernighan y Ritchie)

Básicamente, el puntero es una variable cuyo contenido es la dirección de otra variable. Los punteros son muy
usados en C, y son una de sus características principales.

Una máquina típica tiene un arreglo de celdas de memoria numerados o direccionados de forma consecutiva
que pueden ser manipuladas de forma individual o en grupos contiguos. Una situación común es que cualquier
byte puede ser un char, un par de celdas de un byte puede ser tratado como un short int, y cuatro bytes
adyacentes forman un long. Un puntero es un grupo de celdas (a menudo dos o cuatro) que puede contener
una dirección. Así que si    es un char y   es un puntero que apunta a la misma, se podría representar la
situación de la siguiente manera:

Puntero Variable
El operador & da la dirección de un objeto (variable), entonces la sentencia:
  
asigna al puntero  la dirección de la variable  . Entonces se dice que “ apunta a  ”. El operador “&” sólo se
aplica a elementos almacenados en memoria (como variables y arreglos).

El operador * es el “desreferenciador”. Al aplicarlo a un puntero, se accede al objeto al que apunta. Un


ejemplo del uso de estos operadores e s el siguiente:

int x = 1, y = 2, z[10];
int *ip; /* ip es un puntero a un int */
ip = &x; /* ip apunta a la variable x */
y = *ip; /* ahora y es 1 */
*ip = 0; /* ahora x es 0 */
ip = &z[0]; /* ahora ip apunta a z[0] */

Observación: un puntero está restringido a apuntar a objetos de un tipo en específico (por eso se definen como
un tipo de dato).

Si   , entonces las siguientes operaciones son válidas (para modificar el valor de    o para hacer otras
operaciones):

*ip = *ip + 10; //incrementa x en 10


y = *ip + 1; //es lo mismo que y=x+1
*ip += 1; //incremental el valor de x en 1
++*ip; //también incrementa x en 1;
(*ip)++ //se usa el paréntesis para incrementar x. Si no se coloca, se
incrementa el valor del puntero.

Como los punteros son variables, se pueden hacer operaciones sin “desreferenciar”. Por ejemplo: iq = ip
(con esto,   e  apuntan al mismo objeto).

Paso por valor y paso por referencia:


Ejemplo 1: ¿Qué valor se imprime en pantalla en cada uno de estos programas?
#include<stdio.h> #include<stdio.h>
void sub(int); void sub(int *);
main(){ main(){
int a=3; int a=3;
sub(a); sub(&a);
printf(“%d”,a); printf("%d",a);
} }
void sub(int a){ void sub(int *p){
a=10; *p=10;
} }

Observación: para imprimir el valor de un puntero  (en hexadecimal), se utiliza printf(“%p”,pa);


Ejemplo 2: ¿Son formas equivalentes de definir la función swap?
void swap(int x, int y){ void swap(int *px, int *py){
int temp; int temp;
temp = x; temp = *px;
x = y; *px = *py;
y = temp; *py = temp;
} }

x px
En la función En la función
que llama swap
y py

Punteros y vectores
En C, existe una fuerte relación entre los punteros y los vectores (en general, los arreglos); y la mayoría de las
veces pueden usarse indistintamente (en realidad los nombres de los vectores son punteros constantes   que
apuntan al primer elemento del vector).

La declaración int a[10]; define un bloque de 10 elementos consecutivos llamados a[0],…,a[9].

La notación  se refiere al elemento   del vector. Si  es un puntero a int, entonces:
int *pa;
pa = &a[0];
hace que  apunte el elemento cero de . Es decir, contiene la dirección de .

Si   apunta a un elemento de , entonces      apunta a la siguiente posición, y      apunta a un


elemento alejado  de  (esto se conoce como aritmética de punteros). Por definición, el valor de una variable
o expresión de tipo vector es la dirección del elemento cero del vector. Por lo tanto, estas expresiones son
equivalentes:
pa = &a[0]; pa = a;

Además, las siguientes notaciones son equivalentes:


 a[i] → *(a+i)
 &a[i] → a+i
 Si  es un puntero: pa[i] → *(pa+i)

Si se supone que p=vect, la relación entre punteros y vectores puede resumirse como se indica en las líneas
siguientes:
 *p equivale a vect[0], a *vect y a p[0]
 *(p+1) equivale a vect[1], a *(vect+1) y a p[1]
 *(p+2) equivale a vect[2], a *(vect+2) y a p[2]

Observación: Existe una diferencia entre el nombre de un vector y un puntero: un puntero es una variable, por
lo que pa=a and pa++ son operaciones legales. Pero el nombre de un vector (y en general, el de un arreglo)
es una constante, por lo que a=pa and a++ son operaciones ilegales.
Vectores y funciones

Cuando el nombre de un vector se pasa a una función, lo que se pasa es la ubicación del elemento inicial.
Dentro de la función llamada, este argumento es una variable local, y por lo tanto el nombre de un arreglo
como parámetro es un puntero; esto es, una variable que contiene una dirección (paso por referencia). Un
ejemplo sería la función strlen:

#include<stdio.h>
int strlen(char []);
int strlen2(char *);

int main(){
char cad[30];
gets(cad);
int a;
a=strlen(cad);
//a=strlen2(cad);
printf("%d\n",a);
return 0;
}

int strlen(char cad1[]){ int strlen2(char *pc){


int i=0; int i=0;
while(cad1[i]!='\0') i++; while((*pc++)!='\0') i++;
return i; return i;
} }

Las llamadas como:


strlen("hello, world"); /* constante de tipo cadena */
strlen(array); /* char array[100]; */
strlen(ptr); /* char *ptr; */

funcionan correctamente.

Observación importante: la instrucción char cad[30];ya reserva un espacio en memoria para ese vector.
Si sólo definimos un puntero de tipo char, no se reserva memoria. La asignación dinámica de memoria
(asociada a punteros) se verá más adelante.

Punteros a carácter y funciones

Una constante “cadena”, escrita como: “Hola”, es una arreglo de caracteres. Estas constantes pueden usarse
como parámetros de funciones. Una forma común de usarlas es a través de la función printf:
printf("Hola mundo\n");

Cuando una constante cadena aparece en un programa, lo hace a través de un puntero a carácter; y printf
recibe un puntero que apunta al inicio del arreglo de caracteres. Estas constantes no solo se restringen a
argumentos de funciones. Si  se declara como: char *pc; entonces:
pc = "hola que tal"; asigna a   un puntero al arreglo de caracteres. Esto NO es una copia de la
cadena, sólo es una relación entre punteros.

Existe una diferencia importante entre estas definiciones:


char amesage[] = "now is the time"; /* un vector de caracteres */
char *pmessage = "now is the time"; /* un puntero */

  es un arreglo, lo suficientemente grande para contener a la cadena (se pueden modificar los
elementos del vector); mientras que  es un puntero, inicializado a apuntar una constante cadena. El
puntero puede cambiar de objetivo, pero el resultado es indefinido si se intenta modificar el contenido de la
cadena.
Ejemplo:
#include<stdio.h>
void copia(char *, char *);

int main(){
char cad1[30];
copia(cad1,"hola chau");
printf("%s\n",cad1);
return 0;
}

void copia(char *c1, char *c2){


int i=0;
while(*(c2+i)!='\0'){
*(c1+i)=*(c2+i);
i++;
}
*(c1+i)='\0';
}

Relación entre matrices y punteros

En el caso de las matrices la relación con los punteros es un poco más complicada. Supóngase una declaración
como la siguiente:

int mat[5][3], *q;

Se debe prestar especial atención a lo siguiente: una matriz es un vector de vectores.

Recuérdese también que, por la relación entre vectores y punteros, (mat+i) apunta a mat[i]. Recuérdese que la
fórmula de direccionamiento de una matriz de N filas y M columnas establece que la dirección del elemento (i,
 j) viene dada por:

dirección (i, j) = dirección (0, 0) + i*M + j

Si la matriz tiene M columnas y si se hace q = &mat[0][0] (dirección base de la matriz), el elemento mat[i][j]
puede ser accedido de varias formas. Basta recordar que dicho elemento tiene por delante i filas completas, y j
elementos de su fila:
*(q + M*i + j) // fórmula de direccionamiento
*(mat[i] + j) // primer elemento fila i desplazado j elementos
(*(mat + i))[j] // [j] equivale a sumar j a un puntero
*((*(mat + i)) + j) // accede al elemento mat[i][j]

Todas estas relaciones tienen una gran importancia, pues implican una correcta comprensión de los punteros y
de las matrices. De todas formas, hay que indicar que las matrices no son del todo idénticas a los vectores de
punteros: Si se define una matriz explícitamente por medio de vectores de punteros, las filas pueden tener
diferente número de elementos, y no queda garantizado que estén contiguas en la memoria (aunque se puede
hacer que sí lo sean). No sería pues posible en este caso utilizar la fórmula de direccionamiento y el acceder por
columnas a los elementos de la matriz.

En realidad, una matriz se representa en memoria como un vector, donde se colocan las filas una al lado de
otra; por lo tanto una matriz es un vector, donde sus elementos son vectores. Por ejemplo, una matriz A de
10x10 se representa en memoria por un vector de 100 elementos.

La matriz es un arreglo de arreglos de una dimensión. Como el nombre de un arreglo es un puntero a su primer
elemento (y ese elemento es un arreglo de una dimensión), A es realmente un puntero al primer arreglo de 10
elementos (más información sobre estos temas en http://stackoverflow.com/questions/546860/passing-
arrays-and-matrices-to-functions-as-pointers-and-pointers-to-pointers-in).

Formas de pasar una matriz a una función

En el ejemplo, se pasa el nombre de una matriz de 10x10:

void funcion(int (*arreglo)[10]);
void funcion(int arreglo[][10]);
void funcion(int arreglo[10][10]);
void funcion(int arreglo[42][10]);

Puntero a arreglos vs. Arreglo de punteros

 int (*arreglo)[n]; //Un puntero a un arreglo de n enteros.


 int *arreglo[n]; //Un arreglo de punteros a enteros

Ejemplo de lectura e impresión de datos

#include<stdio.h>
int main(){
int i,j,m,n;
printf("Ingrese la cantidad de filas y columnas de la matriz:");
printf("\nFilas: "); scanf("%d",&m);
printf("Columnas: "); scanf("%d",&n);
int mat[m][n];
int *p[m],(*r)[n],**q;
r=mat;
q=p;
for(i=0;i<m;i++) *(p+i)=*(mat+i);
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("mat[%d][%d]: ",i,j); scanf("%d",(*(p+i)+j));
}
}
printf("\nImprimiendo con p\n");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("%d\t",*(*(p+i)+j));
}
printf("\n");
}
printf("\nImprimiendo con r\n");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("%d\t",(*(r+i))[j]);
}
printf("\n");
}
printf("\nImprimiendo con q\n");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("%d\t",*(*(q+i)+j));
}
printf("\n");
}
return 0;
}
Lista de ejercicios sobre punteros

1) Dada una matriz A de tamaño m*n, obtener un vector B que contenga los me nores elementos de cada fila.
2) Ordenar los elementos de un vector de forma descendente.
3) Ordenar alfabéticamente un conjunto de   palabras que se representan mediante una matriz de
caracteres. Cada palabra es una fila de la matriz y no supera los 20 c aracteres.

E S T A \0
E S \0
U N A \0 \0
P R U E B A \0

4) Escribir un programa que convierta una cadena en m ayúsculas y otro que la convierta en minúsculas.
5) Escribir un algoritmo que elimine todos los espacios de una cadena ingresada mediante la función gets().
6) Escribir una función contpar(int *a, int tam) que recibe un vector y su tamaño, y devuelve el número de
elementos pares del arreglo.
7) Escribir una función letra(char *cad) que reciba una cadena y devuelva el carácter con más apariciones de
la misma.

Algunos ejercicios resueltos


Ejercicio 1
#include<stdio.h>
void menor_elemento(int, int, int **, int *);

int main(){
int i,j,m,n;
printf("Ingrese la cantidad de filas y columnas de la matriz:");
printf("\nFilas: "); scanf("%d",&m);
printf("Columnas: "); scanf("%d",&n);
int mat[m][n],vec[m];
int *p[m],**q=p,*t=vec;
for(i=0;i<m;i++) *(p+i)=*(mat+i);
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("mat[%d][%d]: ",i,j); scanf("%d",(*(p+i)+j));
}
}
for(i=0;i<m;i++) menor_elemento(i,n,q,t);
printf("\nEl vector de menores elementos es:\n");
for(i=0;i<m;i++) printf("%d\n",vec[i]);
return 0;
}

void menor_elemento(int i, int n, int **a, int *b){


int j,menor;
menor=a[i][0];
for(j=1;j<n;j++) if(menor>*(*(a+i)+j)) menor=*(*(a+i)+j);
*(b+i)=menor;
}

Ejercicio 3
#include<stdio.h>
int comparar(char *, char *);
void ordenar(char **, int n);

int main(){
int i,n;
printf("Ingrese la cantidad de palabras: "); scanf("%d",&n);
char pal[n][21];
printf("\nIngrese las palabras:\n");
for(i=0;i<n;i++){
printf("Palabra %d: ",(i+1));
scanf("%s",pal[i]);
}
char *p[n];
for(i=0;i<n;i++) *(p+i)=*(pal+i);
ordenar(p,n);
printf("\nPalabras ordenadas:\n");
for(i=0;i<n;i++){
puts(*(p+i));
}
return 0;
}

int comparar(char *c1, char *c2){


int i=0,a;
while((*(c1+i)!='\0')&&(*(c1+i)==*(c2+i))) i++;
  a=*(c1+i)-*(c2+i);
return a;
}

void ordenar(char **q, int n){


int a,i,j=1,band=1;
char *t;
while(band){
band=0;
for(i=0;i<(n-j);i++){
a=comparar(*(q+i),*(q+i+1));
if(a>0){
t=*(q+i);
*(q+i)=*(q+i+1);
*(q+i+1)=t;
band=1;
}
}
j++;
}
}

Ejercicio 5
#include<stdio.h>
#include<string.h>

int main(){
int i,pos=0;
char cad[100];
char cad2[100];
char *p,*q;
printf("\nIngrese la cadena:\n");
gets(cad);
p=cad;
q=cad2;
for(i=0;i<strlen(p);i++){
if(*(p+i)!=' '){
*(q+pos)=*(p+i);
pos++;
}
}
*(q+pos)='\0';
printf("\nLa cadena sin espacios es:\n");
puts(cad2);
return 0;
}

Ejercicio 7
#include<stdio.h>
#include<string.h>
char letra(char *, int n);

int main(){
int i,pos=0,n;
char cad[100];
char *p;
printf("\nIngrese la cadena:\n");
gets(cad);
p=cad;
n=strlen(cad);
//Se ordenan los elementos de la cadena
int j=1,band=1;
char t;
while(band){
band=0;
for(i=0;i<(n-j);i++){
if((*(p+i))>(*(p+i+1))){
t=*(p+i);
*(p+i)=*(p+i+1);
*(p+i+1)=t;
band=1;
}
}
j++;
}
puts(cad);
char c=letra(p,n);
printf("\nEl caracter que mas aparece es %c.",c);
return 0;
}

char letra(char *p, int n){


int i,cont=1,max=0;
char c=(*p);
for(i=1;i<(n+1);i++){
if((*(p+i))!=(*(p+i-1))){
if((cont>max)&&((*(p+i-1))!=' ')){
max=cont;
c=*(p+i-1);
}
cont=1;
}
else cont++;
}
return c;
}
Apuntes de la clase de PED 2 Fecha: 22 de marzo de 2013
MSc. José Colbes

Lista de ejercicios de repaso

Tema 1: “Amigos de guerra”


Nlogonia está librando una guerra sin cuartel contra el vecino país de Cubiconia. El General en Jefe del Ejército
de Nlogonia decidió atacar al enemigo con una formación lineal de los soldados, que permitan avanzar juntos
hasta la conquista del país vecino. Antes de la batalla, el General en Jefe ordenó que cada soldado en la línea
de ataque, además de protegerse a sí mismo y atacar, también debe proteger a sus dos (más cercanos) vecinos
en la línea, uno a su izquierda y otro a su derecha. El General en Jefe dijo a los soldados que para cada uno de
ellos, sus “amigos” serían estos dos vecinos, si tales vecinos existían (p orque el soldado más a la izquierda no
tiene un vecino de la izquierda y el soldado más a la derecha no tiene un vecino de la derecha). El General en
Jefe también dijo a los soldados que proteger a sus compañeros era muy importante para evitar que la línea de
ataque se rompa. Tan importante que, si el amigo de la izquierda o la derecha de un soldado es asesinado,
entonces el siguiente vecino vivo al lado izquierdo o al lado derecho del soldado, respectivamente, debe pasar
a ser su nuevo amigo.

La batalla es feroz, y muchos soldados en la línea de ataque están siendo asesinados por disparos de fuego,
granadas y bombas. Pero siguiendo las órdenes del general en jefe, inmediatamente después de conocer
acerca de las pérdidas en la línea de ataque, la división de sistemas de información del Ejército tiene que
informar a los soldados quiénes son sus nuevos compañeros.

A usted se le da el número de soldados en la línea de ataque, y una secuencia de informes de pérdidas. Cada
informe describe la pérdida de un grupo de soldados contiguos en la línea de ataque que fueron asesinados
recientemente en la batalla. Escriba un programa que, para cada informe de pérdida, imprime los nuevos
compañeros formados.

Entrada del programa:


Cada caso de prueba se describe utilizando varias líneas. La primera línea de entrada contiene dos enteros S y B
que representan respectivamente el número de soldados en la línea de ataque, y el número de informes de
pérdidas (      ). Los soldados se identifican por diferentes enteros de 1 a , en función de sus
posiciones en la línea de ataque; siendo 1 el soldado más a la izquierda y  el soldado más a la derecha. Cada
una de las   siguientes líneas de entrada describe un informe de pérdida utilizando dos enteros  (izquierda) y
  (derecha), lo que significa que los soldados de izquierda a derecha fueron asesinados (      ). Usted
puede asumir que hasta ese momento los soldados estaban vivos y que acaban de morir.

El último caso de prueba es seguido por una línea que contiene dos ceros.

Salida del programa:


Para cada caso de prueba de salida se imprimen    líneas. En la línea de salida  -ésima se deben escribir los
nuevos compañeros formados mediante la eliminación de la línea de ataque de los soldados que perdieron la
vida de acuerdo con el  -ésimo informe de pérdida. Es decir, para el informe de pérdida ‘  ’, imprima el
primer soldado sobreviviente a la izquierda de , y el primer soldado sobreviviente a la derecha de . Para cada
dirección, imprima el carácter ''(asterisco) si hay no soldado sobreviviente en esa dirección. Imprima una línea
que contiene un solo carácter '' (guión) después de cada caso de prueba.

Ejemplo de entrada Ejemplo de salida

1 1 * *
1 1 -
10 4 1 6
2 5 1 10
6 9 * 10
1 1 * *
10 10 -
5 1 * 2
1 1 -
0 0
Tema 2: “Números Palíndromos”
Se dice que un número es un Palíndromo (capicúa) si es el mismo cuando se lee de izquierda a derecha o de
derecha a izquierda. Por ejemplo, e l número 75457 es un palíndromo.

Por supuesto, la propiedad depende de la base en la que se representa el número. El número 17 no es un


palíndromo en la base 10, pero su representación en base 2 (10001) es un palíndromo.

El objetivo de este problema es el de verificar si un conjunto de números dados son palíndromos en cualquier
base desde 2 a 16.

Entrada del programa:


Varios números enteros comprenden la entrada. Cada número      se da en base decimal en una
línea separada. La entrada termina con un cero.

Salida del programa:


Su programa debe imprimir el mensaje ‘El numero   es palindromo en base’ donde   es el número dado, seguid
de las bases donde la representación del número es un palíndromo. El dígito más a la izquierda de un número
en cualquier base debe ser distinto de cero.

Si el número no es un palíndromo en cualquier base entre 2 y 16, el programa debe imprimir el mensaje ‘El
numero   no es palindromo’.

Ejemplo de entrada Ejemplo de salida

17 El numero 17 es palindromo en base 2 4 16


19 El numero 19 no es palindromo
0

Tema 3: “Permutaciones”
Dada una lista de elementos, definir todas las permutaciones que pueden obtenerse con los mismos.

Entrada del programa:


Varios números enteros comprenden la entrada. Cada número      está separado del siguiente por un
espacio simple. La entrada termina con un salto de línea.

Salida del programa:


Se deben presentar las permutaciones posibles siguiendo el esquema presentado en el caso de prueba.

Ejemplo de entrada Ejemplo de salida

3 2 4 1 1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1
Tema 4: “Dados”
La gente de PEDlandia tienen una tradición de tirar un dado de seis caras para determinar quién elegirá dónde
cenar esa noche.

Una persona tira el dado, y la otra elige “par” o “impar”. Si esta segunda persona acierta, entonces él o ella
tiene que elegir el restaurante, de lo contrario, la persona que lanzó lo hace. Por suerte es obvio que “impar”
gana cuando se lanza un 1, 3 ó 5; y “even” cuando se obtiene 2, 4 ó 6!

Los PEDlandianos también tienen una tradición de repetir la misma elección (ya sea par o impar) varias veces
en sucesión.

Su tarea es verificar los resultados de lanzamientos grabados y determinar cuántas veces gana cada persona.

Entrada del programa:


La entrada para este problema consiste en una secuencia de uno o más escenarios. Cada escenario contiene 3
líneas.

 La primera línea contiene, en orden, los nombres de dos personas, y una de las palabras “par” o
“impar”  (en minúsculas), separadas por un espacio. La primera persona nombrada siempre es la
lanzadora del dado, y la segunda persona nombrada será la que elija “par” o “impar”, como se indica.
En PEDlandia, un nombre es una secuencia no vacía de hasta 20 (inclusive) letras (cualquiera de las
cuales puede estar en mayúsculas o minúsculas).

 La segunda línea será un número entero, ,     , que representa el número de lanzamientos
del dado.

 La tercera línea contiene   enteros, cada uno entre 1 y 6 (inclusive); separados por espacios, que
representan los resultados de cada lanzamiento.

La entrada será terminada por una línea que consta de tres numerales (#), separados por espacios. Esta línea
no debe ser procesada.

Salida del programa:


La salida será una secuencia de líneas, una para cada escenario de entrada. Cada línea contendrá en orden los
siguientes elementos, separados por espacios: el nombre de la primera persona (tal y como aparece en la
entrada), el número de veces que la primera persona ganó, el nombre de la segunda persona (exactamente
como aparece en la entrada), y el número de veces que la segunda persona ha ganado.

Ejemplo de entrada Ejemplo de salida

Bill Susan even Bill 5 Susan 3


8 Sarah 8 Tony 7
1 6 5 3 4 2 5 5
Sarah Tony odd
15
2 4 5 4 3 6 1 2 5 4 3 1 2 5 6
# # #
Tema 5: “Cerradura de combinación”
La cerradura de combinación de este problema, como se muestra en la figura, consiste de un disco circular, que
se puede girar (en sentido horario o anti-horario) y está incrustado en la parte "fija" de la cerradura. El disco
tiene   posiciones espaciadas uniformemente. Las posiciones se numeran de  a   , aumentando en el
sentido horario. La parte fija de la cerradura tiene una "marca" que siempre "apunta a" una posición particular
en el disco. Por supuesto, la marca apunta a posiciones diferentes a medida que el disco gira. (En la figura,
N=40 y la marca apunta a la posición 14.)

La cerradura viene con tres números de código , , . Estos son números enteros no-negativos y cada uno
de ellos es menor que . Ningún par de estos tres códigos son iguales.

La cerradura se abre en tres etapas de operaciones:


 Si la marca inicialmente apunta la posición , gire el disco exactamente dos vueltas completas en
sentido horario y pare. De lo contrario, gire el mando en sentido horario exactamente dos vueltas
completas, y continúe girando en sentido horario hasta que la marca apunta a la posición .
 Gire el disco una vuelta completa en sentido anti-horario y continúe girando en este sentido hasta que
la marca apunte a la posición .
 Gire el disco en sentido horario hasta que la marca apunte a la posición . La cerradura debe abrirse.

Dados los números    , el objetivo de este problema es encontrar el número promedio de posiciones
que el disco debe girar (cambiar) con el fin de abrir la cerradura. Para cualesquiera      y una
configuración inicial particular de la cerradura, el número de posiciones giradas (cambiadas) se define como la
suma de las posiciones giradas (cambiadas) en las tres etapas descritas ante riormente.

Entrada del programa:


El archivo de entrada consiste de un número de casos de prueba, con un caso de prueba por línea. Cada línea
del archivo de entrada contiene cuatro enteros:    , por este orden, separados por espacios en
blanco. El entero  es un múltiplo de 5, y     . Los números   y  satisfacen las restricciones
indicadas en la descripción anterior. La entrada será terminada por una línea que contiene sólo cuatro ceros,
separados por espacios en blanco.

Salida del programa:


Para cada caso de prueba, imprimir el número promedio de cambios de posiciones (redondeado a tres
decimales) que el disco debe hacer al girar para abrir la cerradura. Supongamos que cada una de las  posibles
configuraciones iniciales de la cerradura es igualmente probable.

Ejemplo de entrada Ejemplo de salida

80 20 40 50 369.500
80 10 79 12 415.500
0 0 0 0
Apuntes de la clase de PED 2 Fecha: 22 de marzo de 2013
MSc. José Colbes

Lista de ejercicios de repaso

Tema 1 : “Amigos de guerra”


Nlogonia está librando una guerra sin cuartel contra el vecino país de Cubiconia. El General en Jefe del Ejército
de Nlogonia decidió atacar al enemigo con una formación lineal de los soldados, que permitan avanzar juntos
hasta la conquista del país vecino. Antes de la batalla, el General en Jefe ordenó que cada soldado en la línea
de ataque, además de protegerse a sí mismo y atacar, también debe proteger a sus dos (más cercanos) vecinos
en la línea, uno a su izquierda y otro a su derecha. El General en Jefe dijo a los soldados que para cada uno de
ellos, sus “amigos” serían estos dos vecinos, si tales vecinos existían (p orque el soldado más a la izquierda no
tiene un vecino de la izquierda y el soldado más a la derecha no tiene un vecino de la derecha). El General en
Jefe también dijo a los soldados que proteger a sus compañeros era muy importante para evitar que la línea de
ataque se rompa. Tan importante que, si el amigo de la izquierda o la derecha de un soldado es asesinado,
entonces el siguiente vecino vivo al lado izquierdo o al lado derecho del soldado, respectivamente, debe pasar
a ser su nuevo amigo.

La batalla es feroz, y muchos soldados en la línea de ataque están siendo asesinados por disparos de fuego,
granadas y bombas. Pero siguiendo las órdenes del general en jefe, inmediatamente después de conocer
acerca de las pérdidas en la línea de ataque, la división de sistemas de información del Ejército tiene que
informar a los soldados quiénes son sus nuevos compañeros.

A usted se le da el número de soldados en la línea de ataque, y una secuencia de informes de pérdidas. Cada
informe describe la pérdida de un grupo de soldados contiguos en la línea de ataque que fueron asesinados
recientemente en la batalla. Escriba un programa que, para cada informe de pérdida, imprime los nuevos
compañeros formados.

Entrada del programa:


Cada caso de prueba se describe utilizando varias líneas. La primera línea de entrada contiene dos enteros S y B
que representan respectivamente el número de soldados en la línea de ataque, y el número de informes de
pérdidas (      ). Los soldados se identifican por diferentes enteros de 1 a , en función de sus
posiciones en la línea de ataque; siendo 1 el soldado más a la izquierda y  el soldado más a la derecha. Cada
una de las   siguientes líneas de entrada describe un informe de pérdida utilizando dos enteros  (izquierda) y
  (derecha), lo que significa que los soldados de izquierda a derecha fueron asesinados (      ). Usted
puede asumir que hasta ese momento los soldados estaban vivos y que acaban de morir.

El último caso de prueba es seguido por una línea que contiene dos ceros.

Salida del programa:


Para cada caso de prueba de salida se imprimen    líneas. En la línea de salida  -ésima se deben escribir los
nuevos compañeros formados mediante la eliminación de la línea de ataque de los soldados que perdieron la
vida de acuerdo con el  -ésimo informe de pérdida. Es decir, para el informe de pérdida ‘  ’, imprima el
primer soldado sobreviviente a la izquierda de , y el primer soldado sobreviviente a la derecha de . Para cada
dirección, imprima el carácter ''(asterisco) si hay no soldado sobreviviente en esa dirección. Imprima una línea
que contiene un solo carácter '' (guión) después de cada caso de prueba.

Ejemplo de entrada Ejemplo de salida

1 1 * *
1 1 -
10 4 1 6
2 5 1 10
6 9 * 10
1 1 * *
10 10 -
5 1 * 2
1 1 -
0 0

#include<stdio.h>
int izq[100001];
int der[100001];
int B,S,L,R;
int main(){
scanf("%d %d",&S,&B);
int a,b,i;
while((S!=0)&&(B!=0)){
izq[1]=-1;
der[S]=-1;
for(i=1;i<S;i++){
izq[i+1]=i;
der[i]=i+1;
}
for(i=0;i<B;i++){
scanf("%d %d",&L,&R);
a=izq[L];
b=der[R];
der[a]=b;
izq[b]=a;
if(a==-1) printf("* ");
else printf("%d ",a);
if(b==-1) printf("*\n");
else printf("%d\n",b);
}
printf("-\n");
scanf("%d %d",&S,&B);
}
return 0;
}

Tema 2: “Números Palíndromos”


Se dice que un número es un Palíndromo (capicúa) si es el mismo cuando se lee de izquierda a derecha o de
derecha a izquierda. Por ejemplo, e l número 75457 es un palíndromo.

Por supuesto, la propiedad depende de la base en la que se representa el número. El número 17 no es un


palíndromo en la base 10, pero su representación en base 2 (10001) es un palíndromo.

El objetivo de este problema es el de verificar si un conjunto de números dados son palíndromos en cualquier
base desde 2 a 16.

Entrada del programa:


Varios números enteros comprenden la entrada. Cada número      se da en base decimal en una
línea separada. La entrada termina con un cero.

Salida del programa:


Su programa debe imprimir el mensaje ‘El numero   es palindromo en base’ donde   es el número dado, seguid
de las bases donde la representación del número es un palíndromo. El dígito más a la izquierda de un número
en cualquier base debe ser distinto de cero.

Si el número no es un palíndromo en cualquier base entre 2 y 16, el programa debe imprimir el mensaje ‘El
numero  no es palindromo’.

Ejemplo de entrada Ejemplo de salida

17 El numero 17 es palindromo en base 2 4 16


19 El numero 19 no es palindromo
0

#include<stdio.h>
int main(){
int N,i,aux,d,j;
int dig[16];
int band,band2;
scanf("%d",&N);
while(N!=0){
band=0;
for(i=2;i<17;i++){
d=0;
aux=N;
while(aux>=i){
dig[d]=aux%i;
aux=aux/i;
d++;
}
  dig[d]=aux;
d++;
band2=1;
for(j=0;j<d;j++){
if(dig[j]!=dig[d-1-j]){
band2=0;
j=d;
}
}
if(band2==1){
if(band==0){ printf("El numero %d es palindromo en base %d",N,i); band=1;}
else printf(" %d",i);
}
}
if(band==0) printf("El numero %d no es palindromo",N);
printf("\n");
scanf("%d",&N);
}
return 0;
}

Tema 3: “Permutaciones”
Dada una lista de elementos, definir todas las permutaciones que pueden obtenerse con los mismos.

Entrada del programa:


Varios números enteros comprenden la entrada. Cada número      está separado del siguiente por un
espacio simple. La entrada termina con un salto de línea.

Salida del programa:


Se deben presentar las permutaciones posibles siguiendo el esquema presentado en el caso de prueba.

Ejemplo de entrada Ejemplo de salida

3 2 4 1 1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1

#include<stdio.h>
int lista[100];
int n;
void ordenar(int lista[]){
int b,i=1,j,aux;
do{
b=0;
for(j=0;j<(n-i);j++){
if(lista[j]>lista[j+1]){
aux=lista[j];
lista[j]=lista[j+1];
lista[j+1]=aux;
b=1;
}
}
i++;
}while(b);
}
void imprimir(){
int i;
for(i=0;i<(n-1);i++) printf("%d ",lista[i]); printf("%d\n",lista[n-1]);
}
void Permutacion(int pos){
int i,j,aux;
if(pos==(n-1)) imprimir();
else{
for(i=pos;i<n;i++){
aux=lista[i];
for(j=i;j>pos;j--) lista[j]=lista[j-1];
lista[pos]=aux;
Permutacion((pos+1));
aux=lista[pos];
for(j=pos;j<i;j++) lista[j]=lista[j+1];
lista[i]=aux;
}
}
}
int main(){
n=0;
while(scanf("%d",&lista[n])!=EOF) {n++;}
ordenar(lista);
Permutacion(0);
return 0;
}

Tema 4: “Dados”
La gente de PEDlandia tiene una tradición de tirar un dado de seis caras para determinar quién elegirá dónde
cenar esa noche.

Una persona tira el dado, y la otra elige “par” o “impar”. Si esta segunda persona acierta, entonces él o ella
tiene que elegir el restaurante, de lo contrario, la persona que lanzó lo hace. Por suerte es obvio que “impar”
gana cuando se lanza un 1, 3 ó 5; y “par” cuando se obtiene 2, 4 ó 6!

Los PEDlandianos también tienen una tradición de repetir la misma elección (ya sea par o impar) varias veces
en sucesión.

Su tarea es verificar los resultados de lanzamientos grabados y determinar cuántas veces gana cada persona.

Entrada del programa:


La entrada para este problema consiste en una secuencia de uno o más escenarios. Cada escenario contiene 3
líneas.

 La primera línea contiene, en orden, los nombres de dos personas, y una de las palabras “par” o
“impar” (en minúsculas), separadas por un espacio. La primera persona nombrada siempre es la
lanzadora del dado, y la segunda persona nombrada será la que elija “par” o “impar”, como se indica.
En PEDlandia, un nombre es una secuencia no vacía de hasta 20 (inclusive) letras (cualquiera de las
cuales puede estar en mayúsculas o minúsculas).

 La segunda línea será un número entero, ,     , que representa el número de lanzamientos
del dado.

 La tercera línea contiene   enteros, cada uno entre 1 y 6 (inclusive); separados por espacios, que
representan los resultados de cada lanzamiento.

La entrada será terminada por una línea que consta de tres numerales (#), separados por espacios. Esta línea
no debe ser procesada.

Salida del programa:


La salida será una secuencia de líneas, una para cada escenario de entrada. Cada línea contendrá en orden los
siguientes elementos, separados por espacios: el nombre de la primera persona (tal y como aparece en la
entrada), el número de veces que la primera persona ganó, el nombre de la segunda persona (exactamente
como aparece en la entrada), y el número de veces que la segunda persona ha ganado.
Ejemplo de entrada Ejemplo de salida

Bill Susan par Bill 5 Susan 3


8 Sarah 8 Tony 7
1 6 5 3 4 2 5 5
Sarah Tony impar
15
2 4 5 4 3 6 1 2 5 4 3 1 2 5 6
# # #

#include<stdio.h>
#include<string.h>
char P1[30],P2[30],J[30];
int N;
int vec[256];
int main(){
int i,par,impar;
scanf("%s%s%s",P1,P2,J);
while(strcmp(J,"#")!=0){
scanf("%d",&N);
impar=0;
for(i=0;i<N;i++){
scanf("%d",&vec[i]);
if((vec[i]%2)==1) impar++;
}
par=N-impar;
if(strcmp(J,"impar")==0) printf("%s %d %s %d\n",P1,par,P2,impar);
else printf("%s %d %s %d\n",P1,impar,P2,par);
scanf("%s%s%s",P1,P2,J);
}
return 0;
}

Tema 5: “Cerradura de combinación”


La cerradura de combinación de este problema, como se muestra en la figura, consiste de un disco circular, que
se puede girar (en sentido horario o anti-horario) y está incrustado en la parte "fija" de la cerradura. El disco
tiene   posiciones espaciadas uniformemente. Las posiciones se numeran de  a   , aumentando en el
sentido horario. La parte fija de la cerradura tiene una "marca" que siempre "apunta a" una posición particular
en el disco. Por supuesto, la marca apunta a posiciones diferentes a medida que el disco gira. (En la figura,
N=40 y la marca apunta a la posición 14.)

La cerradura viene con tres números de código , , . Estos son números enteros no-negativos y cada uno
de ellos es menor que . Ningún par de estos tres códigos son iguales.

La cerradura se abre en tres etapas de operaciones:


 Si la marca inicialmente apunta la posición , gire el disco exactamente dos vueltas completas en
sentido horario y pare. De lo contrario, gire el mando en sentido horario exactamente dos vueltas
completas, y continúe girando en sentido horario hasta que la mar ca apunta a la posición .
 Gire el disco una vuelta completa en sentido anti-horario y continúe girando en este sentido hasta que
la marca apunte a la posición .
 Gire el disco en sentido horario hasta que la marca apunte a la posición . La cerradura debe abrirse.

Dados los números    , el objetivo de este problema es encontrar el número promedio de posiciones
que el disco debe girar (cambiar) con el fin de abrir la cerradura. Para cualesquiera      y una
configuración inicial particular de la cerradura, el número de posiciones giradas (cambiadas) se define como la
suma de las posiciones giradas (cambiadas) en las tres etapas descritas ante riormente.

Entrada del programa:


El archivo de entrada consiste de un número de casos de prueba, con un caso de prueba por línea. Cada línea
del archivo de entrada contiene cuatro enteros:    , por este orden, separados por espacios en
blanco. El entero  es un múltiplo de 5, y     . Los números   y  satisfacen las restricciones
indicadas en la descripción anterior. La entrada será terminada por una línea que contiene sólo cuatro ceros,
separados por espacios en blanco.

Salida del programa:


Para cada caso de prueba, imprimir el número promedio de cambios de posiciones (redondeado a tres
decimales) que el disco debe hacer al girar para abrir la cerradura. Supongamos que cada una de las  posibles
configuraciones iniciales de la cerradura es igualmente probable.

Ejemplo de entrada Ejemplo de salida

80 20 40 50 369.500
80 10 79 12 415.500
0 0 0 0

#include<stdio.h>
int N,T1,T2,T3;
double cant;
int main(){
scanf("%d%d%d%d",&N,&T1,&T2,&T3);
int x,y;
while((N!=0)||(T1!=0)||(T2!=0)||(T3!=0)){
cant=0.0;
cant+=(1.0*(N-1)/2);
cant+=(3.0*N);
x=abs(T2-T1);
y=abs(T3-T2);
if(T2>T1) cant+=(1.0*x);
else cant+=(1.0*(N-x));
if(T3>T2) cant+=(1.0*(N-y));
else cant+=(1.0*y);
printf("%.3lf\n",cant);
scanf("%d%d%d%d",&N,&T1,&T2,&T3);
}
return 0;
}
Apuntes de la clase de PED 2 Fecha: 5 de abril de 2013
MSc. José Colbes

Clase de Estructuras, Uniones y Enumeraciones


Fuente principal: Estructuras de Datos – Una Perspectiva en C. Autor: Luis Joyanes Aguilar  – Capítulo 4

Estructuras
Los arreglos son estructuras de datos que contienen un número determinado de elementos del mismo tipo de
dato. Esta característica (estructura de datos homogénea) supone una limitación cuando se requieren grupos
de elementos con tipos diferentes de datos.

Por ejemplo, si se dispone de una lista de información de clientes que contengan elementos tales como el
nombre, la edad, la dirección, el número de cuenta, etc., los arreglos no son adecuados. La solución a este
problema es utilizar un tipo de dato registro, denominado estructura en C.

Los componentes individuales de una estructura se llaman campos  o miembros. Cada campo puede contener
datos de un tipo diferente en relación a otros campos. Por ejemplo, se puede utilizar una estructura para
almacenar diferentes tipos de información sobre una persona, tal como nombre, estado civil, edad y fecha de
nacimiento.

Entonces, una estructura es una colección de uno o más tipos de elementos denominados campos, cada uno de
los cuales puede ser de un tipo de dato diferente.

En otros lenguajes como C#, C++ y Java ( programación orientada a objetos), las estructuras pueden actuar
como clases (en realidad, serían los atributos de la clase).

La sintaxis para la declaración de estructuras e s:

struct <nombre de la estructura>{


<tipo de dato campo1> <nombre campo1>;
<tipo de dato campo2> <nombre campo2>;
...
<tipo de dato campoN> <nombre campoN>;
};

Por ejemplo, se muestra la declaración en C de una estructura llamada paciente :

struct paciente{
char nombre[30];
int edad,peso;
char direccion[60];
};

Otros ejemplos son los registros complejo y coleccion_CD:

struct complejo{
float parte_real;
float parte_imaginaria;
};

struct coleccion_CD{
char titulo[30],artista[25];
int num_canciones;
float precio;
char fecha_compra[8];
};

Definición de variables de estructuras


Las variables de estructuras se pueden definir de dos formas:
1. Listándolas inmediatamente después de la llave de cierre de la declaración de la estructura.

struct coleccion_CD{
char titulo[30],artista[25];
int num_canciones;
float precio;
char fecha_compra[8];
} cd1, cd2, cd3;

2. Listando el tipo de la estructura creado, seguida por las variables correspondientes en cualquier lugar
del programa antes de utilizarlas.

struct coleccion_CD cd1, cd2, cd3;

Uso de estructuras en asignaciones


Como una estructura es un tipo de dato similar a un int o un char, se puede asignar una estructura a otra. Por
ejemplo, se puede hacer que paciente1, paciente2, y paciente3 tengan los mismos valores en sus campos que
paciente4. Por consiguiente, sería necesario realizar las siguientes sentencias:

paciente1 = paciente4;
paciente2 = paciente4;
paciente3 = paciente4;

Inicialización de una declaración de estructuras


Se puede inicializar una estructura de dos formas: dentro de la sección de código de su programa, o bien como
parte de la definición. Cuando se inicializa una estructura como parte de la definición, se especifican los valores
iniciales, entre llaves, después de la definición de variables de la estructura. Por ejemplo:

struct infoLibro{
char titulo[60];
char autor[30];
char editorial[30];
int anho;
} libro1= {"Maravilla del saber",
"Lucas Garcia",
"McGraw-Hill",
2003};

O bien:
#include<stdio.h>
struct estructura_amigo {
char nombre[30];
char apellido[40];
char telefono[10];
int edad;
};

int main(){
struct estructura_amigo amigo= {
"Juanjo",
"Lopez",
"592-0483",
30
};
return 0;
}

Tamaño de una estructura


El operador sizeof() se aplica sobre un tipo de datos, o bien sobre una variable. Se puede aplicar para
determinar el tamaño que una estructura ocupa en memoria. El siguiente programa ilustra el uso de este
operador:

#include<stdio.h>
struct persona{
char nombre[30];
int edad;
float altura;
float peso;
};
void main(){
struct persona jose;
printf("sizeof(persona) = %d\n",sizeof(jose));
}
Al ejecutar este programa se produce la salida (tomando int con dos bytes):

sizeof(persona) = 40

Acceso a estructuras
Se puede acceder a las estructuras de dos formas: mediante el operador punto y mediante el operador
puntero.

1. Acceso a una estructura mediante el operador punto


La sintaxis es:
<nombre variable estructura>.<nombre campo>= datos;

Por ejemplo, considerando ciertas asignaciones para un paciente p1:


strcpy(p1.nombre,“Mario Gonzalez”);
p1.edad = 15;
p1.peso = 48;
strcpy(p1.direccion,“Al lado de su vecino”);

O bien, considerando una lectura por teclado:


gets(p1.nombre);
scanf(“%d”,&p1.edad);
scanf(“%d”,&p1.peso);
gets(p1.direccion);

2. Acceso a una estructura de datos mediante el operador puntero


El operador puntero -> sirve para acceder a los datos de la estructura a partir de un puntero. Para utilizar este
operador se debe definir primero una variable puntero para apuntar a la estructura. A continuación, se utiliza
simplemente el operador puntero para apuntar a un miembro dado. La asignación de datos a estructuras
utilizando el operador puntero tiene el formato:

<puntero estructura> -> <nombre campo> = datos;

Así, por ejemplo, para una estructura estudiante:


struct estudiante{
char nombre[41];
int numEstudiante;
int anho_de_matricula;
float nota;
};

se puede definir ptrEst como un puntero a la estructura:

struct estudiante *ptrEst;


struct estudiante mejor;

A los miembros de la estructura estudiante se les puede asignar datos como sigue:
ptrEst = &mejor;
strcpy(ptrEst->nombre,"Juan Perez");
ptrEst->numEstudiante=3425;
ptrEst->nota=8.5;

Para la utilización de los campos de una estructura para operaciones, también se accede a los elementos de la
estructura mediante los dos operadores mencionados. Por ejemplo, considerando la estructura complejo:
struct complejo{
float pr;
float pi;
float modulo;
};
Se puede hacer lo siguiente:
struct complejo z;
struct complejo *pz;
pz=&z;
scanf("%f",&z.pr);scanf("%f",&z.pi);
z.modulo=sqrt(z.pr*z.pr+(pz->pi)*(pz->pi));
printf("\nNumero complejo (%.1f,%.1f)",z.pi,z.pr);
printf("\nModulo: %.2f",pz->modulo);
Estructuras anidadas

Una estructura puede contener otras estructuras llamadas estructuras anidadas. Las mismas ahorran tiempo
en la escritura de programas que utilizan estructuras similares. Se han de definir los miembros comunes sólo
una vez en su propia estructura y a continuación utilizar esa estructura como un miembro de otra estructura
(sería una forma básica de aplicar el concepto de herencia de la programación orientada a objetos).

Consideremos las siguientes dos definiciones de estructuras:

struct empleado{
char nombre[30];
char direccion[25];
char ciudad[20];
char departamento[20];
int edad;
double salario;
};

struct cliente{
char nombre[30];
char direccion[25];
char ciudad[20];
char departamento[20];
int edad;
double saldo;
};

Estas estructuras contienen datos diferentes, aunque hay datos que están solapados. Así, se podría disponer de
otra estructura, persona, que contenga los miembros comunes.

struct persona{
char nombre[30];
char direccion[25];
char ciudad[20];
char departamento[20];
int edad;
};

struct empleado{
struct persona emp;
double salario;
};

struct cliente{
struct persona cli;
double saldo;
};

Así, si se desea por ejemplo leer el nombre de un cliente c1, se puede hacer lo siguiente:
gets(c1.cli.nombre)

Con c1.cli se accede a la estructura persona que está contenida en c1. Luego, haciendo c1.cli.nombre se accede
al nombre de ese cliente. Esta misma lógica se sigue para acceder a los campos de estructuras con un
anidamiento de mayor nivel.

Entonces, el acceso a los campos dato de estructuras anidadas requiere el uso de múltiples operadores punto.

Arreglos de estructuras
Se puede crear un arreglo de estructuras tal como se crea un arreglo de otros tipos. Los arreglos de estructuras
son idóneos para almacenar en memoria un archivo completo de empleados, un archivo de inventario, o
cualquier otro conjunto de datos que se adapte a un formato de estructura. Mientras que los arreglos
proporcionan un medio práctico de almacenar diversos valores del mismo tipo, los arreglos de estructuras
permiten almacenar juntos diversos valores de diferentes t ipos, agrupados como estructuras.
La declaración de un array de estructuras infoLibro se puede hacer de un modo similar a cualquier arreglo; es
decir:

struct infoLibro libros[100];

asigna un arreglo de 100 elementos denominado libros. Para acceder a los miembros de cada uno de los
elementos estructura se utiliza la notación de índices de los arreglos. Para inicializar el primer elemento de
libros, por ejemplo, su código debe hacer referencia a los miembros de libros[0] de la forma siguiente:

strcpy(libros[0].titulo,"Estructuras en C");
strcpy(libros[0].autor,"Luis Joyanes");
strcpy(libros[0].editorial,"McGraw-Hill");
libros[0].anho=1999;

Utilización de estructuras como parámetros


C permite pasar estructuras a funciones, bien por valor o bien por referencia, utilizando el operador &. Si la
estructura es grande, el tiempo necesario para copiar un parámetro struct a la pila puede ser prohibitivo.
En tales casos, se debe considerar el método de pasar la dirección de la estructura.

El listado siguiente muestra la llamada a una función para entrada de datos que pasa la dirección de una
estructura. La misma variable estructura se pasa por valor a otra función para salida de los campos.
#include<stdio.h>
struct info_persona{
char nombre[20];
char calle[30];
char ciudad[25];
char provincia[25];
char codigopostal[6];
};
void entradaPersona(struct info_persona *pp);
void verInfoPersona(struct info_persona p);
int main(){
struct info_persona per;
entradaPersona(&per); //referencia
verInfoPersona(per); //valor
return 0;
}
void entradaPersona(struct info_persona *pp){} //no nos centramos en estas
subrutinas
void verInfoPersona(struct info_persona p){}

Uniones

Las uniones son similares a las estructuras en cuanto que agrupa a una serie de variables, pero la forma de
almacenamiento es diferente y por consiguiente tiene efectos diferentes. Una estructura (struct) permite
almacenar variables relacionadas juntas en posiciones contiguas de memoria. Las uniones, declaradas con la
palabra reservada unión, almacenan también miembros múltiples en un paquete; sin embargo, en lugar de
situar sus miembros uno detrás de otros, en una unión, todos los campos se solapan entre sí en la misma
posición. El tamaño ocupado por una unión se determina analizando el tamaño de cada variable de la unión; el
tamaño mayor de todas las variables será el tamaño de la unión.

La sintaxis de una unión es la siguiente:


union nombre{
tipo1 miembro1;
tipo2 miembro2;
...
};

Por ejemplo:
union PruebaUnion{
float Item1;
int Item2;
};

La cantidad de memoria reservada para una unión es igual a la longitud de la variable más grande. En el tipo
unión, cada uno de los miembros dato comparten memoria con los otros miembros de la unión. La cantidad
total de memoria utilizada por la unión comparte es de 8 bytes, ya que el elemento double es el miembro dato
mayor de la unión.

union comparte{
char letra;
int elemento;
float precio;
double z;
};

Una razón para utilizar una unión es ahorrar memoria. En muchos programas se pueden tener varias variables,
pero no necesitan utilizarse todas al mismo tiempo.

Para referirse a los miembros de una unión, se utiliza el operador punto (.), o bien el operador -> si se hace
desde un puntero a unión. Así:

cadenas.ayuda;
cadenas.mensaje_error;
pc -> mensaje_error;

En este ejemplo se considera el supuesto de que una pequeña biblioteca tiene dos tipos de objetos: libros y
revistas. Se puede definir una estructura libro, una estructura revista  y una unión con ambas
denominada elemento. En un momento dado la unión va a repre sentar a un libro, en otro a una revista.

struct libro{
char titulo[40];
char autor[30];
char editorial[50];
int numPagina;
};

struct revista{
char nombre[40];
char tema[30];
char fecha[10];
float precio;
};

union elemento{
struct libro lb;
struct revista r;
};

Enumeraciones
Una enumeración (enum) es un tipo definido por el usuario con constantes de nombre de tipo entero. En la
declaración de un tipo enum se escribe una lista de identificadores que internamente se asocian con las
constantes enteras 0,1,2,…

Sintaxis:
enum nombre{
enumerador1,enumerador2,...,enumeradorn
};

En la declaración del tipo enum pueden asociarse a los identificadores valores constantes en vez de la
asociación que por defecto se hace (0,1,2,…). Para ello se utiliza este formato:

enum nombre{
enumerador1 = expresion_constante1,
enumerador2 = expresion_constante2,
...
enumeradorn = expresion_constanten
};
Usos típicos de enum

enum Interruptor{
ENCENDIDO,
APAGADO
};

Define las constantes ENCENDIDO y APAGADO de valores iguales a 0 y 1.

enum Boolean{
FALSE,
TRUE
};

enum colores{
ROJO, VERDE, AZUL, NEGRO, AMARILLO, BLANCO
};

En la siguiente declaración de tipo enumerado se proporciona un nombre al tipo. Además, la primera constante
tiene valor 1 y las demás los siguientes valores e nteros.

enum dias_semana{
LUNES=1, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO
};

Haciendo lo siguiente:
enum dias_semana dia;
for(dia=LUNES;dia<=DOMINGO;dia++) printf("%d ",dia);

La ejecución del ciclo escribiría en pantalla: 1 2 3 4 5 6 7.

Ejercicio propuesto: Leer un texto y contar las vocales leídas. Utilizando la enumeración Boolean, escribir
una función vocal() que devuelva TRUE si el carácter considerado es vocal.

#include<stdio.h>
enum Boolean{
FALSE, TRUE
};
enum Boolean vocal(char c);
int main(){
char cad[100];
int numvocal=0,i=0;
puts("Introduzca una cadena");
gets(cad);
while(cad[i]!='\0'){
if(vocal(tolower(cad[i]))) numvocal++;
i++;
}
printf("\nTotal de vocales leidas: %d\n",numvocal);
return 0;
}
enum Boolean vocal(char c){
switch(c){
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
return TRUE;
default:
return FALSE;
}
}

sizeof de tipos de datos estructurados


El tamaño en bytes de una estructura, de una unión o de un tipo enumerado se puede determinar con el
operador sizeof.
Ejemplos:
x=sizeof(struct (libro)); //x=122
y=sizeof(Boolean); //y=2

typedef
Un operador typedef  permite a un programador crear un sinónimo de un tipo de dato definido por el
usuario o de un tipo ya existente. La sintaxis es:

typedef tipoDeDato nuevoTipo;

A partir de la declaración hecha con typedef se puede hacer uso del nuevo símbolo del tipo de dato para
definir variables, en general, donde se utiliza los tipos de datos.

Normalmente los programadores utilizan sinónimos de tipos de datos estructura para simplificar los tipos y
hacerlos más amigables. En primer lugar se declara el tipo estructura, lo mismo con el tipo unión, y a
continuación se asocia el tipo estructura a un nombre c on typedef.

Ejemplos:
struct num_complejo{
float x,y;
};
typedef struct num_complejo complejo;
complejo v[12];

typedef struct{
char apellido[21];
char nombre[21];
int edad;
char pasaporte[26];
char sexo;
} Pasajero;

//Prototipo de funcion
Pasajero entrada();

La ventaja de typedef es que permite dar nombres a tipos de datos más acordes con lo que representan en
una determinada aplicación.
Ejercicios propuestos

Ejercicio 1: Dada una lista que contiene los nombres (en el formato “Apellido - Nombre”) de los alumnos y sus
notas, calcular el promedio de notas de los alumnos con apellidos de A hasta N (o a hasta n). Mostrar también
el nombre del alumno con la mejor nota (o de los alumnos, si se da el caso).

Ejercicio 2:  Como parte de un experimento para estudiar el comportamiento laboral de su personal, una
empresa decidió liberar los horarios para los 10 empleados de un mismo departamento. Esto quiere decir que
cada uno de estos empleados puede llegar o retirarse de la empresa a la hora que consideren conveniente,
pero siempre dentro del horario de at ención al público que es de 7:00 a 17 :00 hs.

Las condiciones de trabajo normales para estos empleados les exigen cumplir una asistencia mínima de 30
horas por semana, trabajando de lunes a viernes.

El experimento tuvo una semana de duración, y se utilizó un reloj marcador digital para registrar las entradas y
salidas a la empresa de los empleados participantes del est udio.

Cada empleado fue identificado por un número, de 1 a 10, y por cada marcación del reloj se generó un registro
con los siguientes datos:

Campo Tipo Observación


Código Numérico Código del empleado, de 1 a 10
Nombre Cadena Nombre del empleado
Fecha Cadena Fecha de la marcación en formato: “ddmmaaaa”
Hora Cadena Hora de la marcación en formato: “hhmm”
Tipo marcación Cadena “E”=entrada, “S”=salida

Nuestro trabajo consiste en escribir un algoritmo que realice la lectura de los 100 registros obtenidos mediante
el reloj, los cuales se encuentran en el mismo orden en que fueron registrados, y luego procese dicha
información para obtener un arreglo de registros, ordenado por código de empleado, en el formato que se
muestra:

Código Nombre Horario promedio de llegada Horario promedio de salida


1 Luis Saldívar 08:19 14:25
2 Marta Benítez 07:32 13:47
… … … …
10 Roberto Pérez 10:06 16:11

Ejercicio 3: Escribir un programa para calcular el número de días que hay entre dos fechas: declarar fecha como
una estructura.

Ejercicio 4: Escribir un programa que permita hacer las operaciones de suma, resta, multiplicación y división de
números complejos. El tipo complejo ha de definirse como una est ructura.

Ejercicio 5: Un punto en el plano se puede representar mediante una estructura con dos campos. Escribir un
programa que realice las siguientes operaciones en e l plano:
 Dados dos puntos, calcular la distancia entre ellos.
 Dados dos puntos determinar la ecuación de la rect a que pasa por ellos.
 Dados tres puntos, que representan los vértice s de un triángulo, calcular su área.

Ejercicio 6:  Escribir un programa de facturación de clientes. Los clientes tienen un nombre, el número de
unidades solicitadas, el precio de cada unidad y el estado en que se encuentra: moroso, atrasado, pagado. A
partir de una lista de clientes, obte ner los siguientes listados:
 Clientes en estado moroso.
 Clientes en estado pagado con factura mayor a una determinada cantidad.
Apuntes de la clase de PED2 Fecha: 12 de abril de 2013
MSc. José Colbes

Manejo de archivos (Material extraído en parte de


http://informatica.utem.cl/~mcast/PROGRAMACION/PROGRAV/2007/ARCHIVOS/archivos-1.pdf )

Los datos que hemos tratado hasta el momento han residido en la memoria principal. Sin embargo, las grandes
cantidades de datos se almacenan normalmente en un dispositivo de memoria secundaria. Estas colecciones
de datos se conocen como archivos (antiguamente ficheros).

Un flujo (stream) es una abstracción que se refiere a un flujo o corriente de datos que fluyen entre un origen y
un destino. Entre el origen y el destino debe existir una conexión o canal por la que circulen los datos. La
apertura de un archivo supone establecer la conexión del programa con el dispositivo que contiene el archivo,
por el canal que comunica el archivo co n el programa van a fluir las secuencias de datos.

Un archivo es un conjunto de datos estructurados en una colección de entidades elementales o básicas


denominadas registros que son de igual tipo y constan a su vez de diferentes entidades de nivel más bajos
denominadas campos.

Hay dos tipos de archivos, archivos de texto y archivos binarios. Un archivo de texto es una secuencia de
caracteres organizadas en líneas terminadas por un carácter de nueva línea. En estos archivos se pueden
almacenar canciones, fuentes de programas, base de datos simples, etc. Los archivos de texto se caracterizan
por ser planos, es decir, todas las letras tienen el mismo formato y no hay palabras subrayadas, en negrita, o
letras de distinto tamaño o ancho.

Un archivo binario es una secuencia de bytes que tienen una correspondencia uno a uno con un dispositivo
externo. Así que no tendrá lugar ninguna traducción de caracteres. Además, el número de bytes escritos
(leídos) será el mismo que los encontrados en el dispositivo externo. Ejemplos de estos archivos son
fotografías, imágenes, texto con formatos, archivos ejec utables (aplicaciones), etc.

En C, un archivo es un concepto lógico que puede aplicarse a muchas cosas desde archivos de disco hasta
terminales o una impresora. Se asocia una secuencia con un archivo específico realizando una operación de
apertura. Una vez que el archivo está abierto, la información puede ser intercambiada entre éste y el
programa.

Se puede conseguir la entrada y la salida de datos a un archivo a través del uso de la biblioteca de funciones; C
no tiene palabras claves que realicen las operaciones de E/S. Para ello, se utiliza la librería stdio.h. A
continuación se muestra un programa básico que copia el conteni do de un archivo en otro nuevo:

#include<stdio.h>

int main(){
FILE *fp1,*fp2;
char c;
char entrada[100],salida[100];
printf("Ingrese el nombre del archivo origen:\n");
scanf("%s",entrada);
if((fp1=fopen(entrada,"r"))==NULL){
printf("\nError al abrir el archivo %s",entrada);
return 0;
}
printf("Ingrese el nombre del archivo destino:\n");
scanf("%s",salida);
if((fp2=fopen(salida,"w"))==NULL){
printf("\nError al crear el archivo %s",salida);
return 0;
}
c=fgetc(fp1);
while(feof(fp1)==0){
fputc(c,fp2);
c=fgetc(fp1);
}
fclose(fp1);
fclose(fp2);
printf("\nArchivo duplicado con exito");
return 0;
}
  y   son punteros “especiales”, denominados punteros a archivos. El puntero a un archivo es el hilo
común que unifica el sistema de E/S con buffer. Un puntero a un archivo es un puntero a una información que
define varias cosas sobre él, incluyendo el nombre, el estado y la posición actual del archivo. En esencia
identifica un archivo específico y utiliza la secuencia asociada para dirigir el funcionamiento de las funciones de
E/S con buffer. Un puntero a un archivo es una variable de tipo puntero al tipo FILE que se define en stdio.h. Un
programa necesita utilizar punteros a archivos para leer o escribir en los mismos. Para obtener una variable de
este tipo se utiliza una secuencia como e sta:
FILE *fp;

Apertura de un archivo
En el ejemplo, se usa la instrucción fopen(). La misma abre una secuencia para que pueda ser utilizada y la
asocia a un archivo. Su sintaxis es:
fp = fopen(nombre,modo)

Donde nombre  es un puntero a una cadena de caracteres que representan un nombre válido del archivo y
puede incluir una especificación del directorio. La cadena a la que apunta modo  determina como se abre el
archivo. La siguiente tabla muestra los valores permitidos para modo:

La función fopen() devuelve un puntero a archivo. Un programa nunca debe alterar el valor de ese puntero.
Si se produce un error cuando se esta intentando abrir un archivo, fopen() devuelve un puntero nulo (NULL).

Se puede abrir un archivo bien en modo texto o binario. En la mayoría de las implementaciones, en modo
texto, la secuencias de retorno de carro / salto de línea se convierten a caracteres de salto de línea en lectura.
En la escritura, ocurre lo contrario: los caracteres de salto de línea se convierten en salto de línea. Estas
conversiones no ocurren en archivos binarios.

La macro NULL está definida en stdio.h. Este método detecta cualquier error al abrir un archivo: como por
ejemplo disco lleno o protegido contra escritura antes de comenzar a escribir en él.

Si se usa fopen()  para abrir un archivo para escritura, entonces cualquier archivo existente con el mismo
nombre se borrará y se crea uno nuevo. Si no existe un archivo con el mismo nombre, entonces se creará. Si se
quiere añadir al final del archivo entonces debe usar el modo “a”. La apertura de un archivo para las
operaciones de lectura requiere que exista el archivo; si no existe, fopen() devolverá un error. Finalmente, si
se abre un archivo para las operaciones de leer/escribir, la computadora no lo borrará si existe; sin embargo, si
no existe, la computadora lo creará.

Cierre de un archivo.
La función fclose() cierra una secuencia que fue abierta mediante una llamada a fopen(). Escribe toda la
información que todavía se encuentre en el buffer en el disco y realiza un cierre formal del archivo a nivel del
sistema operativo. Un error en el cierre de una secuencia puede generar todo tipo de problemas, incluyendo la
pérdida de datos, destrucción de archivos y posibles errore s intermitentes en el programa. Su sintaxis es:
fclose(fp);

Donde   es el puntero al archivo devuelto por la llamada a fopen(). Si se devuelve un valor cero significa
que la operación de cierre ha tenido éxito. Generalmente, esta función solo falla cuando un disco se ha retirado
antes de tiempo o cuando no queda espacio libre en el mismo.

La siguiente tabla da un breve resumen de las funciones que se pueden utilizar (para archivos de texto). Se
debe incluir la librería stdio.h. Observe que la mayoría de las funciones comienzan con la letra “F” (de File).
fgetc y fputc
Estas funciones son análogas a getchar() y putchar(), pero aplicadas a archivos. Estas funciones
escriben o leen un carácter de un fichero determinado por un puntero a fichero (FILE *fp).

Usando iterativamente fgetc, los caracteres se leen en forma secuencial (no se necesita usar una aritmética de
punteros para el desplazamiento).

fprintf() y fscanf()
Estas funciones se comportan exactamente como prinft() y scanf()  que ya conocemos, excepto que
operan sobre archivo. La sintaxis es de la siguiente forma:
fprintf(fp, “contenido de la cadena”, variables);
fscanf(fp, “formatos”, variables);

Donde    es un puntero al archivo devuelto por una llamada a fopen(). fprintf() y fscanf()
dirigen sus operaciones de E/S al archivo al que apunta .

fgets y fputs
Las funciones fgets() y fputs() pueden leer y escribir cadenas a o desde los archivos. Los prototipos de estas
funciones son:
fputs(“contenido”, fp);
fgets(“contenido”, long, fp );

La función fputs() escribe la cadena a un archivo especifico. La función fgets() lee una cadena desde el
archivo especificado hasta que lee un carácter de nueva línea o longitud-1 caracteres. Si se produce un EOF
(End of File) la función fgets() retorna un NULL.

feof()
Esta función indica con un número distinto a cero que se ha alcanzado el fin del archivo. Su sintaxis es:
feof(fp)

rewind()
La función rewind() inicializa el indicador de posición, al principio del archivo, indicado por su argumento. Su
sintaxis es:
rewind(fp)

ferror()
Determina (devolviendo un número distinto a cero) si se ha producido en error en una operación sobre un
archivo. Su sintaxis es:
ferror(fp)

fseek()
Permite el acceso aleatorio. Su sintaxis es la siguiente:
fseek(fp, offset, posición)

Con esta función, se puede considerar al archivo como una gran cadena. El valor de posición  indica la
posición de referencia para el indicador de posición, y puede ser 0 (inicio), 1(actual) ó 2 (final). El valor de
offset indica el desplazamiento en relación a la posición de referencia. Un ejemplo de uso es:
fseek(fp, 2, 0);

Con esta instrucción, posicionamos el indicador a 2 lugares por delante del origen del archivo.
Para saber cuál es la posición en la que está el puntero del archivo, C proporciona la función siguiente: ftell
(fp); que devuelve la posición actual en bytes del puntero del archivo con respecto al principio del mismo.

Lectura y escritura en archivos binarios (fread y fwrite)

Para leer y escribir en archivos que no sean de texto las operaciones que se deben utilizar son fread y fwrite.

El formato de escritura en bloque es e l siguiente:


fwrite (direcc_dato, tamaño_dato, numero_datos, punt_archivo);

Escribe tantos datos como indique numero de datos en e l fichero, tomando los datos a partir de la dirección del
dato. Los datos tienen que tener tantos bytes como especifique tamaño. La función fwrite  devuelve el
número de elementos escritos, este valor debe coincidir con numero de datos.

Para calcular el tamaño en bytes de un dato o un tipo de dato se suele utilizar la función sizeof(dato) o
sizeof(tipo-de-dato);

Por ejemplo:
int i, V[3];  --> sizeof(i) daría lo mismo que sizeof(int)
--> sizeof(V) daría 3 veces el resultado de sizeof(V[1])

Consideremos el siguiente ejemplo:

#include<stdio.h>
int main(){
int v[6],elem_escritos,i;
int car=(2+'0');
FILE *f;
f=fopen("datos.txt","wb");
for(i=0;i<6;i++) v[i]=i+1+'0';
/* Para escribir los 3 últimos elementos de v (el 2, el 3 y el 4) */
elem_escritos=fwrite(&v[2],sizeof(int),3,f);
/* Para escribir el primer elemento de v, valen las 2 instrucciones siguientes */
fwrite(v,sizeof(int),1,f);
fwrite(&v[0],sizeof(int),1,f);
/* Para escribir un entero valen las dos siguientes */
fwrite(&car,sizeof(int),1,f);
fwrite(&car,sizeof(int),1,f);
return 0;
}

Pregunta:  ¿Qué pasa si abrimos datos.txt? ¿Por qué pasa esto?

La sentencia de lectura de bloque es la siguiente:


fread (direcc_dato, tamaño_dato, numero_datos,punt_fichero);

Lee tantos datos como indique numero de datos del fichero, colocando los datos leídos a partir de la dirección
del dato. Los datos tienen que tener tantos bytes como especifique tamaño del dato. La función fread
devuelve el número de elementos leídos, y e l valor devuelto debe coincidir con numero de datos.

Ejemplo:
#include<stdio.h>
int main(){
FILE *f;
int v[6],elem_escritos,i,num;
f=fopen("datos.txt","rb");
elem_escritos=fread(&v[2],sizeof(int),3,f);
fread(v,sizeof(int),1,f);
fread(&v[0],sizeof(int),1,f);
fread(&num,sizeof(int),1,f);
fread(&num,sizeof(num),1,f);
for(i=0;i<6;i++) printf("%d\n",v[i]);
printf("\nnum: %d",num);
return 0;
}
Lista de ejercicios sobre archivos

1) Copiar el contenido de un archivo de tex to dado a otro. Se debe solicitar un nombre del archivo de entrada,
y otro para la copia.
2) Se tiene un archivo con un conjunto de palabras. Se tiene una palabra por línea, y cada palabra tiene
menos que 50 caracteres. Generar un archivo donde dichas palabras estén ordenadas.
3) Dado un archivo de texto que co ntiene una matriz de enteros, convertirlo a una matriz numérica e imprimir
el resultado. Los elementos de una misma fila están separados por un espacio simple, y la misma termina
con un punto y coma. Sólo se puede usar la sentencia fgetc() para leer el archivo.
4) Diseñar un programa que permita partir un archivo e n  partes, donde  es un entero dado por el usuario.
5) Realizar un programa que cuente el número de líneas, caracteres y palabras que tiene un archivo de texto
pasado cuya dirección se introduce por teclado.
6) Crear una estructura  paciente (nombre, edad, direccion), y crear un archivo que contenga los registros de
 pacientes introducidos por teclado. Hacer también lo inverso, un programa que lea un archivo e imprima
en pantalla los datos de los pacientes que contiene.
7) Obtener una raiz de la función             en el intervalo [0,1], considerando
como criterio de parada un error absoluto (que es valor absoluto de la diferencia de dos aproximaciones
consecutivas   y  ) menor a . En un archivo de salida (llamado “salida.tex”) deben almacenarse
almacenarse los
resultados de las iteraciones siguiendo a grandes rasgos el siguiente esquema (las líneas divisorias
representan tabulaciones y saltos de línea):

Iter      
  Error
1 … … …
2 … … …
3 … … …
4 … … …
5 … … …
6 … … …
7 … … …

8) Realizar un programa que muestre por pantalla el contenido de un fichero fuente de lenguaje C, realizando
una traducción a pseudocódigo sencilla, según la siguiente tabla de traducción:
Lenguaje C Texto a mostrar por pantalla
{ INICIO
} FIN
while MIENTRAS
for PARA
printf MOSTRAR
scanf LEER

9) Se tiene un archivo en el
e l siguiente formato:
Nombre//Apellido//Salario
Las personas están separadas por un salto de línea. Se debe calcular el promedio de todos los salarios de las
personas con apellidos de A a la M. Además, se debe generar un nuevo archivo donde estén todas esas
personas, con sus apellidos en mayúsculas.

Algunos ejercicios resueltos


Ejercicio 2
#include<stdio.h>

int comparar(char *c1, char *c2){


int i=0,a;
while((*(c1+i)!='\0')&&(*(c1+i)==*(c2+i)))
while((*(c1+i)!='\0')&&(*(c1+i)==*(c2+i))) i++;
a=*(c1+i)-*(c2+i);
return a;
}

void ordenar(char **q, int n){


int a,i,j=1,band=1;
char *t;
while(band){
band=0;
for(i=0;i<(n-j);i++){
a=comparar(*(q+i),*(q+i+1));
if(a>0){
t=*(q+i);
*(q+i)=*(q+i+1);
*(q+i+1)=t;
band=1;
}
}
j++;
}
}

int main(){
FILE *fp1,*fp2;
char c;
int i;
char entrada[100],salida[100];
printf("Ingrese el nombre del archivo origen:\n");
scanf("%s",entrada);
if((fp1=fopen(entrada,"r"))==NULL){
printf("\nError al abrir el archivo %s",entrada);
return 0;
}
printf("Ingrese el nombre del archivo destino:\n");
scanf("%s",salida);
if((fp2=fopen(salida,"w"))==NULL){
printf("\nError al crear el archivo %s",salida);
return 0;
}
int cant_pal=0;
c=fgetc(fp1);
while(!feof(fp1)){
if (c == '\n'){
cant_pal = cant_pal + 1;
}
c=fgetc(fp1);
}
rewind(fp1);
char cad[cant_pal][50];
for(i=0;i<cant_pal;i++){
fscanf(fp1,"%s",cad[i]);
}
fclose(fp1);
char *p[cant_pal];
for(i=0;i<cant_pal;i++) *(p+i)=*(cad+i);
ordenar(p,cant_pal);

//Se escribe el nuevo archivo


for(i=0;i<cant_pal;i++){
fprintf(fp2,"%s\n",*(p+i));
}
fclose(fp2);
printf("\nArchivo creado con exito");
return 0;
}

Ejercicio 3
#include<stdio.h>

int main(){
FILE *fp1;
char c;
int i,m,n,j;
char entrada[100],salida[100];
printf("Ingrese el nombre del archivo origen:\n");
scanf("%s",entrada);
if((fp1=fopen(entrada,"r"))==NULL){
printf("\nError al abrir el archivo %s",entrada);
return 0;
}
n=1;
c=fgetc(fp1);
while((!feof(fp1))&&(c!='\n')){
if(c==' ') n++;
c=fgetc(fp1);
}
rewind(fp1);
c=fgetc(fp1);
m=0;
while(!feof(fp1)){
if (c == '\n'){
m++;
}
c=fgetc(fp1);
}
rewind(fp1);
printf("\nLa cantidad de filas es: %d",m);
printf("\nLa cantidad de columnas es: %d\n",n);
int mat[m][n],num,x;
for(i=0;i<m;i++){
  num=0;
x=0;
c=fgetc(fp1);
while(c!='\n'){
if(c==' '){
mat[i][x]=num;
num=0;
x++;
}
else num=num*10+(c-'0');
c=fgetc(fp1);
}
mat[i][x]=num;
}
//salida
printf("\n La matriz es:\n");
for(i=0;i<m;i++){
for(j=0;j<n;j++){
printf("%d\t",mat[i][j]);
}
printf("\n");
}
return 0;
}
Apuntes de la clase de PED2 Fecha: 19 de abril de 2013
MSc. José Colbes

Administración dinámica de memoria (De “Aprenda C como si estuviera en primero”


 – Capítulo 10)

Según lo visto hasta ahora, la reserva o asignación de memoria  para vectores y matrices se hace de forma
automática con la declaración de dichas variables, asignando suficiente memoria para resolver el problema de
tamaño máximo, dejando el resto sin usar para problemas más pequeños. Así, si en una función encargada de
realizar un producto de matrices, éstas se dimensionan para un tamaño máximo (100, 100), con dicha función
se podrá calcular cualquier producto de un tamaño igual o inferior, pero aun en el caso de que el producto sea
por ejemplo de tamaño (3, 3), la memoria reservada corresponderá al tamaño máximo (100, 100). Esta idea de
considerar el tamaño máximo fue aplicada en varios ejercicios, sobre todo en los trabajos prácticos. Sin
embargo, puede notarse que por ejemplo, si aplicamos el mismo concepto para varias estructuras de datos que
podrían aparecer en un mismo programa, el desperdicio de espacio en memoria puede ser enorme o inclusive
el programa podría detenerse por falta de memoria disponible.

Es muy útil el poder reservar más o menos memoria en tiempo de ejecución, según el tamaño del caso
concreto que se vaya a resolver. A esto se llama reserva, gestión o administración dinámica de memoria.

Existen en C dos funciones que reservan la cantidad de memoria deseada en tiempo de ejecución. Dichas
funciones devuelven  –es decir, tienen como valor de retorno – un puntero a la primera posición de la zona de
memoria reservada. Estas funciones se llaman malloc() ycalloc(), y sus declaraciones, que están en la librería
stdlib.h, son como sigue:

void *malloc(int n_bytes)


void *calloc(int n_datos, int tamaño_dato)

La función malloc() busca en la memoria el espacio requerido, lo reserva y devuelve un puntero al primer


elemento de la zona reservada. La función calloc() necesita dos argumentos: el nº de celdas de memoria
deseadas y el tamaño en bytes de cada celda; se devuelve un puntero a la primera celda de memoria. La
función calloc() tiene una propiedad adicional: inicializa todos los bloques a cero.

Existe también una función llamada free() que deja libre la memoria reservada por malloc() o calloc() y que ya
no se va a utilizar. Esta función usa como argumento el puntero devuelto por calloc() o malloc(). La memoria no
se libera por defecto, sino que el programador tiene que liberarla explícitamente con la función free(). El
prototipo de esta función es el siguiente:

void free(void *)

A continuación se presenta un ejemplo de gestión dinámica de memoria para el producto de matriz por vector.
Hay que tener en cuenta que reservando memoria por separado para cada fila de la matriz, no se garantiza que
las filas estén contiguas en la memoria. Por otra parte, de esta forma se pueden considerar filas de distinto
tamaño (veremos con un ejemplo más adelante). El nombre de la matriz se declara como puntero a vector de
punteros, y los nombres de los vectores c omo punteros.

/*Multiplicar una matriz por un vector usando malloc*/


#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("Programa que multiplica una matriz por un vector");
printf("usando malloc\n----------\n");
int m,n,i,j;
printf("Ingrese la cantidad de filas de la matriz: ");
scanf("%d",&m);
printf("Ingrese la cantidad de columnas de la matriz: ");
scanf("%d",&n);
int **p; //matriz
if((p = (int **) malloc((m)*sizeof(int *))) == NULL){
printf("\nmalloc fallido para las filas de la matriz\n");
return 0;
}
for(i=0; i<m; i++){
if ((p[i] = (int *) malloc((n)*sizeof(int))) == NULL){
printf("\nmalloc fallido para la columna %d de la matriz\n",i);
return 0;
}
}
int *q; //vector
if ((q = (int *) malloc((n)*sizeof(int))) == NULL){
printf("\nmalloc fallido para el vector\n");
return 0;
}
//Carga de datos
for(i=0; i<m; i++){
for(j=0; j<n; j++){
printf("p[%d][%d]",i,j);
scanf("%d",&p[i][j]);
}
}
for(i=0; i<n; i++){
printf("q[%d]",i);
scanf("%d",&q[i]);
}
//Proceso - solo para mostrar el uso de malloc y free
int *a; //vector de salida
if ((a = (int *) malloc((m)*sizeof(int))) == NULL){
printf("\nmalloc fallido para las filas del vector de salida\n");
return 0;
}
for(i=0; i<m; i++){
a[i]=0;
for(j=0; j<n; j++) a[i]+=(p[i][j]*q[j]);
free(p[i]);
}
free(p);
free(q);
//Impresion de resultados
printf("\n\nEl vector resultante es:\n");
for(i=0; i<m; i++) printf("%d\t",a[i]);
free(a);
return 0;
}

Usando calloc(), se tendría por ejemplo:

if ((p = calloc(m,sizeof(int *))) == NULL) {


printf("\ncalloc fallido para las filas de la matriz\n");
return 0;
}
if ((q = calloc(n,sizeof(int))) == NULL) {
printf("\ncalloc fallido para el vector\n");
return 0;
}

Ahora, volvamos a considerar el problema de ordenar un conjunto de  palabras. Esta vez, utilizaremos sólo el
espacio necesario para almacenar cada palabra. O sea, los tamaños de cada fila no serán necesariamente
iguales.

/* Ordenar alfabéticamente un conjunto de n palabras que se representan mediante una matriz


de caracteres.
Cada palabra es una fila de la matriz y no supera los 30 caracteres.
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int comparar(char *, char *);


void ordenar(char **, int n);

int main(){
int i,n;
printf("Ingrese la cantidad de palabras: "); scanf("%d",&n);
char *pal[n];
char cad[31];
printf("\nIngrese las palabras:\n");
for(i=0;i<n;i++){
printf("Palabra %d: ",(i+1));
scanf("%s",cad);
if ((pal[i] = (char*) malloc((strlen(cad)+1)*sizeof(char))) == NULL){
printf("\nmalloc fallido para la palabra %d\n",i);
return 0;
}
strcpy(pal[i],cad);
}
ordenar(pal,n);
printf("\nPalabras ordenadas:\n");
for(i=0;i<n;i++){
puts(*(pal+i));
}
return 0;
}

int comparar(char *c1, char *c2){


int i=0,a;
while((*(c1+i)!='\0')&&(*(c1+i)==*(c2+i))) i++;
a=*(c1+i)-*(c2+i);
return a;
}

void ordenar(char **q, int n){


int a,i,j=1,band=1;
char *t;
while(band){
band=0;
for(i=0;i<(n-j);i++){
a=comparar(*(q+i),*(q+i+1));
if(a>0){
t=*(q+i);
*(q+i)=*(q+i+1);
*(q+i+1)=t;
band=1;
}
}
j++;
}
}

Operaciones a nivel de bits (The C Programming Language – Capítulo 2)

C provee seis operadores para manipulación de bits; estos sólo pueden ser aplicados a operadores enteros,
esto es: char, short, int y long, sean con signo o sin él.

& → AND binario


| → OR binario
^ → XOR binario
<< → desplazamiento a la izquierda
>> → desplazamiento a la derecha
~ → complemento a 1

El operador binario AND es a menudo utilizado para desenmascarar un conjunto de bits, por ejemplo:

n = n & 127;

Pone a cero todos los bits de n, excepto los 7 bits de más bajo orden.

A continuación se muestra un ejemplo del operador binario OR:

x = x | SET_ON;

asigna 1 a los bits en x que están como 1 en SET_ON.

El operador binario XOR asigna un 1 en cada posición de bit en donde sus operandos tienen diferentes bits, y
cero cuando son los mismos.

Los operadores de desplazamiento << y >> realizan desplazamientos a la izquierda o derecha tantas veces
como lo indique el operando derecho, el cual debe ser no-negativo.

Entonces, x<<2 desplaza el valor binario de x en dos posiciones, llenando los bits vacantes con cero; esto es
equivalente a la multiplicación por 4. El operador >> realiza un desplazamiento de bits a la derecha del valor de
la izquierda, introduciendo cero por la izquierda, tantas veces como indique el segundo operador; equivale a
dividir por 2 tantas veces como indique el se gundo operando.

El operador unario devuelve el complemento a uno de un entero; esto es, convierte cada bit 1 en 0 y viceversa.
Por ejemplo:

n=n & ~63;

Coloca los seis últimos bits (hacia la derecha) de x a cero. Nótese que es independiente de la longitud de los
números involucrados.
Como una ilustración de algunos operadores de bits, considere la función getbits(x,p,n) que retorna el
valor del campo de n bits de x que comienza en la posición p. Asumimos que la el bit de posición cero está
hacia la derecha y que n y p son enteros positivos. Por ejemplo, getbits(x,4,3) retorna los tres bits en las
posiciones 4, 3 y 2.

#include<stdio.h>
int getbits(int x, int p, int n){
return (x >> (p+1-n)) & ~(~0 << n);
}
int main(){
int a,b,c,d;
printf("Ingrese el numero: ");
scanf("%d",&a);
printf("Ingrese la posicion inicial: ");
scanf("%d",&b);
printf("Ingrese el tamanho del campo: ");
scanf("%d",&c);
d=getbits(a,b,c);
printf("\nResultado: %d",d);
return 0;
}

La expresión x >> (p+1-n) mueve el campo deseado a la punta derecha. ~0 hace que todos los bits sean 1;
desplazándolo a la izquierda n  posiciones con ~0 << n  coloca ceros en los n  bits más a la derecha;
complementándolo con ~ se logra una máscara con unos en los n bits más a la derecha.

Sentencias condicionales abreviadas

Existe el operador ternario “ ?:” que puede utilizarse en una expresión condicional, lo cual provee una
alternativa a la estructura if-else. En la expresión:

expr1 ? expr2 : expr3

La expresión 1 es evaluada primero. Si es distinta de cero (verdadero), entonces la expresión 2 es evaluada, y


ese es el valor de la expresión condicional. De otra forma, la expresión 3 es evaluada, y ese es el valor. Sólo uno
entre expr2 y expr3 es evaluado. Por ejemplo:

z = (a > b) ? a : b; /* z = max(a, b) */

Se debe notar que la expresión condicional es en realidad una expresión, y puede ser usada donde sea que
pueda estar cualquier otra expresión. Si expr2 y expr3  son de diferentes tipos, el tipo del resultado es
determinado por las reglas de conversión. Por ejemplo, si f es float y n es int, entonces la expresión:

(n > 0) ? f : n

es del tipo float, independientemente de que n sea o no positivo.

La expresión condicional usualmente lleva a código abreviado. Por ejemplo, este ciclo imprime n elementos de
un arreglo, 10 por línea, con cada columna separada por un espacio, y con cada línea (incluida la última)
terminada por un salto de línea.

for(i=0;i<n;i++)
printf("%d%c", a[i], (i%10==9 || i==n-1) ? '\n' : ' ');

Un salto de línea se imprime cada 10 elementos, y luego del último. Todos los demás elementos van seguidos
de un espacio simple. Esto puede verse confuso, pero es más compacto que el if-else  equivalente. Otro
buen ejemplo es:

printf("Tienes %d guarani%s.\n", n, n==1 ? "" : "es");

Argumentos de la function main()

Cuando se ejecuta un programa desde la línea de comando tecleando su nombre, existe la posibilidad de
pasarle algunos datos, tecleándolos a continuación en la misma línea. Por ejemplo, se le puede pasar algún
valor numérico o los nombres de algunos ficheros en los que tiene que leer o escribir información. Esto se
consigue por medio de argumentos que se pasan a la función main(), como se hace con otras funciones.
Así pues, a la función main()  se le pueden pasar argumentos y también puede tener valor de retorno. El
primero de los argumentos de main() se suele llamar argc, y es una variable int que contiene el número
de palabras que se teclean a continuación del nombre del programa cuando éste se ejecuta. El segundo
argumento se llama argv, y es un vector de punteros a carácter que contiene las direcciones de la primera
letra o carácter de dichas palabras. A continuación se presenta un ejemplo:

#include<stdio.h>
int main(int argc, char *argv[]){
int cont;
for(cont=0;cont<argc;cont++)
printf("El argumento %d es: %s\n", cont, argv[cont]);
printf("\n");
return 0;
}

Ejemplo:  ordenar ascendentemente   números y guardar el resultado en un archivo. Tanto el nombre del
archivo de salida como los números se introducen por línea de comando.

#include<stdio.h>
void ordenar(int a[], int n){
int i,j,b,aux;
do{
b=0;
for(j=0;j<(n-1);j++){
if(a[j]>a[j+1]){
b=1;
aux=a[j];a[j]=a[j+1];a[j+1]=aux;
}
}
}while(b);
}
int main(int argc, char *argv[]){
int i;
int n=argc-2; //por el nombre del programa y el archivo de salida
int vec[n];
if(n<3){
printf("No hay suficientes parametros de entrada\n");
return 0;
}
for(i=0;i<n;i++) vec[i]=atoi(argv[i+2]);
ordenar(vec,n);
FILE *fp;
if((fp=fopen(argv[1],"w"))==NULL){
printf("Error al crear el archivo\n");
return 0;
}
for(i=0;i<n;i++) fprintf(fp,"%d\n",vec[i]);
fclose(fp);
printf("El archivo ha sido creado con exito!!");
return 0;
}
Ejercicios

1. Escribir un programa (calculadora.c) que acepte como parámetros dos números enteros y una
operación básica (+,-,*,/,^) y muestre el resultado de la operación en pantalla.
2. Escribir un programa que reciba como argumento una cadena e imprima la misma pero en mayúsculas.
3. Escribir un programa que permita copiar un archivo. Se deben recibir como parámetros en la línea de
comando el nombre del archivo original y el del archivo copia.
4. Escribir una función invert(x,p,n) que retorne x con los n bits que comienzan en la posición p de
manera invertida (es decir, el 1 se cambia por el cero y viceversa), dejando los demás sin cambio.
5. Escribir una función righrot(x,n)  que retorne el valor de un entero x  rotado n  posiciones a la
derecha.
6. Escribir una función lower  que reciba una cadena y convierta letras mayúsculas en minúsculas,
utilizando expresiones condicionales en lugar de if-else.
Apuntes de la clase de PED2 Fecha: 26 de abril de 2013
MSc. José Colbes

Listas Enlazadas (Algoritmos y Estructuras de Datos, una Perspectiva en C - Capítulo 9)

Clasificación:
 Listas simplemente enlazadas
 Listas doblemente enlazadas
 Lista circular simplemente enlazada
 Lista circular doblemente enlazada

Implementaciones
 Asignación fija (mediante arreglos)
 Asignación dinámica (mediante punteros y reserva dinámica de memoria)

Operaciones en listas enlazadas


 Declaración de los tipos nodo y puntero a nodo.
 Inicialización o creación
 Insertar elementos en una lista
 Eliminar elementos de una lista
 Buscar elementos de una lista (comprobar la e xistencia de elementos en una lista)
 Recorrer una lista enlazada (visitar cada nodo de la lista)
 Comprobar si la lista está vacía

1. Declaración de un nodo

typedef struct estructura{


...
}Item;

typedef struct Elemento{


Item dato;
struct Elemento* siguiente;
}Nodo;

2. Construcción de una lista

Nodo *cabeza;
Cabeza = NULL;

Pasos para la construcción


 Declarar el tipo de dato y el puntero de cabeza o primero
 Asignar memoria para un elemento del tipo definido anteriormente utilizando asignación dinámica de
memoria.
 Crear iterativamente el primer elemento (cabeza) y los elementos sucesivos de una lista e nlazada
 Repetir hasta que no existan más e ntradas

3. Creación de un nodo

Nodo* crearNodo(Item x){


Nodo *a;
a=(Nodo*)malloc(sizeof(Nodo));
a->dato=x;
a->siguiente=NULL;
return a;
}

4. Insertar un nuevo elemento en la cabeza de una lista


void inserPrimero(Nodo** cabeza, Item entrada){
Nodo *nuevo;
nuevo=crearNodo(entrada);
nuevo->siguiente = *cabeza;
*cabeza = nuevo;
}

Ejercicio de aplicación
Se va a formar una lista enlazada de números aleatorios. El programa que realiza esta tarea inserta los nuevos
nodos por la cabeza de la lista. Una vez creada la lista, se recorre los nodos para mostrar los números pares.

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define MAX 20
typedef int Item;
typedef struct Elemento{
Item dato;
struct Elemento* siguiente;
}Nodo;

void inserPrimero(Nodo** cabeza, Item entrada);


Nodo* crearNodo(Item x);

int main(){
Item d;
Nodo *cabeza, *ptr;
int k;
cabeza=NULL; /* lista vacía */
srand(time(NULL));
d=rand()%MAX;
while(d){/* Bucle termina cuando se genera el número 0 */
printf("%d ",d); //para mostrar los numeros que se generan
inserPrimero(&cabeza,d);
d=rand()%MAX;
}
printf("\n\n");
for(k=0,ptr=cabeza;ptr!=NULL;ptr=ptr->siguiente){
if((ptr->dato)%2==0) /* se recorre la lista para escribir los pares */
{ printf("%d ",ptr->dato);
k++;
printf("%c",(k%12 ? ' ' : '\n')); /* 12 datos por línea */
}
}
printf("\n\n");
return 0;
}

void inserPrimero(Nodo** cabeza, Item entrada){


Nodo *nuevo;
nuevo=crearNodo(entrada);
nuevo->siguiente = *cabeza;
*cabeza = nuevo;
}

Nodo* crearNodo(Item x){


Nodo *a;
a=(Nodo*)malloc(sizeof(Nodo));
a->dato=x;
a->siguiente=NULL;
return a;
}

5. Inserción de un nodo al final de la lista

void inserFinal(Nodo** cabeza, Item entrada){


Nodo *ultimo;
ultimo = *cabeza;
  if(ultimo==NULL){
*cabeza=crearNodo(entrada);
}
else{
while(ultimo->siguiente!=NULL) ultimo = ultimo->siguiente;
ultimo->siguiente=crearNodo(entrada);
}
}

6. Inserción de un nodo entre dos nodos de l a lista

En una de sus versiones, las etapas son las siguientes:


 Asignar el nuevo nodo, con el campo dato, apuntado por el puntero nuevo.
 Hacer que el campo enlace siguiente del nuevo nodo apunte al nodo que va después de la posición
que se desea para el nuevo nodo (o bien NULL si no hay ningún nodo después de la nueva posición)
 En la variable puntero anterior tener la dirección del nodo que está antes de la posición deseada para
el nuevo nodo. Hacer que anterior -> siguiente apunte al nuevo nodo que se acaba de crear.

void insertar(Nodo** cabeza, Item testigo, Item entrada){


Nodo *nuevo, *despues;
nuevo=crearNodo(entrada);
if(*cabeza == NULL) *cabeza = nuevo;
else
{ int esta=0;
despues=*cabeza;
while((despues->siguiente != NULL) && !esta)
{ if(despues->dato != testigo) despues = despues->siguiente;
else esta=1;
}
if(esta) /* Se enlaza el nuevo nodo */
{ nuevo->siguiente = despues->siguiente;
despues->siguiente = nuevo;
}
else free(nuevo);
}
}

7. Búsqueda en listas enlazadas

Nodo* localizar(Nodo* cabeza, Item destino){


Nodo *indice;
for(indice=cabeza;indice!=NULL;indice=indice->siguiente)
if(destino==indice->dato) return indice;
return NULL;
}

8. Eliminación de un nodo en una lista

El algoritmo tiene las siguientes etapas:


 Búsqueda del nodo que contiene el dato (se ha de tener la dirección del nodo a eliminar y la dirección
del anterior).
 El puntero siguiente del nodo anterior ha de apuntar al siguiente del nodo a eliminar.
 En caso de que el nodo a eliminar sea el primero, cabeza, se modifica cabeza para que tenga la
dirección del nodo siguiente.
 Por último, se libera la memoria ocupada por e l nodo.

void eliminar (Nodo** cabeza, Item entrada){


Nodo* actual = *cabeza;
Nodo *anterior = NULL;
int encontrado = 0;
while ((actual!=NULL) && (!encontrado)) /* búsqueda del nodo y del anterior */
{ encontrado = (actual -> dato == entrada);
if (!encontrado)
{ anterior = actual;
actual = actual -> siguiente;
}
}
if (actual != NULL) /* Enlace nodo anterior con siguiente. Si es NULL No existe elemento */
{ if (actual == *cabeza) /* distingue entre el nodo cabecera o el resto de la lista */
*cabeza = actual -> siguiente;
else
anterior -> siguiente = actual -> siguiente;
free(actual);
}
}

Ejercicio de aplicación

Se desea crear una lista enlazada de números enteros ordenada. La lista se organiza de tal forma que el nodo
cabecera tenga el menor elemento, y así en orden creciente los demás nodos. Al iniciar el programa, se debe
desplegar (y por supuesto, implementar) estas opciones para el usuario:
 Agregar elemento con un cierto valor
 Eliminar un elemento con un cierto valor. Si no se encuentra, indicarlo con un mensaje.
 Mostrar la lista
 Salir del programa
Apuntes de la clase de PED2 Fecha: 3 de mayo de 2013
MSc. José Colbes

Pilas y Colas (Capítulo 12 – “Fundamentos de la Programación” – Luis Joyanes Aguilar; Capítulos 10 y 1 1 –
“Estructuras de datos, una perspectiva en C” –  Luis Joyanes Aguilar)

Pilas

Una  pila (stack ) es un tipo especial de lista lineal en la que la inserción y borrado de nuevos elementos se
realiza sólo por un extremo que se denomina cima o tope (top).

La pila es una estructura con numerosas analogías en la vida real: una pila de platos, una pila de monedas, una
pila de cajas de zapatos, una pila de camisas, una pila de bandejas, etc.

Dado que las operaciones de insertar y eliminar se realizan por un solo extremo (el superior), los elementos
sólo pueden eliminarse en orden inverso al que se insertan en la pila. El último elemento que se pone en la pila
es el primero que se puede sacar; por ello, a estas estructuras se les conoce por el nombre de LIFO (last-in,
 first-out, último en entrar, primero en salir ).

Las operaciones más usuales asociadas a las pilas son:


 Apilar o poner (push):  operación de insertar un elemento en la pila
 Desapilar o sacar (pop): operación de eliminar un elemento de la pila.

La pila se puede implementar mediante arrays, en cuyo caso su dimensión o longitud es fija; y mediante listas
enlazadas, en cuyo caso se utiliza memoria dinámica y no existe limitación en su tamaño excepto la memoria
libre del ordenador.

Una pila puede estar vacía (no tiene elementos) o llena (en el caso de tener tamaño fijo, si no caben más
elementos en la pila). Si un programa intenta sacar un elemento de una pila vacía, se producirá un error,
debido a que esa operación es imposible; esta situación se denomina desbordamiento negativo (underflow ).
Por el contrario, si un programa intenta poner un elemento en una pila se produce un error llamado
desbordamiento (overflow ) o rebosamiento . Para evitar estas situaciones se diseñan funciones, que
comprueban si la pila está llena o vacía.

Especificaciones del tipo abstracto de datos Pila


Las operaciones que sirven para definir una pila y poder manipular su contenido son las siguientes (no todas
ellas se implementan al definir una pila):

Tipo de dato: Dato que se almacena en la pila


Operaciones:
CrearPila Inicia la pila como vacía
Insertar (push) Pone un dato en la pila
Quitar (pop) Retira (saca) un dato de la pila
PilaVacia Comprueba si la pila no tiene elementos
PilaLlena Comprueba si la pila está llena de elementos
LimpiarPila Quita todos sus elementos y deja la pila vacía
Cima Obtiene el elemento cima de la pila
TamanhoPila Número de elementos máximo que puede contener la pila
Pila implementada con arreglos

Una implementación estática se realiza utilizando un arreglo de tamaño fijo y una implementación dinámica
mediante una lista enlazada.

La pila implementada con arreglos incluye un arreglo y un índice a la cima de la pila; además, una constante
con el máximo número de elementos.

Operaciones con pilas implementadas con arreglos

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

#define TAMPILA 100


typedef int Item;
typedef struct
{ Item listaPila[TAMPILA];
int cima;
}Pila;

void crearPila(Pila *pila){


pila->cima=(-1);
} /*–1 indica que la Pila existe y está vacia*/

int pilaLlena(Pila pila){


return (pila.cima==(TAMPILA-1));
}

void insertar(Pila *pila, Item elemento){


if(pilaLlena(*pila)){
puts("OverFlow");
exit(1);
}
pila->cima++;
pila->listaPila[pila->cima]=elemento;
}

int pilaVacia(Pila pila){


return(pila.cima==(-1));
}

Item quitar(Pila *pila){


Item temp;
if(pilaVacia(*pila)){
puts("UnderFlow");
exit(1);
}
temp=pila->listaPila[pila->cima];
pila->cima--;
return temp;
}

Item cima(Pila *pila){


Item temp;
if(pilaVacia(*pila)){
puts("Pila Vacia");
exit(1);
}
return pila->listaPila[pila->cima];
}

void limpiarPila(Pila *pila){


pila->cima=(-1);
}
Pila implementada con listas enlazadas

La realización dinámica de una pila se hace almacenando los elementos como nodos de una lista enlazada. Con
la particularidad de que siempre que se desee insertar o poner (empujar) un elemento se hace por el mismo
extremo que se extrae.

Esta realización tiene la ventaja de que el tamaño se ajusta exactamente a los elementos de la pila, no hay un
máximo de elementos y, por tanto, no existe la condición de  pila llena. Sin embargo, para cada elemento es
necesaria una memoria ya que hay que guardar el campo de enlace. En la realización de una pila con arreglos
se requiere establecer un máximo de posibles elementos, como contrapartida el acceso es más rápido ya que
se hace con una variable subindicada.

Operaciones con pilas implementadas con listas enlazadas

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

typedef int Item;


typedef struct nodo{
Item elemento;
struct nodo *siguiente;
}Nodo;

void crearPila(Nodo **pila){


*pila=NULL;
}

void insertar(Nodo** pila, Item elemento){


Nodo *nuevo;
nuevo=(Nodo*)malloc(sizeof(Nodo));
nuevo->elemento=elemento;
nuevo->siguiente=(*pila);
(*pila)=nuevo;
}

int pilaVacia(Nodo *pila){


return(pila==NULL);
}

void suprimir(Nodo** pila){


if(!pilaVacia(*pila)){
Nodo *f;
f=(*pila);
(*pila)=(*pila)->siguiente;
free(f);
}
}

Item cima(Nodo *pila){


if(pilaVacia(pila)){
puts("Pila Vacia");
exit(1);
}
return pila->elemento;
}

Item quitar(Nodo** pila){


Item temp=cima(*pila);
suprimir(pila);
return temp;
}

int main(){
Item x;
int n,i=0;
Nodo *pila;
crearPila(&pila);
scanf("%d",&n);
  while(n!=0){
i++;
insertar(&pila,n);
scanf("%d",&n);
}
while(i--){
x=quitar(&pila);
printf("%d ",x);
}
return 0;
}

Ejercicio de aplicación 1
Leer una cadena, y con la ayuda de una pila determinar si es palíndroma o no.
#include<stdio.h>
#include<stdlib.h>

typedef char Item;


typedef struct nodo{
Item elemento;
struct nodo *siguiente;
}Nodo;

void crearPila(Nodo **pila){


*pila=NULL;
}

void insertar(Nodo** pila, Item elemento){


Nodo *nuevo;
nuevo=(Nodo*)malloc(sizeof(Nodo));
nuevo->elemento=elemento;
nuevo->siguiente=(*pila);
(*pila)=nuevo;
}

int pilaVacia(Nodo *pila){


return(pila==NULL);
}

void suprimir(Nodo** pila){


if(!pilaVacia(*pila)){
Nodo *f;
f=(*pila);
(*pila)=(*pila)->siguiente;
free(f);
}
}

Item cima(Nodo *pila){


if(pilaVacia(pila)){
puts("Pila Vacia");
exit(1);
}
return pila->elemento;
}

Item quitar(Nodo** pila){


Item temp=cima(*pila);
suprimir(pila);
return temp;
}

void limpiarPila(Nodo** pila){


while(!pilaVacia(*pila)){
suprimir(pila);
}
}

int main(){
Item x;
int j;
Nodo *pila;
crearPila(&pila);
Item cad[100];
printf("\nIngrese una cadena:\n");
for(j=0;(x=getchar())!='\n';){
cad[j++]=x;
insertar(&pila,x);
}
cad[j]='\0';
int es_pal=1;
for(j=0;es_pal && !pilaVacia(pila);){
es_pal = cad[j++]==quitar(&pila);
}
  limpiarPila(&pila);
if(es_pal) printf("\nEs un palindromo");
else printf("\nNo es un palindromo");
return 0;
}

Ejercicio de aplicación 2
Dada una línea que representa una expresión matemática, donde se usan paréntesis, llaves y corchetes; indicar
si la misma es correcta o no en relación a estos símbolos.

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef char Item;
typedef struct nodo{
Item elemento;
struct nodo *siguiente;
}Nodo;

void crearPila(Nodo **pila){


*pila=NULL;
}

void insertar(Nodo** pila, Item elemento){


Nodo *nuevo;
nuevo=(Nodo*)malloc(sizeof(Nodo));
nuevo->elemento=elemento;
nuevo->siguiente=(*pila);
(*pila)=nuevo;
}

int pilaVacia(Nodo *pila){


return(pila==NULL);
}

void suprimir(Nodo** pila){


if(!pilaVacia(*pila)){
Nodo *f;
f=(*pila);
(*pila)=(*pila)->siguiente;
free(f);
}
}

Item cima(Nodo *pila){


if(pilaVacia(pila)){
puts("Pila Vacia");
exit(1);
}
return pila->elemento;
}

Item quitar(Nodo** pila){


Item temp=cima(*pila);
suprimir(pila);
return temp;
}

void limpiarPila(Nodo** pila){


while(!pilaVacia(*pila)){
suprimir(pila);
}
}

int main(){
Item x;
int i=0;
Nodo *pila;
crearPila(&pila);
Item cad[100];
printf("\nIngrese una cadena:\n");
gets(cad);
int correcto=1;
while(cad[i]!='\0'){
if(cad[i]=='(' || cad[i]=='[' || cad[i]=='{'){
insertar(&pila,cad[i]);
}
if(cad[i]==')' || cad[i]==']' || cad[i]=='}'){
x=quitar(&pila);
if(cad[i]==')' && x!='('){
correcto=0;
break;
}
if(cad[i]==']' && x!='['){
correcto=0;
break;
}
if(cad[i]=='}' && x!='{'){
correcto=0;
break;
}
}
i++;
}
if(!pilaVacia(pila)) correcto=0;
if(correcto) printf("\nLa expresion es correcta");
else printf("\nLa expresion es incorrecta");
return 0;
}

Colas

Una cola es una estructura de datos que almacena elementos en una lista y permite acceder a los datos por
uno de los dos extremos de la lista. Un elemento se inserta en la cola (parte final) de la lista y se suprime o
eliminar por el frente (parte inicial) de la lista. Las aplicaciones utilizan una cola para almacenar elementos en
su orden de aparición o concurrencia.

Los elementos se eliminan (o quitan) de la cola en el mismo orden en que se almacenan y, por consiguiente,
una cola es una estructura de tipo FIFO ( first-in/first-out , primero en entrar/primero en salir). La fila de
atención a clientes es un ejemplo de cola, o bien una cola de trabajos en una impresora.

Las operaciones usuales en las colas son Insertar  y Quitar . La operación Insertar   añade un elemento por el
extremo final de la cola, y la operación Quitar elimina o extrae un elemento por el extremo opuesto, el frente
de la cola. La organización de elementos en forma de cola asegura que el primero en entrar es el primero en
salir.

Especificaciones del tipo abstracto de datos Cola


Las operaciones que sirven para definir una cola y poder manipular su contenido son las siguientes:

Tipo de dato: Dato que se almacena en la cola


Operaciones:
CrearCola Inicia la cola como vacía
Insertar Añade un dato por el final de la cola
Quitar Retira (extrae) el elemento frente de la cola
ColaVacia Comprueba si la cola no tiene elementos
ColaLlena Comprobar si la cola está llena de elementos
Frente Obtiene el elemento frente o primero de la cola
TamañoCola Número de elementos máximo que puede contener la cola

La forma que los lenguajes tienen para representar el TAD Cola  depende de dónde se almacenen los
elementos, en un arreglo o en una lista dinámica. La utilización de arreglos tiene el problema de que la cola no
puede crecer indefinidamente, está limitada por el tamaño del arreglo; como contrapartida, el acceso a los
extremos es muy eficiente. Utilizar una lista dinámica permite que el número de nodos se ajuste al de
elementos de la cola; por el contrario, cada nodo necesita memoria extra para el enlace y también está el
límite de memoria de la pila del computador.
Cola implementada con arreglos

La definición de una Cola ha de contener un arreglo para almacenar los elementos de la cola, y dos marcadores
o apuntadores para mantener las posiciones  frente y  final   de la cola; es decir, un marcador apuntando a la
posición de la cabeza de la cola y el otro al primer espacio vacío que sigue al final de la cola.

El avance lineal de frente y final tiene un grave problema, deja huecos por la izquierda del arreglo. Puede
ocurrir que final  alcance el índice más alto del arreglo, no pudiéndose insertar nuevos elementos y, sin
embargo, existir posiciones libres a la izquierda de frente.

Una alternativa a esta situación sería mantener fijo el frente de la cola al comienzo del arreglo, lo que supone
mover todos los elementos de la cola una posición cada vez que se desea retirar un elemento. Estos problemas
quedan resueltos considerando el arreglo como circular .

En este último caso, el arreglo sigue siendo lineal, pero se simula un movimiento circular mediante los restos,
de la siguiente forma:

Mover final adelante = (final+1)%MAXTAMQ


Mover frente adelante = (frente+1)%MAXTAMQ

Operaciones con colas implementadas con arreglos

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

#define MAXTAMQ 100


typedef int Item;
typedef struct{
Item listaCola[MAXTAMQ];
int frente;
int final;
}Cola;

int siguiente(int n){


  return((n+1)%MAXTAMQ);
}

void crearCola(Cola *cola){


cola->frente=0;
cola->final=MAXTAMQ-1;
}

int colaLlena(Cola cola){


return (cola.frente==siguiente(siguiente(cola.final)));
}

void insertar(Cola *cola, Item entrada){


if(colaLlena(*cola)){
puts("Cola Llena");
exit(1);
}
cola->final = siguiente(cola->final);
cola->listaCola[cola->final]=entrada;
}

Item quitar(Cola *cola){


Item temp;
if(colaVacia(*cola)){
puts("No hay elementos para sacar");
exit(1);
}
temp=cola->listaCola[cola->frente];
cola->frente=siguiente(cola->frente);
return temp;
}

int colaVacia (Cola cola){


return(cola.frente==siguiente(cola.final));
}

Item frente(Cola cola){


if(colaVacia(cola)){
puts("Error de ejecucion: Cola Vacia");
exit(1);
}
return cola.listaCola[cola.frente];
}

Cola implementada con listas enlazadas

La alternativa a los arreglos reside e n utilizar memoria que se ajusta en todo momento al número de elementos
de la cola, empleando memoria dinámica mediante una lista enlazada, aún a pesar de tener el inconveniente
de la memoria extra utilizada para realizar los encadenamientos entre nodos. Esta implementación utiliza dos
punteros para acceder a la lista, frente y final (los extremos por donde salen los elementos y por donde se
insertan respectivamente).

La variable puntero frente referencia al primer elemento de la cola, el primero en ser retirado de la cola. La
otra variable puntero, final, referencia al último elemento en ser añadido, que será el último en ser retirado.

Al ser una estructura dinámica, puede crecer y decrecer según las necesidades (el límite está en la memoria
libre del computador), y por tanto, no tie ne sentido la operación que prueba si la cola está llena.

Operaciones con colas implementadas con li stas enlazadas

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

typedef int Item;


typedef struct nodo{
Item elemento;
struct nodo *siguiente;
}Nodo;

typedef struct {
Nodo *frente;
Nodo *final;
}Cola;

void crearCola(Cola *cola){


cola->frente=cola->final=NULL;
}

Nodo* crearNodo(Item elemento){


Nodo* t;
t=(Nodo*) malloc(sizeof(Nodo));
t->elemento=elemento;
t->siguiente=NULL;
return t;
}

int colaVacia(Cola cola){


return(cola.frente==NULL);
}

void insertar(Cola *cola, Item entrada){


Nodo *a;
a=crearNodo(entrada);
if(colaVacia(*cola))
cola->frente = a;
else
(cola->final)->siguiente = a;
cola->final = a;
}

Item quitar(Cola *cola){


Item tmp;
if(colaVacia(*cola)){
puts("Error cometido al eliminar de una cola vacia");
exit(-1);
}
else{
Nodo *a = cola->frente;
tmp = (cola->frente)->elemento;
cola->frente = (cola->frente)->siguiente;
free(a);
return tmp;
}
}

Item frente (Cola cola){


if (colaVacia(cola)){
puts("Error: cola vacia");
exit(-1);
}
return((cola.frente)->elemento);
}

void borrarCola(Cola *cola){


Nodo *n;
for(;cola->frente!=NULL;){
n = cola->frente;
cola->frente = (cola->frente)->siguiente;
free(n);
}
}

Ejercicio de aplicación

Una variación del problema matemático llamado “problema de José” permite generar números de la suerte. Se
parte de una lista inicial de n números; esta lista se va reduciendo con el siguiente algoritmo:

1. Se genera un número aleatorio (del 1 al 10) .


2. Si     se quitan de la lista los números que ocupan las posiciones         ;  toma el
valor del número de elementos que quedan e n la lista.
3. Se vuelve al paso 1.
4. Si   , fin del algoritmo; los números de la suerte son los que quedan en la lista.

int main(){
Cola cola;
int n,n1,n2,n3,i;
crearCola(&cola);
srand(time(NULL));
//Numero de elementos de la lista
n=1+rand()%50;printf("n=%d\n",n);
//Se generan n numeros aleatorios
for(i=0;i<n;i++)
insertar(&cola, 1+rand()%1001);
n1=1+rand()%11;
while(n1<=n){
printf("\nSe quitan elementos a distancia %d",n1);
n2=0; //Contador de elementos que quedan
for(i=1;i<=n;i++){
n3=quitar(&cola);
if(i%n1==1) printf("\t%d se quita.",n3);
else{
insertar(&cola,n3); //Se vuelve a meter en la cola
n2++;
}
}
n=n2;
n1=1+rand()%11;
}
printf("\n\nLos numeros de la suerte son:\n");
while(!colaVacia(cola))
printf("%d ",quitar(&cola));
return 0;
}
Apuntes de la clase de PED2 Fecha: 10 de mayo de 2013
MSc. José Colbes

Clases en C++ (Introducción)

Primer ejemplo – Clase Hora


Temas: Definición de una clase, cuerpo de la clase. Atributos y métodos. Constructores. Prueba de las clases.
Alcance de los miembros.
#include<iostream>
using namespace std;

class Hora{
public:
Hora();
void estableceHora(int,int,int);
void imprimeEstandar();
private:
int hora;
int minuto;
int segundo;
};

Hora::Hora(){
hora=minuto=segundo=0;
}

void Hora::estableceHora(int h, int m, int s){


hora = (h>=0 && h<24) ? h : 0;
minuto = (m>=0 && m<60) ? m : 0;
segundo = (s>=0 && s<60) ? s : 0;
}

void Hora::imprimeEstandar(){
cout<<((hora==0 || hora==12) ? 12 : hora%12)
<<":"<<((minuto<10) ? "0" : "")<<minuto
<<":"<<((segundo<10) ? "0" : "")<<segundo
<<((hora<12) ? " AM" : " PM");
}

//Prueba de la clase Hora


int main(){
Hora h;
cout<<"\nLa hora estandar inicial es: ";
h.imprimeEstandar();

h.estableceHora(13,27,6);
cout<<"\n\nLa hora estandar despues de estableceHora es: ";
h.imprimeEstandar();

h.estableceHora(99,99,99);
cout<<"\n\nDespues de intentar establecer valores invalidos:";
cout<<"\nHora estandar: ";
h.imprimeEstandar();
cout<<endl;
return 0;
}

Segundo Ejemplo – Clase Rectangulo


Temas: Encapsulamiento. Getters y Setters. New y delete.
#include<iostream>
using namespace std;

class Rectangulo{
public:
Rectangulo();
double Perimetro();
double Area();
void establecer_ancho(double);
void establecer_longitud(double);
private:
double longitud,ancho;
};

Rectangulo::Rectangulo(){
longitud=1.0;
ancho=1.0;
}

double Rectangulo::Perimetro(){
double p;
p=2*(longitud+ancho);
return p;
}

double Rectangulo::Area(){
double a;
a=longitud*ancho;
return a;
}

void Rectangulo::establecer_ancho(double a){


if(a>0.0 && a<20.0) ancho=a;
else cout<<"El valor debe ser mayor que 0.0 y menor que 20.0"<<endl;
}

void Rectangulo::establecer_longitud(double l){


if(l>0.0 && l<20.0) longitud=l;
else cout<<"El valor debe ser mayor que 0.0 y menor que 20.0"<<endl;
}

int main(){
Rectangulo rect;
rect.establecer_ancho(2.5);
rect.establecer_longitud(3.5);
cout<<"El perimetro es: "<<rect.Perimetro()<<endl;
cout<<"El area es: "<<rect.Area()<<endl;

Rectangulo *ptr_rect = new Rectangulo();


ptr_rect->establecer_ancho(1.0);
ptr_rect->establecer_longitud(2.5);
cout<<"El perimetro es: "<<ptr_rect->Perimetro()<<endl;
cout<<"El area es: "<<ptr_rect->Area()<<endl;
delete ptr_rect;
return 0;
}

Tercer Ejemplo – Clase Cadena


Temas: Constructores y destructores
#include<iostream>
#include<cstring>
using namespace std;

class Cadena{
public:
Cadena(const char *);
~Cadena();
void mayusculas();
void minusculas();
void asignar(const char *);
void imprimir();
private:
char *c;
};

Cadena::Cadena(const char *d){


c = new char[strlen(d)+1];
strcpy(c,d);
}

Cadena::~Cadena(){
delete[] c;
cout<<"Se ejecuto el destructor"<<endl;
}

void Cadena::imprimir(){
cout<<c<<endl;
}

void Cadena::asignar(const char *d){


delete[] c;
c = new char[strlen(d)+1];
strcpy(c,d);
}

void Cadena::mayusculas(){
int i=0;
while(c[i]!=0){
if(c[i]>='a' && c[i]<='z') c[i]+=('A'-'a');
i++;
}
}

void Cadena::minusculas(){
int i=0;
while(c[i]!=0){
if(c[i]>='A' && c[i]<='Z') c[i]-=('A'-'a');
i++;
}
}

int main(){
char c[1000];
cin.getline(c,1000);
Cadena cad(c);
Cadena *cad2 = new Cadena("Prueba");
cad.imprimir();
cad.mayusculas();
cad.imprimir();
cad.minusculas();
cad.imprimir();
cad2->imprimir();
delete cad2;
return 0;
}

Cuarto ejemplo – Clase Operación


Tema: Sobrecarga de métodos
#include<iostream>
using namespace std;

class Operacion{
public:
//Operacion();
int sumar(int,int);
int sumar(int,int,int);
double sumar(double,double);
};

//Operacion::Operacion(){}

int Operacion::sumar(int x,int y){


return(x+y);
}

int Operacion::sumar(int x,int y, int z){


return(x+y+z);
}

double Operacion::sumar(double x,double y){


return(x+y);
}

int main(){
Operacion oper;
int a=5,b=6,c=7;
double d=1.0,e=2.5;
cout<<"Suma 1: "<<oper.sumar(a,b)<<endl;
cout<<"Suma 2: "<<oper.sumar(a,b,c)<<endl;
cout<<"Suma 3: "<<oper.sumar(e,d)<<endl;
return 0;
}

Quinto Ejemplo – Clase CuentaAhorros


Temas: Miembros estáticos
#include<iostream>
using namespace std;

class CuentaAhorros{
private:
static double tasaInteresAnual;
double saldoAhorro;
public:
CuentaAhorros(double);
void ultimoInteres();
static void modificaTasaInteres(double);
};

double CuentaAhorros::tasaInteresAnual=3.0;

CuentaAhorros::CuentaAhorros(double s){
saldoAhorro=s;
}

void CuentaAhorros::ultimoInteres(){
double interes;
interes=saldoAhorro*tasaInteresAnual/100;
cout<<"El interes es: "<<interes<<endl;
saldoAhorro+=interes;

También podría gustarte