Sei sulla pagina 1di 202

Computaci on y Metodos Numericos

P56, libre elecci on


Notas de clase y tareas
M. Araceli Garn
Prof. Titular de Economa Aplicada
Dpto. Economa Aplicada III
Fac. CC. Economicas y Empresariales
Bilbao

Indice general
1. Nociones b asicas sobre algoritmos 1
1.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Que es un algoritmo? Medidas de eciencia . . . . . . . . . . 2
1.3. Complejidad. Complejidad computacional . . . . . . . . . . . 5
1.4. Complejidad agebr aica y analtica . . . . . . . . . . . . . . . 6
1.5. Precision numerica . . . . . . . . . . . . . . . . . . . . . . . . 7
1.6. Analisis de Algoritmos. Lmites de complejidad . . . . . . . . 10
2. Introduccion al lenguaje de programacion C (I) 13
2.1. Nociones sobre trabajo en el Sistema Operativo UNIX. El
editor vi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2. Compilado, ejecucion y depuraci on de programas en C . . . . 20
2.3. Variables escalares, subindicadas (arrays) y constantes . . . . 21
2.3.1. Nombres de variables . . . . . . . . . . . . . . . . . . . 22
2.3.2. Tipo y tama no de datos . . . . . . . . . . . . . . . . . 23
2.4. Un ejemplo. El primer programa en C . . . . . . . . . . . . . 26
2.5. Operadores y funciones standard m as usuales . . . . . . . . . 30
2.5.1. Funciones mas usuales . . . . . . . . . . . . . . . . . . 31
2.6. Ambito de denici on y validez de las variables. Variables ex-
ternas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.7. Precedencia y orden de evaluaci on entre operadores . . . . . . 39
3. Introduccion al lenguaje de programacion C (II) 41
3.1. Declaraciones. Tipos de datos escalares: enteros, caracter, de
coma otante y enumeraci on . . . . . . . . . . . . . . . . . . 41
3.2. Conversiones de tipo . . . . . . . . . . . . . . . . . . . . . . . 45
3.3. Nociones sobre representacion de datos e implicaciones sobre
la precisi on numerica . . . . . . . . . . . . . . . . . . . . . . . 48
3.3.1. Errores de redondeo en computaci on . . . . . . . . . . 51
3.3.2. Error de cancelaci on . . . . . . . . . . . . . . . . . . . 52
4. Introduccion al lenguaje de programacion C (III) 55
4.1. Sentencias de control de ujo: for, if , do, while, switch. Ejem-
plos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
i
ii

Indice general
4.2. Funciones: anatoma y uso . . . . . . . . . . . . . . . . . . . . 64
4.3. Convenciones sobre el paso de argumentos . . . . . . . . . . . 65
4.4. Clases de almacenamiento . . . . . . . . . . . . . . . . . . . . 68
4.5. Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.6. Funciones denidas en stdio y math . . . . . . . . . . . . . . 72
5. Algoritmos iterativos 79
5.1. Introducci on . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.2. Resolucion de ecuaciones . . . . . . . . . . . . . . . . . . . . . 81
5.2.1. Metodo de bisecci on . . . . . . . . . . . . . . . . . . . 81
5.2.2. Metodo de Newton-Raphson . . . . . . . . . . . . . . 83
5.2.3. Metodo de la secante . . . . . . . . . . . . . . . . . . . 85
5.2.4. Regula Falsi . . . . . . . . . . . . . . . . . . . . . . . . 87
5.3. Comparaci on de los distintos ordenes de convergencia . . . . 88
5.3.1. Convergencia del metodo de bisecci on . . . . . . . . . 88
5.3.2. Convergencia del metodo de Newton-Raphson . . . . . 90
5.3.3. Convergencia del metodo de la secante . . . . . . . . . 93
5.3.4. Convergencia del metodo de regula falsi . . . . . . . . 95
5.4. Teora general de los metodos iterativos. Iteracion del punto
jo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.5. Generalizaci on a sistemas de ecuaciones . . . . . . . . . . . . 99
5.5.1. Metodo de Newton-Raphson . . . . . . . . . . . . . . 101
5.5.2. Metodo de Gauss-Seidel . . . . . . . . . . . . . . . . . 102
5.6. Ejercicios de aplicacion . . . . . . . . . . . . . . . . . . . . . . 103
6. Metodos de ordenacion 105
6.1. An alisis teorico: orden de complejidad optimo alcanzable para
algoritmos internos . . . . . . . . . . . . . . . . . . . . . . . . 105
6.2. Metodos simples de complejidad O(n
2
): comparaci on, inser-
cion, burbuja . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
6.2.1. Metodo de comparacion (seleccion) . . . . . . . . . . . 108
6.2.2. Metodo de insercion . . . . . . . . . . . . . . . . . . . 110
6.2.3. Metodo de intercambio (burbuja) . . . . . . . . . . . . 111
6.3. Metodos de complejidad O(nlogn): Quicksort y Heapsort . . . 113
6.3.1. Heapsort . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.4. Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
7. El lenguaje de programaci on C (IV) 125
7.1. Direccionamiento indirecto. Punteros: variables que contienen
la ubicaci on en memoria de otras variables . . . . . . . . . . . 125
7.2. Punteros y funciones: paso de argumentos de referencia ha-
ciendo uso de punteros . . . . . . . . . . . . . . . . . . . . . . 128
7.2.1. Punteros y arrays . . . . . . . . . . . . . . . . . . . . . 131
7.3. Algebra de punteros . . . . . . . . . . . . . . . . . . . . . . . 134

Indice general 1
7.4. Punteros a caracteres . . . . . . . . . . . . . . . . . . . . . . . 137
7.5. Ejemplos de utilizaci on de punteros . . . . . . . . . . . . . . . 139
8. El lenguaje de programaci on C (V) 149
8.1. Tipos de datos denidos por el usuario: estructuras . . . . . . 149
8.2. Operaciones con estructuras . . . . . . . . . . . . . . . . . . . 151
8.3. Punteros a estructuras y estructuras ligadas. Arboles binarios.
Ejemplos de uso . . . . . . . . . . . . . . . . . . . . . . . . . 158
9. Solucion de sistemas de ecuaciones lineales 167
9.1. Metodos directos para la resoluci on de sistemas de ecuaciones
lineales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
9.1.1. Sistemas triangulares . . . . . . . . . . . . . . . . . . . 168
9.2. Eliminaci on gaussiana . . . . . . . . . . . . . . . . . . . . . . 169
9.3. Estabilidad numerica y su mejora. Pivotado total y parcial.
Complejidad algortmica . . . . . . . . . . . . . . . . . . . . . 172
9.4. La variante de Gauss-Jordan . . . . . . . . . . . . . . . . . . 176
10.Solucion de sistemas de ecuaciones lineales sin inversion de
la matriz de coecientes 179
10.1. La descomposicion LU . . . . . . . . . . . . . . . . . . . . . . 179
10.2. Escenas compactas en la eliminaci on gaussiana: metodos de
Doolittle, Crout y Choleski . . . . . . . . . . . . . . . . . . . 184
11.Simulacion (I). Generaci on de n umeros aleatorios 189
11.1. N umeros aleatorios y Metodo de Montecarlo. Integraci on numeri-
ca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
11.2. Otras aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . 195
11.3. Generacion de observaciones pseudoaleatorias uniformes . . . 195
11.4. Generadores multiplicativos: eleccion del multiplicador, m odu-
lo y semilla . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
12.Simulacion (II). Generacion de variables no uniformes 205
12.1. Metodo de inversi on. Ejemplos . . . . . . . . . . . . . . . . . 205
12.2. Metodo de rechazo. Ejemplos . . . . . . . . . . . . . . . . . . 207
12.3. Metodo de transformaci on. Ejemplos . . . . . . . . . . . . . . 210
12.4. Metodos especcos . . . . . . . . . . . . . . . . . . . . . . . . 211
2

Indice general
Captulo 1
Nociones basicas sobre algoritmos
1.1. Introducci on
Es evidente que las matematicas son utilizadas de una forma u otra en
la mayora de las areas de la ciencia y la industria.
Durante el siglo XX avanzados metodos y modelos matematicos han sido
aplicados en distintas areas como la medicina, la economa o las ciencias so-
ciales. A menudo, las aplicaciones generan problemas matematicos que por
su complejidad no pueden ser resueltos de manera exacta. Uno entonces, se
restringe a casos especiales o modelos demasiado simplicados que puedan
ser analizados. En la mayoria de los casos, se reduce el problema a un pro-
blema lineal, por ejemplo, una ecuaci on diferencial, la funci on objetivo en un
problema de optimizacion, etc. Tal aproximacion puede ser muy efectiva y
explicar conceptos y puntos de vista que al menos cualitativamente pueden
ser aplicados al problema inicial. Sin embargo, a veces, tal aproximacion no
es suciente o incluso puede inducir a error.
La alternativa que nosotros plateamos aqui, es la de tratar un problema
menos simplicado, utilizando el potencial del c alculo numerico. La cantidad
de trabajo depender a del grado de precision demandada.
Con la utilizaci on del ordenador, desarrollado en los ultimos cincuenta
a nos, las posibilidades de utilizar metodos numericos han crecido enorme-
mente.
Desarrollar un metodo numerico, signica en la mayora de los casos, la
aplicaci on de un peque no n umero de ideas generales y relativamente simples.
Dichas ideas han de ser combinadas de forma intuitiva con un conocimiento
3
4 1 Nociones b asicas sobre algoritmos
del problema que puede ser obtenido de distintas formas fundamentalmente
con metodos del an alisis matematico.
A lo largo de la asignatura ilustratremos la utilizacion de las ideas ge-
nerales que se esconden detras de los metodos numericos que resuelven los
problemas mas habituales. Normalmente dichos problemas aparecen como
subproblemas o detalles computacionales de problemas m as grandes y com-
plejos.
En este captulo comenzamos presentando conceptos y terminologa b asi-
ca en el desarrollo de posteriores captulos.
1.2. Que es un algoritmo? Medidas de eciencia
Por problema numerico entenderemos una clara e inambigaa descrip-
ci on de una relaci on funcional entre datos (inputs) -es decir, variables in-
dependientes en el problema- y resultados (outputs). Datos y resultados, que
constituyen un conjunto nito de cantidades reales. La relaci on funcional
puede, por su parte, estar implcita o explcitamente representada.
Por algoritmo entenderemos un procedimiento formado por un con-
junto nito de reglas que especican una secuencia nita de operaciones,
mediante las cuales se obtiene la solucion de un problema, o bien, de una
clase especca de problemas.
Analizaremos algunos aspectos de esta ultima denici on:
1. Cada paso de un algoritmo debe estar denido de manera precisa y sin
ambig uedad. Las acciones a llevar a cabo deben estar rigurosamente
especicadas en cada paso.
2. El algoritmo debe llegar a la soluci on del problema en un n umero
nito de pasos. La restriccion general de necesitar un n umero nito
de pasos, en ocasiones no es suciente, ya que dicho n umero aunque
nito puede llegar a ser muy elevado. Un algoritmo util, debe requerir
no s olo un n umero nito, sino adem as un n umero razonable de pasos
hasta obtener la soluci on del problema.
3. Cada algoritmo posee cero o mas datos y proporciona uno o m as resul-
tados. Los datos (inputs) pueden ser denidos como cantidades, que
le son dados al algoritmo inicialmente, antes de ser ejecutado, y los
resultados (outputs), tambien denidos como cantidades, tienen algu-
1.2 Que es un algoritmo? Medidas de eciencia 5
na relaci on con los datos y son proporcionados cuando se completa la
ejecucion del algoritmo.
4. Es preferible que un algoritmo sea aplicable en la resoluci on de cual-
quier miembro de una clase de problemas y no unicamente en un
problema aislado. Esta propiedad de generalidad es, aunque no nece-
saria, siempre deseable en un algoritmo.
5. Finalmente, aclarar que quiz a la noci on de algoritmo es muy general.
A lo largo de la asignatura nos dedicaremos al estudio de algoritmos
dise nados para ser ejecutados en un ordenador. Un algoritmo de este
tipo, debe ser implementado en un lenguaje de programaci on y a la
larga constituir un programa o conjunto de programas.
Como hemos mencionado, para un problema numerico dado se pueden
considerar distintos algoritmos.
Ejemplo 1.1 Determinar la raz real m as grande de la ecuacion:
x
3
+a
2
x
2
+a
1
x +a
0
= 0
con coecientes reales a
0
, a
1
, a
2
es un problema numerico. El vector de datos
es (a
0
, a
1
, a
2
). El resultado es el valor de la raz buscada x, que est a implci-
tamente denida en funcion de los datos. Existen distintos algoritmos para
la resoluci on de este problema, basados en el metodo de Newton Rapson, el
de bisecci on, el de la secante, etc.
Ejemplo 1.2 El problema de resolver la ecuaci on diferencial
d
2
x
d
2
y
= x
2
+y
2
con condiciones de contorno y(0) = 0, y(5) = 1, no es, de acuerdo con la
denici on dada arriba un problema numerico. En este problema los datos
(input) se reducen a una funci on y, que no puede ser especicada simple-
mente por un conjunto nito de n umeros. El problema es entonces un pro-
blema matem atico, que puede ser aproximado por un problema numerico, en
el sentido de que el resultado ser a un conjunto de valores que aproximaran
a la funci on y(x), para x = h, 2h, 3h, ....
Medidas de eciencia
Es muy f acil inventar algoritmos. En la pr actica, sin embargo, uno no
quiere s olo algoritmos, quiere buenos algoritmos. El objetivo entonces, es
inventar buenos algoritmos y probar que dichos algoritmos son buenos. La
6 1 Nociones b asicas sobre algoritmos
bondad de un algoritmo puede ser valorada de acuerdo con varios crite-
rios. Uno de los m as importantes es el tiempo que tarda en ejecutarse. Hay
tambien, distintos aspectos dentro del criterio que controla el tiempo. Uno
de ellos, es el tiempo de ejecucion requerido por distintos algoritmos para la
resolucion de un mismo problema en un ordenador concreto. Esta medida
emprica, esta fuertemente relacionada con el programa y con la m aquina
utilizada en la implementaci on del algoritmo. Un cambio en un programa
puede no representar un cambio signicativo en el algoritmo correspondiente
y, sin embargo, afectar a su rapidez de ejecucion. Adem as, si dos programas
son comparados, primero en una maquina y luego en otra, ambas compara-
ciones pueden generar diferentes conclusiones.
As, mientras la comparaci on de programas corriendo en ordenadores
es una importante fuente de informaci on, los resultados estaran siempre
afectados tanto por las caractersticas de la maquina como por la destreza
y habilidad del programador.
Una alternativa util a esta medida emprica, es la realizaci on de un an ali-
sis matematico de la dicultad intrnseca de la resolucion computacional del
problema. Juiciosamente utilizado, tal analisis proporciona una importante
medida de evaluaci on del costo de ejecucion de un algoritmo.
El tiempo de proceso de un algoritmo es una funci on del tama no del pro-
blema computacional a resolver. Sin embargo, suponiendo que tenemos un
programa de ordenador que eventualmente acaba, la resoluci on de un proble-
ma solo requiere de suciente tiempo y suciente memoria para almacenar
informaci on.
De mayor interes son los algoritmos que pueden ser aplicados a una
coleccion de problemas de un cierto tipo. Para estos algoritmos, el tiempo y
el espacio de memoria requeridos por un programa, variar a con el ejemplo
particular a ser resuelto. Veamos por ejemplo la siguiente clase de problemas.
1. Encontrar el mayor elemento en una secuencia de n n umeros enteros.
2. Resolver un conjunto de ecuaciones lineales Ax = b, donde A es una
matriz real nxn y b es un vector real de longitud n.
3. Ordenar en orden decreciente un vector de n n umeros enteros distintos.
4. Evaluar un polinomio de grado n, P
n
(x) = b
n

k=0
a
k
x
k
en x = x
0
.
En cada uno de estos problemas, el par ametro n proporciona una medida
del tama no del problema, en el sentido de que, o bien el tiempo requerido
1.3 Complejidad. Complejidad computacional 7
para resolver cada problema o el espacio de memoria requerido por el algo-
ritmo, o ambos a la vez, crecen con n.
1.3. Complejidad. Complejidad computacional
Para medir el coste de ejecucion de un programa, deniremos una funci on
de complejidad (o costo) F, donde F(n) es una medida del tiempo requerido
para ejecutar el algoritmo sobre un problema de tama no n, o una medida del
espacio de memoria requerido para tal ejecuci on. De esta forma, hablaremos
de la complejidad en cuanto a tiempo o de la complejidad en cuanto a
memoria del algoritmo. Tambien podemos referirnos a cualquiera de las dos,
simplemente como funcion de complejidad (o costo) del algoritmo.
En general, el costo de obtenci on de una soluci on crece de acuerdo con
el incremento del tama no del problema, n. Si el valor n es muy peque no,
incluso un algoritmo ineciente tardar a poco en ejecutarse, de manera que
la eleccion de un algoritmo para un problema peque no no es crtico (a menos
que el problema sea resuelto muchas veces). En la mayora de los casos, sin
embargo, al incrementar n, se llega a una situacion, en la que el algoritmo
no puede ejecutarse en un periodo razonable de tiempo. Este punto se ilus-
tra en la siguiente tabla, donde puede verse c omo crecen las funciones de
complejidad de determinados algoritmos, al crecer n.
F(n) n = 10 n = 10
2
n = 10
3
n = 10
4
log
2
n 3.3 6.6 10 13.3
n 10 10
2
10
3
10
4
nlog
2
n 0,33 10 0,7 10
2
10
4
1,3 10
5
n
2
10
2
10
4
10
6
10
8
n
3
10
3
10
6
10
9
10
12
2
n
1024 1,3 10
30
> 10
100
> 10
100
n! 3 10
6
> 10
100
> 10
100
> 10
100
n: tama no del problema
F(n): funci on de complejidad temporal
En la practica es importante controlar el comportamiento del algorit-
mo para valores grandes de n, es decir, el comportamiento asint otico de la
funci on de complejidad.
8 1 Nociones b asicas sobre algoritmos
La complejidad asint otica es el crecimiento en el lmite de la funci on
de complejidad cuando crece el tama no del problema n. De esta forma, la
funci on asint otica (temporal o espacial) determina el tama no m aximo del
problema a ser resuelto por el algoritmo.
En estos terminos diremos que la complejidad del algoritmo es O(nlogn)
y leeremos de orden n logaritmo (en base dos) de n, si el tiempo de proceso
del algoritmo para un ejemplo de tama no n es proporcional a nlogn, o en
cualquier caso no supera esta cantidad. A lo largo de diferentes captulos,
hablaremos en los siguientes terminos, todos ellos para decir lo mismo:
1. La complejidad temporal del algoritmo es de orden nlogn, que puede
ser expresado como O(nlogn).
2. El algoritmo es ejecutado en un tiempo O(nlogn).
3. La cantidad de trabajo requerida por el algoritmo es proporcional a
nlogn, o es O(nlogn).
1.4. Complejidad agebraica y analtica
Entenderemos por algoritmo numerico, un algoritmo que resuelve un
problema en el que el contenido numerico es esencial. Hay muchos ejemplos
de algoritmos numericos entre los que se encuentran los siguientes:
1. El c alculo de las raices de una ecuaci on no lineal, o bien de un sistema
de ecuaciones no lineales.
2. La evaluacion de un polinomio en un valor dado, etc.
Los algoritmos no numericos, resuelven problemas y la solucion de los
mismos, aunque en ocasiones se representa por un n umero (o conjunto de
n umeros), es de naturaleza no numerica. Dos importantes ejemplos de pro-
blemas no numericos son, la ordenaci on de un conjunto de elementos des-
ordenados (si es un conjunto de n umeros, en orden creciente o decreciente,
mientras que si es un conjuntos de letras o palabras, en orden alfabetico) y la
b usqueda de un elemento en un conjunto. En los Temas 5 y 6 del programa
analizaremos algoritmos para resolver estos problemas.
En el an alisis de la complejidad computacional de los algoritmos numeri-
cos es conveniente distinguir entre la medida de complejidad algebr aica y
1.5 Precisi on numerica 9
analtica, donde ambas representan ramas de la propia complejidad com-
putacional. En el caso de los algoritmos no numericos, la complejidad es
unicamente abgebr aica.
Los objetivos de la complejidad algebr aica son m ultiples. Por una parte
intenta estimar el n umero de operaciones aritmeticas requeridas por un algo-
ritmo. Adem as intenta estimar el n umero mnimo de operaciones necesario
para resolver un problema dado. Tambien intenta describir un algoritmo o
algoritmos que resuelvan un problema en un n umero mnimo de operaciones.
La evaluacion de un polinomio en uno o varios valores dados y la resoluci on
de ecuaciones o sistemas de ecuaciones lineales mediante metodos directos
son ejemplos de problemas que pueden ser estudiados en terminos de su
complejidad algebr aica. Un aspecto importante de los algoritmos que son
utilizados para resolver estos problemas, es el n umero nito de pasos que
son necesarios hasta la obtenci on de la solucion del problema. Esto implica,
evidentemente, que el n umero de operaciones aritmeticas necesarias tambien
es nito.
Por otra parte, la complejidad analtica resuelve la cuestion de cu anta
computaci on ha de ser desarrollada hasta obtener un resultado con un deter-
minado grado de precisi on y se centra mas en los procesos computacionales
que en cierto sentido nunca terminan. Los procesos iterativos son un evi-
dente ejemplo. En este caso, el proceso es interrumpido en un punto y, si
el valor actual del resultado satisface el problema con un margen de error
acotado, dicho resultado se toma como soluci on del problema, en otro caso
la computaci on contin ua hasta obtener un resultado satisfactorio. En este
caso, uno estima el n umero de operaciones aritmeticas por paso en cada ite-
racion y un buen algoritmo en terminos de su complejidad computacional
es denido como aquel que requiere el menor n umero total de operaciones
aritmeticas para alcanzar un resultado, con un nivel de precisi on prejado.
1.5. Precisi on numerica
En el an alisis de algoritmos que resuelven problemas numericos, la pre-
cision de los resultados obtenidos es otro importante criterio a la hora de
distinguir entre un buen y un mal algoritmo. La necesidad de examinar la
precision de los calculos matematicos viene del hecho de considerar que un
ordenador es una m aquina nita, que es capaz de representar los n umeros
unicamente en un n umero nito de posiciones. La mayora de los n umeros,
incluso si son enteros, si son muy grandes para ser representados en el or-
denador, se redondean y s olo una aproximaci on nita a dicho n umero es
almacenada en el ordenador. Esto implica que si no se tiene cuidado, algu-
10 1 Nociones b asicas sobre algoritmos
nos algoritmos numericos implementados en un ordenador pueden producir
aproximaciones al verdadero valor de la soluci on, bastante imprecisas.
Aunque todava no hemos introducido el concepto de error de redondeo
(lo haremos en el Captulo 3), con el siguiente ejemplo vamos a ver como los
errores de redondeo pueden destrozar el resultado de un c alculo si se elige
un mal algoritmo.
Ejemplo 1.3 Vamos a analizar el problema de computar para n =
0, ..., 8, la integral:
y
n
=

1
0
x
n
x + 5
dx
Vamos a utilizar un procedimiento iterativo basado en una relaci on de
recurrencia. En general las relaciones de recurrencia son ubicuas en es-
tadstica computacional y probabilidad. En la mayora de los casos surgen
de considerar el primero y el ultimo de una cadena de eventos. Este ejemplo
sera el primero en ilustrar este principio.
En nuestro caso la relaci on de recurrencia viene dada por:
y
n
=
1
n
5y
n1
y se sigue directamente de la identidad:
y
n
=

1
0
x
n1
(x + 5 5)
x + 5
dx =

1
0
x
n1
dx 5

1
0
x
n1
x + 5
dx =
1
n
5y
n1
En teora esta relacion de recurrencia permitir a crear un proceso iterativo
y computar y
n
a partir del valor inicial y
0
=

1
0
dx
x+5
= [ln(x + 5)]
1
0
= ln6
ln5 = 0,182.
y
1
= 1 5y
0
0,09
y
2
=
1
2
5y
1
0,05
y
3
=
1
3
5y
2
0,083 extra no que y
3
> y
2
y
4
=
1
4
5y
3
0,165 absurdo!!
La razon del absurdo resulta ser un error de redondeo en y
0
cuya mag-
nitud puede ser como mucho de 5 10
4
. Dicho valor es multiplicado por -5
1.5 Precisi on numerica 11
en el c alculo de y
1
, que entonces tiene un error de 5. Dicho error produce
un error en y
2
de 25, etc. De esta forma el error en y
4
es 625 que puede
alcanzar el valor 625 5 10
4
= 0,3125.
Si se comienza en y
0
utilizando mas decimales, lo que sucede es que se
retrasa la aparici on del absurdo.
Este algoritmo es un ejemplo de un fen omeno denominado inestabili-
dad numerica. Podremos evitar dicha inestabilidad numerica eligiendo un
algoritmo m as adecuado.
Otra forma de explicar dicha inestabilidad numerica es utilizando cono-
cimientos de analisis matematico. En este caso sabemos que, para n mode-
radamente grande, la mayora de la masa de la integral se agrupa en torno
a x = 1. Por lo tanto, una buena aproximaci on de y
n1
sera:
y
n1

1
1 + 5

1
0
x
n1
dx =
1
6n
Es decir:
y
n

1
n

5
6n
=
1
n
(1
5
6
)
Por lo tanto perdemos precision porque en cada iteracion restamos dos
n umeros del mismo signo y parecida magnitud.
Una soluci on al problema en este caso, puede ser utilizar la f ormula de
recurrencia en la otra direcci on.
y
n1
=
1
5n

y
n
5
Pero necesitamos un valor inicial. Podemos ver directamente de la de-
nicion que y
n
decrece cuando n crece. Se puede suponer entonces que y
n
decrece suavemente cuando n es grande. De manera que y
10
y
9
, de donde:
y
9
+ 5y
9

1
10
, y
9

1
60
0,017
De este modo:
y
8
=
1
45

y
9
5
0,019
12 1 Nociones b asicas sobre algoritmos
y
7
=
1
40

y
8
5
0,021
y
6
0,025
y
5
0,028
y
4
0,034
y
3
0,043
y
2
0,058
y
1
0,088
y
0
0,182 Correcto!!
Este mismo algoritmo hacia atr as puede elegir como valor inicial y
10
= 0.
La aproximacion inicial es distinta pero el error se divide por 5 en cada
iteraci on y a partir de y
7
se obtienen los mismos resultados.
Aunque en este caso la solucion ha sido elegir una f ormula de recursi on
hacia atras, esto no supone siempre una mejor alternativa.
En adelante utilizaremos el termino metodo numerico para denotar
un procedimiento util en algunos casos para aproximar un problema ma-
tematico con un problema numerico, y en otros para resolver un problema
numerico. Especicar la formula de recursi on que permite resolver una su-
cesion de integrales, es proporcionar un metodo numerico. As, un metodo
numerico es mas general que un algoritmo y hace menos enfasis en detalles
computacionales.
1.6. Analisis de Algoritmos. Lmites de compleji-
dad
Es conveniente distinguir entre el an alisis de un algoritmo en particular
y el an alisis de una clase de algoritmos. En el an alisis de un algoritmo en
concreto deberamos probablemente investigar sus caractersticas mas im-
portantes tales como su complejidad temporal, cu antas veces es conveniente
ejecutar cada parte del algoritmo y la complejidad espacial, esto es, cuanta
memoria va a ser necesaria. En el analisis de una clase de algoritmos, por
otra parte, estudiaremos la clase de algoritmos que resuelven un problema
particular e intentaremos seleccionar el mejor algoritmo de la clase capaz
de hacerlo. Si tal algoritmo no se encuentra, entonces intentaremos estable-
cer lmites de la complejidad de los algoritmos de dicha clase.
1.6 Analisis de Algoritmos. Lmites de complejidad 13
An alisis del primer tipo han sido realizados desde los comienzos de la pro-
gramacion en ordenador. An alisis del segundo tipo, comienzan a realizarse,
salvo raras excepciones, mucho tiempo despues. Es facil ver que an alisis del
segundo tipo son bastante mas potentes y utiles ya que permiten estudiar
varios algoritmos simultaneamente.
Lmites de Complejidad
El an alisis de la complejidad est a relacionado entre otras cosas con la
obtenci on de lmites superiores e inferiores de la ejecucion de un algoritmo o
una clase de algoritmos que resuelve una clase de problemas. La existencia
de lmites de la complejidad de algunos algoritmos nos puede servir como
base para la clasicaci on de algunos problemas.
Hay algunos problemas para los que los lmites de complejidad de su
resolucion pueden ser determinados utilizando teoremas apropiados, sin la
necesidad de construir el algoritmo para el problema. En algunos de estos
casos las demostraciones de los teoremas no son constructivas en el sentido
de que no indican la forma de construir un algoritmo para la resoluci on del
problema y ni siquiera nos dicen si es posible su construccion. Para otros pro-
blemas ha sido posible acotar su complejidad, pero no se conoce algoritmo
capaz de alcanzar dicha cota. Un ejemplo de este caso es la multiplicaci on
de matrices, donde una cota superior mnima de O(n
2
) es facilmente visua-
lizada y sin embargo el mas rapido de todos los metodos conocidos tiene
una complejidad de O(n
2,496
). Este intervalo sugiere que la cota O(n
2
) no
es alcanzada en la practica.
Otros problemas son tales que se conoce la cota inferior de su com-
plejidad, se pueden construir algoritmos que satisfacen dichas cotas y sin
embargo dichos algoritmos son numericamente inestables. Metodos rapidos
de multiplicaci on de matrices pertencen a esta categora.
Finalmente, hay problemas para los cuales los lmites inferiores de com-
plejidad son conocidos y los algoritmos que alcanzan dichas cotas pueden
construirse y adem as son numericamente estables.
14 1 Nociones b asicas sobre algoritmos
Captulo 2
Nociones sobre arquitectura de
ordenadores
2.1. Introducci on
Las aplicaciones de un ordenador caen, en su mayora, dentro de los
grupos:
1. Calculo numerico.
2. Almacenamiento, recuperacion procesamiento y analisis de datos o in-
formacion.
El primer punto ha sido la causa por la que se invent o y desarroll o el
ordenador. Sin embargo el segundo punto es el que ha experimentado un
mayor desarrollo en los ultimos a nos y ya domina en todos los campos de la
tecnologa inform atica. Estamos sin duda en la era de la gesti on inform atica
de la informaci on. Facturaci on y control de grandes compa nas, edici on y
composicion editorial de las p aginas en los periodicos, b usqueda de sntomas
en grandes bases de datos que ayudan al diagn ostico de enfermedades, no
son mas que algunos ejemplos en los que interviene un ordenador.
El unico lmite existente proviene del n umero de personas expertas en
un determinado campo, que posean la habilidad y conocimientos necesarios
para aplicar la inform atica a ese campo.
15
16 2 Nociones sobre arquitectura de ordenadores
2.1.1. Representaci on binaria de la informaci on
Las operaciones que realiza internamente un ordenador no son, en prin-
cipio, demasiado complicadas. Desde hace siglos se sabe que un conmutador
al tener dos estados (abierto y cerrado) puede representar los n umeros 0 y
1. Si disponemos de muchos conmutadores, podremos representar grandes
cantidades de ceros y unos. Por otra parte podremos representar los n umeros
en base 2 en lugar de utilizar la base de numeraci on decimal como hacemos
normalmente. Los n umeros expresados en base dos solamente utilizan los
dgitos 0 y 1, as que podemos representar un determinado n umero por me-
dio del estado de una serie de conmutadores. Por ejemplo, el 372 se puede
representar en base decimal
372 = 3(10
2
) + 7(10) + 2(10
0
)
pero tambien podemos escribirlo como
372 = 1(2
8
) + 0(2
7
) + 1(2
6
) + 1(2
5
) + 1(2
4
) + 0(2
3
) + 1(2
2
) + 0(2
1
) + 0(2
0
) =
= 1(256) + 0(128) + 1(64) + 1(32) + 1(16) + 0(8) + 1(4) + 0(2) + 0(1)
o bien
(372)
2
= (101110100)
2
es decir necesitaremos nueve posiciones para representar el n umero 372.
Ademas de n umeros podemos representar otros tipos de datos en c odigo
binario, por ejemplo, cada una de las letras del abecedario. Podemos con-
vertir de forma aproximada, la informaci on contenida en una imagen, en
una serie de puntos cuyos valores de posici on color e intensidad podremos
codicar y procesar o transmitir a traves de lneas telef onicas.
2.2. Memoria, dispositivos de entrada/salida, uni-
dad central de proceso o CPU
En sus mas remotos orgenes cada uno de los conmutadores que consti-
tuan un ordenador era un dispositivo electromec anico conocido como rele.
El resultado fue una maquina monstruosa en tama no, ruido y consumo. El
siguiente paso fue sustituir los ruidosos y poco ables reles por calurosas y,
tambien, poco ables v alvulas de vacio, que, por lo menos eran m as rapidas.
A continuaci on se introdujo el transistor, lo que supuso un gran avance (ya
estamos en los a nos sesenta). Pero lo que ha hecho posible la revoluci on
inform atica ha sido la aparici on de los circuitos integrados.
2.2 Memoria, dispositivos de entrada/salida, unidad central de proceso o CPU17
Existe un circuito b asico formado por dos transistores llamado ip-op
o biestable. Este circuito posee dos estados de funcionamiento estable. Si
asignamos el valor 1 a uno de estos estados y el 0 al otro, de nuevo tendremos
nuestro sistema de representacion de n umeros binarios. El estado en el que
se encuentra el circuito puede medirse (lectura) o alterarse (escritura).
Gracias a la microelectronica se pueden colocar varias decenas de mi-
les de circuitos dentro de un chip. Las tecnicas que permiten un grado de
empaquetamiento tan elevado de denominan tecnologa VLSI (Very Large
Scale of Integration). El tama no resultante no suele superar el centmetro
cuadrado, lo que da idea del grado de miniaturizaci on del proceso. Estos
avances tecnologicos han hecho posible que los ordenadores actuales sean
mucho mas rapidos, ables, y baratos que sus antecesores.
La memoria principal de un ordenador consiste en varios millones de cir-
cuitos elementales biestables capaces de almacenar un cero o un uno. Cada
unidad de informaci on binaria es un bit. Sin embargo, no suele accederse
de forma individual a un bit, sino que suelen agruparse de ocho en ocho,
formando un byte. La unidad de informaci on que se transere a o desde la
memoria principal recibe el nombre de palabra. En los ordenadores perso-
nales la longitud de una palabra suele ser de 8, 16 o 32 bits, mientras que
en los grandes puede oscilar entre 12 y 64, o m as, bits. Los detalles sobre
la construcci on y organizaci on de la memoria principar de un odenador no
suelen tener demasiada importancia a la hora de escribir un programa. Los
dos unicos puntos importantes a considerar son los siguientes:
1. Hay dos valores asociados con cada elemento de memoria: Su contenido
que sera su dato o instrucci on en c odigo binario y la direcci on de esa
posici on de memoria, que nos permite acceder a ella.
2. El tama no de la palabra de cualquier ordenador es nito. Esto signi-
ca que dispondremos solamente de un n umero nito de dgitos para
representar las cantidades durante su c alculo. As los n umeros cuya
representacion binaria sea ilimitada, seran truncados al guardarse en
una direcci on de memoria. Nos estamos reriendo a los n umeros cuya
representacion binaria (no decimal) tiene innitos decimales. Por ejem-
plo 0.1, tiene una representaci on peri odica en base 2. La longitud de la
memoria ocupada por una dato nos limitar a la precisi on de los calculos
numericos y es una caracterstica importante de cada ordenador.
El tiempo de acceso (tiempo necesario para leer informacion almacena-
da en una direcci on determinada) es un par ametro de importancia crucial
en el funcionamiento de un ordenador. Una suma se suele realizar en me-
nos tiempo del necesario en buscar los sumandos en memoria. El tiempo
18 2 Nociones sobre arquitectura de ordenadores
de acceso en una memoria de gran tama no es del orden de 5 10
7
segun-
dos. Este tiempo aunque es extraordinariamente peque no, puede reducirse
colocando los datos e instrucciones mas recientes en otra memoria mas pe-
que na y r apida, es la memoria cache. La idea es que los terminos utilizados
ultimamente tienen grandes probabilidades de ser necesarios de nuevo. La
memoria principal recibe el nombre de memoria de acceso aleatorio (random
access memory, o RAM). Con este termino se indica que el tiempo de acceso
a una determinada posici on de memoria es practicamente independiente de
su direcci on.
En una RAM la informaci on no se guarda de forma secuencial, aunque
puede ser c omodo imaginar las direcciones ordenadas consecutivamente.
La memoria principal no es el unico sitio en el que se almacena la in-
formacion; existen otros lugares donde a cambio de unos tiempos de acceso
mayores se pueden conservar grandes cantidades de informaci on. Estos dis-
positivos reciben el nombre de memoria secundaria o memoria masiva, y
suelen consistir en cintas magneticas, discos jos, diskketes, cds, etc. Los
datos de la memoria secundaria no son accesibles directamente, sino que
deber an pasar a la memoria principal.
La seccion del ordenador que realiza las operaciones con los datos, si-
guiendo las instrucciones almacenadas en la memoria principal, es la unidad
central de proceso (central processing unit, o CPU). La CPU realiza dos
tipos de tareas:
1. Control y observaci on del sistema completo. Este consta de todos los
dispositivos de entrada y salida de informaci on del ordenador junto
con el tr aco de informaci on interna al ordenador.
2. Ejecucion de las instrucciones recibidas desde la memoria principal.
La CPU consta de dos secciones que realizan cada una de estas funciones.
La unidad de control se encarga de la realizaci on de las tareas de control,
mientras que la unidad de proceso de datos e instrucciones ser a la respon-
sable de ejecutar las ordenes elementales que forman el programa. Estas
ordenes consisten principalmente en operaciones aritmeticas y de compa-
racion entre dos datos. Esta segunda secci on recibe el nombre de unidad
aritmetico-logica (arithmetic-logic unit, o ALU).
La unidad de control es la responsable de la b usqueda de la siguiente
instrucci on y del c alculo de las direcciones de los datos, as como del alma-
cenamiento de ambos en registro de acceso inmediato. Las instrucciones se
traen de la memoria principal en el orden indicado por el programa. Si hay
2.3 Lenguaje de m aquina, lenguajes de ensamblado, lenguajes de alto nivel19
que realizar alguna operaci on con los datos, estos se transeren a la ALU
junto con la orden de sumarlos, restarlos, compararlos, etc. La unidad de
control, se encarga, una vez realizada la operaci on, de colocar el resultado
en la memoria principal.
Recuerda que todos los elementos procesados en la CPU, datos e instruc-
ciones, estan escritos en codigo binario. Esta es la unica forma de comunica-
cion que puede entender el ordenador. Los programas escritos de esta forma
reciben el nombre de programas en lenguaje maquina o microprogramas.
Dos de los errores mas comunes son el de realizar una serie de opera-
ciones con datos que no se han introducido en la memoria principal y el
de ejecutar un programa que carece de las instrucciones para la impresi on
de los resultados. Pr acticamente todos los programas constan de tres fases:
introducci on de los datos, procesamiento e impresi on de los resultados. Los
dispositivos de entrada m as comunes son el teclado y los discos magneticos.
Los resultados de un programa pueden presentarse en una pantalla o en una
impresora o bien guardarse en un disco. Los dispositivos m as com unmen-
te usados, especialmente en los ordenadores personales, son el teclado y la
pantalla.
Todos los componentes fsicos tales como la CPU, las memorias principal
y secundaria y los dispositivos de entrada E/S forman parte del llamado
hardware del ordenador. El conjunto de programas forman el software.
Nota 2.1. La metafora del enano computador Lee atentamente este
artculo que te servir a para comprender mejor lo descrito en esta seccion.
2.3. Lenguaje de maquina, lenguajes de ensambla-
do, lenguajes de alto nivel
Como se ha comentado anteriormente, la denibilidad de un algoritmo
exige especicar de modo riguroso y sin ambig uedades las acciones a realizar.
Para ello se han creado los lenguajes de programaci on denidos formalmente
para especicar algoritmos. La transcripcion del algoritmo en sentencias de
un lenguaje de programaci on se denomina codicaci on y constituye el
programa inform atico. Un lenguaje de programaci on esta formado por un
conjunto de c odigos que permiten describir los algoritmos y sus datos, de
manera que, por una parte, se puedan ejecutar por un ordenador; y por otra,
su descripci on sea comprensible por el usuario.
El lenguaje de programaci on es un medio de comunicaci on entre el hom-
20 2 Nociones sobre arquitectura de ordenadores
bre y el ordenador. Para describir al ordenador un metodo para resolver
un problema, es necesario formalizarlo adecuadamente para que lo pueda
entender.
Dado que la circuitera del ordenador s olo acepta, e interpreta ceros y
unos, el lenguaje binario es la base del lenguaje m aquina. Como la palabra
lo dice, el lenguaje m aquina es particular de cada ordenador en concreto
y aunque su base sea binaria existen entre unos y otros grandes diferencias.
Interesa precisar dos hechos:
1. Un ordenador s olo entiende y acepta su propio lenguaje de m aquina.
2. La utilizaci on de un lenguaje m as evolucionado y por lo tanto los pro-
gramas escritos en ese lenguaje, exigira la existencia de un traductor
(escrito en el propio lenguaje m aquina) para que convierta estos pro-
gramas al lenguaje m aquina.
Logicamente el lenguaje maquina es muy inc omodo de utilizar. Por una
parte hay que conocer los c odigos de todas las operaciones, y por otra, hay
que disponer de las direcciones que los datos toman en la memoria central.
Todo ello oblig o a las empresas informaticas a desarrollar una forma de
programaci on m as sencilla y plantearon distintas formas de automatizarla.
Un primer paso fue utilizar bases numericas superiores a dos para la
representacion de las instrucciones, en la entrada y la salida del ordenador:
octal, decimal, hexadecimal, etc.
Pero la verdadera evoluci on la marcaron dos aspectos:
a) La simbolizacion.
b) La utilizaci on de lenguajes intermedios.
Al igual que se ha descrito en el artculo del enano computador, en el
ordenador real se numeran las direcciones de memoria. Ello presenta una
gran inconveniente en la programaci on, por el hecho de que el programador
debe conocer en todo momento la ubicaci on de sus datos en la memoria
central.
Los lenguajes simbolicos obvian este inconveniente al permitir reem-
plazar los codigos de operacion y las direcciones numericas de memoria por
smbolos que representan esos codigos y direcciones.
2.3 Lenguaje de m aquina, lenguajes de ensamblado, lenguajes de alto nivel21
Los nombres simbolicos de las direcciones de memoria exigen la observan-
cia de determinadas reglas de formacion, por ejemplo: limitacion del n umero
de caracteres, obligacion de que aparezca un car acter en particular, etc.
Las instrucciones escritas de manera simbolica deben ser transcritas en
instrucciones en lenguaje m aquina, ya que los s ombolos no pueden ser ser
interpretados directamente por el ordenador. Por ello es necesario crear unas
tablas de correspondencia a n de traducir estos smbolos en c odigo de
operaci on y direcciones numericas. Esta funcion la desarrolla un progra-
ma particular que se denomina traductor. Por lo tanto, todos los lenguajes
intermedios necesitan la presencia de un traductor, cuya funci on es la de
traducir el programa fuente en el programa-m aquina, dando como resultado
un programa objeto que es directamente ejecutable por el ordenador.
Entre los lenguajes intermedios m as importantes tenemos:
1.- Los lenguajes de ensamblaje o ensambladores.
2.- Los lenguajes autocode.
El lenguaje ensamblador es muy pr oximo a la maquina, y en el cada
orden corresponde directamente a una instrucci on en c odigo maquina, o a
la descripcion de un dato elemental tal como se presenta en la memoria del
ordenador.
La accion del ensamblador consiste en traducir todos los elementos simb oli-
cos del programa fuente, tanto los c odigos de operaci on como las direcciones
de memoria. Con respecto a los codigos de operaci on, el ensamblador com-
para cada smbolo encontrado en el programa con los existentes en una tabla
preestablecida, cargada en memoria en el momento del ensamblaje. Cuando
se produzca la correspondencia, el codigo simb olico sera sustituido por su
correspondiente numerico de la tabla. Para las direcciones, el proceso es al-
go diferente. En una primera pasada, el ensamblador crea en memoria una
tabla, asignando una direcci on real a cada direccion simb olica que encuen-
tra. En la segunda pasada, el ensamblador reexamina cada instrucci on y
reemplaza los smbolos por las direcciones correspondientes de la tabla. Es
entonces preciso que el ensamblador lea, por lo menos, dos veces el programa
fuente para obtener el programa en lenguaje m aquina (programa objeto).
La funci on del ensamblador no se reduce a estas operaciones que se
acaban de indicar. Tambien se encarga de reservar zonas de memoria, de
cargar alg un dato inmediato en la memoria del ordenador, etc.
Por su parte el lenguaje autocode tambien es un lenguaje muy pr oximo
22 2 Nociones sobre arquitectura de ordenadores
al lenguaje m aquina. Se diferencia del ensamblador por la introducci on de la
macro-instruccion. Se denomina as porque en el momento de la traducci on
genera varias instrucciones de codigo maquina.
La noci on de macroinstrucci on esta ligada al concepto de subprograma,
procedimiento o rutina. Supongamos que en un programa se desea leer datos
en diferentes puntos del mismo. Lo l ogico sera disponer de un conjunto
de instrucciones independientes del resto del programa (macro-instruccion),
que nos realice esta operacion de lectura, y poder llamar a esta macro-
instrucci on, cuando lo precisemos.
En el lenguaje autocode, la tecnica de ensamblaje es esencialmente la
misma que en el ensamblador. Al ensamblador del autocode se le denomina
macroensamblador.
El autocode presenta adem as, con respecto al ensamblador, la ventaja
de poder utilizar subprogramas ya existentes en el sistema, denidos por el
programador o por otro usuario del equipo.
Los lenguajes intermedios se suelen utilizar, bien porque las operaciones
que se desean programar no son posibles con los lenguajes mas evoluciona-
dos, o bien porque los programas a construir son de ejecuci on permanente
y precisan una gran optimizaci on tanto de memoria como de tiempo de eje-
cucion. Es es el caso de algunos sistemas operativos y procesadores basicos.
La llegada de los lenguajes de alto nivel marca una etapa importante en la
evoluci on de los lenguajes intermedios. Mientras que estos estan orientados
a la maquina, los de alto nivel van orientados al problema. Esto ultimo
explica que la estructura de los lenguajes evolucionados sea completamente
diferente al de los de ensamblaje, estando los primeros mucho mas pr oximos
al lenguaje natural. Los lenguajes evolucionados tienden a una universalidad
de empleo, permitiendo ser procesados, casi, con independencia del tipo de
ordenador.
Tomando como criterio el tipo de aplicaci on, se distinguen dos categoras:
1. Lenguajes de prop osito general: que permiten resolver, en principio
cualquier tipo de problema. Se dividen en dos grupos dependiendo de
n para el que han sido desarrollados.
a) Lenguajes denidos para aplicaciones tecnico-cientcas: FOR-
TRAN, ALGOL y BASIC que fueron los primeros en ser des-
arrollados, entre 1954 y 1958, y el PASCAL y el lenguaje C que
se desarrollaron posterioremente, en 1970 y 1972 respectivamente,
son los mas representativos en esta categora.
2.3 Lenguaje de m aquina, lenguajes de ensamblado, lenguajes de alto nivel23
b) Lenguajes de gesti on, como el COBOL, RPG, etc. El COBOL
ha sido b asicamente estudiado y pensado para programar aplica-
ciones de gestion. Hoy en da en la explotaci on y tratamiento de
grandes bases de datos, se emplea ORACLE, ACCES, etc
2. Lenguajes especializados: que han sido dise nados para un tipo especial
de aplicaci on (por ejemplo, manejo de m aquina herramienta, dise no,
ense nanza asistida, etc). Son a ttulo de ejemplo el CAD, GPSS, SI-
MULA, LISP, entre otros. Dentro de una especializaci on m as cercana,
S-plus o R.
En los lenguajes de alto nivel se sustituye el traductor (ensamblador o
macroensamblador) por el compilador, y la traducci on se denomina compi-
laci on.
Un compilador, es pues, un traductor de programas escritos en un len-
guaje de alto nivel. El compilador es un programa complejo (normalmente
escrito en ensamblador) que tiene como mision traducir expresiones y sen-
tencias, escritas en un lenguaje de alto nivel, en instrucciones que comprenda
e interprete el ordenador. M as adelante, veremos con mas detalle la forma
de compilar un programa escrito en C.
24 2 Nociones sobre arquitectura de ordenadores
Captulo 3
Introduccion al lenguaje de programaci on
C (I)
C es un lenguaje de programaci on de uso general. Ha sido y es utilizado
para escribir programas numericos, programas de procesamiento de textos y
bases de datos. Dentro de los lenguajes de alto nivel, es clasicado como un
lenguaje de relativo bajo nivel, esto es, trabaja con la misma clase de objetos
que la mayora de los ordenadores: caracteres, n umeros y direcciones, que
pueden ser combinados con los operadores aritmeticos y logicos, utilizados
normalmente en las maquinas. No contiene elementos de alto nivel, que
deber an siempre ser aportados por funciones llamadas explcitamente.
De igual forma, las sentencias de control de ujo en C son sencillas,
secuenciales, de seleccion, de iteracion, bloques y subprogramas, pero no
multiprogramaci on, paralelismo sincronizaci on o corrutinas.
La ausencia de estas caractersticas es lo que da ventajas al lenguaje. C
es relativamente peque no, puede escribirse en poco espacio y aprenderse con
facilidad. Por el contrario es tremendamente vers atil y port atil, es decir se
puede hacer casi cualquier cosa y ejecutar sin cambios en una amplia va-
riedad de m aquinas. Los compiladores se escriben facilmente. Exceptuando
los programas que necesariamente dependen en alguna forma de la m aqui-
na, como el compilador, ensamblador y depurador, el software escrito en
C es identico en casi cualquier m aquina que soporte a C. El lenguaje es
independiente de cualquier arquitectura de la m aquina en particular.
El sistema operativo UNIX est a escrito en su mayor parte en lenguaje
C. Los programas en C tienden a ser lo sucientemente ecientes como para
que no haya que escribirlos en lenguaje ensamblador. De hecho, casi todas
las aplicaciones de UNIX estan escritas en C.
25
26 3 Introducci on al lenguaje de programaci on C (I)
En nuestro caso, hemos optado por describir la situaci on del sistema
UNIX, de la HP9000 de nuestro Centro de C alculo, pues dicho entorno de
trabajo es el habitual de la mayora de programadores de C.
Muchas de las ideas principales de C provienen de un lenguaje mucho m as
antiguo pero a un vigente: el lenguaje BCPL inventado por Martin Richars.
La inuencia de BCPL le llega indirectamente a traves del lenguaje B, escrito
por Ken Thompson en 1970 para el primer sistema UNIX.
En C los objetos fundamentales son caracteres, enteros de varios ta-
ma nos y n umeros en punto otante. Existe tambien una jerarqua de tipos
de datos derivados, creados con apuntadores, arreglos estructuras, uniones
y funciones.
C posee las construcciones fundamentales de control de ujo para es-
cribir programas bien estructurados: agrupamiento de sentencias, toma de
decisiones (if), ciclos (bucles), con comprobacion de la condici on de termi-
naci on al principio (while, for) o al nal (do) y selecci on entre un conjunto
de casos posibles (switch). C incluye apuntadores o punteros y capacidad
aritmetica de direcciones.
Mostraremos estos y otros elementos esenciales del lenguaje C en este y
los siguientes captulos.
3.1. Nociones sobre trabajo en el Sistema Opera-
tivo UNIX. El editor vi
UNIX es un sistema operativo con las siguientes caractersticas:
a. Interactivo: El sistema acepta peticiones, las procesa en forma
de di alogo con el usuario.
b. Multiusuario: El sistema permite atender a varios usuarios
de forma simult anea. Para ello, utiliza la tecnica denominada de
tiempo compartido.
c. Multiprogramado: El sistema es capaz de realizar mas de una
tarea para cada uno de los diversos usuarios.
Basicamente consta de los siguientes elementos:
1. KERNEL: Constituye el n ucleo central del S.O., con entre otras fun-
ciones:
3.1 Nociones sobre trabajo en el Sistema Operativo UNIX. El editor vi27
a) Control de los recursos del ordenador, asign andolos entre los dis-
tintos usuarios.
b) Planicaci on de la ejecucion de los distintos programas realizados
por los usuarios.
c) Control de los dispositivos de hardware (discos, impresoras,..).
d) Mantenimiento del sistema de archivos.
2. SHELL: Es la capa externa al n ucleo. Su misi on es la de proporcionar
un interface entre el usuario y el S.O. Se puede decir que el SHELL es:
a) Un interprete de comandos.
b) Un lenguaje de programaci on.
c) Un controlador de procesos.
3. HERRAMIENTAS Y UTILIDADES: Son un conjunto de programas
que se entregan con el S.O. y que realizan tareas mas complicadas y
de diversa ndole. Por ejemplo:
a) Manipulaci on de cheros.
b) Manipulaci on del contenido de los cheros.
c) Edici on de textos.
d) Compilaciones de programas.
e) Gestion de ventanas.
4. COMANDOS: Son un conjunto de expresiones prejadas que permi-
ten dar ordenes al S.O.. UNIX es un sitema operativo orientado a
comandos: hay que escribir las ordenes. Windows es por el contrario
un sistema visual: hay que seleccionar iconos.
a) Claves de acceso a tu cuenta personal. Normalmente hay un su-
pervisor del sistema que tiene acceso a todas las cuentas. Los
usuarios tienen unicamente acceso a su cuenta personal.
login: etpgamaa
password: ara2411
b) Estructura del arbol de directorios:
Como todo sistema operativo, maneja cheros los cuales son almace-
nados en distintas divisiones del disco duro, denominados directorios.
Tanto los directorios como los cheros poseen un nombre.
Ficheros: guardan datos, programas, etc..
Nombre de cheros:
28 3 Introducci on al lenguaje de programaci on C (I)
- Se pueden usar como maximo 14 caracteres (en versiones modernas,
incluso m as).
- Hay caracteres prohibidos (/, ?, , )
- Para trabajar con distintos cheros:
Ejemplo 1: p* denota todos cheros cuyo nombre comienza por p
Ejemplo 2: p?pe denota todos los cheros cuyo nombre comienza por
p, luego cualquier cosa (incluso nada) y luego pe.
Usualmente los nombres de cheros tienen lo que se llama extensi on.
Por ejemplo, los cheros fuente en FORTRAN tienen extensi on .f, en
C tienen extension .c, etc..
Directorios: guardan cheros u otros directorios. No guardan infor-
macion como tal, sino que sirven para estructurar dicha informaci on.
(0) Raz: se denota por /.
(1) bin: guarda los comandos del sistema.
(2) etc: guarda los sistemas de arranque.
(3) lost+found: se va guardando lo que el sistema va perdiendo.
(4) mnt: montaje del arbol. Depende del mismo arbol y es quien orga-
niza el arbol (utilidades del montaje,...).
(5) users: todos los usuarios del sistema estan aqu.
- Cada cuenta tiene el nombre de un usuario que cuelga de aqu.
- Cada usuario puede hacer lo que quiera por debajo, pero con un
lmite de espacio (p.e. en mi caso 10 megas). Todo lo demas es accesible,
dependiendo de los correspondientes permisos. Es posible leer o incluso
copiar, cheros existentes en directorios ajenos a la cuenta del usuario.
- En mi caso etpgamaa es el directorio del usuario (directorio home),
que coincide con el nombre de la cuenta a la que accedo.
3.1 Nociones sobre trabajo en el Sistema Operativo UNIX. El editor vi29
/
bin etc ls.+fd. users
pract
p9mnu
p9mnuaa p9mnuga p9mnups
Estructura de comandos:
...>orden -[opciones] [par ametros]
([] puede no estar. Las opciones siempre empiezan con un gui on). UNIX
diferencia min usculas y may usculas. La mayora de las ordenes son en
min usculas. Para separar las distintas componentes (orden, opciones,
par ametros , se usan espacios en blanco (solo aqu, y en ning un si-
tio mas se usa y esto es la clave de numerosos errores que se suelen
cometer).
Comandos para manejar el arbol de directorios
pwd, mkdir, rmdir, cd
a) ...>pwd
D onde estoy, dame el directorio activo.
b) ...>mkdir nombre-de-directorio
Crea un directorio que se llame nombre-de-directorio.
Ejemplo 3:...>mkdir docencia metnu
Crea ambos directorios.
c) ...>rmdir nombre-de-directorio
Borra el directorio que se llame nombre-de-directorio. Para ello
se requieren dos condiciones: tiene que estar vacio y no tiene que
estar activo (no estar en el ni por debajo de el).
Ejemplo 4:...>rmdir docencia metnu
Borra ambos directorios.
d) ...>cd nombre-de-directorio
Cambia al directorio nombre-de-directorio.
30 3 Introducci on al lenguaje de programaci on C (I)
Ejemplo 5:...>cd .. (Sube uno hacia arriba)
Ejemplo 6:...>cd (Sube hasta el raz)
Ejemplo 7:...>cd /users/etpgamaa/docencia/metnu
5. Comandos para manejar cheros:
cat,ls,rm, cp, mv, chmod, lp,vi
a) >cat nombre-de-chero visualiza el chero nombre-de-chero, pe-
ro no lo modica (similar al comando TYPE de MS-DOS). La
b usqueda la realiza en el directorio activo. Si el chero no est a en
dicho directorio, me dir a que no lo encuentra. Si en mi directorio
tengo creados dos subdirectorios: Programas y Datos, me encuen-
tro en este ultimo y quiero que me ense ne un chero que est a en
Programas, hay que dar el camino:
...>cat /users/etpgamaa/programas/nombre-de-chero
Esto es dar el path absoluto. Otro modo de hacerlo es dar el path
relativo.
...>cat ../Programas/nombre-de-chero
La orden .., sube un paso hacia arriba.
Ademas dicho comando permite concatenar:
...>cat pepe ana > alumnos existen tres cheros pepe, ana y alum-
nos que contiene la uni on de pepe y ana.
...>cat pepe ana > alumnos mantiene el chero alumnos (existen-
te) y le a nade el contenido de pepe y ana.
...>cat p* > pruebas une todos los cheros que empiezan por p
creando uno de nombre pruebas.
b) >ls [-opciones] [directorios] lista el contenido del directorio en el
que estoy.
Sin opciones ni par ametros: informacion completa del
directorio actual.
...>ls -l /users/etpgamaa informaci on completa de di-
cho directorio.
...>ls -R -l /users/etpgamaa informaci on completa y
recursiva de todos los dicrectorios. El resultado de esto:
- - - - - - - - - - n.-enlaces propiet. grupo tama no fecha n.-chero
(1) (2) (3) (4) (5) (6) (7) (8) (9) (10)
(1)Tipo de chero:
- chero ordinario (Programa, texto, datos).
d directorio.
3.1 Nociones sobre trabajo en el Sistema Operativo UNIX. El editor vi31
c,s,w,r controladores de dispositivos de e/s (impresora, modem,
cd-rom,...)
Permisos: aparece en tres bloques de tres.
(2) Propietario
(3) Otros usuarios del mismo grupo que el propietario
(4) Todos los demas usuarios del sistema
En cada grupo de tres:
r Lectura: se puede ver pero no modicar.
w Escritura: se puede modicar.
x Ejecuci on. (La tienen los ejecutables).
(5)N.-enlaces: n umero de lugares (directorios) desde los que es
accesible el chero (frente a lo que sucede con el MS-DOS, un
chero en UNIX es accesible desde distintos lugares).
(6)Propietario
(7)Grupo: grupo del propietario
(8)Tama no: memoria que ocupa el chero.
(9)Fecha: Fecha y hora de la ultima modicaci on del chero.
(10)N.-chero: nombre del chero.
c) >rm [-opciones] cheros o directorios borra los cheros indicados.
Puede tener como opciones:
-r: borrado recursivo dentro de un directorio. -i: pe-
ticion de conrmacion.
Ejemplo 8: ...>rm -r * borra todo lo que hay en el direc-
torio. Es conveniente pedir conrmaci on.
Ejemplo 9: ...>rm pepe ana borra los cheros pepe y ana.
Ejemplo 10: ...>rm p* borra todos los cheros cuyo nom-
bre empieza por p.
d) >cp chero-origen chero-destino copia el chero-origen en el
chero-destino, machacando cualquier contenido que tuviera este
ultimo y manteniendo intacto el chero-origen. Si hay cambio de
directorio hay que indicar el camino.
Ejemplo 11: ...>cp pepe /users/etpgamma/alumnos/pepe.1
e) >mv chero-origen chero-destino Frente al comando cp, este
comando no mantiene el chero-origen, su contenido est a unica-
mente en el chero-destino. (Es similar al comando RENAME de
MSDOS).
f ) >chmod [modo] chero/directorio cambia los permisos de acceso.
modo: (1) usuario, (2) operador, (3) permiso
(1) u=user (2) + a nadir (3) r lectura
g=group - quitar w escritura
o=other = asignar x ejecucion
a=all
32 3 Introducci on al lenguaje de programaci on C (I)
Ejemplo 12: ...>chmod g+w pepe ana a nade a la gente del grupo
el permiso de escritura sobre los cheros pepe y ana.
Ejemplo 13: ...>chmod g+wx o-rwx pepe
Ejemplo 14: ...>chmod g=rx pepe
g) >lp pepe imprime el chero pepe.
h) >vi pepe crea el chero pepe. Es el comando de edici on de textos
en UNIX. Puede estar en modo COMANDO, para posicionarse,
moverse, abrir lneas nuevas, eliminar lneas, etc. pulsando la tecla
ESCAPE. Tambien puede estar en modo EDICION, para escribir.
Se entra en modo EDICION tecleando i o a.
Para salir: ESCAPE :wq sale guardando; ESCAPE :q! abandona.
Otros comandos:
date, who, passwd
a) >date dame la fecha que tiene registrada la m aquina.
b) >who inf ormame de los actuales usuarios del sistema.
Sin opciones ni par ametros: usuarios conectados.
...>who -a todos los usuarios.
...>who am i solo de mi mismo.
c) >passwd Sentencia para cambiar el password (palabra de paso).
Aparece:
Old password:
New password:
Re-enter new password:
Para desconectarse en la terminal: CTRL+D o teclear EXIT
3.2. Compilado, ejecuci on y depuracion de progra-
mas en C
En entornos UNIX, los programas se editan utilizando los editores del
sistema. En nuestra m aquina el de uso habitual es vi.
Todo el texto o codigo de un programa se almacena en un chero de ex-
tensi on .c, denominado chero fuente. Para compilar el programa, se invoca
al compilador desde el prompt de sistema operativo, utilizando el comando
cc, seguido del nombre completo del chero fuente. Por ejemplo:
$ cc programa1.c
3.3 Variables escalares, subindicadas (arrays) y constantes 33
Si el chero fuente contiene errores, aparecen por pantalla todos ellos.
Hay que volver al programa fuente y subsanar dichos errores. Se vuelve a
compilar el programa. Si dicho programa no tiene errores, el compilador crea
el chero objeto, que tiene el mismo nombre que el chero fuente, pero con
extension .o. Si el chero fuente era unico, la sentencia cc produce tambien el
linkaje, y crea ademas del chero objeto el chero ejecutable, que por defecto
se llama a.out. Si solo queremos que cree el objeto, hay que especicarlo:
$ cc -c programa1.c
Si queremos que el ejecutable tenga otro nombre (por ejemplo programa),
hay que especicarlo tambien:
$ cc -o programa programa1.c
Si existen distintos cheros fuente, programa1.c, programa2.c, progra-
ma3.c, se pueden compilar todos ellos, crear los correspondientes programas
objeto y linkarlos de manera que tengamos un unico ejecutable de nombre
programa.
$ cc -o programa programa1.c programa2.c programa3.c
Para ejecutar el programa, basta escribir su nombre
$ programa
En C existe adem as lo que se denomina en ingles debugger. Un debuger
es un depurador de c odigo. No genera c odigo, sino que realiza una com-
probaci on muy estricta de tantos aspectos de un programa como puedan
ser comprobados durante la compilaci on. Detecta inconsistencias en los ti-
pos de datos, uso incongruente de argumentos, variables no utilizadas o no
inicializadas, problemas potenciales de portabilidad, etc.
La forma de depurar un programa en nuestro entorno es mediante la
sentencia lint.
$ lint programa1.c
3.3. Variables escalares, subindicadas (arrays) y
constantes
La sentencia
34 3 Introducci on al lenguaje de programaci on C (I)
j = 5 + 10;
signica lo siguiente: suma los valores 5 y 10 y asigna el resultado a la varia-
ble denominada j. En la lectura de la sentencia anterior, estamos suponiendo
conocidos todos los smbolos involucrados. Sabemos que 5 y 10 son valores
enteros, que + e = son operadores y que j es una variable, lo cual signica
que su valor puede cambiar. Para el ordenador, sin embargo, todos estos
signos son una mera combinaci on de ceros y unos. Para que dicha expresi on
tenga sentido para un ordenador, hay que decirle previamente el signicado
de cada uno de los smbolos mencionados. Esta es una de las funciones del
compilador.
Las variables y las constantes son los objetos basicos que se manipulan en
un programa. Como sus propios nombres indican una constante es un valor
que nunca cambia, mientras que una variable puede representar distintos
valores. Una variable consigue su variabilidad representando una posici on,
una direcci on de memoria.
La variable j esta localizada en alguna posici on, por ejemplo en la 2486.
De manera que la sentencia anterior, para el ordenador signica: suma los
valores 5 y 10 y coloca su resultado en la posici on de memoria 2486.
La sentencia,
j = j 2;
es entendida entonces como: coge el contenido de la posici on 2486 de la
memoria, restale la constante 2 y almacena el resultado de nuevo en dicha
posici on de memoria.
Variable Direcci on
2482
j 2486
2490
Memoria



4 bytes
Las declaraciones iniciales en un programa indican las variables que se
van a utilizar, establecen su tipo y tal vez las inicializan, es decir las dan un
valor inicial.
3.3.1. Nombres de variables
En el lenguaje C, se puede poner nombre casi a cualquier cosa: variables,
constantes, funciones, e incluso localizaciones en un programa. Los nombres
3.3 Variables escalares, subindicadas (arrays) y constantes 35
pueden contener letras, n umeros, o el caracter barra baja , pero deben
comenzar por una letra o por dicho caracter. Hay algunas otras restricciones
en los nombres de las variables y constantes simbolicas.
El lenguaje C diferencia entre may usculas y min usculas, de manera que:
var, Var, VAR
son nombres diferentes.
No se pueden elegir como nombres las palabras reservadas. (Hay una
lista de ellas en casi cualquier manual de C). En general no existe lmite
en cuanto al n umero de caracteres que puede tener un nombre en C. Al-
gunos compiladores antiguos lo jan en 8 caracteres, otros en 31. Es buena
costumbre por la portabilidad del programa, intentar que los ocho primeros
caracteres de cada nombre sean unicos.
3.3.2. Tipo y tama no de datos
La primera clasicaci on que se puede hacer en cuanto al tipo de datos en
C es, como simples y compuestos. Dentro de los datos simples, tenemos:
1. Numericos, que a su vez pueden ser:
a) enteros: int o long (dependiendo de su tama no)
b) reales (en punto otante): oat o double.
2. Car acter, que a su vez pueden ser:
a) simples: char
b) cadenas: char*
3. Punteros: representan direcciones de memoria.
Dentro de los datos compuestos, dependiendo de su composicion:
1. Arrays: secuencia (vector) de datos simples del mismo tipo.
2. Estructuras: composici on de datos de distinto tipo.
Variables Numericas
Todas las variables hay que declararlas antes de usarlas en el programa,
es decir, dar su nombre, tipo y contenido. Por ejemplo:
36 3 Introducci on al lenguaje de programaci on C (I)
int x;
int lower, upper, step;
oat factorial;
El contenido de la variables no es necesario darlo al principio del progra-
ma, pero s antes del uso de dicha variable.
int x=0;
oat factorial=1,0E-5 (que es lo mismo que 0.00001);
Variables Car acter
Las declaraciones son:
char respuesta;
char* nombre;
si ademas las inicializamos:
char respuesta=s; (la s es el valor simple que representa inicialmente la
variable respuesta)
char* nombre =Jaime ; (es una cadena de caracteres)
Cuando se inicializa as, el ordenador entiende:
(Jaime`0)
Nota: s representa el caracter simple s; mientras que ses entendido
como una cadena de caracteres dada por (s `0).
Variables Subindicadas (arrays)
EJEMPLO 1
int tabla [10];
dene un vector de elementos enteros con 10 posiciones, (tabla [j], j=0,9).
tabla
2 5 200 520 3 0 1 850 1230 58
0 1 2 3 4 5 6 7 8 9
3.3 Variables escalares, subindicadas (arrays) y constantes 37
Se inicializara:
int tabla=2, 5, 200, 520, 3, 0, 1, 850, 1230, 58;
tabla[3]=520;
x=tabla[9]; /* el valor de la variable x es 58*/
EJEMPLO 2
char nombre [20];
dene un vector de elementos de tipo car acter con 20 posiciones, (nombre[j],
j=0,19).
nombre

0 1 19
La inicializaci on es:
char nombre[20]=Jaime;
o bien;
char nombre[20]=J ,a,i,m,e,`0;
EJEMPLO 3
int tabla2 [10][20]; /* es una matriz 10x20 */
dene una matriz de 10 las y 20 columnas, de elementos enteros.
La inicializaci on para una casilla, sera:
tabla2 [1][3]=800;
tabla2
0
1 800


9
0 3 19
Variables Estructura
EJEMPLO 4
38 3 Introducci on al lenguaje de programaci on C (I)
struct persona
{ char nombre [20];
struct fecha
{ int dia, mes, a~ no; } fnacimiento;
int sueldo;
} empleado;
Todo esto dene una estructura para cada persona. La variable es em-
pleado y esta compuesta por tres tipos de datos: el nombre (de tipo caracter),
la fecha de nacimiento (a su vez una estructura compuesta por tres datos
enteros), y el sueldo (una variable escalar de tipo entero).
Para inicializar esto:
empleado.nombre=Pepito;
empleado.fnacimiento.mes=10;
empleado.fnacimiento.a no=72;
empleado.sueldo=150000;
O bien:
empleado= Pepito, 3,10,72,150000;
O agrupando los datos para la estructura fnacimiento:
empleado.fnacimiento=3, 10, 72;
3.4. Un ejemplo. El primer programa en C
El siguiente programa imprime la tabla de conversi on de temperatu-
ras Fahrenheit a grados centgrados o Celsius, utilizando la f ormula: C =
(5/9)(F 32).
0 -17.8
20 -6.7
40 4.4
3.4 Un ejemplo. El primer programa en C 39
60 15.6
... ...
260 126.7
280 137.8
300 148.9
El programa es
/* imprime la tabla Fahrenheit-Celsius
para f=0,20,...,300 */
main()
{
int lower, upper, step;
float fahr, celsius;
lower=0; /* l mite inferior de la tabla de temperaturas*/
upper=300; /* l mite superior */
step=20; /*tama~ no del incremento */
fahr=lower;
while (fahr<=upper){
celsius=(5.0/9.0) * (fahr-32.0);
printf("%4.0f %6.1f\n", fahr, celsius);
fahr=fahr+step;
}
}
Las dos primeras lneas de programa son un comentario que, en este caso
explica lo que hace el programa. El compilador prescinde de los caracteres
comprendidos entre / y /.
A continuaci on comienza el cuerpo de programa, donde aparece la fun-
cion main. Todos los programas en C deben tener una funci on main en
alg un sitio. Normalmente main har a uso de otras funciones para efectuar su
trabajo. El cuerpo del programa est a dentro de unas llaves, que encierran el
bloque de todas las sentencias de que consta este.
A continuaci on aparece la lista de deniciones de las variables a utilizar
(su tipo) y su inicializaci on (valor inicial). Como hemos visto anteriormente
el tipo int implica que las variables de la lista son enteros; float indica punto
otante, es decir n umeros que poseen parte fraccionaria. La precisi on, tanto
de int como de float depende de la m aquina que se este utilizando. En el
Captulo 4 se especican mas detalles al respecto.
Las sentencias o proposiciones individuales se terminan con punto y co-
ma. Cada lnea de la tabla se calcula de la misma forma, para lo cual se utiliza
40 3 Introducci on al lenguaje de programaci on C (I)
un ciclo (bucle) que se repite una vez por cada lnea. Este es el prop osito
de la sentencia while. Se examina la condici on encerrada entre parentesis.
Si es cierta (fahr es menor o igual que upper), se ejecuta el cuerpo del ciclo
(todas las sentencias encerradas entre llaves). A continuacion se vuelve a
evaluar la condici on y si es cierta se ejecuta de nuevo el cuerpo del ciclo.
Cuando la condici on es falsa, naliza el ciclo y el control del programa pasa
a la sentencia que lo sigue.
M as adelante hablaremos de las reglas que rigen la conversion entre en-
teros y punto otante. Por ahora basta observar, que tanto la asignaci on
fahr = lower; como la comprobacion while(fahr <= upper) trabajan co-
mo se esperaba: la variable de tipo int es convertida a float antes de realizar
la operaci on.
En el ejemplo tambien se muestra como funciona la funci on printf.
printf es una funci on con formatos de conversi on de uso general que de-
tallaremos m as adelante. Su primer argumento es una cadena de caracteres
que imprimir, indicando cada signo % el lugar en que ha de sustituirse cada
uno de los restantes (segundo, tercero,...) argumentos y en que forma ha
de ser impreso. Por ejemplo, en la sentencia escrita en el programa la es-
pecicaci on de conversi on %4,0f indica que se ha de imprimir un n umero
en punto otante que ocupe un espacio de a lo sumo cuatro caracteres, sin
dgitos despues del punto decimal. %6,1f pide otro n umero que ocupar a a
lo sumo 6 posiciones, con un dgito despues del punto decimal. Despues de
la especicaci on de formato y separadas por comas, aparecen las variables
cuyos valores se van a imprimir.
Para terminar, hay que se nalar que printf no forma parte del lenguaje.
En C no se dene ni la entrada ni la salida. Es unicamente una funci on
util de la biblioteca est andar de funciones de entrada/salida habitualmente
accesible desde los programas en C.
Evidentemente, hay muchas formas de escribir un programa en C. El
siguiente es una modicaci on del anterior:
/* imprime la tabla Fahrenheit-Celsius */
main()
{
int fahr;
for (fahr=0; fahr<= 300; fahr=fahr+20)
printf("%4d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
}
El programa anterior produce el mismo resultado, pero su aspecto es
diferente. Se han eliminado la mayora de las variables. La variable fahr
3.4 Un ejemplo. El primer programa en C 41
permanece como int, hasta que en la propia funci on printf se realiza la
conversion %4d.
Se ha utilizado una nueva expresi on iterativa, la proposici on for. Consta
de tres partes separadas por punto y coma. La primera se ejecuta una sola
vez, antes de entrar en el ciclo propiamente dicho. La segunda es el criterio
o condici on que controla la ejecuci on. Esta condici on se eval ua; si es cierta,
se ejecuta el cuerpo del ciclo, y a continuacion se ejecuta la tercera parte
que es la parte de reinicializaci on y se vuelve a evaluar la condici on. El ciclo
termina cuando la condici on se torna falsa. Como en while el cuerpo del
ciclo puede ser una sola sentencia o un grupo de ellas colocadas entre llaves.
La decision de utilizar un while o un for es arbitraria.
Una ultima observaci on es la siguiente. Es una mala costumbre sepultar
en el interior del programa n umeros como 0, 300 o 20 en el caso del programa
anterior. Siendo difcil cambiarlos sistematicamente si la ocasion lo merece.
Para ello se utilizan en C las sentencias #define y las constantes simboli-
cas. Mediante la construcci on #define, al comienzo del programa se puede
denir un nombre simb olico como una determinada cadena de caracteres y
darlas un valor.
#include <stlib.h>
#include <stdio.h>
#define LOWER 0
#define UPPER 300
#define STEP 20
/* imprime la tabla Fahrenheit-Celsius */
main()
{
int fahr;
for (fahr=LOWER; fahr<= UPPER; fahr=fahr+STEP)
printf("%4d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
exit(0);
}
Las tres cantidades denidas son constantes y por lo tanto no pueden
aparecer en las declaraciones. Observese ademas que no hay punto y coma
despues de las deniciones.
42 3 Introducci on al lenguaje de programaci on C (I)
3.5. Operadores y funciones standard mas usuales
Los operadores son las herramientas mediante las cuales se construyen
las expresiones que manipulan los datos. Una clasicacion inicial puede ser
la siguiente:
1. Unitarios: (menos unitario, x representa la negaci on de x) y + (m as
unitario, +x representa el valor de x, esta disponible en compiladores
ANSI). Por ejemplo, si m es igual a 5, m es igual a -5. No hay que
confundir estos operadores con los operadores binarios aritmeticos de
diferencia y suma.
2. Aritmeticos: + (suma), (diferencia y tambien existe el unitario;
p.e. x representa la negaci on de x), (producto), / (cociente), %
(division entera: x%y produce el resto de dividir x entre y, y por
lo tanto es cero, si x es divisible entre y). El operador % no puede
utilizarse con datos de tipo oat o double (reales).
Ejemplo 1: j = 3x, es interpretado como j = (3(x)). El primer
se nala una resta, el segundo es un unitario.
Ejemplo 2: 3/4 da como resultado el valor 0, puesto que es una divisi on
entera (la parte fraccional se trunca). Si queremos realizar la divisi on,
hay que escribir 3,0/4,0.
3. Relacionales: == (igual), ! = (distinto), > (mayor que), < (menor
que), >= (mayor o igual que), <= (menor o igual que).
4. Asignaci on: = (Usado en inicializaciones), + = (suma-asignaci on),
= (resta-asignacion), = (multiplica-asignaci on), / =(divide-asignaci on),
% = (resto-asignacion).
Ejemplo 3: a = b pon el valor de b en a.
Ejemplo 4: a+ = b pon el valor de a +b en a.
Ejemplo 5: a = b pon el valor de a b en a.
Ejemplo 6: a = b pon el valor de a b en a.
Ejemplo 7: a/ = b pon el valor de a/b en a.
Ejemplo 8: a % = b pon el valor de a %b en a.
El termino de la izquierda en las anteriores expresiones (a), puede
referirse a una posici on de memoria.
5. L ogicos: && (and), [[ (or), ! (not).
Ejemplo 9: a&&b vale 1 si a y b son no nulos, vale 0 en otro caso.
Ejemplo 10: a[[b vale 1 si a o b son no nulos, vale 0 en otro caso.
3.5 Operadores y funciones standard mas usuales 43
Ejemplo 11: !b vale 1 si b es nulo, vale 0 en otro caso.
6. Acceso a miembros (Operadores de memoria): [] (usado en declaracio-
nes de arrays) , (usado en declaraciones de estructuras), (acceso a
estructuras , cuando se dispone del puntero), () (para pasar par ame-
tros en funciones, & (pon un objeto en una direcci on de memoria),
(obten el objeto situado en esa direcci on de memoria).
Ejemplo 12: &x obtiene la direcci on de x.
Ejemplo 13: x obtiene el objeto situado en la direcci on x.
Ejemplo 14: x[5] obtiene el valor del vector x en la posici on 5.
Ejemplo 15: x.y obtiene el valor del miembro y en la estructura x.
Ejemplo 16: p y obtiene el valor del miembro y en la estructura
punteada por p.
7. Operadores de incremento y decremento: C dispone de dos operadores
un poco raros, que incrementan y decrementan el valor de las variables.
El operador ++ le suma 1 a su operando; el operador le resta 1. La
caracterstica especial de estos operadores es que pueden ser utilizados
como prejos (antes de la variable, como en ++n) o bien como sujos
(despues de la variable, n ). En los dos casos ++ aumenta (
disminuye) en 1 el valor de la variable n, pero la expresi on + + n
incrementa n antes de utilizar su valor, mientras que n + + lo hace
despues de que se ha empleado su valor. Por ejemplo, si n = 5:
x = + +n; pone un 6 en x (incrementa primero el valor de n), pero
x = n + +; pone un 5 (toma primero el valor de n).
8. Operador condicional ? : que resulta ser una versi on abreviada de la
expresion if...else.... Por ejemplo: z = (x < y)?x : y), comprueba si
x < y, en cuyo caso asigna z = x, si x y, la asignaci on que hace es
z = y.
9. Operadores para la manipulaci on de bits: >>, <<, &, [, , .
10. Operador cast, de conversi on de tipo.
11. Operador sizeof, que devuelve el tama no en bytes, de datos o expre-
siones.
3.5.1. Funciones mas usuales
Una funci on es una coleccion de componentes del lenguaje agrupadas
bajo un mismo nombre. Si la funci on esta bien dise nada, debe representar
44 3 Introducci on al lenguaje de programaci on C (I)
un peque no n umero de operaciones, que son indicadas por el nombre de
la funci on. A menudo, una funci on aparece unas pocas lneas m as abajo
de donde se la llama una sola vez, para claricar una parte del programa.
Vamos a mostrar los mecanismos para la denici on de funciones escribiendo
la funci on power(m, n) que eleva el entero m a la potencia representada por
el entero positivo n.
/* prueba de la funcion power */
main()
{
int i;
for (i=0; i< 10; ++i)
printf("%d %d %d\n",i,power(2,i), power(-3,i));
}
power(x,n) /* elevar x a la n- esima potencia */
int x,n;
{
int i,p;
p=1;
for(i=1; i<=n; ++i)
p=p * x;
return(p);
}
Las funciones aparecen en cualquier orden, y en uno o m as archivos
fuente. Por el momento supondremos que est an en el mismo archivo, por lo
que se mantiene lo que hemos aprendido sobre ejecuci on de programas en
C.
En la lnea de impresion se llama dos veces a la funcion power. En cada
llamada se pasan dos argumentos a power, que cada vez devuelve un entero
que se formatea e imprime. Los argumentos de la funcion power se declaran
para que se conozca su tipo. La declaracion de los argumentos se realiza
entre la lista de argumentos y la llave de apertura. Cada declaracion debe
acabar con un punto y coma. Los nombre de los argumentos en power son
completamente locales a la funcion. Esto tambien se aplica a las variables i
y p: la i de power no tiene nada que ver con la i de main.
El valor que calcula power se devuelve al programa principal main me-
diante la sentencia return. Puede aparecer cualquier expresi on entre los
parentesis de una sentencia return. Una funci on no tiene por que devolver
un valor; una sentencia return sin expresi on devuelve el control al programa
principal aunque no le devuelve un valor util.
3.5 Operadores y funciones standard mas usuales 45
En C todos los argumentos se pasan por valor. Esto signica que la fun-
cion recibe los valores de sus argumentos en variables temporales, en lugar
de recibir sus direcciones. La diferencia principal es que en C la funci on
llamada no puede alterar el valor de una variable de la funci on que llama,
unicamente puede cambiar el valor de su copia privada y temporal. Esto es
una ventaja y permite escribir programas m as compactos, con pocas varia-
bles externas, ya que los argumentos se tratan como variables locales a la
rutina llamada, si est an debidamente inicializadas. Veamos una versi on de
power que utiliza este hecho.
power(x,n) /* elevar x a la n- esima potencia. Versi on 2 */
int x,n;
{
int p;
for(p=1; n>=0; --n)
p=p * x;
return(p);
}
El argumento n se utiliza como variable temporal, siendo decrementada
hasta que llega a ser 0. Cualquier cosa que hagamos con n dentro de power
no tendr a ningun efecto sobre el argumento. Cuando sea necesario, es po-
sible hacer que una funci on modique el valor de una variable en la rutina
llamante. Trataremos esto con mas detalles cuando hablemos de direcciones
de memoria y paso de argumentos por referencia.
En C como en otros lenguajes de programaci on existen cientos de fun-
ciones prefabricadas almacenadas en libreras. Algunas de las standard, apa-
recen a continuaci on:
Funci on Descripci on
fgetc() Lee un caracter de un chero
fopen() Abre un chero
fclose() Cierra un chero
time() Calcula el tiempo actual
sin() Calcula el seno de un angulo
log() Calcula el logaritmo de un n umero
Como hemos visto tambien es posible crear nuevas funciones. Por ejem-
plo, podramos denir una funci on para calcular el cuadrado de un n umero
entero. Sera un caso particular de la funci on power:
int square(int num)
{
46 3 Introducci on al lenguaje de programaci on C (I)
int answer;
answer=num*num;
return answer;
}
La funci on main()
Por otro lado, todo programa ejecutable debe contener una funci on espe-
cial denominada main(), que indica donde comienza la ejecuci on. Por ejem-
plo para invocar a la funci on square(), que eleva un n umero al cuadrado,
deberamos escribir:
#include <stdlib.h>
int main(void)
{
extern int square(int);
int solution;
solution=square(5);
exit(0);
}
Este peque no programa asigna el cuadrado de 5 a la variable denominada
solution. Las reglas de utilizacion de esta funci on son las mismas que las
de cualquier otra. Sin embargo, no hemos declarado el tipo de datos que
devuelve, ni los argumentos. Adoptaremos por ahora, que dicha funci on
main() genera un valor y trabaja con dos argumentos. Analizaremos este
detalle en el Tema 5.
La funci on exit() causa el nal del programa, devolviendo el control
al sistema operativo. Si el argumento de exit() es el cero, signica que el
programa termina sin errores. Argumentos distintos del cero, denotan errores
en la ejecucion. La llamada de exit() dentro de una funci on main() hace
exactamente lo mismo que la sentencia return. Es decir exit(0); es lo mismo
que return 0;. Se debe incluir cualquiera de las dos, dentro de cada funci on
main().
Si se usa exit() debe incluirse al comienzo del programa el chero stdlib.h,
que es donde esta almacenada dicha funci on.
3.5 Operadores y funciones standard mas usuales 47
En el caso anterior declaramos dos nombres en main(). El primero es
la funci on square(), que vamos a llamar. La palabra extern indica que di-
cha funci on esta denida en cualquier sitio, posiblemente en otro chero
fuente. La otra variable solution, es un entero que nosotros utilizamos para
almacenar el valor que devuelve la funci on square().
La siguiente sentencia es en la que se invoca a la funci on square(). El
argumento que aparece entre parentesis, indica que es el valor pasado como
argumento a dicha funci on, esto es, es el valor del que se va a calcular el
cuadrado. El operador de asignaci on =, indica que el resultado que devuelve
dicha funci on sera almacenado en la variable solution.
La funci on printf()
El programa anterior es correcto pero poco operativo. Una de las razones
es que no nos deja ver su salida. Una soluci on para este problema es utilizar
la funci on printf().
#include <stdio.h> /*Fichero donde se encuentra printf()*/
#include <stdlib.h>
int main(void)
{
extern int square(int);
int solution;
solution=square(27);
printf("El cuadrado de 27 es %d \n", solution);
exit(0);
}
Se nalar que hemos tenido que a nadir en la cabecera el chero stdio.h,
donde se encuentra la funci on de Input/Output printf(). Aunque describire-
mos dicha funci on con mas detalle en otro captulo, se nalar que el smbolo
%d indica que el argumento que va a imprimir es un entero decimal. La
secuencia `n indica a dicha funci on que salte de lnea (newline).
Si tienes almacenado el programa anterior en un chero de nombre poten-
cia2.c y la funci on square() en otro denominado square.c, podras compilar
ambos programas mediante la sentencia:
48 3 Introducci on al lenguaje de programaci on C (I)
$ cc -o potencia2 potencia2.c square.c
que crea un ejecutable con nombre potencia2. Basta a continuaci on que
teclees
$ potencia2
y obtentr as
El cuadrado de 27 es 729
En la sentencia que aparece utilizada la funci on printf(), se utilizan dos
argumentos, el primero adem as de incluir un peque no texto, especica el
formato de la variable que se va a imprimir. As %d, indica que la variable
solution es un entero decimal. Hay otros especicadores para otros tipos de
datos, por ejemplo:
Car acter Especicaci on
%s Vector caracter
%x Entero Hexadecimal
%f Dato real (punto otante)
%o Entero octal
La version general de la funci on printf(), debe contener tantas especi-
caciones de formato como variables se van a imprimir:
printf(Imprime tres valores: %d, %d, %d, num1, num2, num3);
La funci on scanf()
Todava el programa anterior es poco operativo, ya que permite calcular
unicamente el cuadrado del n umero 27. Para obtener el cuadrado de otro
n umero, habra que modicar el chero fuente potencia2.c, volver a compilar
y volver a ejecutar. Esto se puede evitar con el uso de la funci on scanf().
Dicha funci on realiza el trabajo contrario al de printf(), mientras esta escribe
la salida del programa en la pantalla, la funci on scanf() lee el valor del dato
introducido mediante el teclado y se lo asigna a una variable. Si a nadimos
esta posibilidad al programa anterior:
#include <stdio.h> /*Fichero donde se encuentra printf()*/
#include <stdlib.h>
int main(void)
{
3.5 Operadores y funciones standard mas usuales 49
extern int square(int);
int solution;
int input_val;
printf("Introduce un valor entero:");
scanf("%d", &input_val);
solution=square(input_val);
printf("El cuadrado de %d es %d\n", input_val, solution);
exit(0);
}
Ahora hemos declarado una nueva variable input val, que sirve para al-
macenar el valor entero introducido por el teclado, luego pasamos este valor
como argumento a square(). La expresi on &input val, signica la direcci on
de memoria de la variable imput val. En realidad, la funci on scanf() alma-
cena el valor introducido directamente en dicha direcci on de memoria. La
ejecucion del programa tendr a el aspecto:
$ potencia2
Introduce un valor entero: 22
El cuadrado de 22 es 484
Hemos visto que la funcion scanf() realiza la tarea contraria a printf().
Su sintaxis es similar. La diferencia es que en la funci on scanf(), los nombres
de variables, van precedidas del smbolo &. Esto hace que el sistema lea el
dato de la terminal y lo coloque en una direcci on de memoria.
Sentencias #include
Esta sentencia hace que el compilador lea texto de otros cheros, al
mismo tiempo que lee el chero que esta compilando. Una sentencia de este
tipo puede tener dos estructuras:
#include < chero >
y
#include chero
50 3 Introducci on al lenguaje de programaci on C (I)
En el primer caso, el compilador busca en alg un lugar designado por el
sistema operativo. En el segundo caso, el compilador busca en el directorio
donde se encuentre el chero que esta compilando.
Sentencias #dene
Sirven para asociar una constante a una variable, que ocupa una direc-
cion de memoria. Por ejemplo:
#dene var 1 34
asigna el valor 34 a la variable var 1, de manera que las sentencias
j = 1 + 34;
j = 1 +var 1;
dan el mismo resultado.
3.6. Ambito de denici on y validez de las varia-
bles. Variables externas
Las variables de main son privadas o locales a main. Lo anterior tam-
bien es valido para las variables de otras funciones. Cada variable local de
una rutina comienza a existir cuando se llama a la funci on, y desaparece
cuando la funci on acaba. Por esta razon, tales variables reciben el nombre
de automaticas.
Las variables automaticas aparecen y desaparecen con la invocacion de
la funci on, por eso no conservan su valor entre dos llamadas sucesivas, y se
les ha de dar un valor a la entrada de la funci on.
Como alternativa es posible denir variables externas a todas las fun-
ciones; esto es, variables globales a las que puede acceder cualquier funci on
mediante su nombre. Las variables externas son accesibles globalmente; por
ello pueden usarse en lugar de listas de argumentos para comunicar datos
entre funciones. Adem as mantienen sus valores, ya que existen permanente-
mente en lugar de aparecer y desaparecer, incluso despues de que nalizan
las funciones que las cambian.
Una variable externa ha de denirse fuera de las funciones. Esto les
asigna memoria real. La variable ha de ser declarada en toda funci on que
3.7 Precedencia y orden de evaluaci on entre operadores 51
quiera acceder a ella; esto puede hacerse mediante una declaraci on extern
explcita, o en forma implcita a traves del contexto. Veamos un ejemplo:
#include <stdio.h>
/* definici on de la variable global var1 */
int var1=50;
main(){ /* comienzo del programa */
printf("%d\n", var1); /* escribe 50 */
{ /* var1 y var2 variables locales al bloque 1 y 2*/
int var1=100, var2=200;
printf("%d %d\n", var1, var2); /* escribe 100 y 200 */
{ /* bloque 2*/
int var1=0;
printf("%d %d\n", var1, var2); /* escribe 0 y 200 */
}
printf("%d\n", var1); /* escribe 100 */
}
printf("%d\n", var1); /* escribe 50 */
} /* final de programa*/
3.7. Precedencia y orden de evaluacion entre ope-
radores
Los operadores tiene dos propiedades, precedencia y asociatividad. Las
dos estan estrechamente relacionadas. Veamoslas en un ejemplo:
La expresion 2 +3 4 evaluada por la m aquina es 14, igual que 3 4 +2.
Sin embargo la m aquina hace la cuenta siempre en el mismo orden. Primero
eval ua 3 4 y luego al resultado le suma un 2. Esto se resume diciendo que
el operador tiene una precedencia mas alta que el operador +. Si se quiere
establecer otro orden hay que utilizar parentesis. Por ejemplo, 3,0 4,0/8,0
da como resultado 2,5, distinto al de la expresi on (3,0 4,0)/8,0 que da
0,125.
Por otra parte, la expresi on de asignacion x = y = 3, es asociativa de
derecha a izquierda, es decir se eval ua comenzando por la derecha. En primer
lugar se asigna un 3 a la variable y, y posteriormente se asigna este mismo
valor a la variable x. Un operador puede ser asociativo de derecha a izquierda
o de izquierda a derecha.
52 3 Introducci on al lenguaje de programaci on C (I)
En la siguiente tabla aparecen clasicados los operadores de acuerdo a
su precedencia y asociatividad.
Clase de Operador Operadores en la clase Asociatividad Precedencia
Primario () [] Izda. a dcha. Alta
Unitario cast
sizeof
& (direccion en memoria) Dcha. a izda.
(de referencia)
+
++ !
Aritmetico multiplicativo / % Izda. a dcha.
Aritmetico aditivo + Izda. a dcha.
Manipulaci on de bytes << >> Izda. a dcha.
Relacional de desigual. < <= > >= Izda. a dcha.
Relacional de igual. == ! = Izda. a dcha.
Manipulaci on de bytes & Izda. a dcha.
Manipulaci on de bytes Izda. a dcha.
Manipulaci on de bytes [ Izda. a dcha.
Logico && Izda. a dcha.
Logico [[ Izda. a dcha.
Condicional ? : Dcha. a izda.
Asignacion = + = = =
/ = % = >> n = <<= Dcha. a izda.
& = = Baja
Captulo 4
Introduccion al lenguaje de programaci on
C (II)
Un tipo de dato es una interpretaci on que da el compilador a una cadena
de bits. En el tema anterior vimos una clasicaci on de datos como simples (o
escalares) y compuestos (o agregados). Hay ademas un tipo de dato -void-
que no es ni simple ni compuesto. En este tema vamos a describir los tipos
de datos escalares y el tipo void.
4.1. Declaraciones. Tipos de datos escalares: ente-
ros, caracter, de coma otante y enumeraci on
Todas las variables deben ser declaradas antes de utilizarlas, aunque cier-
tas declaraciones se realizan implcitamente por el contexto. La declaraci on
le dice al compilador c omo interpretar y almacenar una serie de bytes. Para
declarar la variable j como entera, debemos escribir:
int j;
La palabra int es una palabra reservada que especica que el dato es
de tipo entero. Hay nueve palabras reservadas para especicar los tipos de
datos simples o escalares. Los cinco dados por: char, int, oat, double,
enum son tipos b asicos. Los otros: short, long, signed y unsigned son
modicaciones calicadoras de datos simples.
53
54 4 Introducci on al lenguaje de programaci on C (II)
char un byte (octeto) capaz de contener un car acter del juego de
caracteres de la instalaci on local
int un entero, normalmente del tama no de los enteros de la
instalaci on local
oat un n umero en punto otante de precisi on normal
double un n umero en punto otante de doble precisi on
Ademas hay una serie de calicadores que se aplican a los enteros: short,
long, signed y unsigned. short y long se reeren a diferentes tama nos de
los n umeros. Algunos compiladores dedican cuatro bytes para almacenar un
entero, mientras que otros solo dedican dos. Por otra parte el tama no de un
byte tampoco es constante. En la mayora de las m aquinas un byte son ocho
bits, pero hay excepciones.
Si no tienes interes en el tama no del entero dentro de la m aquina, puedes
usar int. En otro caso debes especicar short o long. En la mayora de las
maquinas short int es un entero que ocupa dos bytes, mientras que long
int es un entero que ocupa cuatro bytes.
short int j;
long int k;
es lo mismo que
short j;
long k;
El n umero de bits utilizados para representar un entero determina el
rango de valores que pueden ser almacenados en ese tipo.
Tipo Tama no (en bytes) Rango de valores
int 4 2
31
a 2
31
1
short int 2 2
15
a 2
15
1
long int 4 2
31
a 2
31
1
unsigned short int 2 0 a 2
16
1
unsigned long int 4 0 a 2
32
1
signed char 1 2
7
a 2
7
1
unsigned char 1 0 a 2
8
1
Consideremos por ejemplo un entero short int (16 bits). Cada bit repre-
senta un valor de 2 elevado a la potencia n, donde n representa la posicion
4.1 Declaraciones. Tipos de datos escalares: enteros, car acter, de coma otante y enumeracion55
del bit:
2
15
2
14
2
13
2
12
2
11
2
10
2
9
2
8
2
7
2
6
2
5
2
4
2
3
2
2
2
1
2
0
Por ejemplo para expresar el n umero decimal 9 = 2
3
+ 2
0
= 8 + 1.
0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1
Ademas en C existe la posibilidad de declarar una variable como no
negativa (unsigned). Para hacer esto se utiliza el calicador unsigned:
unsigned int k;
unsigned short j;
unsigned long m;
En los compiladores standard ANSI, existe la posibilidad de declarar a
los enteros signed, es decir, con valores positivos o negativos. Un calicador
de este tipo parece superuo, dado que por defecto una variable no declarada
como unsigned, debera ser signed. La diferencia est a en las variables de tipo
caracter, que pueden ser signed o unsigned por defecto. La mayora de los
compiladores utilizan signed char por defecto.
Vamos a ver ahora que la notaci on cientca usual
123,45e
7
o bien
0,12E3
tambien es valida para valores oat. Toda constante en punto otante se
toma por defecto como double, por lo que la notaci on e sirve tanto para
oat como para double. Si queremos declarar e inicializar una variable en
punto otante
oat eps = 1.5e-5;
(le estamos asignando a la variable real eps el valor inicial 0,000015 = 1,5
10
5
).
Hay una notaci on para constantes octales y hexadecimales: un cero que
encabece una constante int indica octal; el grupo 0x (o bien OX) indica he-
56 4 Introducci on al lenguaje de programaci on C (II)
xadecimal. Por ejemplo, el decimal 31 de puede escribir como 037 en octal, o
como 0x1f (0X1F) en hexadecimal. Las constantes octales o hexadecimales
pueden acabar en L para hacerlas long. (Ver tabla de conversi on ASCII,
para las equivalencias entre los distintos tipos de representaci on).
Una constante car acter es un unico car acter escrito entre apostrofos. El
valor numerico de una constante car acter es el valor numerico del caracter en
el conjunto de caracteres de la representaci on. Por ejemplo, en el conjunto
de caracteres ASCII, el caracter cero, 0, es 48, diferente del valor numerico
0. Las constantes caracter pueden participar en las operaciones numericas
como cualquier otro n umero, aunque es m as frecuente encontrarlas en com-
paraciones con otros caracteres.
Ciertos caracteres no imprimibles se representan como constantes caracter
mediante secuencia de escape como `n (nueva lnea), `t (tabulador); `0 (nu-
lo), `` (barra invertida), `

(ap ostrofo), etc. Aunque aparecen como dos


caracteres, en realidad son solo uno. La constante

`0

representa el caracter
con valor nulo.
El tipo de dato de enumeraci on enum, es particularmente util, cuando
quieres crear un unico conjunto de valores que pueden ser asociados con
una variable. En el siguiente ejemplo, vamos a declarar dos variables de
enumeraci on llamadas color e intensidad. A color le vamos a poder asignar
uno de los cuatro valores constantes rojo, azul, amarillo, verde. A intensidad
le vamos a poder asignar los valores brillante, media, oscura. La declaraci on
sera:
enum rojo, azul, amarillo,verde color;
enum brillante, media, oscura intensidad;
Este tipo de dato no forma parte de la versi on de C dada por Kernighan y
Ritchie, por lo que la implementaci on puede variar de un compilador a otro.
Un buen compilador, sin embargo, dara se nal de problemas ante sentencias
como:
color=brillante;
intensidad= azul;
color=1;
color=azul+verde;
4.2 Conversiones de tipo 57
El tipo de dato void
Tampoco forma parte de la versi on de C dada por Kernighan y Ritchie.
El tipo dato void, tiene dos importantes usos. El primero es indicar que
una determinada funci on no devuelve un valor. Por ejemplo, puedes ver una
funci on denida como:
void func (a, b)
int a, b;
{
.
.
}
Esto indica que la funci on no devuelve ning un valor. Tambien podamos
haberlo utilizado en la llamada de func()
extern void func();
Esto informa al compilador de que no se puede asignar el valor de retorno
de dicha funci on, por ejemplo:
num=func(x,y);
dara un error.
El otro uso de void es declarar un puntero generico. Lo veremos con mas
detalle, mas adelante.
4.2. Conversiones de tipo
Los operandos de tipos diferentes que aparecen en una expresi on se con-
vierten a un mismo tipo de acuerdo con unas cuantas reglas. Las unicas
conversiones que se realizan autom aticamente son las que tiene sentido, co-
mo la conversi on de un entero a punto otante antes de sumar dicho entero
con un n umero en punto otante. (Por ejemplo: 3+2.5=5.5).
Por otra parte no est an permitidas expresiones que carecen de sentido,
por ejemplo, utilizar un n umero en punto otante como subndice.
Ademas los caracteres y los enteros (char e int) se mezclan en expresiones
aritmeticas: todo char de una expresi on se convierte automaticamente en int.
58 4 Introducci on al lenguaje de programaci on C (II)
Ejemplo 4.1: Uso de la funci on atoi() que convierte una cadena de
dgitos en su valor numerico correspondiente.
atoi(s) /* convierte el caracter s en entero*/
char s[];
{
int i,n;
n=0;
for(i=0; s[i] >= 0 && s[i] <= 9; ++i)
n=10*n+s[i]-0;
return(n);
}
La comparaci on
s[i] >=0 && s[i]<=9
determina si el car acter que hay en s[i] es un dgito. Si lo es, el valor numeri-
co del dgito es s[i] - 0. Esto ultimo funciona correctamente s olo si 0, 1,...
etc., son positivos y est an en orden creciente, y s olo si hay dgitos entre los
caracteres 0 y 9. Afortunadamente esto es cierto para todos los juegos de
caracteres ordinarios.
Por denici on, toda aritmetica en la que intervienen valores de tipo char
e int, se realiza convirtiendo los valores char al tipo int. Las variables y
constantes de tipo char son esencialmente identicas, en cuanto al tipo, a los
valores int en contextos aritmeticos. Por ejemplo, s[i]-0, es una expresi on
entera con un valor entre 0 y 9 seg un sea el car acter almacenado en s[i],
y dicha expresi on se puede utilizar como subndice.
Por ultimo la sentencia
n=10*n+s[i]-0;
expresa el valor del dgito s[i]-0, en sistema decimal (base diez).
Otro ejemplo de conversi on de char a int, es la funci on lower(), que asigna
a cada letra may uscula su correspondiente min uscula. (V alido para caracte-
res ASCII). Si el car acter no es una letra may uscula, la funci on devuelve la
misma letra sin alteraciones.
lower(c) /* convierte c en min uscula, s olo para ASCII */
int c;
4.2 Conversiones de tipo 59
{
if( c >= A && c <= Z)
return(c+a- A);
else
return(c);
}
Esta funci on act ua correctamente en ASCII, ya que las letras may usculas
y min usculas correspondientes estan situadas a una distancia ja, conside-
radas como valores numericos, y ademas el alfabeto es continuo, es decir, no
contiene ning un otro car acter entre dichas letras.
Otra utilidad de la conversi on autom atica de tipos esta relacionada con
expresiones como i > j y expresiones logicas que utilizan && y [[, en las que
se ha establecido que devuelvan el valor 1, en caso de que el resultado sea
cierto y el valor 0 en caso contrario.
Las conversiones autom aticas se realizan de acuerdo con las siguientes
reglas. Si un operador como + o con dos operandos (un operador binario)
tiene los operandos de distinto tipo, el de tipo inferiores ascendido al tipo
superior, antes de realizar la operaci on. El resultado es de tipo superior.
En general los operandos que intervienen en una operaci on, son convertidos
al tipo del operando de precisi on m as alta.
Las reglas de conversion se resumen en las siguientes:
char y short se convierten en int, y oat se convierte en double.
Si alg un operando es de tipo double, el otro se convierte en double
y el resultado es double.
Si no se aplica la regla anterior, y si alg un operando es de tipo
long, el otro se convierte en long y el resultado es long.
Si no se aplica la regla anterior y alg un operando es de tipo
unsigned, el otro se convierte en unsigned y el resultado es de
tipo unsigned.
Si no se puede aplicar ninguna de las reglas anteriores, los ope-
randos deben ser de tipo int y el resultado es de tipo int.
En C la aritmetica del punto otante se realiza en doble precisi on. Las
conversiones tambien tienen lugar en las asignaciones; el valor de la parte
derecha se convierte al de la izquierda, que es el tipo del resultado. De
oat a int la conversi on provoca un truncamiento de la parte fraccionaria.
De double a oat, un redondeo. Los enteros de mayor magnitud (long) se
60 4 Introducci on al lenguaje de programaci on C (II)
convierten en tipo short o en caracteres (char) por eliminaci on de los bits
de orden superior.
Los argumentos de una funci on son expresiones y por ello las reglas de
converi on de tipo tambien se aplican con ellos. Concretamente, char y short
se convierten en int y oat se vuelve double. A esto se debe el que hayamos
declarado los argumentos de la funci on como int y double, incluso cuando
se llama a la funci on con char y oat.
Por ultimo se puede forzar la conversi on explcita de tipo coaccionadade
una expresi on, mediante una construcci on denominada cast. En la construc-
cion
(nombre de tipo) expresi on
la expresion se convierte al tipo citado mediante las anteriores reglas de con-
version. Por ejemplo, la rutina sqrt() espera que se le pase un argumento de
tipo double, con lo cual transmitir a mensajes de error si recibe argumentos
de otro tipo. Si n es entero,
sqrt((double) n)
convierte n al tipo double antes de pas arselo a sqrt. El operador cast tiene
la misma precedencia que cualquier otro operador unitario, seg un vimos en
la tabla del tema anterior.
4.3. Nociones sobre representacion de datos e im-
plicaciones sobre la precisi on numerica
El ordenador digital representa los n umeros en base 2, de manera que
cada n umero se almacena en un n umero nito de dgitos binarios. Hay dos
formas de almacenar estos n umeros, en punto jo y en punto otante.
La representacion en punto jo es la que utilizamos normalmente. Por
ejemplo: 30,421 o 0,3245 son representaciones en punto jo. Como hemos
dicho, el n umero de dgitos de cada n umero que almacena un ordenador es
nito n = n
1
+n
2
, donde n
1
son los dgitos de la parte entera y n
2
los de la
fraccionaria.
n
1
n
2
30,421 0030 421000
0.3245 0000 324500
4.3 Nociones sobre representaci on de datos e implicaciones sobre la precisi on numerica61
n
1
= 4, n
2
= 6 son valores jos del ordenador. De esta forma hay valores
que se pueden representar de forma exacta y otros que no. Por ejemplo,
el n umero 31,8923467, en un ordenador con n
1
= 4 y n
2
= 6 puede ser
representado como
0031 892346
representacion que se denomina corte. O bien
0031 892347
en cuyo caso, la representaci on se denomina redondeo. En general la re-
presentacion de n umeros en punto jo es muy poco util, se utiliza s olo
para operaciones elementales (calculadoras,..). Por ejemplo, los errores de
representacion de n umeros como: 35420,87 o 0,00000056, son brutales. En
el primer caso, el ordenador aproximara por 9999 y en el segundo por 0.
Para c alculos mas ambiciosos, el ordenador debe operar en punto otan-
te.
La representacion en punto otante de un n umero x es x = ax10
b
con
0,1 [a[ < 1 y b entero.
Por ejemplo:
342,17 (en punto jo) 0,34217x10
3
(en punto otante)
x = (.
1

2
...
n
)x10
bm...b
0
donde (.
1

2
...
n
) se denomina mantisa y b
m
...b
0
es el exponente. La
notaci on a emplear sera:
,34217E3
El n umero de dgitos en la mantisa se denominan dgitos signicativos.
El n umero 0,0001423 = ,1423E-3 tiene cuatro dgitos signicativos. Con la
condici on
1
> 0, la representaci on anterior es unica.
En la representaci on numerica en punto otante, es necesario reservar
un n umero de dgitos para la mantisa (t) y un n umero para el exponente (e).
Si t = 4 y e = 2, el n umero 0,00143 se puede expresar 0,00143 = ,1430E-2.
Los valores de t y e son caractersticos de cada ordenador.
62 4 Introducci on al lenguaje de programaci on C (II)
Por ejemplo, si t = 12 y e (500, 500) el rango de valores a representar
en pantalla es ,999999999999E499.
Existen valores que no se pueden representar de forma exacta. Para re-
presentar estos n umeros se pueden emplear dos tecnicas de aproximacion:
corte y redondeo.
Para t = 4: x = 3,87184 = 0,387184E1
fl(x) = 0,3871E1 corte (*)
fl(x) = 0,3872E1 redondeo.
Sea A el conjunto de n umeros maquina (representables de forma exacta
para un cierto valor de t). A es un conjunto nito.
Para cualquier n umero x, fl(x) A. Esta aplicaci on debe cumplir
[x fl(x)[ [x y[ y A (4.1)
(observar que la aproximaci on por corte (*) no satisface esta condici on).
Si a = .
1

2
...
t

t+1
... [a[ = .
1
...
t+1
... y su representacion por re-
dondeo a

es:
a

.
1

2
...
t
si
t+1
4
.
1

2
...
t
+ 10
t
si
t+1
5
fl(x) = signo(x)xa

x10
e
y esta aplicaci on s satisface (4.1).
Si x = 0

x fl(x)
x

[a[10
e
a

10
e
[a[10
e

[a[ a

[a[

5 10
(t+1)
[a[ 10
1
5 10
t
(4.2)
Es decir [[a[ a

[ 5 10
(t+1)
. Esta segunda cantidad se denomina error
absoluto. No da idea de la proximidad de los n umeros. Por ejemplo:
Si x
1
= 5437,43215 y x
2
= 5437,43214, [x
1
x
2
[ = 0,00001.
Si y
1
= 0,00005 y y
2
= 0,00004, [y
1
y
2
[ = 0,00001.
La diferencia es la misma y s olo en el primer caso diramos que x
1
es
una buena aproximaci on de x
2
.
4.3 Nociones sobre representaci on de datos e implicaciones sobre la precisi on numerica63

x
1
x
2
x
1

= 0,183910341 10
8
.
[
y
1
y
2
y
1
[ = 0,2 > 5 10
5
.
La cantidad (4.2) se denomina error relativo, que est a acotado como
vemos por 5 10
t
.
Si
xfl(x)
x
= , fl(x) = x(1 ) con [[ eps. eps se denomina precision
del ordenador.
Por ejemplo si escribimos 3,1415 y decimos que tiene cuatro dgitos sig-
nicativos correctos, signica que su error relativo es < 5 10
5
.
4.3.1. Errores de redondeo en computaci on
Sean por ejemplo x = 0,845E7 e y = 0,324E 2. El resultado de sumar
estos dos n umeros es
845000 0
0.00324
845000 0.00324
con t = 8.
x y = x y en general si [y[ <
eps
10
[x[ x y = x.
Se puede decir que en general la suma, resta, divisi on y multiplicaci on
de n umeros maquina no son operaciones asociativas y tampoco distributivas
una con respecto a la otra. El resultado de una serie de operaciones depende
del orden en que se realicen.
Ejemplo 4.2 (t = 8) a = 0,023371258E 4, b = 0,33678429E2 y
c = 0,33677811E2. Veamos que (a b) c = a (b c).
0.000023 371258
33.678429
33.678452 371258
no aparece si t = 8 (= a b)
-33.677811
0.000641 371258 (a b) c = 0,641E 3
Por otra parte: b c = 0,000618. (b c) a = 0,64137E 3.
64 4 Introducci on al lenguaje de programaci on C (II)
Ejemplo 4.3 En sumas que involucran sumandos de distinta maginitud,
el orden es importante. Si sumamos de manera exacta estos tres n umeros:
(t = 8)
0.00000007
0.00000009
3
3.00000016
Si t = 8 = 0,30000001E1
Sin embargo si los sumamos en disntinto orden:
3
0.00000007
0.00000009
3.0000000
= 0,3000000E1
Este tipo de errores es caracterstico de la suma de series.

i=1
a
i

n
i=1
a
i
.
En las series convergentes el termino general tiende a 0 y los terminos
van de mayor a menor. El error relativo a la hora de sumar los terminos
peque nos es mnimo si los terminos se suman de menor a mayor; de esa
forma, el valor de la suma va aumentando y se va aproximando al tama no
de los ultimos sumandos (los primeros terminos en la serie). Luego una serie
convergente hay que sumarla siempre en orden regresivo.
4.3.2. Error de cancelaci on
Aparece cuando restamos dos n umeros del mismo signo y muy proximos:
x y, x y.
Por ejemplo, con t = 8:
0.76545421 E1
-0.76545264 E1
0.00000157 E1
Hemos pasado de tener 8 dgitos signicativos correctos a tener 3, luego
hemos perdido 5 dgitos correctos.
4.3 Nociones sobre representaci on de datos e implicaciones sobre la precisi on numerica65
Ejemplo 4.4 Para resolver: ax
2
+bx +c = 0
x
1
x
2
=
b

b
2
4ac
2a
Si ac << b
2
[b
2
4ac[ b
2
y uno de los signos da un error de
cancelaci on.
La forma de evitarlo sera:
x
1
= signo(b)
[b[ +

b
2
4ac
2a
x
2
=
c
ax
1
Con t = 5, al resolver: x
2
+ 111,1x + 1,2121 = 0
De la primera forma: x
1
= 111,10, x
2
= 0,01000.
De la segunda: x
1
= 111,10, x
2
= 0,010910, esta ultima mas proxima
al valor real.
Las races seran (con t = 9): x
1
= 111,09909..., x
2
= 0,01091008...
Recuerda la relacion de recurrencia utilizada en el Ejemplo 1.3. Repre-
senta, sin duda, un caso mas en el que se pierde precisi on al restar dos
n umeros del mismo signo y magnitd comparable. Debes extremar precau-
ciones cuando utilices relaciones de recurrencia que envuelvan restas.
66 4 Introducci on al lenguaje de programaci on C (II)
Captulo 5
Introduccion al lenguaje de programaci on
C (III)
Las sentencias o proposiciones de control de ujo de un lenguaje especi-
can el orden en que se realizan las computaciones.
5.1. Sentencias de control de ujo: for, if , do, whi-
le, switch. Ejemplos de uso
Una expresi on como x=0 o i++ o printf() se convierte en una proposi-
cion cuando va seguida de punto y coma, como en
x=0;
i++;
printf(...);
En el lenguaje C, el punto y coma es un terminador de sentencia, y
no un separador. Con las llaves se agrupan declaraciones y sentencias en
una proposici on compuesta o bloque, y son sint acticamente equivalentes a
una proposici on simple. Las llaves que rodean las proposiciones m ultiples
despues de if, else, while, o for son otro ejemplo. Nunca se pone punto y
coma despues de la llave derecha que cierra un bloque.
If-Else
La proposici on if-else sirve para tomar decisiones. Formalmente la sinta-
xis es
67
68 5 Introducci on al lenguaje de programaci on C (III)
if (expresi on)
proposici on-1
else
proposici on-2
donde la parte else es opcional. Se eval ua la expresi on; si es cierta (es decir,
si tiene un valor distinto de cero), se ejecuta la proposici on-1. Si es falsa,
(expresi on es cero) y existe parte else, se ejecuta proposicion-2. Un if sim-
plemente comprobar a el valor numerico de una expresion; por ello pueden
aplicarse abreviaciones. Lo mas elemental es escribir
if (expresi on)
en lugar de
if (expresi on !=0)
Dado que la parte else de un if-else es opcional, hay ambig uedad cuando
se omite un else en una secuencia de if anidados. Esto se resuelve asociando
el else al if sin el else mas cercano. Por ejemplo:
if (n > 0)
if(a > b)
z=a;
else
z=b;
La parte else se asocia con el if mas interior, como se indica en el sangrado.
Si no fuera as, hay que utilizar llaves, como en:
if (n > 0){
if(a > b)
z=a;
}
else
z=b;
Else-if
La construcci on
if (expresi on)
proposici on
5.1 Sentencias de control de ujo: for, if , do, while, switch. Ejemplos de uso69
else if (expresi on)
proposici on
else if (expresi on)
proposici on
else
proposici on
es la forma mas general de escribir una decision m ultiple. Las expresiones se
eval uan en orden; si alguna es cierta, se ejecuta la proposici on asociada con
ellas y se acaba la cadena. El codigo para una proposici on puede ser simple
o un bloque entre llaves.
La ultima parte else maneja el caso ninguno de los anteriores, o el de
defecto, cuando no se sustituye ninguna de las otras condiciones. Si no hay
accion explcita para el defecto, se puede omitir el ultimo else.
Ejemplo 5.1: Vamos a ver un programa que decribe una funci on de
b usqueda binaria, que decide si un valor concreto x aparece en un arreglo
ordenado v de dimension n. Los elementos de v estan ordenados en orden
creciente. La funci on devolver a la posici on (un n umero entre 0 y n 1) si
x esta en v, y un -1 si no est a.
binary(x,v,n) /* buscar x en v[0],...,v[n-1]*/
int x, v[], n;
{
int low, high, mid;
low=0;
high=n-1;
while( low<= high){
mid=(low+high) / 2.;
if(x < v[mid])
high = mid-1;
else if(x > v[mid])
low = mid+1;
else /* encontrado */
return(mid);
}
return(-1);
}
La decisi on basica es si x es menor, mayor o igual que el elemento central
v[mid] en cada paso.
Switch
70 5 Introducci on al lenguaje de programaci on C (III)
La proposici on switch es una herramienta especial para decisiones m ulti-
ples que comprueba si una expresi on iguala uno entre varios valores cons-
tantes, y se bifurca a partir de ellos.
int switch_example( char input_arg )
{
switch (input_arg)
{
case A: return 1;
case B: return 2;
case C: return 3;
case D: return 4;
default : return -1;
}
}
La misma funci on puede ser escrita con proposiciones if-else:
5.1 Sentencias de control de ujo: for, if , do, while, switch. Ejemplos de uso71
int switch_example( char input_arg )
{
if (input_arg ==A)
return 1;
else if(input_arg == B)
return 2;
else if(input_arg == C)
return 3;
else if(input_arg == D)
return 4;
else
return -1;
}
La expresion switch debe de ir seguida de un parentesis que encierra otra
expresion. Esta ultima expresi on debe de ser entera, es decir, puede ser char,
long o short; pero no double, oat o long double. En la versi on de Kernigham
y Ritchie, debe ser una expresion int. La proposici on default, es opcional.
Ejemplo 5.2:
/* Programma que imprime mensajes de error, basados en el
* valor de la variable error_code. La funci on es declarada
* void porque no devuelve ning un valor*/
#include <stdio.h>
#define ERR_INPUT_VAL 1
#define ERR_OPERAND 2
#define ERR_OPERATOR 3
#define ERR_TYPE 4
void print_error (int error_code )
{
switch (error_code)
{
case ERR_INPUT_VAL:
printf("Error: Valor inicial no v alido.\n");
break;
case ERR_OPERAND:
printf("Error: Operando no v alido.\n");
break;
case ERR_OPERATOR:
printf("Error: Operador no v alido.\n");
break;
case ERR_TYPE:
printf("Error: Dato incompatible.\n");
break;
default:
printf("Error: C odigo de error desconocido %d\n",
error_code");
break;
}
72 5 Introducci on al lenguaje de programaci on C (III)
}
La proposici on break, hace que se produzca una salida inmediata de la
instrucci on switch.
Iteraciones while y for
En
while ( expresi on )
proposici on
se eval ua una expresi on. Si es cierta (si no es cero), se ejecuta la proposicion
y se vuelve a evaluar la expresi on. El ciclo contin ua hasta que la expresi on
vale cero, momento en que la ejecucion contin ua despues de la proposicion.
La sintaxis en la proposici on for es:
for ( expr1; expr2; expr3 )
proposici on
que es equivalente a:
expr1;
while ( expr2 )
{
proposici on
expr3;
}
Ejemplo 5.3: La siguiente funci on devuelve el factorial del argumento:
long int factorial ( long val)
{
int j, fact=1;
for (j=2; j <= val; j++)
fact=fact*j;
return fact;
}
que escrita mediante la proposici on while es:
5.1 Sentencias de control de ujo: for, if , do, while, switch. Ejemplos de uso73
long int factorial ( long val)
{
int j, fact=1;
j=2;
while (j <= val)
{
fact=fact*j;
j++;
}
return fact;
}
Gramaticalmente las tres componentes de un for son expresiones. M as
com unmente, expr1 y expr3 son asignaciones o llamadas a funcion y expr2
es una expresi on de relacion. Pueden omitirse cualquiera de las tres, aunque
deben mantenerse los puntos y coma. Si la comprobaci on expr2 no esta se
supone que es cierta.
La eleccion entre un ciclo while o for suele ser una cuestion de gusto.
Otro operador de C es la coma ,que casi siempre encuentra aplicacion
en la sentencia for. Una pareja de expresiones separadas por una coma se
eval ua de izquierda a derecha, y el tipo del resultado es el mismo que el de
operando derecho. Por tanto en una proposici on for es posible situar varias
expresiones en cada una de sus partes.
Iteraciones do-while
Las iteraciones while y for comparten la util caracterstica de comprobar
la condici on de terminaci on al comienzo en lugar de hacerlo al nal. El tercer
tipo de iteraci on en C, do-while, hace la comprobaci on al nal, despues de
la pasada sobre le cuerpo del ciclo. Y este se ejecuta al menos una vez. La
sintaxis es
do
proposici on
while ( expresi on);
Primero se ejecuta la proposicion y luego se eval ua la expresi on. En caso
de ser cierta se ejecuta de nuevo y as sucesivamente. La ejecucion termina
cuando la expresi on resulta falsa.
Break
A veces es necesario tener la posibilidad de salir de un ciclo por un lugar
distinto al de las comprobaciones del comienzo o del nal. La sentencia break
74 5 Introducci on al lenguaje de programaci on C (III)
proporciona una salida forzada de for, while y do, en la misma forma que
de switch. Una sentencia break obliga a una salida inmediata del ciclo (o
switch) mas interior.
Continue
La proposici on continue est a relacionada con break, pero se usa me-
nos. Obliga a ejecutar la siguiente iteracion del ciclo (for, while, do), que
la contiene. En while y do, signica que la parte de comprobaci on se eje-
cute inmediatamente; en for, el control pasa a la etapa de reinicializaci on
(continue se aplica solo a ciclos, no a switch).
Ejemplo 5.4:
for (i=0; i < N; i++){
if (a[i] < 0) /*salta los elementos negativos*/
continue;
.....; /* trata los positivos */
}
Notar que cada vez que se ejecuta la sentencia continue, el cuerpo del
for se abandona, inici andose la ejecuci on del mismo para un nuevo valor de
i.
Saltos (goto) y etiquetas
El lenguaje C contiene la innitamente seductora sentencia goto, junto
a las etiquetas a las que saltar. Formalmente goto no es necesario nunca y
siempre se puede evitar. Sin embargo en algunas (pocas) ocasiones, puede
resultar util. El uso m as normal consiste en abandonar el proceso en alguna
estructura profundamente anidada, tal como la salida de dos ciclos a la vez.
La proposici on break, no resulta efectiva en este caso, ya que solo abandona
el ciclo interno.
for(...)
for(...){
...
if (desastre)
goto error;
}
...
error: arregla el desatre
Ejemplos de uso
5.1 Sentencias de control de ujo: for, if , do, while, switch. Ejemplos de uso75
/******* Fichero salir.c *************/
#include <stdio.h>
main()
{
int c,f ;
int m[4][4]={2,4,3,1,2,-1,5,-1,-1,2,4,2,1,-1,-1,0};
for (f=0;f<=3;f++)
for(c=0; c<=3; c++)
if(m[f][c]==-1)
goto salir;
salir:
if(f<4 && c<4)
printf("(%d, %d)\n", f,c);
}
/***********Fichero salir2.c********/
#include <stdio.h>
main()
{
int c,f ;
int m[4][4]={2,4,3,1,2,-1,5,-1,-1,2,4,2,1,-1,-1,0};
for (f=0;f<=3;f++)
for(c=0; c<=3; c++)
if(m[f][c]==-1)
{
printf("(%d, %d)\n", f,c);
break;}
if(f<4 && c<4)
printf("(%d, %d)\n", f,c);
}
/**************Fichero salir 3.c ********/
#include <stdio.h>
main()
{
int c,f ;
int m[4][4]={2,4,3,1,2,-1,5,-1,-1,2,4,2,1,-1,-1,0};
for (f=0;f<=3;f++)
for(c=0; c<=3; c++)
if(m[f][c]==-1)
printf("(%d, %d)\n", f,c);
if(f<4 && c<4)
printf("(%d, %d)\n", f,c);
76 5 Introducci on al lenguaje de programaci on C (III)
}
Objetivo de los tres programas anteriores:
1. salir.c: lee la matriz por las y en cuanto encuentra un 1, nos da su
posicion y acaba.
2. salir2.c: lee la matriz por las. Cuando encuentra en una la el valor
1, nos da su posici on y salta de la.
3. salir3.c: nos indica la posici on de todos los 1 de la matriz.
5.2. Funciones: anatoma y uso
Las funciones dividen grandes trabajos de computaci on en partes m as
breves y aprovechan la labor realizada por otras personas en lugar de par-
tir de cero. Los programas escritos e C normalmente constan de una gran
cantidad de peque nas funciones en lugar de pocas y grandes. Un programa
puede residir en uno o m as cheros fuente en cualquier forma conveniente;
los archivos fuente pueden compilarse por separado y enlazarse junto con
funciones de biblioteca compiladas con antelaci on.
En cuanto a sintaxis, hay dos formatos para denir una funci on. La nueva
sintaxis conocida como forma prototipo viene a ser
/* Prototipo de definici on de una funci on*/
int func_def(int a, int b, int c);
{
.
.
que es equivalente en la forma tradicional; a
/* Forma tradicional de definici on de una funci on*/
int func_def( a, b, c)
int a,b,c;
{
.
.
Es decir en la nueva sintaxis, dentro del parentesis que recoge la lista
de argumentos, aparecen a la vez sus declaraciones, mientras que tradicio-
5.3 Convenciones sobre el paso de argumentos 77
nalmente, dichas declaraciones aparecan a continuaci on. Posteriormente, el
cuerpo de la funci on aparece entre llaves.
El nombre de la funci on deber a ir precedido de un tipo, si la funci on
devuelve un valor no entero. Si la funci on no devuelve ning un valor, se
especica el tipo void.
Un programa es entonces un conjunto de deniciones de funciones. La
comunicacion entre las funciones se efect ua (en este caso) a traves de ar-
gumentos y valores devueltos por la funci on. Tambien se pueden realizar
mediante variables externas.
Las funciones aparecen en cualquier orden en el chero fuente, y el chero
fuente puede estar dividido en varios cheros. Sin embargo, las funciones son
indivisibles.
La sentencia return es el mecanismo para devolver un valor desde la
funci on llamada a su llamador; return puede ir seguido de una expresi on.
return (expresion);
Tanto la expresi on, como los parentesis de esta son opcionales. Una fun-
cion puede contener cualquier n umero de sentencias return. Si no hay sen-
tencia return, el control del programa vuelve al programa llamador, cuando
se alcanza la llave que cierra el cuerpo de la funci on. En este caso el valor
devuelto es indenido.
float f()
{
float f2;
int a;
char c;
f2=a; /* OK, convierte a float */
return a; /* OK, convierte a float */
f2=c; /* OK, convierte a float */
return c; /* OK, convierte a float */
}
5.3. Convenciones sobre el paso de argumentos
Hemos visto en el apartado anterior, los aspectos relacionados con la
denici on de una funci on. A lo largo de un programa, se van utilizando
78 5 Introducci on al lenguaje de programaci on C (III)
funciones, mediante su llamada o alusi on.
Una alusion a una funci on es una declaraci on de una funci on que esta de-
nida en cualquier sitio, generalmente en otro chero distinto. El prop osito
general de una alusi on es decir al compilador que tipo de valor devuelve la
funci on, y declarar el n umero y tipo de argumentos que toma dicha funci on.
Se considera por defecto que todas las funciones devuelven un valor entero.
No es necesario declarar funciones que devuelven valores enteros, sin em-
bargo es una buena costumbre declarar todas las funciones que van a ser
llamadas.
En general hay dos tipos de alusiones: prototipo y no-prototipo. Una
alusi on no-prototipo a una funci on declara el tipo de retorno de la funci on,
pero no el n umero o tipo de argumentos que emplea:
extern old_function();
Las alusiones prototipo permiten a nadir el n umero y tipo de argumento
empleado:
extern int new_function(int j, float x);
Para declarar una funci on que no toma argumentos se emplea el tipo
void.
extern int f(void);
Tpicamente aparecen las declaraciones de las funciones en la cabecera
del programa, junto a las declaraciones de las variables y antes de la llamada
a la correspondiente funci on.
Una llamada a una funci on es la invocaci on a dicha funci on. En este
punto el programa pasa el control a la correspondiente funci on. La sintaxis
general es
nombre_funcion ( argumento1, argumento2,...,argumentok);
En las llamadas de una funci on debe haber el mismo n umero de argu-
mentos que en su declaracion. En cuanto al tipo, puede haber conversiones
autom aticas, con la consiguiente falta de precision. Si la conversi on no es
posible, el compilador manda mensajes de error. Por ejemplo:
5.3 Convenciones sobre el paso de argumentos 79
{
extern void f(int *);
float x;
f(x); /* ilegal, no se puede convertir un float a un
puntero*/
...
Sin embargo en:
{
extern void f(float, short);
double x;
long j;
f(j,x); /* se convierte un long a float y double a short */
...
A menos que en la declaracion de la funci on aparezca que el tipo de
retorno es void, las funciones siempre devuelven un valor de retorno, que es
sustitudo por la llamada a la funci on. Por ejemplo, si f() devuelve un 1, la
sentencia
a=f()/3;
es equivalente a
a=1/3;
En el lenguaje de programaci on C, la funci on llamada recibe una copia
temporal, privada, de cada argumento. Esto signica que la funci on no puede
alterar el valor del argumento original en la funci on que la llama. En una
funci on, cada argumento es una variable temporal inicializada con el valor
con el que se llam o la funci on.
Cuando aparece el nombre de un arreglo como argumento de una funci on,
se pasa la direcci on de comienzo del mismo; no se copian los elementos. La
funci on puede alterar el valor de elementos del arreglo subindexando a partir
de esta posicion. Ello hace que el arreglo se pase por referencia. M as adelante
veremos el uso de apuntadores para alterar el valor de argumentos que no
sean arreglos.
80 5 Introducci on al lenguaje de programaci on C (III)
5.4. Clases de almacenamiento
Hemos comentado ya alg un aspecto sobre el ambito denici on y validez
de las variables. Una variable puede ser limitada a un bloque, a un chero,
a una funci on o a una declaraci on de una funci on prototipo.
Hay cuatro tipos de ambito de validez de las variables en C: local o
bloque, global o chero, funci on y estructura.
Por defecto todas las variables llevan asociada una clase de almacena-
miento que determina su accesibilidad y existencia. La clase de almacena-
miento puede alterarse con los calicadores:
1. auto (almacenamiento automatico, de ambito local).
2. register (almacenamiento en un registro, de ambito local).
3. static (local, global o funci on).
4. extern (global o funci on).
Variables declaradas a nivel externo (fuera de toda denicion
de funci on)
Las funciones y variables externas que constituyen un programa en C
no tienen que ser compiladas todas a la vez. El texto fuente del programa
puede mantenerse en varios archivos, y puede enlazarse.
El ambito de validez de una variable externa abarca desde el punto de
su declaraci on en un archivo fuente hasta el n del archivo.
Se pueden utilizar los calicadores static o extern o ninguno. La declara-
cion extern es obligatoria si se ha de hacer referencia a una variable externa
antes de su denici on o si esta denida en un archivo fuente que no es aquel
en el que se usa.
Es importante distinguir entre la declaraci on de una variable externa y
su denici on. Una declaraci on da a conocer las propiedades de una variable
(su tipo, tama no, etc.); una denici on produce tambien una asignaci on de
memoria. Si las lneas:
int sp;
double val[MAXVAL];
5.4 Clases de almacenamiento 81
aparecen fuera de cualquier funci on definen las variables externas sp y val,
obligan a asignar memoria y sirven de declaracion al resto del archivo. Las
lneas:
extern int sp;
extern double val[];
declaran para el resto del archivo fuente que sp es un entero y val un arreglo
double (cuyo tama no se ja en otro lugar), pero no crean las variables ni les
asignan memoria. Solo debe de haber un denici on de una variable externa
entre todos los archivos que constituyen el programa fuente. Los otros archi-
vos deber an contener declaraciones extern para acceder a el. La declaracion
de una variable externa inicializa la variable a 0 por defecto, o a un valor
especicado. Si se utiliza el calicador static, solo se puede acceder a ella
desde el propio chero fuente.
Variables declaradas a nivel interno (dentro de un bloque)
Pueden ser:
1. no declarada o auto: en este caso solo es visible dentro del bloque. Hay
que inicializarlas explcitamente.
2. extern a nivel interno, hace accesible la variable a m odulos a los cuales
no lo es.
3. static: las variables estaticas constituyen la tercera clase de almacena-
miento adem as de las extern y las automaticas, ya tratadas. El cali-
cativo static se puede utilizar a nivel externo o interno. Las variables
internas estaticas son locales a una funcion en la misma forma que las
autom aticas, pero a diferencia de estas, su existencia es permanente,
en lugar de aparecer y desaparecer al activar la funci on. Se iniciali-
zan por defecto a 0. El almacenamiento estatico, interno o externo, se
especica al prejar la declaracion normal con la palabra static. La
variable es externa si se dene fuera de las funciones e interna si se
dene dentro de una funci on.
4. register es la cuarta y ultima clase de almacenamiento. Una decla-
racion register avisa al compilador de que la variable en cuestion
sera muy usada. Si es posible, la variable register ser a almacenada
en los registros de la maquina, lo que producir a programas cortos y
r apidos. Este tipo de almacenamiento es valido para los tipos int char
y punteros. La declaracion es del tipo:
82 5 Introducci on al lenguaje de programaci on C (III)
register int x;
register char c;
La parte int puede omitirse; register solo se aplica a variables automati-
cas y a los parametros formales de una funci on. La declaraci on sera:
f(c,n)
register int c,n;
{
register int i;
...
}
S olo unas pocas variables en cada funci on se pueden mantener en re-
gistros. La declaraci on se ignora si hay declaraciones excesivas o no
permitidas. Una variable register ha de ser inicializada explcitamen-
te.
Ejemplo de uso
main()
{
extern int var1;
static int var2;
register int var3=0;
int var4=0;
var1+=2;
printf("%d %d %d %d\n", var1, var2, var3, var4);
funcion_1();
}
int var1=5;
funcion_1()
{
int var1=15;
static var2=5;
var2+=5;
printf("%d %d \n", var1, var2);
}
Que valores se imprimen en el programa anterior?
Soluci on: 7 0 0 0
15 10
5.5 Recursi on 83
(Si se llamase otra vez a la funci on funcion 1, var2 entrara valiendo 10).
extern int var;
main()
{
var++;
printf("%d \n", var); /* escribe 6*/
funcion_1();
}
int var=5;
funcion_1()
{
var++;
printf("%d \n", var); /*escribe 7*/
funcion_2();
}
---------------------------
/* En otro fichero*/
extern int var;
funcion_2()
{
var++;
printf("%d \n"; var); /* escribe 8*/
}
5.5. Recursion
Una funci on recursiva es una funci on que se llama a s misma. Por ejem-
plo:
printd(int n) /* imprime n en decimal (recursiva) */
{
int i;
if ( n < 0 ){
putchar (-);
n=-n;
}
if(( i=n/10)!= 0)
printd(i);
putchar(n % 10+0);
}
84 5 Introducci on al lenguaje de programaci on C (III)
Cuando una funci on se llama a s misma recursivamente, cada invocaci on
crea una nueva copia de todas las variables autom aticas, que es independien-
te de la anterior. Por tanto, en printd(), la primera vez n=123. Pasa 12 a
la segunda printd. La segunda printd pasa 1 a la tercera. La tercera toma n
igual a 1, hace i = 0 y escribe 1. En la vuelta hacia arriba la segunda printd
escribe un 2 y la primera escribe un 3.
La recursividad generalmente no ahorra memoria pues ha de mantenerse
una pila con los valores que est an siendo procesados. Tampoco sera mas
r apida, pero el c odigo recursivo es mas compacto y a menudo mas senci-
llo de escribir y comprender. Esta especialmente pensado para estructuras
denidas recursivamente, como los arboles, que veremos mas adelante.
Nota: Trata de escribir la funci on anterior, sin emplear su recursividad.
5.6. Funciones denidas en stdio y math
En stdio.h se encuentran las funciones I/O. Dicho chero contiene las
declaraciones prototipo de todas las funciones I/O. Algunas de ellas son:
printf()
#include <stdio.h>
int printf (const char *formato[,argumento 1,...],...);
Salida con formato. Escribe los datos formateados en la cadena de salida
standard (stdout). El primer argumento es un vector car acter que puede con-
tener texto y expresiones de control de formato. Los siguientes argumentos
representan los datos a ser escritos.
Ejemplo de uso
#include <stdio.h>
main()
{
int a,b,c;
a=20;b=350;c=1991;
printf ("\n Los resultados son:\n");
printf ("a=%6d\t b=%6d\t c=%6d\n",a,b,c);
}
5.6 Funciones denidas en stdio y math 85
Resultado:
Los resultados son:
a = 20 b = 350 c = 1991
scanf()
#include <stdio.h>
int scanf (const char *formato[,argumento 1,..]...);
Entrada con formato. Lee datos de stdin en la forma especicada por
un vector de formato. La sintaxis y sem antica de scanf() es la inversa de la
de printf(), donde el argumento representa un puntero a la variable que se
quiere leer. Vimos esta funcion en el primer captulo dedicado a C.
Ejemplo de uso
#include <stdio.h>
main()
{
int a;float b; char c;
scanf ("%d %f %c", &a,&b, &c);
}
Resultado:
Lee de la pantalla, por ejemplo: 5 23.4 b y lo almacena en las variables
correspondientes: a, b, c.
getchar()
#include <stdio.h>
int getchar (void);
Entrada de caracteres. La funci on getchar() lee del chero estandar de
pantalla (stdin) un car acter y avanza una posici on. Cuando llega al nal del
chero lee el caracter EOF.
Ejemplo de uso
#include <stdio.h>
main()
{
86 5 Introducci on al lenguaje de programaci on C (III)
int car;
car=getchar();
}
Resultado:
Lee un car acter y lo almacena en la variable car.
putchar()
#include <stdio.h>
int putchar (int c);
Salida de caracteres. Escribe su argumento en la cadena de salida stan-
dard (stdout) y devuelve el car acter escrito. La expresi on putchar (c), es
equivalente a putc(c, stdout).
Ejemplo de uso
#include <stdio.h>
main()
{
int car=4;
putchar(car);
}
Resultado:
Escribe en pantalla el valor 4.
Funciones estandar de entrada/salida. Manipulando cheros
Permiten escribir y leer datos a, y desde cheros y dispositivos. La E/S,
en el procesamiento de cheros, se realiza a traves de un buer o memoria
intermedia. Esta tecnica, implementada en software, hace las operaciones de
entrada y salida m as ecientes. Dependiendo de los datos que se deseen leer
o escribir se utilizan las siguientes funciones:
1. Datos ledos o escritos caracter a caracter:
fgetc(), fputc()
2. Datos ledos o escritos palabra a palabra (palabra m aquina=valor
int(normalmente 2 bytes):
5.6 Funciones denidas en stdio y math 87
fgetw(), fputw()
3. Datos ledos o escritos como cadena de caracteres:
fgets(), fputs()
4. Datos ledos o escritos con formato:
fscanf(), fprintf()
5. Datos de longitud ja (estructuras o arrays) ledos como registros o
bloques:
fread(), fwrite()
88 5 Introducci on al lenguaje de programaci on C (III)
Abrir y cerrar cheros
fopen()
#include <stdio.h>
FILE *fopen (const char *nombrefichero, const char *modo de
acceso);
Abre el chero identicado por nombrechero y asocia una cadena con
dicho chero. El segundo argumento es un puntero a una cadena de carac-
teres que identica el tipo de acceso al chero.
fclose()
#include <stdio.h>
int fclose (FILE *cadena);
Cierra el chero asociado con la cadena especicada. Dicha funci on de-
vuelve un cero, si termina satisfactoriamente y un elemento nocero (EOF)
si sucede alg un error.
Salidas y entradas con formato
fprintf()
#include <stdio.h>
int fprintf (FILE *pf, const char *formato[,arg]...);
Escribe sus argumentos (arg) en el chero apuntado por pf, con el formato
especicado. La descripcion del formato es la misma que en printf.
fscanf()
#include <stdio.h>
int fscanf (FILE *pf, const char *formato[,arg]...);
Lee sus argumentos (arg) del chero apuntado por pf, con el formato
especicado. Cada formato debe ser un puntero a una variable en la que
queremos almacenar el valor ledo.
Ejemplo de escritura en un chero de nombre resul
5.6 Funciones denidas en stdio y math 89
/* Este fichero realiza el mismo calculo que primos.c, pero escribe*/
/* el resultado en un fichero*/
/* Calculo de numeros primos mediante la criba de Eratostenes*/
#include <stdio.h>
main()
{
FILE *presul;
presul = fopen ("resul", "w");
if(presul != NULL)
{
#define SIZE 1000
char flags[SIZE+1];
int i, k, primo, cuenta;
cuenta = 0;
for(i=0; i<=SIZE; i++) /* contador */
flags[i]=1; /* inicio de la tabla a 1 */
for(i=0; i<=SIZE; i++) {
if(flags[i]) { /*primo encontrado */
primo=i+i+3;
fprintf (presul, "%d\n" , primo);
for (k=i+primo; k<= SIZE; k+= primo)
flags[k]=0; /* quitar todos los multiplos */
cuenta++;
}
}
fprintf (presul, "Hay %d numeros primos\n ", cuenta);
}
}
Nota: Extrae el algoritmo del programa anterior.
Funciones denidas en math.h
En < math.h > se encuentran las funciones matematicas, divididas en
tres grupos: trigonometricas e hiperb olicas, exponenciales y logartmicas y
miscelaneas. Todas ellas operan con valores en double.
90 5 Introducci on al lenguaje de programaci on C (III)
Dentro de las trigonometricas, se encuentran: acos(), asin(), atan(),
cos(), cosh(), sin(), sinh(), tan(), tanh().
Dentro de las exponenciales y logartmicas: exp(), log(), log10(), sqrt()
entre otras.
Por ultimo dentro de las miscel aneas: fabs(), fmod(), son quiz as las mas
utilizadas.
Captulo 6
Algoritmos iterativos
6.1. Introducci on
Las races de la ecuacion no lineal f(x) = 0 no pueden obtenerse ge-
neralmente de forma exacta. Por ello, si queremos resolver ecuaciones no
lineales, nos veremos obligados a utilizar metodos aproximados. Estos meto-
dos se basan en ideas como la aproximaci on sucesiva o la linearizaci on. Tales
metodos son iterativos, esto es, comienzan en una aproximacion inicial a la
raz y producen una sucesi on x
0
, x
1
,..., x
n
,... que presumiblemente converge
a la raz deseada.
En determinados metodos, es suciente (en terminos de convergencia)
conocer un intervalo [a, b] en el que se encuentra la raz buscada. Otros
metodos requieren de una aproximaci on inicial que este cerca de la raz
deseada, ello hace que converjan mas rapidamente.
Por simplicidad, nos dedicaremos a estudiar el problema de determinar
una raz real simple, p, de la ecuaci on f(x) = 0; es decir, supondremos que
p R y f

(p) = 0.
Al nal del captulo, describiremos someramente algunos procedimientos
para la resoluci on de sistemas de ecuaciones no lineales. Veremos que aunque
los metodos utilizados para la resolucion de ecuaciones no lineales son gene-
ralizables a la resolucion de sistemas de ecuaciones, todos ellos presuponen
el conocimiento de una buena aproximaci on de la raz. Se sabe muy poco,
de como resolver el problema si no se dispone de informaci on a priori de la
localizaci on de las races.
Una forma de obtener un aproximaci on inicial a la raz de una ecuaci on
91
92 6 Algoritmos iterativos
Figura 6.1: y = senx
x
2
4
, x [

2
, 2]
1.6 1.7 1.8 1.9
-0.1
0.1
0.2
0.3
f(x) = 0, es mediante su representacion gr aca. Para ello, se pueden con-
siderar las abscisas de los puntos de interseccion de la gr aca de la funci on
y = f(x) con el eje X.
A veces es aconsejable sustituir la ecuacion dada por otra ecuaci on equi-
valente (es decir con las mismas races), (x) = (x), donde las funciones
y son mas sencillas que f. Se representan entonces las gracas de las
funciones y = (x) e y = (x), y las races buscadas seran entonces las
abscisas de los puntos de interseccion de dichas gr acas.
Ejemplo 6.1 Obtener una aproximaci on de la raz real positiva que tiene
la ecuacion:
x
2
4
senx = 0
Expresando la ecuacion de forma equivalente:
x
2
4
= senx. Si se repre-
sentan gr acamente las curvas y =
x
2
4
e y = senx, se observa que una raz
queda en el intervalo (

2
, 2), posiblemente cerca de x = 1,9.
Otra posibilidad es dar valores a la funci on f(x) y observar los cambios
de signo de los resultados obtenidos.
6.2 Resoluci on de ecuaciones 93
x
x
2
4
senx(calculado en radianes) f(x)
1.6 0.64 0.9996 0
1.8 0.81 0.974 0
2.0 1.0 0.909 0
De esta forma se puede concluir que p (1,8, 2,0).
En general, un resultado existente en este sentido es el siguiente:
Teorema del Valor Intermedio Si una funcion f(x) ([a, b] y k es
un n umero cualquiera entre f(a) y f(b), entonces existe c (a, b), tal que
f(c) = k.
Corolario Si una funcion f(x) ([a, b] y f(a) f(b) < 0, la ecuaci on
f(x) = 0 tiene al menos una raz en el intervalo (a, b). Es decir, existe al
menos un n umero p (a, b), tal que f(p) = 0.
La raz p sera unica si f

(x) existe y mantiene el signo en el intervalo


(a, b).
6.2. Resoluci on de ecuaciones
A lo largo de esta seccion mostraremos distintos metodos de resolucion
de la ecuaci on f(x) = 0. El problema consiste en encontrar los valores de la
variable x que satisfacen la ecuaci on f(x) = 0, para una funci on f dada.
El primer metodo basado en el Teorema del Valor Intermedio, se deno-
mina metodo de bisecci on, de b usqueda binaria o metodo de Bolzano.
6.2.1. Metodo de bisecci on
Sea f(x) una funci on continua en el intervalo (a
0
, b
0
) tal que f(a
0
)f(b
0
) <
0. Determinaremos entonces, una secuencia de intervalos (a
1
, b
1
) (a
2
, b
2
)
(a
3
, b
3
)..., tales que todos ellos contendran en su interior una raz de la ecua-
cion f(x) = 0. Supongamos en particular que f(a
0
) < 0 y f(b
0
) > 0 (esto
no supone ninguna limitaci on puesto que en otro caso se podra considerar
f(x) = 0). Los intervalos I
k
= (a
k
, b
k
), k = 1, 2, ... son determinados de
manera recursiva de la siguiente forma. El punto medio del intervalo I
k1
es:
m
k
=
1
2
(a
k1
+b
k1
)
94 6 Algoritmos iterativos
Podemos suponer que f(m
k
) = 0, puesto que en otro caso, tendramos
determinada la raz de la ecuaci on. Computamos f(m
k
) y tenemos:
(a
k
, b
k
) =

(m
k
, b
k
), si f(m
k
) < 0
(a
k
, m
k
), si f(m
k
) > 0
De la construcci on de (a
k
, b
k
) se sigue inmediatamente que f(a
k
) < 0
y f(b
k
) > 0, y adem as cada intervalo I
k
contiene una raz de la ecuaci on
f(x) = 0.
Despues de n pasos, tenemos la raz dentro del intervalo (a
n
, b
n
) de lon-
gitud:
b
n
a
n
=
1
2
(b
n1
a
n1
) =
1
2
2
(b
n2
a
n2
) = ... =
1
2
n
(b
0
a
0
)
Tenemos ademas que m
n+1
es un estimador de la raz que buscamos, y
p = m
n+1
d
n
, d
n
=
1
2
n+1
(b
0
a
0
)
6.2 Resoluci on de ecuaciones 95
Algoritmo
Tolerancia = TOL. N umero de iteraciones= NUMITE. Valores
iniciales a
0
, b
0
.
Paso 1: Sea i = 1
Paso 2: Mientras que i NUMITE, hacer:
Paso 3: m
i
= a
i1
+
1
2
(b
i1
a
i1
)
Paso 4: Si f(m
i
) = 0, o

b
i1
a
i1
2

< TOL, STOP. La raz


buscada es m
i
.
Paso 5: Si f(m
i
) < 0, a
i
= m
i
, b
i
= b
i1
.
Si f(m
i
) > 0, a
i
= a
i1
, b
i
= m
i
.
Hacer i = i + 1, e ir al Paso 3.
Paso 6: SALIDA. Despues de NUMITE iteraciones el proceso
ha terminado sin exito.
Ejemplo 6.2 El metodo de bisecci on aplicado a la ecuaci on
x
2
4
senx = 0
con I
0
= (1,5, 2), genera la secuencia de intervalos:
k a
k1
b
k1
m
k
f(m
k
)
1 1.5 2 1.75 0
2 1.75 2 1.875 0
3 1.875 2 1.9375 0
4 1.875 1.9375 1.90625 0
5 1.90625 1.9375
6.2.2. Metodo de Newton-Raphson
La idea b asica del metodo de Newton Raphson para la resoluci on de una
ecuaci on f(x) = 0, ha sido descrita en la introducci on del tema. A partir
de una aproximaci on inicial x
0
, se computa una secuencia x
1
, x
2
, ..., x
n
, ...
donde x
n+1
se determina como explicamos a continuacion:
Sea f(x) una funci on continuamente diferenciable dos veces en el inter-
valo [a, b], es decir f (
2
[a, b]. Sea x
0
una aproximaci on de la raz p, tal que
f

(x
0
) = 0 y [x
0
p[ peque no. Si consideramos el desarrollo de Taylor hasta
grado dos, alrededor de x
0
:
f(x) = f(x
0
) + (x x
0
)f

(x
0
) +
(x x
0
)
2
2
f

((x)),
96 6 Algoritmos iterativos
donde (x) esta entre x y x
0
. Como f(p) = 0:
0 = f(x
0
) + (p x
0
)f

(x
0
) +
(p x
0
)
2
2
f

((p)), (6.1)
El metodo de Newton Rapson se deriva suponiendo que el termino cua-
dr atico de la anterior expresi on es pr acticamente nulo y que:
0 f(x
0
) + (x x
0
)f

(x
0
), (6.2)
Es decir, la funci on f(x) es aproximada por su tangente en el punto
(x
n
, f(x
n
)), y x
n+1
es la abscisa del punto de intersecci on de la tangente
con el eje X. Luego para determinar x
n+1
tenemos que resolver la siguiente
ecuaci on:
f(x
n
) + (x
n+1
x
n
)f

(x
n
) = 0 (6.3)
El metodo de Newton-Raphson es denido por la siguiente f ormula ite-
rativa:
x
n+1
= x
n
+h
n
, h
n
=
f(x
n
)
f

(x
n
)
(6.4)
Algoritmo
Tolerancia = TOL. N umero de iteraciones= NUMITE. Valor
inicial x
0
.
Paso 1: Sea i = 1.
Paso 2: Mientras que i NUMITE, hacer:
Paso 3: x
i
= x
0

f(x
0
)
f

(x
0
)
Paso 4: Si [x
i
x
0
[ < TOL, STOP. La raz buscada es x
i
.
Paso 5: Hacer x
0
= x
i
, i = i + 1, e ir al Paso 3.
Paso 6: SALIDA. Despues de NUMITE iteraciones el proceso
ha terminado sin exito.
Ejemplo 6.3 Dada f(x) = senx
x
2
4
, f

(x) = cosx
x
2
. Queremos
determinar una raz positiva con cinco decimales correctos, i.e. TOL = 0,5
10
5
. Dado el Ejemplo 2, tomamos, x
0
= 1,5.
6.2 Resoluci on de ecuaciones 97
i x
i
f(x
i
) f

(x
i
) h(x
i
)
0 1.5 0.434995 -0.67926 0.64039
1 2.14039 -0.303197 -1.60948 -0.18838
2 1.95201 -0.024372 -1.34805 -0.01826
3 1.93393 -0.000233 -0.00018 0.00018
4 1.93375 0.000005 -1.32191 < 0,5 10
5
La raz buscada es entonces p = 1,93375. S olo han sido necesarias 4 ite-
raciones, incluso teniendo en cuenta que la aproximaci on inicial es bastante
mala. Vemos tambien que [h
i
[ decrece mas y mas rapidamente hasta que los
errores de redondeo empiezan a dominar. Sin embargo, cuando uno est a lejos
de la raz, se puede evitar trabajar con tantos decimales. En el ejemplo ante-
rior, es unicamente necesario trabajar con f(x
n
) con tantos decimales como
deberan ser correctos en x
n+1
. En este caso, la derivada ha sido calculada
con una innecesaria precisi on. Entonces no es necesario calcular f

(x
n
) con
tanta precisi on como f(x
n
). Como la precisi on de f(x
n
) es la que nos dice
como x
n
se aproxima a la raz, se podra utilizar f

(x
2
) en las iteraciones
i = 3 o 4.
6.2.3. Metodo de la secante
El metodo de Newton Raphson es una tecnica extremadamente poderosa,
pero tiene una dicultad grande: la necesidad de conocer el valor de la
derivada de f en cada aproximacion de la raz. Habitualmente la expresion
de f

(x) es todava mas complicada y necesita mas operaciones aritmeticas


para su c alculo que f(x).
El metodo de la secante trata de evitar este problema, aproximando el
valor de la derivada f

(x
n
) por el cociente incremental
f(xn)f(x
n1
)
xnx
n1
. Esto ge-
nera el siguiente metodo. Calcular, a partir de dos aproximaciones iniciales,
x
0
y x
1
, la secuencia x
2
, x
3
, ..., a partir de la f ormula iterativa:
x
n+1
= x
n
+h
n
, h
n
= f(x
n
)
x
n
x
n1
f(x
n
) f(x
n1
)
, f(x
n
) = f(x
n1
)
La interpretacion geometrica de este metodo es que x
n+1
se calcula como
la abscisa del punto de intersecci on entre la secante a traves de (x
n1
, f(x
n1
))
y (x
n
, f(x
n
)) y el eje de las X. Se nalar que el metodo de la secante a diferen-
cia del metodo de Newton Raphson requiere de dos aproximaciones iniciales,
pero solo se requiere de una evaluacion de la funci on en cada paso.
Los valores iniciales x
0
y x
1
, son los extremos del intervalo de referencia
en el que se encuentra la raz buscada, de manera que se cumple: f(x
0
)
98 6 Algoritmos iterativos
f(x
1
) < 0. Como en el metodo de bisecci on consideraremos, por simplicidad
que f(x
0
) < 0 y f(x
1
) > 0. En caso de suceder lo contrario resolveremos la
ecuaci on f(x) = 0.
Nota: La ecuaci on de la secante que pasa por (x
n1
, f(x
n1
)) y por
(x
n
, f(x
n
)) es:
x x
n1
x
n
x
n1
=
y f(x
n1
)
f(x
n
) f(x
n1
)
En el punto de intersecci on con el eje X, y = 0, de donde se deduce la
f ormula iterativa anterior.
Algoritmo
Tolerancia = TOL. N umero de iteraciones= NUMITE. Valores
iniciales x
0
y x
1
.
Paso 1: Sea i = 2.
Paso 2: Mientras que i NUMITE, hacer:
Paso 3: x
i
= x
i1
f(x
i1
)
(x
i1
x
i2
)
(f(x
i1
)f(x
i2
))
Paso 4: Si [x
i
x
i1
[ < TOL, STOP. La raz buscada es x
i
.
Paso 5: Hacer i = i + 1, e ir al Paso 3.
Paso 6: SALIDA. Despues de NUMITE iteraciones el proceso
ha terminado sin exito.
Ejemplo 6.4 Tomamos de nuevo la ecuacion f(x) = senx
x
2
4
. Quere-
mos determinar una raz positiva con cinco decimales correctos, i.e. TOL =
0,5 10
5
. Tomamos, x
0
= 1, x
1
= 2. f(1) = 0,5915 > 0, f(2) = 0,0907 <
0. Por lo tanto trabajaremos con g(x) = f(x) =
x
2
4
senx.
i x
i
g(x
i
) h(x
i
)
0 1 -0.59147
1 2 0.090703
2 1.86704 -0.084980 -0.13296
3 1.93135 -0.003177 0.06431
4 1.93384 0.000114 0.00249
5 1.93375 -0.000005 -0.00009
6 1.93375
Este ejemplo, proporciona la misma precision con el mismo n umero de
iteraciones en el metodo de Newton-Raphson que en el de la secante. Esto
6.2 Resoluci on de ecuaciones 99
puede ser debido a que una de las aproximaciones x
1
, estaba muy cerca de
la raz. Cuando [x
n
x
n1
[ es peque no, el cociente
(xnx
n1
)
f(xn)f(x
n1
)
sera deter-
minado en general con una pobre precisi on relativa. Si por ejemplo, elegimos
las aproximaciones iniciales x
0
, x
1
muy cerca de p, los errores de redondeo
pueden hacer que [x
n
p[ sea grande. El an alisis de errores que haremos
posteriormente nos dir a que el metodo de la secante proporciona en general
una secuencia tal que [x
n
x
n1
[ >> [x
n
p[.
Una an alisis mas minucioso muestra que la contribuci on dominante al
error relativo de h
n
proviene del error cometido en el c alculo de f(x
n
), una
pobre precisi on en el otro factor, resulta ser de menor importancia.
Se nalar, sin embargo, que la ecuaci on de recurrencia anterior puede ser
expresada de la forma:
x
n+1
=
x
n1
f(x
n
) x
n
f(x
n1
)
f(x
n
) f(x
n1
)
, f(x
n
) = f(x
n1
)
de manera que se puede obtener una cancelaci on cuando x
n
x
n1
y
f(x
n
)f(x
n1
) > 0.
El metodo de Newton-Raphson o el de la secante se utilizan frecuente-
mente para renar las respuestas obtenidas mediante otras tecnicas, como
el metodo de bisecci on. El motivo es que dan convergencia r apida, pero
necesitan una buena primera aproximaci on.
6.2.4. Regula Falsi
El metodo de regula falsi o de la falsa posici on entra dentro de los meto-
dos de interpolaci on, muy utiles a la hora de determinar los ceros de una
funci on real f(x) cualquiera. A diferencia del metodo de Newton-Raphson,
en los metodos de interpolaci on no se necesita calcular la derivada de f, y
ademas convergen mas rapidamente.
En este sentido, se puede considerar tambien como una variante del
metodo de la secante. En el metodo de regula falsi, se elige en cada iteracion
la secante entre (x
n
, f(x
n
)) y (x

n
, f(x

n
)), donde n

es el mayor ndice, n

< n,
para el cual f(x
n
)f(x

n
) < 0.
Las aproximaciones iniciales x
0
y x
1
, deben ser elegidas tambien, de
manera que f(x
0
) f(x
1
) < 0. La ventaja del metodo de regula falsi, al igual
que el de bisecci on, es que es siempre convergente para funciones continuas
f(x). En contraste con el metodo de la secante, sin embargo, el metodo
100 6 Algoritmos iterativos
de regula falsi es un metodo de primer orden (convergencia lineal). Esto lo
probaremos mas adelante.
El metodo de regula falsi es considerado un buen metodo siempre que no
se utilice en un entorno muy pr oximo de la raz. Puede ser utilizado como
parte de un metodo hbrido que tendra buenas propiedades cerca de la raz.
El planteamiento es similar al del metodo de bisecci on donde a partir
de un intervalo inicial [a
0
, b
0
] = [x
0
, x
1
], (f(x
0
) f(x
1
) < 0), se determina
en cada iteraci on un intervalo [a
n
, b
n
], de forma que f(a
n
) f(b
n
) < 0. El
intervalo [a
n
, b
n
] contiene entonces al menos un cero de f(x), y los valores
a
n
se determinan de manera que convergen hacia uno de estos ceros. En
general se sigue la siguiente formula iterativa:
x
n
= a
n
f(a
n
)
b
n
a
n
f(b
n
) f(a
n
)
= (6.5)
=
b
n
f(a
n
) a
n
f(b
n
)
f(a
n
) f(b
n
)
(6.6)
El hecho de que f(a
n
) f(b
n
) < 0, hace que f(a
n
) f(b
n
) = 0, luego x
n
esta bien denido. Adem as, satisface a
n
< x
n
< b
n
o b
n
< x
n
< a
n
. En este
caso, a menos que f(x
n
) = 0, se dene:
a
n+1
= x
n
, b
n+1
= b
n
, si f(x
n
) f(a
n
) > 0
a
n+1
= a
n
, b
n+1
= x
n
, si f(x
n
) f(a
n
) < 0
El algoritmo termina cuando f(x
n
) = 0.
Ejercicio 1
Intenta especicar el algoritmo para el metodo de regula falsi, y probarlo
con la ecuaci on dada en los Ejemplos 1,2,3 y 4 anteriores. Comenta los
resultados que obtienes.
6.3. Comparaci on de los distintos ordenes de con-
vergencia
6.3.1. Convergencia del metodo de bisecci on
El metodo de bisecci on, aunque conceptualmente claro, tiene inconve-
nientes importantes. Converge muy lentamente, (es decir NUMITE puede
6.3 Comparacion de los distintos ordenes de convergencia 101
ser muy grande antes de que [x
n
p[ sea sucientemente peque no) y, m as
a un, una buena aproximaci on intermedia puede ser desechada sin que nos
demos cuenta. Sin embargo, no debemos olvidar una propiedad importante
del metodo, y es que se trata de un metodo que converge siempre a una
soluci on.
Denicion 1 Diremos que la sucesi on
n

n=1
converge a con rapidez
de convergencia O(
n
), donde
n

n=1
es otra sucesi on con
n
= 0 para cada
n, si
[
n
[
[
n
[
K, para n sucientemente grande
donde K es una constante independiente de n. Esto se indica escribiendo

n
= +O(
n
), o bien
n
con una rapidez O(
n
).
Teorema 2 Sea f ([a, b], tal que f(a) f(b) < 0. El procedimiento de
bisecci on genera una sucesi on x
n
que aproxima a p con la propiedad
[x
n
p[
1
2
n
(b a), n 1. (6.7)
Demostracion: para cada n 1, tenemos
b
n
a
n
=
1
2
n1
(b a), y p (a, b)
Ya que x
n
=
1
2
(a
n
+b
n
), para todo n, se sigue que
[x
n
p[ = [
1
2
(a
n
+b
n
) p[ [
1
2
(a
n
+b
n
) a
n
[ =
1
2
(b
n
a
n
) = 2
n
(b a).
De acuerdo con la denici on de rapidez de convergencia, la desigualdad
(6.7) implica que x
n

n=1
converge a p y esta acotada por una sucesi on que
converge a 0 con una rapidez de convergencia O(2
n
). Es importante hacer
notar que teoremas como este, unicamente acotan superiormente los errores,
que en la pr actica pueden ser muy inferiores.
Ejemplo 6.5 Determinar aproximadamente el n umero de iteraciones
necesarias para resolver la ecuaci on f(x) = x
3
+ 4x
2
10 = 0 con una
precisi on de 10
5
, tomando como intervalo inicial [a, b] = [1, 2].
Se trata de encontrar n tal que:
[x
n
p[ = 10
5

1
2
n
(b a) = 2
n
(6.8)
102 6 Algoritmos iterativos
Tomando logaritmos (en base diez, pues la tolerancia esta dada en base 10),
tenemos que
log
10
(10
5
) = 5 log
10
(2
n
)
nlog
10
2 5 n
5
log
10
2
16,6
Seg un la expresi on anterior se requieren como mucho 16 iteraciones para
obtener una aproximaci on con precision 10
5
.
Otra caracterstica a se nalar de este metodo es que su velocidad de con-
vergencia es completamente independiente de la ecuacion a resolver.
6.3.2. Convergencia del metodo de Newton-Raphson
Denicion 2 Sea x
n

n=0
una sucesi on que converge a p y e
n
= x
n
p,
para cada n 0. Si existen dos n umeros positivos y tales que
lim
n
[x
n+1
p[
[x
n
p[

= lim
n
[e
n+1
[
[e
n
[

=
entonces se dice que x
n

n=0
converge a p con orden , con una constante
de error asintotico , o que la convergencia es de orden .
Si = 1, el metodo se denomina lineal o de primer orden. Si = 2, el
metodo se denomina cuadr atico o de segundo orden.
Teorema 3 Sea f (
2
[a, b]. Si f(a) f(b) < 0, y f

(x) y f

(x) son
no nulas y conservan el signo para a x b, entonces, a partir de una
aproximacion inicial x
0
[a, b] que satisface f(x
0
) f

(x
0
) > 0, es posible
utilizando el metodo de Newton (formula (6.2)), calcular una raz unica, p,
de la ecuaci on f(x) = 0 con cualquier grado de precisi on.
Por esta razon, al aplicar el metodo de Newton-Raphson, se debe aplicar
la siguiente regla: para el punto inicial x
0
eljase el extremo del intervalo
(a, b) asociado con una ordenada del mismo signo que el de f

(x
0
).
Teorema 4 Sea f ((, ), f(a) f(b) < 0, f

(x) = 0 para a x
b. Si f

(x) existe en cualquier punto y conserva el signo, entonces puede


tomarse cualquier valor c [a, b] como valor inicial x
0
al utilizar el metodo
de Newton para hallar una raz de la ecuaci on f(x) = 0, que caiga en el
intervalo [a, b]. Se puede tomar, por ejemplo, x
0
= a o x
0
= b.
6.3 Comparacion de los distintos ordenes de convergencia 103
Notese que en la f ormula iterativa (6.3) est a claro que cuanto mayor sea
el valor numerico de f

(x) en un entorno de la raz, tanto menor sera la


correccion que ha de a nadirse a la aproximaci on n-esima para obtener la
aproximacion (n+1). El metodo de Newton, es por lo tanto, muy conveniente
cuando la gr aca de la funci on tiene una gran pendiente en un entorno de la
raz dada, pero si el valor numerico de la derivada es peque no, las correciones
seran entonces mayores, y calcular la raz mediante este procedimiento puede
ser un proceso largo o a veces imposible. Es decir: no utilices el metodo de
Newton para resolver una ecuaci on f(x) = 0, si la curva y = f(x) es casi
horizontal en un entorno del punto de intersecci on con el eje X.
La obtenci on de la f ormula iterativa del metodo de Newton, a partir de la
serie de Taylor, resalta la importancia de una buena aproximaci on inicial. La
hip otesis necesaria para pasar de (6.1) a (6.2) es que el termino que contiene
a (p x
0
)
2
puede ser eliminado. Esto, no se cumple a menos que x
0
sea
una buena aproximaci on de p. El siguiente resultado ilustra la importancia
teorica de la eleccion de x
0
.
Teorema 5 Sea f ([a, b]. Si p [a, b] es tal que f(p) = 0, f

(p) = 0,
entonces existe un > 0, tal que, el metodo de Newton-Raphson genera
una sucesi on x
n

n=1
que converge a p, para cualquier aproximaci on inicial
x
0
[p , p +].
Bajo las anteriores condiciones, vamos a obtener una f ormula que rela-
cione los errores absolutos de dos aproximaciones sucesivas. Obtendremos
entonces una relacion entre e
n
= x
n
p y e
n+1
. Utilizando el desarrollo de
Taylor, en un entorno de la raz:
0 = f(p) = f(x
n
) +f

(x
n
)(p x
n
) +
1
2
f

(
n
)(p x
n
)
2
, (6.9)
donde
n
, esta entre p y x
n
. Dividiendo por f

(x
n
) y despejando p:
p = x
n

f(x
n
)
f

(x
n
)

1
2

f

(
n
)
f

(x
n
)
(p x
n
)
2
, (6.10)
y teniendo en cuenta la expresion (6.4), tenemos
p x
n+1
=
1
2

f

(
n
)
f

(x
n
)
(p x
n
)
2
, (6.11)
Entonces
e
n+1
e
2
n
=
1
2

f

(
n
)
f

(x
n
)
(6.12)
104 6 Algoritmos iterativos
y cuando x
n
p,
e
n+1
e
2
n

1
2

f

(p)
f

(p)
(6.13)
Como e
n+1
es proporcional al cuadrado de e
n
, y de acuerdo con la De-
nici on 2, se dice que el metodo de Newton-Raphson tiene convergencia
cuadr atica, o que es un metodo de segundo orden.
Un razonamiento intuitivo que demuestra el teorema anterior es el si-
guiente:
Supongamos que I es un intervalo en torno a p, tal que
1
2

[f

(y)[
[f

(x)[
m, x, y I (6.14)
Si x
n
I de (6.12), se tiene que [e
n+1
[ me
2
n
[me
n+1
[ (me
n
)
2
.
Supongamos que [me
0
[ < 1 y que el intervalo [p [e
0
[, p +[e
0
[] I. Por
inducci on podemos ver que x
n
I n y [e
n
[
1
m
(me
0
)
2
n
.
Por este motivo, puede probarse que el metodo de Newton-Raphson siem-
pre converge (a una raz simple) si x
0
ha sido elegido lo sucientemente cerca
de la raz p. (Si [me
0
[ = m[x
0
p[ < 1).
Sin embargo en la pr actica p es desconocido y la condici on anterior es
difcil de comprobar. El teorema que damos a continuaci on da un criterio
mas pr actico a la hora de demostrar la convergencia.
Teorema 6 Sea x
0
una aproximaci on inicial y denimos x
n
y h
n
tales
que x
n+1
= x
n
+ h
n
, h
n
=
f(xn)
f

(xn)
. Sea I
0
el intervalo (x
0
, x
0
+ 2h
0
) y
supongamos que
2[h
0
[M [f

(x
0
)[, M = max
x
0
I
0
[f

(x)[ (6.15)
Entonces, x
n
I
0
, n = 1, 2, ..., y lim
n
x
n
= p, donde p es la unica
raz de f(x) = 0 en I
0
.
Otro criterio que en ocasiones es mas f acil de aplicar, viene dado por el
siguiente resultado.
Teorema 7 Supongamos que f

(x) = 0 y f

(x) no cambia de signo en


6.3 Comparacion de los distintos ordenes de convergencia 105
el intervalo [a, b], y que f(a) f(b) < 0. Si

f(a)
f

(a)

< b a,

f(b)
f

(b)

< b a (6.16)
entonces el metodo de Newton-Raphson converge a partir de una aproxima-
ci on inicial x
0
[a, b].
6.3.3. Convergencia del metodo de la secante
Para probar la convergencia del proceso, consideraremos que la raz
esta separada y la segunda derivada f

(x) tiene signo constante en el in-


tervalo [a, b] = [x
0
, x
1
].
Supongamos que f

(x) > 0 para a x b. La curva y = f(x) en este


caso sera convexa hacia abajo y por lo tanto estar a localizada por debajo
de la secante que pasa por (a, f(a)), (b, f(b)). Si hemos supuesto en la des-
cripci on del metodo, f(a) < 0, el extremo b esta jo y las aproximaciones
sucesivas:
x
0
= a, x
n+1
= x
n
f(x
n
)
(b x
n
)
f(b) f(x
n
)
, n = 0, 1, 2... (6.17)
forman una secuencia mon otona creciente y acotada:
x
0
< x
1
< ... < x
n
< x
n+1
< ... < p < b (6.18)
Resumiendo:
1. El extremo jo es aquel para el cual el signo de la funci on f(x) coincide
con el signo de su segunda derivada f

(x).
2. Las aproximaciones sucesivas caen en el lado de la raz p, donde el
signo de la raz es opuesto al signo de su segunda derivada f

(x).
En cualquier caso, cada aproximaci on x
n+1
esta mas pr oxima a la raz
p, que la precedente x
n
.
Supongamos que p = lim
n
x
n
, a < p < b. Dicho lmite existe, puesto
que la sucesi on es monotona y est a acotada. Tomando lmites en la expresion
(6.17),
p = p f(p)
(b p)
f(b) f(p)
, (6.19)
106 6 Algoritmos iterativos
de donde f(p) = 0. Como p es la unica raz de f(x) = 0 en el intervalo (a, b),
se deduce que p = p.
Se trata de un metodo que no converge globalmente. Al igual que en el
caso del metodo de Newton, tenemos convergencia local.
El siguiente resultado nos permite obtener el orden de convergencia.
Teorema 8 (de interpolacion) Sea f ([a, b], p, x
n1
, x
n
, x
n+1

[a, b], tales que:
1. f

(x) = 0 x [a, b]
2. f(p) = 0
3. x
n+1
= x
n
f(x
n
)
xnx
n1
f(xn)f(x
n1
)
Entonces , (a, b) (, entre p, x
n1
, x
n
, x
n+1
), tal que:
(p x
n+1
) =
f

()
2f

()
(p x
n
)(p x
n1
). (6.20)
Tenemos entonces que, para n sucientemente grande, bajo las hip otesis
del teorema anterior:
[e
n+1
[ C[e
n
[[e
n1
[, C =
|f

()|
2|f

()|
Intentamos determinar el orden de convergencia, haciendo uso de la si-
guiente conjetura:
[e
n+1
[ K[e
n
[
a
, [e
n
[ K[e
n1
[
a
, a?
Sustituyendo en la ecuaci on anterior:
K[e
n
[
a
C[e
n
[K

1
a
[e
n
[
1
a
Esta relacion se mantiene solo si:
a = 1 +
1
a
a =
1
2
(1

5)
C = K
1+
1
a
= K
a
La raz a =
1
2
(1

5) = 0,618 puede ser ignorada, pues [e


n+1
[ K[e
n
[
a
y en dicho caso el error cometido en la iteraci on n+1 sera superior al error
en la iteraci on n (o lo que es lo mismo el metodo no sera convergente).
6.3 Comparacion de los distintos ordenes de convergencia 107
Por lo tanto [e
n+1
[ C
1
a
[e
n
[
a
, a =
1
2
(1 +

5) = 1,618.
6.3.4. Convergencia del metodo de regula falsi
Para probar la convergencia del metodo de regula falsi, supondremos por
simplicidad que f

(x) existe y que para alg un valor i,


a
i
< b
i
, f(a
i
) < 0, f(b
i
) > 0 (6.21)
f

(x) 0, x [a
i
, b
i
] (6.22)
Con estas hip otesis, o f(x
i+1
) = 0 o f(x
i+1
) f(a
i
) > 0, y entonces
a
i
< a
i+1
= x
i
< b
i+1
= b
i
.
Ademas es facil ver que las f ormulas (6.21), (6.22) son validas para todo
i i
0
si lo son para i
0
. Entonces, b
i
= b, para i i
0
y las a
i
forman una
sucesion mon otona creciente y acotada, por lo tanto es convergente, es decir
existe p = lim
n
a
i
. Por el hecho de ser f continua y por (6.21) y (6.22),
se tiene
f(b) > 0, f(p) 0 (6.23)
Ademas tomando lmites en (6.6)
p =
bf(p) pf(b)
f(p) f(b)
(6.24)
que implica (p b)f(p) = 0.
Pero p = b, y entonces f(p) = 0.
En el caso particular que estamos analizando, (6.22), se ve claramente
que las sucesivas iteraciones matienen jo el extremo b, de manera que,
utilizando esto en la misma relacion obtenida para el metodo de la secante:
lim
n
[e
n+1
[
[e
n
[
= C

(6.25)
puesto que:
p x
n+1
=
f

()
2f

()
(p x
n
)(p x
n1
) (6.26)
108 6 Algoritmos iterativos
Si x
n
= b;
[e
n+1
[
[e
n
[
[
f

()
2f

()
(p b)[ = C

(6.27)
Entonces = 1 y tenemos convergencia lineal. El metodo de regula falsi,
es en general un metodo de primer orden. Es convergente siempre que f sea
continua.
6.4. Teora general de los metodos iterativos. Ite-
raci on del punto jo
El metodo de Newton-Raphson y el de la secante pueden ser conside-
rados como casos especiales de un metodo iterativo m as general. Sea x
n+1
determinado por el valor de una funci on y de su derivada en m puntos
x
n
, x
n1
, ..., x
nm+1
y sea
x
n+1
= (x
n
, x
n1
, ..., x
nm+1
)
Llamaremos a funci on de iteracion.
Ejemplo 6.6 En el metodo de Newton-Raphson:
(x) = x
f(x)
f

(x)
, m = 1
En el de la secante:
(x) = x f(x)
x y
f(x) f(y)
, m = 2
La teora general de los procesos iterativos es mas simple cuando m = 1.
En este caso tenemos:
x
n+1
= (x
n
)
que se denomina metodo de iteracion uni-punto o del punto jo. Nos dedi-
caremos a continuaci on al estudio de este tipo de procesos.
Supongamos ahora que tenemos una sucesi on x
n
generada a partir
de cierto valor inicial x
0
, y que lim
n
x
n
= p. Si es continua, p =
6.4 Teora general de los metodos iterativos. Iteracion del punto jo 109
lim
n
x
n+1
= lim
n
(x
n
) = (p); es decir el valor p es una raz de la
ecuaci on x = (x).
Es decir, para construir un metodo iterativo para la resoluci on de f(x) =
0, podemos intentar escribir dicha expresi on de la forma x = (x), lo cual
dene un metodo de iteracion: x
n+1
= (x
n
).
Ejemplo 6.7 Sea la ecuaci on: x
3
x5 = 0 que puede ser escrita como
x =
1
(x) = x
3
5; x =
2
(x) =
3

x + 5; x =
3
(x) =
5
x
2
1
es decir el metodo iterativo puede no ser unico.
Tampoco podemos asegurar que todos ellos sean convergentes. Una con-
dici on suciente para la convergencia la da el siguiente resultado:
Teorema 9 Supongamos que la ecuaci on x = (x) tiene una raz p, y
que en el intervalo
J = x : [x p[

(x) existe y satisface la condici on


[

(x)[ m < 1
Entonces, para todo x
0
J:
1. x
n
J, n = 0, 1, 2, 3....
2. lim
n
x
n
= p.
3. p es la unica raz en J de x = (x).
Demostracion:
1. Por inducci on: sea x
0
J y supongamos que x
n1
J. Por el teorema
del valor medio,
x
n
p = (x
n1
) (p) =

()(x
n1
p), J
[x
n
p[ m[x
n1
p[ m < x
n
J
110 6 Algoritmos iterativos
2. Utilizando la desigualdad anterior repetidas veces:
[x
n
p[ m[x
n1
p[ ... m
n
[x
0
p[
Como m < 1, se tiene el resultado.
3. Supongamos nalmente que x = (x) tiene otra raz, , en J. = p.
Entonces:
p = (p) () =

()(p ), J
[p [ m[p [ < [p [,
lo cual es absurdo.
En el Teorema 9, hemos partido de suponer la existencia de una raz p.
El teorema puede ser modicado y utilizado para probar la existencia de
una raz de la ecuaci on x = (x).
Teorema 10 Sea J un intervalo cerrado en el cual

(x) existe y sa-


tisface la desigualdad [

(x)[ m < 1. Entonces la sucesi on x


n
denida
como x
n+1
= (x
n
), x
0
J satisface los apartados 1,2 y 3 del Teorema 9 si
x
1

m
1 m
[x
1
x
0
[ J
El metodo iterativo x
n+1
= (x
n
) es en general un metodo de primer
orden, a menos que (x) sea elegida de manera especial. Si (x) es veces
diferenciable y sus derivadas continuas (

, en un entorno de p, donde
p = (p), y
(j)
(p) = 0, j = 1, ..., 1,
()
(p) = 0. De acuerdo con el
teorema de Taylor tenemos:
x
n+1
= (x
n
) = p +
1
!

()
()(x
n
p)

, (x
n
, p)
Si lim
n
x
n
= p,
lim
n
[e
n+1
[
[e
n
[

=
1
!
[
()
(p)[ = 0,
n
= x
n
p (6.28)
Luego el metodo de iteracion es de orden para p.
6.5 Generalizaci on a sistemas de ecuaciones 111
Este argumento da una demostraci on alternativa del hecho de que el
metodo de Newton-Raphson sea al menos de segundo orden para races
simples, ya que:
(x) = x
f(x)
f

(x)

(x) =
f(x) f

(x)
(f

(x))
2
= 1
[(f

(x))
2
f(x) f

(x)]
(f

(x))
2
Si p es una raz simple, f

(p) = 0

(p) = 0
6.5. Generalizaci on a sistemas de ecuaciones
Algunos de los metodos que hemos visto para resolver ecuaciones no li-
neales simples, pueden ser generalizados a sistemas de ecuaciones no lineales.
Consideremos un sistema general de n ecuaciones no lineales con n
inc ognitas.
f
i
(x
1
, ..., x
n
) = 0, i = 1, ..., n
Un metodo de iteracion uni-punto para la resoluci on de este sistema
puede ser construdo escribiendo el sistema de la forma:
x
i
=
i
(x
1
, ..., x
n
), i = 1, ..., n
que sugiere el metodo iterativo:
x
(k+1)
i
=
i
(x
(k)
1
, ..., x
(k)
n
), i = 1, ..., n
La similitud formal al caso n = 1 es mas f acil de ver si utilizamos notacion
vectorial.
x = (x
1
, ..., x
n
)
t
, (x) = (
1
(x), ...,
n
(x))
t
El metodo iterativo puede entonces describirse como:
x
(k+1)
= (x
(k)
), k = 0, 1, ...
112 6 Algoritmos iterativos
Un criterio de convergencia similar al descrito en el Teorema 9 puede
utilizarse en este caso.
Supongamos que p = (p) y que las derivadas parciales
d
ij
(x) =

i
x
j
(x), 1 i, j n
existen para x R, R = x : [[x p[[ < . Sea D(x) una matriz nxn
cuyos elementos son d
ij
(x). En este caso una condici on suciente para que
el metodo de iteracion anterior converja para cada x
0
R es que para alguna
eleccion de la norma se cumpla
[[D(x)[[ m < 1, x R (6.29)
Nota: Normas de vectores:
L
p
: [[x[[
p
= ([x
1
[
p
+... +[x
n
[
p
)
1
p
, 1 p
p = 2 (norma eucldea) [[x[[
2
= ([x
1
[
2
+... +[x
n
[
2
)
1
2
p = [[x[[

= max
i=1,...,n
[x
i
[
Norma matricial: [[A[[

= max
i=1,..,n

n
j=1
[a
ij
[
Denicion 3 Se dice que es una funci on de contraccion cuando [[D(x)[[
m < 1, x R, puesto que
en este caso dicha condicion implica la desigualdad
[[(x) (y)[[ m[[x y[[, x, y R
Una condici on necesaria para que el proceso iterativo anterior converja
es que el radio espectral de D(p) sea menor o igual que 1. La velocidad de
convergencia depende linealmente de m y tenemos
[[x
(k+1)
p[[ = [[(x
(k)
) (p)[[ m[[x
(k)
p[[, k = 0, 1, ...
Nota: En algunas aplicaciones nos puede interesar resolver un
sistema de la forma
x = a +h(x)
6.5 Generalizaci on a sistemas de ecuaciones 113
donde a es un vector constante y h es un par ametro 0 < h << 1.
Si (x) tiene derivadas parciales acotadas, entonces el criterio
de convergencia anterior, (6.29), se satisface siempre para h su-
cientemente peque no y el metodo de iteracion es:
x
(k+1)
= a +h(x
(k)
), k = 0, 1, ...
6.5.1. Metodo de Newton-Raphson
Para n = 1 hemos deducido este metodo a partir de la f ormula de Taylor:
f(x) = f(x
k
) + (x x
k
)f

(x
k
) +O([x x
k
[
2
)
Si nos olvidamos del ultimo termino, obtenemos para f(x) = 0 el metodo
iterativo:
f(x
k
) + (x
k+1
x
k
)f

(x
k
) = 0, k = 0, 1, ...
Utilizando la f ormula de Taylor en dimensi on n:
f(x) = f(x
(k)
) +f

(x
(k)
)(x x
(k)
) +O([[x x
(k)
[[
2
)
donde f

(x) es una matriz nxn, el jacobiano de f, denotado en ocasiones


por J, cuyos elementos son
f

ij
(x) =
f
i
x
j
(x), 1 i, j n
De esta forma el metodo de Newton-Raphson en dimensi on n es:
f(x
(k)
) + (x
(k+1)
x
(k)
)f

(x
(k)
) = 0, k = 0, 1, ...
Este es un sistema de ecuaciones lineal para x
(k+1)
y si f

(x
(k)
) es no
singular puede ser resuelto como veremos mas adelante.
El metodo de Newton en dimensi on n es de segundo orden, es decir existe
una constante C = C(), tal que, x
(k)
, [[x
(k)
p[[ , y se tiene
[[x
(k+1)
p[[ C[[x
(k)
p[[
2
Desigualdad que se sigue directamente de:
x
(k+1)
= f(x
(k)
) = p +
1
2!
f
(2)
()[[x
(k)
p[[
2
, (x
(k)
, p)
114 6 Algoritmos iterativos
De aqui se puede demostrar su convergencia supuesto que x
(0)
este lo
sucientemente cerca de p.
Como hemos visto cada iteracion del metodo de Newton-Raphson re-
quiere la resoluci on de un sistema de ecuaciones lineales el cual, cuando n es
grande puede ser difcil de resolver. Ademas en cada paso hay que calcular
los n
2
elementos de f

(x
(k)
). Esto es imposible de hacer a menos que dichos
elementos tengan una forma funcional sencilla.
Existen metodos derivados de este, que proponen estimar f

(x), como el
metodo de la secante que lo aproxima por un cociente de diferencias. Una
aproximacion utilizada en le pr actica es:
f
i
x
j
(x)
ij
(x, h) =
f
i
(x +h
j
e
j
) f
i
(x)
h
j
donde e
j
= (0, ..., j, ..,0) y h es un par ametro n-dimensional con componentes
h
j
= 0, j = 1, ..., n.
6.5.2. Metodo de Gauss-Seidel
Otro metodo de resoluci on de un sistema no lineal f(x) = 0 consiste en
utilizar la i-esima ecuacion a resolver para obtener x
(k+1)
i
, i = 1, 2, ..., n. De
esta forma en cada iteracion se utiliza la ecuaci on no lineal de dimensi on
uno:
f
i
(x
(k+1)
1
, ..., x
(k+1)
i1
, x
i
, x
(k)
i+1
, ..., x
(k)
n
) = 0
para obtener x
(k+1)
i
.
Ejemplo de aplicacion Sea un sistema no lineal de dimension n = 3.
f
1
(x
1
, x
2
, x
3
) = 0
f
2
(x
1
, x
2
, x
3
) = 0
f
3
(x
1
, x
2
, x
3
) = 0
En k = 0:
i = 1: f
1
(x
1
, x
(0)
2
, x
(0)
3
) = 0 x
(1)
1
i = 2: f
2
(x
(1)
1
, x
2
, x
(0)
3
) = 0 x
(1)
2
i = 3: f
3
(x
(1)
1
, x
(1)
2
, x
3
) = 0 x
(1)
3
6.6 Ejercicios de aplicaci on 115
En k = 1:
i = 1: f
1
(x
1
, x
(1)
2
, x
(1)
3
) = 0 x
(2)
1
i = 2: f
2
(x
(2)
1
, x
2
, x
(1)
3
) = 0 x
(2)
2
i = 3: f
3
(x
(2)
1
, x
(2)
2
, x
3
) = 0 x
(2)
3

6.6. Ejercicios de aplicaci on
Como aplicacion de los metodos anteriores, puedes intentar resolver las
siguientes ecuaciones:
1. x = tg(x) en [1, 2].
2. x
3
x + 1 = 0 en [2, 2].
3. x senx 0,25 = 0 en [1, 2].
4. e
3
= xe
x
en [0, 1].
5. (1 +x)senx 1 = 0 en [0,5, 1].
6. senx x + 2 = 0 en [2, 3].
Si tienes muchas inquietudes, intenta resolver el sistema:
x = 1 +h
2
(e
y

x
+ 3x
2
)
y = 0,5 +h
2
tang(e
x
+y
2
)
x
0
= 1, y
0
= 0,5
Prueba con valores de h = 0,1 o 0.01.
116 6 Algoritmos iterativos
Captulo 7
Metodos de ordenaci on
7.1. Analisis te orico: orden de complejidad optimo
alcanzable para algoritmos internos
Denicion 1
Un orden parcial en un conjunto S es una relacion R, tal que para cada
a, b, c en S:
1. aRa es verdad (R es reexiva).
2. aRb y bRa implican a = b (antisimetrica).
3. aRb y bRc implican aRc (transitiva).
La relacion R = entre enteros y la relacion R = entre
conjuntos son dos ordenes parciales.
Un orden total o lineal en un conjunto S es un orden parcial en el que
ademas se cumple que para cada dos elementos a, b S o bien aRb o bRa.
La relacion sobre enteros es un orden total (lineal), sobre conjuntos no
lo es.
El problema de la ordenaci on puede ser planteado como sigue. Dada una
sucesion de n elementos a
1
, ..., a
n
elegidos de un conjunto con orden total,
que usualmente denotaremos por , vamos a encontrar una permutaci on
de esos n elementos que describira la secuencia dada en forma no decreciente
a
(1)
, ..., a
(n)
, tal que a
(i)
a
(i+1)
, para 1 i n 1. Usualmente
daremos la secuencia ordenada en lugar de la permutaci on de ordenaci on .
117
118 7 Metodos de ordenacion
1 [tnpos=r]a < b 2 [tnpos=r]b < c 4 [tnpos=r]Orden:a b c
5 [tnpos=r]a < c 8 [tnpos=r]Orden: a c b 9 [tnpos=r]Orden:
c a b 3 [tnpos=r]a < c 6 [tnpos=r]Orden: b a c
7 [tnpos=r]b < c 10 [tnpos=r]Orden: b c a 11 [tnpos=r]Orden: c b a
Figura (a)
Los metodos de ordenaci on son clasicados como internos, donde los
datos residen en la propia memoria del ordenador, o externos (los datos est an
principalmente fuera de la memoria, almacenados en mecanismos como cds
o cintas).
Otra clasicaci on posible surge atendiendo a la estructura de los ele-
mentos que van a ser ordenados. La diferencia est a en si los elementos son
n umeros enteros con un rango jo, o por el contrario son elementos de los
que no se conoce nada de su estructura en particular. Esta segunda clase de
algoritmos tiene como operacion b asica la comparacion entre distintos pares
de elementos. Con algoritmos de esta naturaleza veremos que se necesitan
al menos nlogn comparaciones para ordenar una secuencia de n elementos.
Arboles de decision
Vamos a considerar un algoritmo en el que en cada paso se realiza una
bifurcaci on (ramicaci on en dos ramas) depeniendo de una comparaci on
realizada entre dos elementos. La representacion usual para un algoritmo
de este tipo es un arbol binario denominado arbol de decisi on. Cada vertice
interior representa una decisi on. Es test que representa el nodo raz es el
primero en realizarse, y luego el control pasa a uno de sus hijos, dependiendo
de la respuesta. En general, el control contin ua pasando de cada vertice a
uno de sus hijos, la elecci on en cada caso depende del resultado del test,
hasta que se llega a un nal de rama (hoja). El resultado del algoritmo se
obtiene cuando se alcanza dicho nodo (nal de rama).
Ejemplo 1 Vamos a ilustrar mediante un arbol de decisi on el algoritmo
de ordenacion de tres elementos a, b y c en orden alfabetico. Los tests estan
indicados a la derecha del un crculo que respresenta el nodo correspondiente.
El control se mueve hacia la izquierda si la respuesta es si, y hacia la
derecha si la respuesta es no.
La complejidad computacional (en cuanto a tiempo), de un arbol de de-
7.1 Analisis te orico: orden de complejidad optimo alcanzable para algoritmos internos119
cision es la altura del arbol, como una funci on del tama no del problema.
Normalmente deseamos contar el maximo n umero de comparaciones nece-
sarias para encontrar el camino de la raz a un vertice de abandono (n umero
bifurcaciones). Es de destacar que el n umero total de vertices en el arbol
puede ser mucho mayor que su altura. En cualquier caso, podemos probar
un buen resultado acerca de la altura de un arbol de decision que ordena n
elementos.
Lema 1 Un arbol binario de altura h tiene a lo sumo 2
h
hojas.
Demostracion: Por inducci on sobre h. Se necesita simplemente obser-
var que un arbol binario de altura h esta compuesto por una raz y a lo
sumo dos sub arboles de altura a lo m as h 1.
Teorema 2 Cualquier arbol de decisi on que orden n elementos tiene de
altura al menos log
2
n!.
Demostracion: Como el resultado de la ordenaci on de n elementos
puede ser una de las n! permutaciones de los datos, debe haber al menos
n! hojas en el arbol de decisi on. Por el Lema 1, la altura debe ser al menos
log
2
n!.
Corolario 1 Cualquier algoritmo que ordene mediante comparaciones
requiere al menos cnlogn comparaciones para ordenar n elementos, para
c > 0 y n sucientemente grande.
Demostracion: Para n > 1:
n! n(n 1)(n 2)...

n
2
|

n
2
n
2
de manera que
logn!
n
2
log

n
2

n
4
logn para n 4
De la aproximacion de Stirling podemos obtener una aproximaci on m as
precisa de n! como

n
e

n
, de manera que n(log
2
n log
2
e) = nlog
2
n 1,44n,
resulta ser una buena aproximaci on inferior del n umero de comparaciones
necesarias para ordenar n elementos.
120 7 Metodos de ordenacion
7.2. Metodos simples de complejidad O(n
2
): com-
paraci on, inserci on, burbuja
7.2.1. Metodo de comparaci on (selecci on)
Es uno de los metodos de ordenaci on m as simples y trabaja de la si-
guiente forma:
1. Encontrar el elemento mas grande del vector.
2. Intercambiarlo con el elemento de la ultima posici on.
3. Encontrar el segundo mayor elemento.
4. Intercambiarlo con el elemento de la ante ultima posici on.
5. Continuar con dicho proceso hasta obtener el vector ordenado.
Tambien se denomina algoritmo de seleccion porque trabaja repetida-
mente seleccionando el actual mayor elemento.
Algorithm selectsort
for index := n to 2, 1, do
largest := index
for loop := index 1 to 1, 1, do
if k(loop) > k(largest) then largest := loop endif
enddo
//intercambio de posiciones de los dos elementos//
temp := k(largest)
k(largest) := k(index)
k(index) := temp
enddo
Para estimar el n umero de comparaciones es importante se nalar que el
algoritmo se reduce a dos ciclos que estan anidados y la comparaci on se
realiza una vez en cada paso del ciclo interior. El ciclo interior tiene j 1
pasos por cada paso j en el ciclo exterior y hay n 1 pasos en el ciclo
exterior, luego el n umero de comparaciones requerido por el algoritmo es:
(n 1) + (n 2) +... + (2) + (1) =
n(n 1)
2
7.2 Metodos simples de complejidad O(n
2
): comparaci on, inserci on, burbuja121
Este n umero es constante sea cual sea la ordenacion inicial de los ele-
mentos.
En el algoritmo, el n umero de veces que los elementos cambian sus po-
siciones esta dado por el n umero de veces S
n
, que se ejecuta la sentencia
largest := loop. Este n umero S
n
, depende de la ordenaci on inicial de los
elementos y puede describirse por sus valores mnimo, m aximo y medio.
Si el vector inicial esta ordenado, dicha sentencia no se ejecuta nunca y
S
n,min
= 0. Si el vector inicial esta ordenado en el orden decreciente, en
cada paso del ciclo exterior se intercambian dos elementos y la longitud del
vector desordenado es reducida en uno en cada paso. Esto nos da el maximo
valor para S
n,max
, que es:
(n 1) + (n 3) +... + 1 =

n
2
4
si n es par
(n
2
1)
4
si n es impar
Para determinar el n umero medio de elementos intercambiados hay que
se nalar que:
1. Si el algoritmo comienza con una permutaci on aleatoria de 1, 2, ..., n,
entonces el primer paso en el ciclo externo genera una permutacion
aleatoria de 1, ..., n1 seguida por n, ya que la permutaci on q
1
, ..., q
n1
, n
es producida por cada uno de los datos.
n q
2
q
3
q
n1
q
n
q
1
n q
3
q
n1
q
n

q
1
q
2
q
3
q
n1
n
2. La ocurrencia de cada permutaci on de 1, 2, ..., n1 en k(1), k(2), ..., k(n
1) es igualmente probable y el n umero medio de veces que se ejecuta
la sentencia largest := loop durante el primer paso del ciclo externo
viene dada por H
n
1 =
1
2
+
1
3
+... +
1
n
.
En general, el valor medio de S
n
, S
n
satisface la relacion de recurrencia
S
n
= H
n
1 +S
n1
, de manera que
S
n
=
n

j=2
H
j
(n 1) = (n + 1)H
n
2n
Nota: Hemos utilizado el resultado

n
j=1
H
j
= (n+1)H
n
n, H
1
= 1,
que puede ser probado por inducci on.
122 7 Metodos de ordenacion
En resumen, el n umero de comparaciones entre elementos es
n(n 1)
2
= O(n
2
)
y el n umero de intercambios es:
min = 0
media = (n + 1)H
n
2n = O(nlogn) (Notar que H
n
log
2
n, para n
grande)
max =

n
2
4
si n es par
(n
2
1)
4
si n es impar
= O(n
2
)
La complejidad total del algoritmo es entonces O(n
2
) y queda determi-
nada por el n umero de operaciones.
7.2.2. Metodo de inserci on
Los metodos de ordenaci on por inserci on est an basados en la siguiente
idea:
Sea un vector ordenado de n elementos distintos
k(1) < k(2) < ... < k(n)
Queremos insertar un elemento X en el vector anterior comparandolo
con los elementos del vector y situandolo en su posici on moviendo despues
los elementos posteriores un lugar a la derecha. Un algoritmo que realiza
esto se denomina algoritmo de insercion.
Si queremos aplicar un algoritmo de este tipo a la ordenacion de un
vector de n elementos, tendremos que ir considerando elemento a elemento
y situ andolo en su propio lugar en un vector ya ordenado.
Un posible algoritmo sera:
Algorithm insertsort
k(0) := minvalue
for i := 2 to n, do
pivot := k(i)
7.2 Metodos simples de complejidad O(n
2
): comparaci on, inserci on, burbuja123
j := i
while k(j 1) > pivot do
k(j) := k(j 1)
j := j 1
enddo
k(j) := pivot
enddo
El algoritmo anterior requiere
n(n1)
2
movimientos de elementos en el
peor caso. Esta alta complejidad es debida a que cada pasada del ciclo interno
produce unicamente intercambios entre elementos adyacentes. Esto produce
muchos movimientos redundantes de elementos, porque, si por ejemplo, el
elemento mas peque no esta situado en el extremo contrario del vector hacen
falta n pasos para moverle a la posicion que le corresponde.
Una modicaci on de este algoritmo es el que se denomina Shellsort. (Ver
por ejemplo p ag. 231 y ss. de [9]).
7.2.3. Metodo de intercambio (burbuja)
Otra familia de algoritmos de ordenaci on por comparaci on esta basada
en la idea de que cada comparaci on entre dos elementos debe ser seguida
sistematicamente por el intercambio de pares de elementos desordenados,
hasta que no aparezcan mas pares a ordenar.
El algoritmo trabaja de la siguiente manera: dada una secuencia de
elementos desordenados k(1), ..., k(n), generalmente no todos distintos; el
metodo de la burbuja los ordena intercambiando elementos adyacentes, si es
necesario, en sucesivas pasadas. Cuando no es necesario ning un intercambio
mas, la secuencia esta ordenada.
Algorithm bubblesort
repeat
pivot := 1
for k := 2 to n do
if A(k 1) > A(k) then
temp := A(k 1)
A(k 1) := A(k)
A(k) := temp
pivot := k 1
endif
enddo
124 7 Metodos de ordenacion
until pivot = 1
Ejemplo 2Vamos a ver como ordena la secuencia 3 4 2 1 7
3 4 2
3 2 4 1
3 2 1 4 7 pasada 1, pivot=4
3 2
2 3 1
2 1 3 4 7 pasada 2, pivot=3
2 1
1 2 3 4 7 pasada 3, pivot=1
Hay tres cantidades involucradas en el tiempo de desarrollo del metodo
de la burbuja: el n umero de comparaciones, el n umero de intercambios y
el n umero de pasadas a traves de los datos. Para estimar dichas cantida-
des podemos ver que el algoritmo se ha implementado utilizando dos ciclos
anidados. Un paso del ciclo exterior corresponde a una pasada a traves del
vector inicial, de manera que el n umero de pasadas corresponde al n umero
de pasos ejecutados en el ciclo exterior.
Si los elementos estan ordenados, la pasada inicial ser a tambien la ultima
y no har an falta intercambios de elementos. Entonces el valor mnimo para
el n umero de comparaciones, el n umero de intercambios y el n umero de
pasadas son n 1, 0 y 1 respectivamente.
Si los datos iniciales estan en orden inverso, entonces en la primera pasa-
da se colocar a el elemento mas grande en su propia posici on, y se utilizar an
n 1 comparaciones y n 1 intercambios. La segunda pasada colocar a el
segundo mayor elemento en su posicion y utilizara para ello n 2 compara-
ciones y n2 intercambios. Continuando con este argumento, concluiremos
que este metodo requiere de
n(n1)
2
comparaciones e intercambios y n pasa-
das.
El an alisis de valores del n umero medio de comparaciones, pasadas e
intercambios no es facil de realizar. El motivo es que dicho c alculo requiere
de una interesante herramienta matem atica como son las tablas de inversion.
(Ver por ejemplo p ag. 220 y 223 de [9]). En cualquier caso podemos concluir
que la complejidad del metodo de la burbuja en media no es mejor que
O(n
2
).
7.3 Metodos de complejidad O(nlogn): Quicksort y Heapsort 125
23 [tnpos=r]A(1) 19 [tnpos=r]A(2) 11 [tnpos=r]A(4) 1 [tnpos=r]A(8) 2 [tnpos=r]A(9)
7 [tnpos=r]A(5) 4 [tnpos=r]A(10) 6 [tnpos=r]A(11)
10 [tnpos=r]A(3) 5 [tnpos=r]A(6)
3 [tnpos=r]A(7)
Figura (b)
7.3. Metodos de complejidad O(nlogn): Quicksort
y Heapsort
7.3.1. Heapsort
Es un elegante metodo de ordenaci on que utiliza la idea de una repeti-
da seleccion de elementos. Fue inventado por Williams y Floyd (1964). El
proceso consiste en dos etapas: los primeros n pasos construyen un vector
con estructura de mont on(o pila) y los siguientes n extraen los elementos
en orden decreciente y construyen la secuencia ordenada nal, situada de
derecha a izquierda en un vector. En la etapa de creaci on del mont on, la
informaci on relativa al orden de los elementos que es obtenido despues de
cada comparaci on es implementada en forma de estructura de datos amon-
tonados. Veamos esto con el siguiente ejemplo.
Consideremos un vector A(1 : n). Asociamos a este vector una estructura
de arbol binario. Un arbol binario puede representarse gr acamente de dife-
rentes formas. Normalmente se representa como si creciese hacia abajo, y el
nodo m as alto, A(1), se denomina raz. Una forma usual de numerar todos
los nodos es de izquierda a derecha en cada nivel, de manera que si A(k) es
el nodo raz de un sub arbol, A(2k) y A(2k+1) son los dos nodos inmediatos
colocados detr as de el, que son llamados nodos hijos. Inversamente A(k) es
el nodo padre de A(2k) y A(2k + 1).
Denimos la raz de un arbol como el nodo que est a en el nivel 1. Si A(k)
esta en el nivel i, A(2k) y A(2k +1) estan en el nivel i +1. Un nodo que no
tiene hijos se denomina nodo terminal (hoja), y si no es un nodo terminal, es
un nodo interno o de bifurcaci on. El n umero de nodos que hay que recorrer
desde la raz hasta el nodo A(j), se denomina longitud del camino hasta
A(j). El nodo raz es un nodo de longitud 1, sus hijos est an a una longitud
2 y as sucesivamente. En general un nodo de nivel i tiene un camino de
longitud i hasta la raz.
126 7 Metodos de ordenacion
Denicion 2
Una pila o monton es un arbol binario con nodos A(1) a A(n) inclusive,
el cual tiene todos sus nodos terminales al mismo nivel, o en dos niveles
adyacentes a lo sumo; y tiene la propiedad de que el valor de un nudo padre
es mayor o igual que los valores de los hijos.
A(k) max(A(2k), A(2k + 1)), para 1 k
n
2
|, n 2
La secuencia de elementos en el camino de la raz a un nodo terminal
esta linealmente ordenada. Por ejemplo: 23 > 19 > 11 > 1. El elemento mas
grande en un sub arbol es siempre la raz de ese arbol. El algoritmo heapsort
funciona de la siguiente forma: dado un vector desordenado de tama no n:
A(1 : n), el algoritmo en una primera parte construye la pila o mont on y
luego sistematicamente selecciona la raz actual y situa dicho elemento en
la ultima posici on del vector A.
Algorithm createheap A(1 : n)
for r :=
n
2
to 1, 1 do
k := r
10 if A(k) < max(A(2k), A(2k + 1)) then
index :=ndice del mayor elemento de A(2k), A(2k + 1) (si solo
hay un hijo, el ndice del mayor elemento es el de ese hijo)
temp := A(k)
A(k) := A(index)
A(index) := temp
if(index
n
2
) (index no es una hoja)
k := index
goto 10
endif
endif
enddo
Ejemplo 3
7.3 Metodos de complejidad O(nlogn): Quicksort y Heapsort 127
6 [tnpos=r] 19 [tnpos=r] 11 [tnpos=r] 1 [tnpos=r] 2 [tnpos=r]
7 [tnpos=r] 4 [tnpos=r] 23 [tnpos=r]
10 [tnpos=r] 5 [tnpos=r] 3 [tnpos=r] 19 [tnpos=r] 11 [tnpos=r] 6 [tnpos=r]
1 [tnpos=r] 2 [tnpos=r]
7 [tnpos=r] 4 [tnpos=r] 23 [tnpos=r]
10 [tnpos=r] 5 [tnpos=r] 3 [tnpos=r]
A(1) A(2) A(3) A(4) A(5) A(6) A(7) A(8) A(9) A(10) A(11)
11 2 3 1 4 5 10 23 19 7 6
k = 5 11 2 3 1 (7) 5 10 23 19 (4 6)
k = 4 11 2 3 (23) 7 5 10 (1 19) 4 6
k = 3 11 2 (10) 23 7 (5 3) 1 19 4 6
k = 2 11 (23) 10 (2 7) 5 3 1 19 4 6
11 23 10 (19) 7 5 3 (1 2) 4 6
k = 1 (23) (11 10) (19) 7 5 3 1 2 4 6
23 (19) 10 (11 7) 5 3 1 2 4 6
23 19 10 (11) 7 5 3 (1 2) 4 6
La ultima la de la tabla anterior, es la que estaba representada en el
arbol binario de la Figura (b).
En la segunda etapa, Heapsort extrae el elemento mas grande de la
raz y lo almacena en A(n), intercambiando los elementos A(1) y A(n).
Ademas la posicion n no es considerada posteriormente parte del mont on.
Para reordenar los elementos en las posiciones 1, ..., n 1 en un mont on, el
elemento de A(1) es hundido tan abajo en el arbol como es necesario. El
proceso de intercambiar A(1) y A(n 1) se repite y ademas el arbol ocupa
las posiciones de los elementos 1, ..., n 2.
Pasamos de:
6 19 10 11 7 5 3 1 2 4 23
a:
19 11 10 6 7 5 3 1 2 4 23
Algorithm updateheap A(1 : s)
(A(1 : s) es el nuevo vector amontonado, s > 1)
Intercambiamos el elemento mas grande que esta en A(1) con A(s)
pivot := A(1)
128 7 Metodos de ordenacion
A(1) := A(s)
A(s) := pivot
k := 1
while 2k < (s 1) do
index := ndice mas grande de A(2k), A(2k + 1)
if A(k) < A(index) then
//intercambiar A(k) con A(index)//
pivot := A(index)
A(index) := A(k)
A(k) := pivot
k := index
else goto n
endif
enddo
n. Return (A)
El algoritmo Heapsort completo se implementa como sigue:
Algoritmo Heapsort
createheap A(1:n)
for s := n to 2, 1 do
updateheap A(1 : s)
enndo
N umero de comparaciones necesario en Heapsort
El primer algoritmo createheap consta de dos ciclos que estan anidados.
El ciclo exterior tiene
n
2
pasos, mientras que el interior no se realiza mas
veces que la altura, h, que tiene el mont on considerado. Adem as en cada
paso del ciclo interior se realizan al menos dos comparaciones. El n umero de
comparaciones necesarias es O(
n
2
h) = O(nh).
El algoritmo updateheap contiene unicamente un ciclo. Se realiza una
comparaci on por cada paso del ciclo y el ciclo se realiza al menos el n umero
de veces que corresponde a la altura del mont on. Es decir el n umero de
7.4 Quicksort 129
comparaciones es O(h), donde h representa la altura del mont on. Si el arbol
tiene n elementos y en cada nodo se produce una bifurcaci on, h log
2
n.
El n umero total de comparaciones requeridas por el algoritmo completo
heapsort es:
O(nh) + (n 1)O(h) = O(nlog
2
n) +nO(log
2
n) = O(nlogn)
El n umero de intercambios de elementos requerido por heapsort para
cualquier vector inicial, es tambien O(nlogn).
7.4. Quicksort
Supongamos un vector de elementos desordenado K(1), ..., K(n). Quick-
sort trabaja particionando el vector en dos partes y ordenando cada una de
las partes independientemente. La posicion ex acta de la partici on depende
de los datos. En la etapa en la que se realiza la partici on, el vector se ordena
de manera que se mantienen las siguientes condiciones:
1. El elemento que particiona K(k), esta en su propia posici on.
2. Todos los elementos K(1), ..., K(k1) son menores o iguales que K(k).
3. Todos los elementos K(k+1), ..., K(n) son mayores o iguales que K(k).
Esto puede ser implementado f acilmente utilizando la siguiente estrate-
gia. Declarar dos punteros i, j con i = 1, j = n inicialmente. Comparar
K(i) con K(j), K(j 1), ..., K(s) hasta enontrar un elemento K(s) menor
que K(i). Intercambiar ambos elementos, incrementar i por 1 y continuar
la comparaci on despues de incrementar el valor de i y hasta que se produz-
ca otro intercambio. Despues del intercambio disminuir j en 1 y continuar
la comparaci on hasta que se produzca otro intercambio. Despues de dicho
intercambio incrementar i y as sucesivamente hasta que i = j.
Para un mejor desarrollo del algoritmo, se produce un intercambio in-
cluso cuando para i = j, K(i) y K(j) son elementos con el mismo valor.
Cuando los punteros i, j alcanzan la misma posicion, dicha posici on deter-
mina la partici on del vector en dos subvectores. Cada uno de los dos vectores
es ordenado independientemente. Esto signica que despues de cada parti-
cion del vector, una de las partes necesita ser almacenada temporalmente
mientras Quicksort ordena la otra parte. Esto requiere de alguna memoria
auxiliar por parte del algoritmo.
130 7 Metodos de ordenacion
Una descripci on completa del algoritmo sera la siguiente:
Algorithm Quicksort (r, s)
i := r
j := s + 1
pivot := K(i)
if s > 1 then
repeat
repeat j := j 1 until K(j) pivot
repeat i := i + 1 until K(i) pivot
if i < j
temp := K(i)
K(i) := K(j)
K(j) := temp
endif
until j i
K(r) := K(j)
K(j) := pivot (el elemento que determina la particion esta en la posicion j)
endif
if r < s then
quicksort(r, j 1)
quicksort(j + 1, s)
endif
Tiempo medio requerido por Quicksort
Antes de calcular el tiempo medio de ejecucion de un algoritmo, vamos
a determinar c ual es la distribuci on de probabilidad de los datos. Para al-
goritmos de ordenaci on, una hip otesis natural, y que nosotros haremos, es
que cada permutaci on de la secuencia a ordenar es igualmente probable a
la hora de aparecer como datos. Bajo esta hip otesis, f acilmente se puede
acotar inferiormente el n umero de comparaciones necesarias para ordenar
una secuencia de n elementos.
7.4 Quicksort 131
El metodo general es asociar con cada hoja del arbol de decisi on , la
probabilidad de alcanzar dicha hoja, para unos datos dados. Si conocemos
la distribuci on de probabilidad de los datos, se pueden determinar las pro-
babilidades de cada una de las hojas. Entonces, se puede calcular el n umero
esperado de comparaciones realizado por un algoritmo de ordenacion parti-
cular, evaluando sobre todas las hojas del arbol de decision, la suma

i
p
i
d
i
,
donde p
i
es la probabilidad de alcanzar la hoja i y d
i
su altura. Esta suma
es la altura esperada de un arbol de decisi on.
Teorema 3 Bajo la hip otesis de que todas las permutaciones de la se-
cuencia de n elementos son igualmente probables, cualquier arbol de decisi on
que ordene n elementos tiene una altura esperada de al menos logn!.
Demostracion: Sea D(T) la suma de las alturas de las hojas de un
arbol binario T. Sea D(m) el valor mas peque no de D(T), elegido de entre
todos los arboles T con m hojas. Demostraremos por induccion sobre m,
que D(m) mlogm. Para m = 1 el resultado es trivial. Supongamos que
es cierto para valores de m menores que k y consideremos un arbol T con
k hojas. T esta formado por una raz y por un arbol a la izquierda T
i
con i
hojas y un arbol a la derecha T
ki
con k i hojas (la raz de T no pertenece
a ninguno de los dos sub arboles T
i
ni T
ki
), para alg un i, 1 i k.
Claramente
D(T) = i +D(T
i
) + (k i) +D(T
ki
)
Ademas, la suma mnima esta dada por
D(k) = min
1ik
k +D(i) +D(k i)
De la hip otesis inductiva, tenemos:
D(k) k +min
1i<k
[ilogi + (k i)log(k i)] = k +min
1i<k
f(i)
Derivando f

(i) = 0, se deduce que i =


k
2
con f

(
k
2
) > 0. El mnimo se
alcanza en i =
k
2
; luego:
D(k) k +klog
k
2
= k +klogk klog2 = klogk
Conclumos entonces que D(m) mlogm, m 1.
Recordemos del Teorema 2, que un arbol de decisi on T que ordene n
elementos cualesquiera tiene al menos n! hojas. M as a un, tiene ex actamente
132 7 Metodos de ordenacion
n! hojas con probabilidad
1
n!
cada una, y las restantes con probabilidad
0. Podemos eliminar de T todos los nodos antecesores solo de hojas con
probabilidad 0, sin cambiar la altura esperada de T. Tenemos entonces un
arbol T

con n! hojas, cada una de probabilidad


1
n!
. Como D(T

) n!logn!,
la altura esperada de T

y por lo tanto de T es al menos


1
n!
n!logn! = logn!.

Corolario 2 Cada algoritmo de ordenacion mediante comparaciones rea-


liza al menos cnlogn comparaciones en media, para alguna constante c > 0.
Veremos ahora que el algoritmo Quicksort tiene un tiempo medio de eje-
cucion acotado inferiormente por cnlogn para alguna constante c. El hecho
signicativo es que el tiempo de ejecuci on para el peor caso en dicho algo-
ritmo es cuadr atico. Sin embargo no es un hecho importante en la mayora
de los casos.
Teorema 4 El algoritmo Quicksort ordena una secuencia de n elementos
en un tiempo esperado de O(nlogn).
Demostracion: Sea una secuencia de n elementos a
1
, ..., a
n
. Por simpli-
cidad supongamos que todos los elementos en S son distintos. Esta hip otesis
maximizara los tama nos de los vectores resultantes de su particion.
Sea T(n) el tiempo medio necesario por Quicksort para ordenar una se-
cuencia de n elementos. Claramente T(0) = T(1) = b para alguna constante
b.
Supongamos que despues de la primera particion hemos creado dos vec-
tores en S. Denotamos por T(s) y T(n s), respectivamente, los tiempos
medios requeridos por Quicksort para ordenar cada uno de los dos vectores.
Como s es iguamente probable al resto de valores entre 1 y n, y el tiempo
necesario para obtener la primera particion es cn, donde c es una constante,
1 < c < 2 (se sigue del hecho de que se necesitan n comparaciones y n
intercambios), tenemos:
T(0) = T(1) = b
T(n) =
1
n
n

s=1
(T(s) +T(n s)) +cn, n 2
=
1
n

s=1
T(s) +
n1

j=0
T(j) +cn
=
1
n

n1

s=1
T(s) +
n1

j=0
T(j) +T(n) +cn
7.4 Quicksort 133
=
2
n
n1

s=1
T(s) +
1
n
T(n) +cn
o escrito de otro modo:
(n 1)T(n) = 2
n1

s=1
T(s) +cn
2
, 1 < c < 2
Expresion que nos da una relaci on de recurrencia. Si la escribimos para
n + 1.
nT(n + 1) = 2
n

s=1
T(s) +c(n + 1)
2
, 1 < c < 2
(n 1)T(n) = 2
n1

s=1
T(s) +cn
2
restando la segunda de la primera, se obtiene:
nT(n + 1) (n 1)T(n) = 2T(n) +c(2n + 1)
o lo que es lo mismo:
nT(n + 1) = (n + 1)T(n) +c(2n + 1)
T(n + 1)
n + 1
=
T(n)
n
+c
(2n + 1)
n(n + 1)
que escrita recursivamente:
T(n + 1)
n + 1
=
T(n)
n
+c
(2n + 1)
n(n + 1)
=
T(n)
n
+c(
1
(n + 1)
+
1
n
)
T(n)
n
=
T(n 1)
n 1
+c(
1
n
+
1
n 1
)

T(2)
2
=
T(1)
1
+c(
1
2
+ 1)
De donde:
T(n + 1)
n + 1
=
b
1
+c

1
n + 1
+
1
n
+... +
1
2

1
n
+
1
n 1
+... + 1

=
= b +c

(H
n+1
1) + (H
n+1

1
n + 1
)

=
= b + 2cH
n+1
c
n + 2
n + 1
134 7 Metodos de ordenacion
Si y s olo si:
T(n + 1) = (n + 1)b + 2c(n + 1)H
n+1
c(n + 2)
T(n) = nb + 2cnH
n
c(n + 1)
Donde:
H
n
log
e
n =

n=1
1
n + 1
de manera que:
T(n) nb c(n + 1) + 2cnlog
e
n = O(nlogn)
Notar que:
k = log
e
n e
k
= n
l = log
2
n 2
l
= n
2 < e, luego n = 2
l
< e
l
, de donde k < l y por tanto log
e
n < log
2
n.
Captulo 8
El lenguaje de programacion C (IV)
Los apuntadores o punteros son variables que contienen la direcci on de
otra variable. Son frecuentemente utilizados en C, puesto que son, en ocasio-
nes, la unica forma de expresar algunos c alculos, mediante codigo compacto
y eciente.
Los apuntadores se han comparado con la proposici on goto, como una
manera de crear programas imposibles de entender. Esto es completamente
cierto cuando se emplean sin cuidado. Sin embargo, con disciplina se pueden
emplear para conseguir claridad y simplicidad. Este es el aspecto que vamos
a intentar desarrollar en este tema.
8.1. Direccionamiento indirecto. Punteros: varia-
bles que contienen la ubicaci on en memoria
de otras variables
Puesto que un puntero contiene la direcci on de un objeto, se puede acce-
der al objeto indirectamente a traves de el. Sea x una variable de tipo int,
y px un puntero. El operador unitario & devuelve la direcci on de un objeto,
por lo que la proposici on
px = &x;
asigna la direcci on de x a la variable px; ahora se dice que px apunta a x.
El operador & s olo se puede aplicar a variables y elementos de un arreglo
(array). Construcciones como &(x + 1) o &3 son ilegales. Tambien es ilegal
obtener la direcci on de una variable register.
135
136 8 El lenguaje de programaci on C (IV)
El operador unitario toma su operando como una direcci on para obte-
ner su contenido. Si y tambien es de tipo int,
y=*px;
asigna a y el contenido de cualquier parte a la que apunte px. La secuencia
px = &x;
y = *px;
asigna a y el mismo valor de x.
Tambien es necesario declarar las variables que intervienen, la forma de
hacerlo es:
int x,y;
int *px;
Esta ultima declaraci on se entiende como un mnemotecnico. Se quiere
indicar que la combinaci on px es de tipo int, es decir si px aparece en
el contexto px, ello equivale a una variable int. La sintaxis en la declara-
cion de una variable imita a la sintaxis de las expresiones en las que puede
aparecer la variable. Este razonamiento es util cuando aparecen expresiones
complicadas. Por ejemplo:
double atof(), *dp;
indican que atof() y dp toman valores de tipo double si aparecen en
una expresi on. Hay que se nalar que en la declaraci on de un apuntador se
restringe el tipo de los objetos a los que puede apuntar. Los apuntadores
pueden aparecer en expresiones. Si px apunta al entero x, entonces px
puede aparecer en cualquier contexto que pudiera hacerlo x.
A un puntero se le puede sumar o restar un entero. En el caso del ejemplo
anterior, px ha sido declarado como un puntero a un entero x. La expresion
y=*px+1;
asigna a y una unidad m as que x. La expresi on
8.1 Direccionamiento indirecto. Punteros: variables que contienen la ubicacion en memoria de otras variab
printf("%d\n", *px);
imprime el valor actual de x; y
d=sqrt((double)*px);
asigna a d la raiz cuadrada de x, variable que es convertida a double para
poder utilizar sqrt.
En expresiones como
y=*px+1;
los operadores unitarios y & tienen mayor precedencia que los opera-
dores aritmeticos, por lo que al evaluar la expresi on se toma el valor del
objeto al que apunta px y se le suma 1, y el resultado es asignado a y. M as
adelante veremos el signicado de (px + 1).
Tambien pueden aparecer referencias a apuntadores en la parte izquierda
de una asignaci on. Si px apunta a x, entonces
*px=0;
pone x a 0, y
*px+=1;
incrementa en 1 el valor de x, al igual que
(*px)++;
En este ultimo ejemplo se necesitan los parentesis. En otro caso, se in-
crementara en 1 px y no el objeto al que apunta, ya que los operadores
unitarios como y ++ se eval uan de derecha a izquierda.
Ademas como los apuntadores son variables, se pueden manipular igual
que cualquier variable. Si py es otro apuntador a enteros,
py=px;
copia el contenido de px en py, con lo que se consigue que ahora py
apunte al mismo objeto que px.
138 8 El lenguaje de programaci on C (IV)
8.2. Punteros y funciones: paso de argumentos de
referencia haciendo uso de punteros
Cuando hablamos de las funciones en C, dijimos que los argumentos se
pasaban por valor. Tambien es cierto que no hay forma directa de hacer que
la funci on llamada altere una variable de la funci on que la llama. Que hacer
entonces cuando realmente queremos modicar alguno de los argumentos?
Por ejemplo, un programa de clasicaci on podra intercambiar dos ele-
mentos no ordenados con una funci on llamada swap. La llamada sera
swap (a,b);
y la denici on (incorrecta, como veremos) sera:
swap(x,y)
int x,y;
{
int temp;
temp=x;
x=y;
y=temp;
}
Pero, por defecto de la llamada por valor, la funci on swap no puede
modicar los argumentos a y b. Sin embargo hay una forma de hacerlo, que
permite conseguir el fn deseado. En la llamada a la fuci on swap se utilizan
apuntadores a los valores que se desea cambiar:
swap (&a,&b);
El operador & devuelve la direcci on de una variable, por lo que &a es un
apuntador a a. En la denici on (correcta) de la funci on swap se tienen que
declarar los argumentos como apuntadores, a traves de los cuales se accede
a los operandos reales.
swap(px,py) /* intercambio de *px y *py */
int *px,*py;
{
int temp;
temp=*px;
8.2 Punteros y funciones: paso de argumentos de referencia haciendo uso de punteros139
*px=*py;
*py=temp;
}
Los apuntadores como argumentos suelen emplearse en el caso de funcio-
nes que devuelven m as de un valor simple. Podra decirse que swap devuelve
dos valores: los nuevos valores de sus argumentos.
Ejemplo: Consideremos una funci on getint() que realice la conversi on
de una cadena de caracteres en valores enteros, sin formato y un entero por
llamada. getint debe devolver el valor hallado o una indicaci on de n de
archivo, si no hay caracteres. Dichos valores hay que devolverlos en objetos
independientes, puesto que en otro caso, el valor empleado para EOF, podra
coincidir con el del entero hallado en la entrada.
Una soluci on consiste en que getint devuelva EOF si detecta el n de
chero o cualquier otro valor, para indicar un entero normal. El valor numeri-
co del entero hallado se devuelve a traves del argumento, que deber a ser un
apuntador a un entero. Esta organizaci on diferencia la indicacion de n de
archivo y los valores numericos.
Con las siguientes sentencias se consigue llenar un array con valores
enteros llamando a getint:
int n, v, array[SIZE];
for (n=0;n<SIZE && getint(&v)!=EOF;n++)
array[n]=v;
Cada llamada deja en v el entero hallado. Observa que es imprescindible
escribir &v en lugar de v, como argumento de getint. Si se utiliza v se puede
cometer un error de direccionamiento puesto que getint supone que se le
pasa un puntero como argumento. La denici on de getint es la siguiente:
getint(pn) /* toma el siguiente entero de la entrada */
int *pn;
{
int c, sign;
while((c=getch())== || c==\n || c==\t)
; /* salta espacios en blanco */
sign=1;
if(c==+ || c==-)
{ /* registra signo */
sign= (c==+) ? 1: -1;
c=getch();
}
140 8 El lenguaje de programaci on C (IV)
for(*pn=0; c>=0 && c<=9; c=getch())
*pn=10* *pn + c - 0;
*pn *= sign;
if(c != EOF)
ungetch(c);
return ();
}
pn se utiliza en getint como cualquier variable de tipo int.
Nota: Que hacen getch y ungetch? A menudo se da el caso
de que un programa que lee no puede determinar si ha ledo
suciente hasta que ha ledo demasiado. Un ejemplo es la reco-
leccion de los caracteres que constituyen un n umero: mientras no
se encuentra el primer caracter que no sea dgito se sabe que el
n umero no esta completo. Pero entonces el programa habr a ledo
un car acter de mas para el que no est a preparado.
El problema estara resuelto si fuera posible desleer el caracter
no deseado. Entonces, cada vez que un programa leyera un car acter
de mas, podra devolverlo a la entrada y el resto del codigo se
comportara como si nunca hubiera sido ledo. Afortunadamen-
te es facil simular la devoluci on de un caracter con s olo escribir
un par de funciones cooperantes. getch toma un car acter de la
entrada y ungetch lo devuelve, para que la siguiente llamada
de getch pueda encontrarlo de nuevo. La forma de funcionar es
sencilla. ungetch sit ua el car acter devuelto en un buer compar-
tido (un arreglo de caracteres). getch lee en el buer, si contiene
algo. Llama a getchar si el buer esta vaco. Tambien debe de
existir una variable ndice que recuerda la posici on siguiente del
caracter en el buer. Ya que el buer y el ndice son compartidos
por getch y ungetch y deben conservar sus valores entre llama-
das, deber an ser externos a ambas. Una forma de escribir getch
y ungetch y sus variables compartidas es:
#define BUFSIZE 100
char buf[BUFSIZE]; /* buffer para ungetch */
int bufp=0; /* sigue posici on libre en buf */
getch() /* lee un (posiblemente devuelto) car acter */
{
return ((bufp > 0) ? buf[--bufp] : getchar());
}
ungetch(c) /* devuelve un car acter */
8.2 Punteros y funciones: paso de argumentos de referencia haciendo uso de punteros141
int c;
{
if(bufp > BUFSIZE)
printf("ungetch: demasiados caracteres \n");
else
buf[bufp++]=c;
}
8.2.1. Punteros y arrays
En C existe entre punteros y arrays un relaci on tal que, cualquier opera-
cion que pueda realizarse mediante la indexaci on de un array se puede reali-
zar tambien con punteros. La versi on con estos sera mas rapida en terminos
generales, pero para los no iniciados, m as difcil de entender.
La declaracion
int a[10];
dene un array de tama no 10, es decir un bloque con diez objetos conse-
cutivos denominados a[0], a[1], ..., a[9]. La notaci on a[i] signica que el ele-
mento del array se encuentra a i posiciones del comienzo. Si pa es un puntero
a un entero, declarado como
int *pa;
entonces la asignacion
pa=&a[0];
hace que pa apunte al elemento 0 de a; es decir, pa contiene la direcci on
de a[0]. Ahora la asignaci on
x=*pa;
copiar a el contenido de a[0] en x.
Si pa apunta a un elemento particular de un arreglo a, entonces, por
denici on pa+1 apunta al siguiente elemento, y en general pai al elemento
situado i posiciones antes que pa, y pa + i apunta al elemento situado i
posiciones despues. Si pa apunta a a[0], entonces
142 8 El lenguaje de programaci on C (IV)
*(pa+1)
se reere al contenido de a[1], pa +i es la direccion de a[i] y (pa +i) es
el contenido de a[i]. Estos comentarios son ciertos cualquiera que sea el tipo
de variables del array a. Y por extension, toda la aritmetica de punteros
establece que el incremento se adec ua al tama no en memoria del objeto
apuntado. En pa +i, i se multiplica por el tama no de los objetos a los que
apunta pa antes de ser sumado a pa.
La correspondencia entre indexaci on y aritmetica de punteros es eviden-
temente muy estrecha. El compilador convierte toda referencia a un array en
un puntero al comienzo del arreglo. El efecto es que el nombre de un array
es una expresion de tipo puntero. Este hecho tiene algunas implicaciones
muy utiles. Puesto que el nombre de un arreglo es sin onimo de la posicion
del elemento cero, la asignacion
pa=&a[0]
se puede escribir as
pa=a
Del mismo modo, una referencia a a[i] se puede escribir tambien como
(a+i). Al aplicar & a los dos lados de esta equivalencia se deduce tambien
que &a[i] y a + i tambien son identicas. a + i es la direccion del i-esimo
elemento de a. Por otra parte, si pa es un puntero, en las expresiones puede
aparecer como un subndice: pa[i] es identico a (pa+i). En suma, cualquier
expresion en que aparezca un array y un subndice se puede escribir como un
puntero y un desplazamiento y viceversa, incluso en la misma proposici on.
Sin embargo, hay una diferencia entre el nombre de un array y un pun-
tero, que hay que tener en cuenta. Un puntero es una variable, por lo que
operaciones como pa = a y pa++ son correctas. Pero el nombre de un array
es una constante, no una variable; de manera que expresiones como a = pa
o a + + son ilegales.
Cuando se pasa el nombre de un array a una funci on, se pasa la direccion
del comienzo del array. En la funci on, este argumento es una variable, por lo
que el nombre de un array como argumento es un puntero, o sea una variable
que contiene una direcci on. Podemos ver esto en el siguiente ejemplo:
strlen(s) /* regresa la longitud de la cadena s */
8.2 Punteros y funciones: paso de argumentos de referencia haciendo uso de punteros143
char *s;
{
int n;
for (n = 0; *s != \0; s++)
n++;
return n;
}
Es perfectamente legal incrementar s, puesto que es un puntero; s + +
no modica la cadena de caracteres s, no hace mas que incrementar la copia
privada de la direcci on.
Las deniciones de par ametros
char s[];
y
char *s;
son completamente equivalentes; la forma como debe escribirse depende
del tipo de expresiones en las que aparece. Cuando se pasa el nombre de un
array a una funci on, la funci on puede establecer a su conveniencia si va a
manejar un array o un puntero, y hacer las manipulaciones de acuerdo con
esto. Incluso puede emplear los dos tipos de operaciones si le parece claro y
conveniente.
Tambien es posible pasar parte de un arreglo a una funci on, pasando un
puntero al comienzo del subarreglo. Por ejemplo si a es un array
f(&a[2]);
y
f(a+2);
pasan a la funci on f la direcci on del elemento a[2], ya que &a[2] y a +2
son expresiones de tipo puntero que apuntan al tercer elemento de a. En f,
la declaraci on de los argumentos podra ser
144 8 El lenguaje de programaci on C (IV)
f(arr)
int arr[];
{
...
}
o bien
f(arr)
int *arr;
{
...
}
8.3. Algebra de punteros
El lenguaje C es consistente y formal en su manejo de la aritmetica de
direcciones; la integracion de punteros, arrays y aritmetica de direcciones
es una de sus principales virtudes. Veremos a continuaci on algunas de sus
propiedades, escribiendo un rudimentario administrador de memoria. Hay
dos rutinas: alloc(n) devuelve un puntero p a n caracteres consecutivos;
free(p) libera la memoria adquirida de esa manera, para poder ser utiliza-
da posteriormente. Las rutinas son rudimentarias, en el sentido de que las
llamadas a free deben hacerse en orden inverso a las llamadas a alloc. Es
decir, la memoria administrada por alloc y free es una pila, o estructura
ultima-en-entrar, primera-en-salir. La biblioteca estandar de C tiene funcio-
nes an alogas que no tienen las restricciones anteriores. Sin embargo, muchas
aplicaciones solo necesitan una sencilla funci on alloc que les proporcione pe-
que nas zonas de memoria de tama no impredecible y en instantes tambien
impredecibles.
La realizaci on m as sencilla consiste en que alloc devuelve trozos de un
gran array de caracteres que llamaremos allocbuf. Este array es exclusivo de
alloc y free. Puesto que se trabaja con punteros y no con subndices, nin-
guna otra rutina necesita conocer el nombre del array, por lo que se declara
como externo y estatico, es decir, local al archivo fuente que contenga alloc
y free e invisible fuera de el. En aplicaciones pr acticas, el array no necesita
nombre; en lugar de ello se puede obtener pidiendo al sistema operativo una
zona libre de memoria.
En cuanto a la manipulaci on del array allocbuf, se empleara un puntero
al primer elemento libre, llamado allocp. Cuando se le piden a alloc n ca-
8.3 Algebra de punteros 145
racteres, se comprueba si hay espacio suciente en allocbuf (el comienzo de
un bloque libre) y se incrementa su valor en n para que apunte a la nueva
zona libre. free(p) le da el valor p a allocp, si p se encuentra en allocbuf.
#define NULL 0 /* valor del puntero para devolver errores */
#define ALLOCSIZE 1000 /* tama~ no del area disponible */
static char allocbuf[ALLOCSIZE]; /* espacio para alloc */
static char *allocp = allocbuf; /* siguiente posici on libre */
char *alloc(n) /* devuelve puntero a n caracteres */
int n;
{
if (allocp + n <= allocbuf+ALLOCSIZE){ /* se encontr o */
allocp += n;
return (allocp - n); /* p anterior */
}
else /* no hubo espacio suficiente */
return (NULL);
}
free(p) /* p apunta al area libre */
char *p;
{
if(p >= allocbuf && p < allocbuf+ ALLOCSIZE)
allocp = p;
}
En general un apuntador se puede inicializar como cualquier otra varia-
ble, aunque normalmente los valores signicativos son NULL o una expre-
sion en que aparezcan direcciones de objetos del tipo apropiado denidas
previamente.
La declaracion
static char *allocp = allocbuf;
dene allocp como un puntero a caracteres y lo inicializa para que apunte
a allocbuf, que es la zona libre cuando comienza el programa. Esto se poda
haber hecho escribiendo
static char *allocp = &allocbuf[0];
ya que el nombre del array es la direcci on del elemento cero.
La pregunta
146 8 El lenguaje de programaci on C (IV)
if (allocp + n <= allocbuf+ALLOCSIZE)
comprueba si hay espacio suciente para satisfacer la petici on de n ca-
racteres; si lo hay, el nuevo valor de allocp sera como maximo una posici on
mas alla del n de allocbuf. Si la petici on se puede satisfacer, alloc devuelve
un apuntador. En caso contrario, alloc debe devolver alguna indicaci on de
que no tiene espacio.
El lenguaje C garantiza que un apuntador que apunte a datos v alidos
nunca tendr a valor cero. Se puede emplear para se nalar una condici on anor-
mal: en este caso, indica que no hay espacio. Se escribe NULL en lugar de
cero, para indicar que es un valor especial para punteros. En general no tiene
sentido asignar enteros a punteros, el cero es un caso especial.
Preguntas como las aparecidas en las sentencias if, del ejemplo anterior,
muestran diferentes facetas de la aritmetica de punteros. Los punteros se
pueden comparar en ciertas circunstancias. Si p y q apuntan a miembros de
un mismo array, se pueden utilizar relaciones como <, >, =, etc.
Tambien se pueden utilizar relaciones como == y ! =. Todo puntero
se puede comparar con igualdad o desigualdad con el valor NULL. No se
pueden comparar punteros a distintos arreglos. (Esto puede funcionar en al-
gunas m aquinas, lo que puede hacer que nuestro programa deje de funcionar
misteriosamente cuando lo trasladamos de maquina).
Ademas se pueden sumar o restar un puntero y un entero. La construc-
cion
p + n
signica el n-esimo objeto a partir del que apunta p. Esto es cierto in-
dependientemente del objeto al que apunte p. Si p y q apuntan a miembros
del mismo array, tambien se pueden restar: p q es el n umero de elementos
entre p y q. Esto nos permite escribir otra version de la funci on strlen:
strlen(s) /* regresa la longitud de la cadena s */
char *s;
{
char *p = s;
while (*p != \0)
p++;
return (p-s);
}
En esta version p se inicializa con s, es decir, apunta al primer car acter.
En la iteraci on while se examina cada caracter hasta encontrar el `0. Puesto
8.4 Punteros a caracteres 147
que `0 es cero y la iteracion while comprueba s olo si la expresion es cero,
se puede omitir la comprobaci on explcita, y escribir la iteracion como
while (*p)
p++;
Como p apunta a caracteres, p + + actualiza p para que apunte al si-
guiente caracter cada vez, y p s indica el n umero de caracteres tratados,
es decir la longitud de la cadena. Si estuviesemos apuntando a oat, que
ocupan m as memoria que los char y si p fuese un puntero a oat, p + + se
posicionara en el siguiente oat.
No se pueden hacer mas operaciones con punteros de las se naladas aqu.
No se permite sumar dos punteros, multiplicarlos, dividirlos, sumarles valo-
res oat o double, etc.
8.4. Punteros a caracteres
Vamos a analizar inicialmente dos funciones de la biblioteca estandar de
C, string.h. La primera strcpy(s, t) que copia la cadena t en la cadena s. La
version con arrays es:
strcpy(s,t) /* copia t a s */
char s[], t[];
{
int i;
i = 0;
while((s[i] = t[i]) != \0)
i++;
}
Mientras que la versi on con punteros es:
strcpy(s,t) /* copia t a s */
char *s, *t;
{
while((*s = *t) != \0)
{
s++;
t++;
}
}
Como los argumentos se pasan por valor, strcpy puede usar s y t con
absoluta libertad. Son punteros que recorren el array car acter a caracter
148 8 El lenguaje de programaci on C (IV)
hasta que el `0 con que termina t se ha copiado a s. La tercera version m as
compacta es:
strcpy(s,t) /* copia t a s */
char *s, *t;
{
while(*s++ = *t++)
;
}
Por una parte se ha introducido los incrementos de s y de t en la parte de
comprobaci on de la iteraci on. El valor de t++ es el caracter al que apuntaba
t antes de ser incrementado; el operador sujo ++ no se aplica hasta que se
ha obtenido ese car acter. De la misma manera el caracter se almacena en la
antigua posici on donde apuntaba s antes de ser incrementado. Este caracter
es tambien el valor que se compara con `0 para controlar la iteraci on. El
efecto neto es la copia de los caracteres de t a s hasta copiar el `0 nal.
Por otra parte se ha eliminado la comparaci on explcita con `0 por re-
sultar redundante.
La segunda rutina que vamos a examinar es strcmp(s, t), que compara
las cadenas de caracteres s y t, y devuelve un valor entero negativo, cero o
positivo, seg un que s sea lexicogracamente menor, igual o mayor que t. El
valor se obtiene al restar los caracteres de la primera posicion donde s y t
dieren.
strcmp(s,t) /* devuelve <0 si s<t, =0 si s=t, >0 si s>t */
char s[], t[];
{
int i;
i = 0;
while(s[i] == t[i])
if(s[i++] == \0)
return 0;
return(s[i]-t[i]);
}
La version con punteros es:
strcmp(s,t) /* devuelve <0 si s<t, =0 si s=t, >0 si s>t */
char *s, *t;
{
for( ; *s == *t; s++, t++ )
if(*s == \0)
return 0;
return(*s - *t);
}
8.5 Ejemplos de utilizaci on de punteros 149
Generalmente se ha visto que en la mayor parte de las computadoras
se puede asignar un puntero a un entero y viceversa, sin que se altere el
puntero. Lamentablemente esto ha conducido a abusar de rutinas que de-
vuelven punteros que pasan posteriormente a otras rutinas. En ocasiones se
omiten declaraciones, de manera que el codigo resultante sigue funcionan-
do aunque arriesgadamente, puesto que su funcionamiento depende de la
particular arquitectura del ordenador empleado.
8.5. Ejemplos de utilizaci on de punteros
Vamos a considerar como primer ejemplo, el problema de la conversi on
de la fecha, del da del mes a da del a no y viceversa. Por ejemplo, el 1 de
Marzo es el da 60 en un a no no bisiesto, y el 61 de uno bisiesto. Veremos
dos funciones: day of year, que realizara la primera operaci on y month day,
que convertir a un da del a no, en mes y da.
Ambas funciones necesitan de la misma informacion inicial, una tabla
con los das que tiene cada mes (treinta das tiene Junio, Septiembre,...).
Puesto que el n umero de das del mes de Febrero vara entre a nos bisiestos
y no bisiestos, mantendremos dicha informacion en el siguiente array de dos
las:
static int day_tab[2][13]={
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}
};
La primera funci on sera:
day_of_year(year, month, day) /* calcula el d a del a~ no
* a partir de mes y d a */
int year, month, day;
{
int i, bisiesto;
bisiesto= year % 4==0 && year % 100 != 0 || year % 400 == 0;
for(i=1; i < month ; i++)
day += day_tab[bisiesto][i];
return (day);
}
month_day(year, yearday, pmonth, pday) /* calcula mes, d a
150 8 El lenguaje de programaci on C (IV)
* a partir del d a y a~no */
int year, yearday, *pmonth, *pday;
{
int i, bisiesto;
bisiesto= year % 4==0 && year % 100 != 0 || year % 400 == 0;
for(i=1; yearday > day_tab[bisiesto][i]; i++)
yearday -= day_tab[bisiesto][i];
*pmonth= i;
*pday=yearday;
}
El array day tab ha de ser externo a ambas funciones para poder ser
utilizado por ambas.
day tab es el primer array bidimensional con el que trabajamos. En reali-
dad, en C, un array bidimensional es por denici on un array unidimensional,
en el que cada elemento vuelve a ser un array. Los subndices se escriben
como
day_tab[i][j]
en lugar de
day_tab[i,j]
empleado en la mayora de los demas lenguajes. Por otra parte, los ele-
mentos se almacenan por las, es decir, el subndice mas a la derecha vara
mas rapidamente cuando se accede a los elementos por orden de almacena-
miento. La inicializaci on se realiza listando los valores entre llaves. Cada la
se inicializa mediante la correspondiente sublista. En este caso se ha comen-
zado con una columna de ceros, para que los ndices correspondientes a los
distintos meses varen entre 1 y 12.
En el caso de tener que transferir un array a una funci on, la declaraci on
del argumento en la funci on deber a incluir el n umero de columnas. El n umero
de las es irrelevante, pudiendose utilizar un apuntador para declarar este
dato. Son posibles las siguientes declaraciones de f:
f(day_tab)
int day_tab[2][13];
{
...
}
8.5 Ejemplos de utilizaci on de punteros 151
O bien:
int day_tab[][13];
o tambien
int (*day_tab)[13];
que indica que el argumento es un apuntador a un array de 13 ente-
ros. Los parentesis son necesarios puesto que los corchetes [] tienen mayor
precedencia que el operador de indirecci on *. Sin parentesis, la declaracion
int *day_tab[13];
declara un array de 13 apuntadores a enteros, como veremos en el si-
guiente ejemplo.
Vamos a ver ahora un ejemplo de un programa que ordene un conjunto
de lneas de texto en orden alfabetico, versi on reducida de la utilera sort de
UNIX.
La dicultad del ejemplo estriba en el hecho de trabajar con lneas de
texto, de longitud variable, que exigen ser comparadas y movidas en m as de
una sola operaci on, a diferencia de lo que sucede con un n umero entero. Se
necesita por lo tanto una representaci on eciente de los datos, que funcione
correctamente con lneas de texto de longitud variable.
Utilizaremos en este caso un array de punteros. Si las lneas a ordenar se
almacenan una tras otra en un gran array de caracteres, se podr a acceder a
cada lnea a traves de un puntero a su primer car acter. Los punteros pueden
almacenarse a su vez en un array. Se pueden comparar dos lneas con s olo
pasar sus punteros a strcmp. Cuando tengamos que intercambiar dos lneas
desordenadas, se intercambiaran los punteros en el arreglo y no las lneas.
Como siempre es mejor dividir el programa principal en funciones que
sigan esta divisi on y que la rutina principal controle las cosas.
Si comenzamos pensando en la entrada de datos, la rutina de entrada
ha de recoger y guardar los caracteres de cada lnea y construir un array
de punteros a las lneas. Tambien tiene que contar el n umero de lneas de
entrada, pues esta informaci on sera necesaria para el proceso de ordenaci on
152 8 El lenguaje de programaci on C (IV)
y el de salida. La funci on de entrada devolver a un -1, si existen demasiadas
lneas. La funci on de salida solo tiene que imprimir las lneas en el orden en
el que aparecen los correspondientes punteros.
#define NULL 0
#define MAXLINES 100 /* n umero m aximo de l neas a ordenar */
main() /* ordena las l neas */
{
char *lineptr[MAXLINES]; /* punteros a las l neas de texto */
int nlines; /* n umero de l neas le das */
if((nlines = readlines (lineptr, MAXLINES)) >= 0)
{
sort(lineptr, nlines);
writelines(lineptr, nlines);
}
else
printf(" Demasiadas l neas a ordenar\n ");
}
Veamos las deniciones de las funciones utilizadas
#define MAXLEN 1000
readlines (lineptr, maxlines) /* lee l neas de entrada */
char *lineptr[];
int maxlines;
{
int len, nlines;
char *p, *alloc(), line[MAXLEN];
nlines=0;
while((len=getline(line, MAXLEN)) > 0)
if(nlines >= maxlines)
return -1;
else if((p=alloc(len) == NULL)
return -1;
else{
line[len-1] = \0; /* quita el car acter nueva l nea */
strcpy(p, line);
lineptr[nlines++]=p;
}
return nlines;
}
El car acter nueva lnea al nal de cada lnea se elimina, pues no afecta
a la forma de ordenar.
8.5 Ejemplos de utilizaci on de punteros 153
writelines(lineptr, nlines) /* escribe l neas */
char *lineptr[];
int nlines;
{
int i;
for(i = 0; i < nlines; i++)
printf("%s \n", lineptr[i]);
}
la novedad es la declaraci on de lineptr.
char *lineptr[LINES];
indica que lineptr es un array de LINES elementos, cada uno de los
cuales es un puntero a un car acter y lineptr[i] tiene acceso a un caracter.
Una vez que la entrada y salida est an controladas, podemos proceder
a su ordenaci on. El metodo elegido en este caso, no ha sido desarrollado
previamente en teora, se denomina Shellsort.
sort(v,n) /* ordena las cadenas v[0],...v[n-1] *
* en orden creciente */
char *v[];
int n;
{
int gap, i,j;
char *temp;
for( gap = n/2; gap > 0; gap /= 2)
for(i = gap; i < n; i++)
for(j = i - gap; j >= 0; j -=gap){
if(strcmp(v[j], v[j+gap]) <= 0)
break;
temp=v[j];
v[j]=v[j+gap];
v[j+gap]=temp;
}
}
Debido a que los elementos de v son punteros, tambien debe serlo temp.
Podra mejorarse el programa sustituyendo el metodo de ordenaci on Shell
por ejemplo por Quicksort. Intentalo como ejercicio.
El programa anterior ha sido implementado, junto con las funciones re-
adlines, writelines, sort, getline y alloc. El resultado es el chero siguiente:
#include<stdio.h>
154 8 El lenguaje de programaci on C (IV)
#include<string.h>
#define NULL 0
#define MAXLINES 100 /*numero maximo de lineas a ordenar */
#define MAXLEN 1000
main(){
char *lineptr[MAXLINES];
int nlines; /* numero de lineas leidas */
if((nlines=readlines(lineptr, MAXLINES))>=0)
{
sort(lineptr, nlines);
writelines(lineptr, nlines);
}
else
printf("Demasiadas lineas a ordenar\n ");
}
readlines(lineptr, maxlines) /* lee las lineas de la entrada */
char *lineptr[];
int maxlines;
{
int len, nlines;
char *p, *alloc(), line[MAXLEN];
nlines=0;
while((len=getline(line,MAXLEN))>0)
if(nlines >= maxlines)
return (-1);
else if ((p=alloc(len)) == NULL)
return (-1);
else{
line[len-1]=\0; /* quita el caracter nueva linea */
strcpy(p,line);
lineptr[nlines++]=p;
}
return (nlines);
}
writelines(lineptr,nlines) /*escribe lineas */
char *lineptr[];
int nlines;
{
int i;
for (i=0; i < nlines; i++ )
printf("%s\n", lineptr[i]);
}
sort(v,n) /*ordena las cadenas v[0],...v[n-1] en orden creciente */
char *v[];
int n;
{
int gap,i,j;
char *temp;
8.5 Ejemplos de utilizaci on de punteros 155
for(gap=n/2; gap>0; gap/=2)
for(i=gap; i<n; i++)
for(j=i-gap; j>=0; j-=gap){
if(strcmp(v[j],v[j+gap])<=0)
break;
temp=v[j];
v[j]=v[j+gap];
v[j+gap]=temp;
}
}
getline(s,lim) /* para la cadena s da su longitud */
char s[];
int lim;
{
int c,i;
i=0;
while(--lim>0 && (c=getchar())!= EOF && c!= \n)
s[i++]=c;
if(c==\n)
s[i++]=c;
s[i]=\0;
return(i);
}
#define ALLOCSIZE 1000 /*tama~ no del area disponible */
static char allocbuf[ALLOCSIZE];
static char *allocp=allocbuf;
char *alloc(n) /*devuelve apuntador a cadena de caracteres */
int n;
{
if(allocp+n<=allocbuf+ALLOCSIZE){
allocp+=n;
return(allocp-n);
}
else
return(NULL);
}
Observaciones sobre E/S
La entrada y salida del programa anterior est an sin especicar. Una
opcion a utilizar desde el sistema operativo, es direccionar la entrada desde
un chero, por ejemplo lineas.txt. Para ello basta teclear
$ a.out<lineas.txt
Si adem as queremos que la salida se escriba en un chero, basta direc-
cionar la salida del programa a dicho chero:
156 8 El lenguaje de programaci on C (IV)
$ a.out<lineas.txt>salida.txt
Por ejemplo si en lineas.txt, aparecen los nombres de los alumnos de
Metodos Numericos del curso 98-99. El chero salida.txt resultante sera:
Beato Unai
Blanco Maria Isabel
Cao Rosa Maria
Caracena Jose Antonio
Fuente Rosa Maria de la
Gomez Pedro Alberto
Guisasola Ainhoa
Herran Belen
Juan Mario de
Morillo Alberto
Nunez David
Quintela Monica
Rivas Silvia
Ruiz Diego
Otra forma de hacer lo mismo, esta vez desde dentro del programa, es
la que aparece a continuaci on:
/* este es el programa en el fichero fordena.c */
/* ordena alfabeticamente cadenas de caracteres */
/* que lee del fichero lineas.txt */
/* la salida va al fichero salida.txt */
#include<stdio.h>
#include<string.h>
#define MAXLINES 500
#define MAXLENGTH 100
main(){
char *lineptr[MAXLINES];
char linea[MAXLINES][MAXLENGTH];
int c,i,n;
FILE *fp;
FILE *fresul;
fp=fopen("lineas.txt","r");
fresul=fopen("salida.txt", "a");
i=1;
while((lineptr[i] = fgets(&linea[i][0], MAXLENGTH,fp))
!= NULL && (i<MAXLINES))
8.5 Ejemplos de utilizaci on de punteros 157
i++;
n=i-1;
for(i=1; i<=n; i++)
{
printf("%s", lineptr[i]);
}
sort(lineptr+1, n);
fprintf(fresul,"\n\n Lineas ordenadas lexicograficamente: \n\n");
for (i=1; i<=n; i++)
{
fprintf(fresul, "%s", lineptr[i]);
}
return 0;
}
sort(lineptr, n)
char *lineptr[];
int n;
{
int gap,i,j;
char *temp;
for ( gap=n/2; gap >0; gap/=2)
for (i=gap; i<n; i++)
for (j=i-gap; j>=0; j-=gap){
if(strcmp(lineptr[j],lineptr[j+gap])<=0)
break;
temp=lineptr[j];
lineptr[j]=lineptr[j+gap];
lineptr[j+gap]=temp;
}
}
El cambio de la entrada de datos a un programa tambien es invisible si la
entrada proviene de la salida de otro programa a traves de una interconexi on,
(pipe). En este caso, la linea de comando
$ prog1.out |prog2.out
ejecuta los programas prog1.out y prog2.out, y conecta la salida est andar
de prog1.out con la entrada est andar de prog2.out.
En la practica muchos programas leen de un solo archivo y escriben en un
unico archivo. Para estos programas, la entrada/salida mediante getchar(),
putchar() y printf es totalmente adecuada.
Ademas varios archivos se pueden unir usando por ejemplo la utilera
cat de UNIX. Ello hace innecesario aprender a acceder a los archivos desde
158 8 El lenguaje de programaci on C (IV)
dentro del programa.
Por ejemplo, con un sencillo programa como el siguiente:
#include<stdio.h>
main()
{
int c;
while((c=getchar())!=EOF)
putchar(issuper(c) ? tolower(c) :c );
}
se traduce la entrada a min usculas. Las funciones issuper y tolower son
macros denidas en stdio.h, que convierten en may uscula y min usculas, res-
pectivamente, el caracter ledo. Con la lnea de comando:
$ cat lineas.txt lineas2.txt | lower.out> output
se unen los cheros lineas.txt y lineas2.txt y se ejecuta sobre el chero
unido, el programa lower.out. La salida aparece en el archivo output.
Captulo 9
El lenguaje de programacion C (V)
9.1. Tipos de datos denidos por el usuario: es-
tructuras
Una estructura es un conjunto de diversas variables, posiblemente de
distinto tipo, agrupadas bajo un mismo nombre, lo que hace m as eciente
su manejo.
Las estructuras ayudan a organizar datos complicados, particularmen-
te en programas grandes, puesto que permiten considerar como unidad un
conjunto de variables relacionadas, en lugar de tratarlas como unidades in-
dependientes.
Una fecha tiene por ejemplo varios componentes, da, mes, a no, da del
a no y nombre del mes. Estas cinco variables se pueden agrupar bajo una
estructura como la siguiente:
struct date {
int day;
int month;
int year;
int yearday;
char mon_name[4];
};
La palabra clave struct introduce la declaraci on de una estructura, que
no es mas que un conjunto de declaraciones encerradas entre llaves. Op-
cionalmente se puede a nadir un nombre despues de la palabre clave struct
(como el date en el ejemplo anterior). A dicho nombre se le denomina nom-
bre de la estructura y se puede emplear en declaraciones posteriores como
159
160 9 El lenguaje de programaci on C (V)
abreviatura de la estructura.
Los elementos o variables citados en una estructura se denominan miem-
bros. Un miembro de una estructura, la propia estructura y una variable
ordinaria pueden tener el mismo nombre; siempre que se puedan distinguir
a traves del contexto.
La llave de cierre que termina la declaraci on de la lista de miembros
puede ir seguida de una lista de variables
struct{...} x, y, z;
en el sentido de que x, y, z son declaradas las tres como estructuras del
mismo tipo y se reserva la memoria correspondiente.
Si la declaraci on de una estructura no va seguida de una lista de variables,
no se reserva memoria, y se considera unicamente descrita una plantilla de
la estructura. Posteriormente se puede utilizar dicha plantilla, para denir
estructuras del mismo tipo. Por ejemplo, una vez construda la plantilla de
la estructura date,
struct date d;
declara la variable d como una estructura de tipo date. Si la variable
estructura es de tipo externa o estatica, se puede inicializar de la siguiente
forma:
struct date d={4 ,7 ,1776, 186, "Jul"};
Si nos queremos referir a un elemento de la estructura, tenemos que
utilizar una construcci on de la forma
nombre_de_estructura.miembro
El operador miembro de estructura conecta el nombre de la estructura
y el del miembro. Para dar un valor a la variable bisiesto a partir de la
estructura date se hara:
bisiesto=d.year % 4 == 0 && d.year % 100 != 0
|| d.year % 400 == 0;
9.2 Operaciones con estructuras 161
o para comprobar el nombre del mes:
if (strcmp (d.mon_name, "Aug") == 0) . . .
o para convertir a min uscula el primer car acter del nombre del mes:
d.mon_name[0] = lower(d.mon_name[0]);
Por otra parte las estructuras se pueden anidar. Un registro de la n omina
puede ser por ejemplo:
struct person{
char name[NAMESIZE]; /* dimension = tama~no del nombre */
char address[ADRSIZE];
long zipcode;
long ss_number;
double salary;
struct date birthdate;
struct date hiredate;
};
La estructura persona tiene dos fechas. Si declaramos la variable emp
como una estructura del mismo tipo:
struct person emp;
entonces
emp.birthdate.month
se reere al mes de nacimiento. El operador de miembro de una estruc-
tura es asociativo de izquierda a derecha.
9.2. Operaciones con estructuras
Con una variable declarada como una estructura pueden realizarse las
siguientes operaciones:
162 9 El lenguaje de programaci on C (V)
1. Tomar su direccion mediante el operador &.
2. Acceder a uno de sus miembros.
3. Asignar una estructura a otra con el operador de asignaci on.
Esta ultima operaci on es posible solo en las nuevas versiones de C, pero
no en el C estandar. En general si se desean hacer programas portables, hay
que tener cuidado con lo que no se puede hacer en C estandar.
Si queremos copiar una estructura como una unidad, o pasarla como
argumento a una funci on, tendremos que utilizar punteros.
Por otra parte, las estructuras autom aticas no se pueden inicializar, s olo
pueden inicializarse estructuras externas o estaticas.
Como ejemplos de uso, volveremos a escribir las funciones de conversi on
de fechas que vimos en el Tema 8, utilizando estructuras. Como no es po-
sible pasar una estructura a una funci on como argumento, pasaremos las
componentes por separado, o bien pasaremos un puntero a la estructura.
La primera alternativa emplea day of year igual que se escribi o en el
Tema 8.
d.yearday = day_of_year(d.year, d.month, d.day);
La otra alternativa es pasar un puntero. Si hemos declarado hiredate
(fecha de contrato) como una estructura del tipo date, es decir:
struct date hiredate;
hay que modicar la funci on day of year, para que su argumento sea un
puntero y no una lista de variables. La modicaci on puede ser:
day_of_year (pd) /* convertir en dia del a~ no */
struct date *pd;
{
int i, day, bisiesto;
day = pd -> day;
bisiesto=pd -> year % 4 == 0 && pd -> year % 100 != 0
|| pd -> year % 400 == 0;
for (i=1; i< pd -> month; i++)
day += day_tab[bisiesto][i];
return (day);
}
9.2 Operaciones con estructuras 163
con lo que la llamada a la funci on sera:
d.yearday = day_of_year(&hiredate);
La declaracion
struct date *pd;
especica que pd es un puntero a una estructura de tipo date. La notaci on
pd >year sirve para describir el operador de acceso al miembro de una
estructura. En este caso apunta al miembro denominado year. Puesto que
pd apunta a una estructura, el miembro year se puede referenciar mediante
(*pd).year
pero dada la frecuencia con la que se emplean los punteros a estructu-
ras, se ha introducido la notaci on > mas abreviada que la anterior. En
(pd).year hacen falta los parentesis, ya que la precedencia del operador de
miembro de estructura es mayor que la del operador *. Tanto > como
son asociativos de izquierda a derecha, de manera que
p -> q -> memb
emp.birthdate.month
equivalen a
(p -> q) -> memb
(emp.birthdate).month
Podemos modicar en el mismo sentido, la funcion month day para uti-
lizar estructuras:
month_day (pd) /* convertir a mes y dia */
struct date *pd;
{
int i, bisiesto;
bisiesto=pd -> year % 4 == 0 && pd -> year % 100 != 0
|| pd -> year % 400 == 0;
pd -> day = pd -> yearday;
for (i=1; pd -> day > day_tab[bisiesto][i]; i++)
pd -> day -= day_tab[bisiesto][i];
pd -> month =i;
}
164 9 El lenguaje de programaci on C (V)
Los operadores de estructuras > y junto con los parentesis y los cor-
chetes tienen la maxima precedencia entre todos los operadores. Por ejemplo,
dada la declaraci on
struct{
int x;
int *y;
} *p;
la sentencia
++p -> x
incrementa x en lugar de p, ya que los parentesis implcitos son ++(p >
x). Se deben utilizar parentesis para alterar el orden de aplicacion de los
operadores. (+ + p) > x incrementa p antes de acceder a x, mientras que
(p++) > x lo incrementa despues. En este ultimo caso, no se necesitaran
parentesis verdad?
Las estructuras tienen una aplicacion bastante util en arrays de variables
relacionadas entre s. Vamos a analizar un ejemplo de aplicaci on en este
sentido.
Considerese un programa que intenta contar las veces que aparece una
palabra clave de C. En tal programa necesitaramos un array de caracteres
para almacenar los nombres de las distintas palablas claves y otro para
contar el n umero de ocurrencias.
Una posibilidad consistira en mantener dos arreglos en paralelo keyword
y keycount
char *keyword[nkeys];
int keycount[nkeys];
Sin embargo el hecho de que los dos arreglos se vayan a manipular en
paralelo indica otra posible organizaci on. Cada palabra clave es en realidad
un par:
char *keyword;
int keycount;
9.2 Operaciones con estructuras 165
y se necesita un array de pares. Se puede entonces declarar una estructura
de la forma
struct key {
char *keyword;
int keycount;
}
keytab[nkeys];
que dene un array keytab de estructuras de este tipo y le asigna me-
moria. Cada elemento del array es una estructura. Otra posible denici on
es:
struct key {
char *keyword;
int keycount;
};
struct key keytab[nkeys];
La estructura keytab puede inicializarse de una vez para siempre con el
conjunto de nombres de las palabras clave de C. En cuanto al n umero de
ocurrencias, se inicializa en todas ellas a 0.
struct key {
char *keyword;
int keycount;
} keytab[]={
"break", 0,
"case", 0,
"char", 0,
"continue", 0,
/* . . . */
"unsigned", 0,
"while", 0
};
Los inicializadores se agrupan por pares. Tambien se podran agrupar
entre llaves los inicializadores en cada la.
{"break", 0},
{"case", 0},
. . .
166 9 El lenguaje de programaci on C (V)
Estas llaves no son necesarias si los inicializadores son variables simples
o cadenas de caracteres y si ademas estan todos presentes. La dimensi on del
array en el caso de que esten todos presentes la calcula el compilador y [] se
deja vaco.
Escribimos a continuaci on el programa que cuenta palabras clave. Se de-
ne inicialmente keytab. El programa principal lee datos llamando repetida-
mente a getword que busca en keytab mediante una funci on que implementa
un algoritmo de b usqueda binaria. (Dicha funci on de b usqueda necesita que
las palabras clave en keytab esten ordenadas en orden creciente, o lo que es
lo mismo en orden alfabetico).
#define MAXWORD 20
main() /*cuenta palabras clave de C */
{
int n, t;
char word[MAXWORD];
while ((t = getword(word,MAXWORD))!= EOF)
if(t == LETTER)
if ((n = binary(word, keytab, NKEYS)) >= 0)
keytab[n].keycount++;
for (n=0; n < NKEYS; n++)
if(keytab[n].keycount > 0)
printf("%4d %s\n",
keytab[n].keycount, keytab[n].keyword);
}
binary(word, tab, n) /*busca palabra en tab[0]...tab[n-1]*/
char *word;
struct key tab[];
int n;
{
int low, high, mid, cond;
low = 0;
high = n-1;
while (low <= high){
mid = (low+high) / 2;
if ((cond = strcmp(word, tab[mid].keyword)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return(mid);
}
return(-1);
}
9.2 Operaciones con estructuras 167
La funci on getword la veremos un poco mas adelante. De ella solo sabe-
mos por ahora, que devuelve LETTER cada vez que encuentra una palabra,
y copia la palabra en su primer argumento word.
La constante NKEY S es el n umero de palabras que caben en keytab.
Esta cantidad puede determinarse al contar las palabras clave en la tabla,
pero es mas comodo y seguro que esta cantidad se calcule automaticamente,
puesto que ademas dicha lista puede variar. Una posibilidad es utilizar el
hecho de que el tama no del array se conoce en el momento de la compilaci on.
El n umero de elementos es
tama no de keytab (40) / tama no de struct key (2)
C tiene un operador unitario llamado sizeof que calcula el tama no de
cualquier objeto en el momento de la compilaci on. La expresi on sizeof(objeto)
devuelve un entero igual al tama no del objeto especicado. Dicho objeto
puede ser una variable, un array o una estructura, el nombre de un tipo
b asico como int o double, o el nombre de una estructura. En nuestro caso,
el n umero de palabras clave se obtiene de la siguiente manera.
#define NKEYS (sizeof(keytab) /sizeof(struct key)) /% (20) %/
Vamos a describir ahora la funci on getword. Dicha funci on devuelve la
constante LETTER si el smbolo es una palabra (cadena de letras y dgitos
que comienza con una letra, o bien un unico car acter). Si se alcanza el nal
del archivo, la funci on devuelve EOF.
getword(w, lim) /* leer la siguiente palabra */
char *w;
int lim;
{
int c, t;
if(type(c = *w++ = getch()) != LETTER){
*w = \0;
return (c);
}
while (--lim >0) {
t = type(c = *w++ =getch());
if (t != LETTER && t != DIGIT) {
ungetch(c);
break;
}
}
*(w-1) = \0;
return(LETTER);
}
168 9 El lenguaje de programaci on C (V)
getword utiliza las rutinas getch y ungetch que vimos en el Tema 8.
Cuando acaba la recoleccion de una cadena alfanumerica se llama a la rutina
ungetch para devolver el ultimo car acter ledo, que ser a el siguiente en ser
procesado en la pr oxima llamada.
getword llama a type para determinar el tipo de car acter de entrada.
La funci on type descrita a continuaci on, es valida s olo para el conjunto de
caracteres ASCII.
type(c) /*devuelve el tipo de un caracter ASCII*/
int c;
{
if(c >= a && c <= z || c >= A && c <= Z)
return LETTER;
else if (c >= 0 && c <= 9)
return DIGIT;
else
return c;
}
Las constantes simbolicas LETTER y DIGIT pueden tener cualquier
valor que no entre en conicto con un car acter alfanumerico o EOF; las
elecciones obvias son:
#define LETTER a
#define DIGIT 0
El programa correspondiente est a implementado y su nombre es cuenta.c.
9.3. Punteros a estructuras y estructuras ligadas.
Arboles binarios. Ejemplos de uso
Vamos a volver a escribir un programa en C que cuente palabras clave.
Esta vez vamos a utilizar punteros en lugar de ndices del array. Hay que
cambiar main y binary, que quedaran de la siguiente forma:
main() /*cuenta palabras clave de C */
{
int t;
char word[MAXWORD];
struct key *binary(), *p;
while ((t = getword(word,MAXWORD))!= EOF)
if(t == LETTER)
9.3 Punteros a estructuras y estructuras ligadas. Arboles binarios. Ejemplos de uso169
if ((p = binary(word, keytab, NKEYS)) != NULL)
p->.keycount++;
for (p = keytab; p < keytab + NKEYS; p++)
if(p -> keycount > 0)
printf("%4d %s\n", p->keycount, p ->keyword);
}
struct key *binary(word, tab, n) /*busca palabra en
tab[0]...tab[n-1]*/
char *word;
struct key tab[];
int n;
{
int cond;
struct key *low = &tab[0];
struct key *high = &tab[n-1];
struct key *mid;
while (low <= high){
mid = low + (high-low) / 2;
if ((cond = strcmp(word, mid -> keyword)) < 0)
high = mid - 1;
else if (cond > 0)
low = mid + 1;
else
return(mid);
}
return(NULL);
}
En la nueva versi on se aprecian varios cambios. La declaracion de binary
indica que devuelve un puntero a una estructura de tipo key, en lugar de
un entero; esta declaraci on hay que hacerla en main y en binary. Si binary
encuentra la palabra, devuelve un puntero a ella, en caso contrario devuelve
NULL.
Ademas el acceso a los elementos de keytab se hace a traves de punteros.
Esto provoca bastante cambio en binary: el c alculo del elemento central no
puede hacerse como antes
mid =
(low+high)
2
ya que la suma de los punteros no produce ning un tipo de respuesta util
(tampoco dividir por 2), que de hecho es ilegal. El c alculo hay que cambiarlo
por
mid = low +
(highlow)
2
que hace que mid apunte al elemento situado en la mitad de low y high.
170 9 El lenguaje de programaci on C (V)
Tambien hay que observar los inicializadores de low y high. Se puede
inicializar un puntero con la direcci on de un objeto denido previamente,
que es en este caso lo que se hace.
En main se tendra:
for (p = keytab; p < keytab + NKEYS; p++)
Si p es un puntero a una estructura, cualquier operaci on aritmetica que
se realice con el tendr a en cuenta el tama no de la estructura, por lo que
p + + incrementa p en la cantidad adecuada para acceder al siguiente ele-
mento del arreglo de estructuras. No se debe suponer que el tama no de una
estructura es la suma de los tama nos de cada uno de los miembros, ya que
los requerimientos de alineaci on de los diferentes objetos pueden ocasionar
huecos en la estructura.
Por ultimo hay que comentar algo sobre el formato de un programa.
Cuando una funci on devuelve un tipo complicado como
struct key *binary(word, tab, n)
puede ser difcil encontrar el nombre de la funci on. Una forma alternativa
de escribirlo, quiza mas clara puede ser
struct key *
binary(word, tab, n)
Vamos a analizar por ultimo el siguiente ejemplo. Sup ongase que se de-
sea contar el n umero de ocurrencias de todas las palabras de alg un texto.
Dado que de entrada no se conoce la lista de palabras, no se pueden ordenar
para utilizar un algoritmo de b usqueda binaria. Tampoco sera correcta una
b usqueda lineal de cada palabra encontrada, puesto que sera un procedi-
miento muy lento. C omo podemos organizar nuestros datos para controlar
ecientemente una lista de palabras arbitrarias?
Una soluci on es mantener en todo momento las palabras ordenadas, co-
locando en su sitio cada palabra que llega. Esto no es posible hacerlo, des-
plazando las palabras en un array lineal, pues se empleara mucho tiempo.
Lo que s podemos utilizar es una estructura de datos denominada arbol
binario.
El arbol va a contener un nodo por cada palabra diferente, en cada nodo
tendremos:
9.3 Punteros a estructuras y estructuras ligadas. Arboles binarios. Ejemplos de uso171
un puntero a los caracteres de la palabra
un contador del n umero de ocurrencias
un puntero a su hijo izquierdo (que es un nodo)
un puntero a su hijo derecho (que tambien es un nodo)
Ning un nodo puede tener m as de dos hijos, pero puede tener uno o
ninguno. La estructura del arbol est a denida, de manera que: dado un
nodo, el sub arbol izquierdo contiene palabras que son (lexicogr acamente)
menores que la palabra del nodo, y el sub arbol derecho contiene palabras que
son mayores que la palabra del nodo actual. Para averiguar si una palabra ya
esta en el arbol, se comienza comparando dicha palabra con la almacenada
en el nodo raz del arbol.
Si coinciden, es que la palabra ya est a en el arbol, en este caso se ac-
tualiza el contador de ocurrencias. Si la palabra es menor, se contin ua la
b usqueda por el sub arbol izquierdo; si la palabra es mayor, es investigada
en el sub arbol derecho. Si no existe descendiente en la direccion examinada
es que la palabra no esta en el arbol, y su lugar es el del descendiente que
no existe.
Este proceso de b usqueda es claramente recursivo, puesto que la b usque-
da en cualquier nodo se realiza de igual manera en cualquiera de sus hijos.
La estructura de cada nodo, puede estar denida de la siguiente manera:
struct tnode{ /* el nodo*/
char *word; /* puntero a los caracteres */
int count;
struct tnode *left; /*hijo izquierdo */
struct tnode *right; /*hijo derecho */
};
Es ilegal que una estructura contenga un elemento que sea ella misma,
pero la sentencia
struct tnode *left; /*hijo izquierdo */
declara left como un puntero a un nodo, no como un nodo en s.
El c odigo del programa es muy corto ya que utiliza funciones que han sido
denidas anteriormente. Dichas funciones son getword que lee la siguiente
palabra de la entrada y alloc que obtiene memoria para las palabras.
172 9 El lenguaje de programaci on C (V)
El programa principal lee palabras llamando a getword y las a nade al
arbol mediante la funci on tree que luego veremos:
#define MAXWORD 20
main() /* cuenta frecuencia de palabras*/
{
struct tnode *root, *tree();
char word[MAXWORD];
int t;
root=NULL;
while(( t=getword(word, MAXWORD))!= EOF)
if(t == LETTER)
root = tree(root, word);
treeprint(root);
}
main presenta una palabra a comparar comenzando por la raz del arbol.
En cada etapa, dicha palabra se compara con la almacenada en el nodo
y se prosigue la b usqueda por el sub arbol derecho o izquierdo mediante
una llamada recursiva a tree. Pueden darse dos posibilidades, si la palabra
coincide con una que est a en el arbol, se incrementa el contador, mientras
que si se encuentra un puntero nulo, esto quiere decir que hay que crear y
a nadir un nodo al arbol. Si se crea un nodo, tree devuelve su puntero para
poder instalarlo en el nodo padre.
struct tnode *tree(p,w) /*introduce w en p o a continuaci on de p */
struct tnode *p;
char *w;
{
struct tnode *talloc();
char *strsave();
int cond;
if(p == NULL)
{ /* llega una nueva palabra */
p=talloc();
p->word = strsave(w);
p->count = 1;
p->left = p->right = NULL;
}
else if ((cond = strcmp(w, p->word)) == 0)
p->count++; /*palabra repetida */
else if (cond < 0) /* las menores va al izdo.*/
p->left=tree(p->left, w);
else
p->right=tree(p->right, w);/*las mayores al dcho. */
return (p);
}
La memoria para crear el nuevo nodo se obtiene llamando a la rutina
talloc, adaptaci on de la rutina alloc vista anteriormente. Devuelve un pun-
9.3 Punteros a estructuras y estructuras ligadas. Arboles binarios. Ejemplos de uso173
tero a la zona de memoria donde almacenar el nodo.
La nueva palabra se copia a una zona vaca mediante strsave, el contador
se inicializa y los dos hijos toman el valor NULL. Esta parte de la rutina se
ejecuta al alcanzar un extremo del arbol para a nadir un nuevo nodo.
freeprint imprime el arbol en orden simetrico; dado un nodo, se imprime
el sub arbol izquierdo (todas las palabras menores que la del nodo), el propio
nodo, y luego el sub arbol derecho (todas las palabras mayores).
treeprint(p) /*imprime el arbol recursivamente */
struct tnode *p;
{
if(p!= NULL)
{
treeprint(p->left);
printf("%4d %s\n", p->count, p->word);
treeprint(p->right);
}
}
Por ultimo una observaci on pr actica: el arbol puede estar desequilibrado
o no balanceado, si las palabras no aparecen aleatoriamente y el tiempo
de ejecucion puede, entonces, ser muy alto. En el peor de los casos, si las
palabras ya est an en orden, el programa realiza una b usqueda lineal.
Antes de acabar con el ejemplo, vamos a realizar una peque na reexi on
sobre administradores de memoria. Parece deseable que a lo largo de un
programa exista un unico administrador de memoria, aunque maneje dife-
rentes tipos de objetos. Pero si un gestor acepta peticiones de apuntadores
a char y a struct tnode se plantean dos cuestiones. En primer lugar c omo
satisfacer los requerimientos de casi todas las computadoras que imponen
restricciones de alineacion a diferentes tipos de objetos? (por ejemplo, los
enteros a menudo suelen ubicarse en una direcci on par). En segundo lugar,
como se puede expresar el hecho de que alloc devuelva diferentes tipos de
apuntadores?.
La declaracion del tipo en alloc puede ser un inconveniente para cualquier
lenguaje que realice una comprobaci on de tipos. En C, un procedimiento es
declarar que alloc devuelva un apuntador a caracteres y forzarlo mediante
una declaraci on encastrada cast al tipo deseado.
Si p se declara como
char *p;
174 9 El lenguaje de programaci on C (V)
(struct tnode *)p;
lo convierte en un puntero a la estructura tnode. De esta forma la rutina
talloc se escribe:
9.3 Punteros a estructuras y estructuras ligadas. Arboles binarios. Ejemplos de uso175
struct tnode *talloc()
{
char *alloc();
return((struct tnode *)alloc(sizeof(struct tnode)));
}
Observar que devuelve un puntero a estructura, de la posici on donde
puede empezar a ser almacenada una estructura de ese tama no.
176 9 El lenguaje de programaci on C (V)
Captulo 10
Soluci on de sistemas de ecuaciones
lineales
Un sistema de ecuaciones lineales con m ecuaciones y n inc ognitas (n
m)
a
11
x
1
+ +a
1n
x
n
= b
1

a
m1
x
1
+ +a
mn
x
n
= b
m
puede escribirse con notaci on matricial como Ax = b. Si b = 0 el sistema
se dice homogeneo. Un sistema homogeneo siempre tiene la solucion trivial
x = 0. Si rang(A) = r < n, el sistema Ax = 0 tiene (n r) soluciones
linealmente independientes.
El sistema anterior puede escribirse de la forma
x
1
a
1
+ +x
n
a
n
= b
que expresa el vector b como una combinaci on lineal de los vectores columna
de A. Con esta representacion, se puede ver que la condici on para que un
sistema lineal tenga solucion es que b R(A), donde R(A) es el subespacio
generado por todas las columnas de A. Esto puede escribirse diciendo que
rang(A, b) = rang(A)
Si m = n = r, entonces R(A) = R
n
y el sistema puede resolverse para
cualquier b. En este caso la solucion es unica y se obtiene como x = A
1
b.
Si sucede que rang(A) = r < n, entonces el sistema Ax = b tiene n r
soluciones.
177
178 10 Soluci on de sistemas de ecuaciones lineales
10.1. Metodos directos para la resoluci on de sis-
temas de ecuaciones lineales
Entenderemos como metodo directo aquel que necesita de un n umero
nito de pasos para obtener la soluci on, en este caso de un sistema de ecua-
ciones lineales. Resultan m as ecientes para sistemas Ax = b en los que la
matriz A es densa (la mayora de sus elementos son no nulos). Sin embar-
go, si la matriz es dispersa (una gran porci on de sus elementos son nulos)
suelen ser mas apropiados los metodos iterativos. Dichos metodos propor-
cionan una sucesi on de soluciones aproximadas que converge a la verdadera
soluci on cuando el n umero de iteraciones tiende a innito. En general, dan
buenas soluciones con pocas operaciones elementales (no hay casi error de
redondeo) y trabajan, principalmente con los elementos no nulos de la ma-
triz, lo que permite que sean aplicados a sistemas de dimension mayor que
un metodo directo.
La eleccion entre un metodo directo y un metodo iterativo depende nor-
malmente del tama no del sistema y de la forma (densa o dispersa) de la
matriz A. En este captulo y en el siguiente, nos dedicaremos a comentar
algunos de los metodos directos mas habituales.
10.1.1. Sistemas triangulares
Los sistemas en los que la matriz A es triangular, son especialmente
sencillos de resolver.
Consideremos por ejemplo un sistema triangular superior:
u
11
x
1
+ + + u
1n
x
n
= b
1

u
n1,n1
x
n1
+ u
n1,n
x
n
= b
n1
u
nn
x
n
= b
n
Si u
ii
= 0, i = 1, ..., n, entonces las incognitas pueden ser despejadas y
obtenidas en el orden x
n
, x
n1
, ..., x
1
.
x
n
=
b
n
u
nn
x
n1
=
b
n1
u
n1n
x
n
u
n1,n1

10.2 Eliminaci on gaussiana 179
x
1
=
b
1
u
1n
x
n
u
12
x
2
u
11
sistema que puede ser escrito en forma compacta como:
x
i
=
b
i

n
k=i+1
u
ik
x
k
u
ii
, i = n, n 1, ..., 1
Como las incognitas se obtienen en orden decreciente, el algoritmo se
denomina de sustituci on hacia atr as. Si el sistema fuese triangular inferior,
la resoluci on sera similar. Suponiendo los coecientes en la diagonal prin-
cipal no nulos, l
ii
= 0, i = 1, ..., n, las variables, en este caso se obtienen
sustituyendo hacia adelante, a partir de la expresi on:
x
i
=
b
i

i1
k=1
l
ik
x
k
l
ii
, i = 1, 2, ..., n
De las expresiones anteriores, se deduce que la obtencion de las n inc ogni-
tas necesita n divisiones. Adem as las sumas y multiplicaciones son
n

i=1
(i 1) =
n(n 1)
2

n
2
2
Podemos decir entonces que la complejidad computacional, para la ob-
tenci on de las soluciones en un sistema lineal triangular es O(
n
2
2
).
10.2. Eliminaci on gaussiana
Se trata del metodo mas importante de resolucion de sistemas de ecua-
ciones lineales. La idea consiste en eliminar inc ognitas de forma sistematica
hasta que el sistema tenga forma triangular, y entonces resolverlo. Conside-
remos el sistema:
a
11
x
1
+ +a
1n
x
n
= b
1

a
n1
x
1
+ +a
nn
x
n
= b
n
o Ax = b, donde la matriz A = (a
ij
) es no singular. En este caso el sistema
anterior tiene solucion unica.
Si a
11
= 0, podemos eliminar x
1
de las (n1) ultimas ecuaciones restando
de la i-esima ecuacion la primera multiplicada por m
i1
=
a
i1
a
11
, i = 2, ..., n
180 10 Soluci on de sistemas de ecuaciones lineales
Las n 1 ultimas ecuaciones se pueden escribir despues de dicha opera-
cion como:
a
(2)
22
x
2
+ +a
(2)
2n
x
n
= b
(2)
2

a
(2)
n2
x
2
+ +a
(2)
nn
x
n
= b
(2)
n
donde los coecientes:
a
(2)
ij
= a
ij
m
i1
a
1j
b
(2)
i
= b
i
m
i1
b
1
i = 2, ..., n
Este es un sistema de (n1) ecuaciones con (n1) inc ognitas. Si a
(2)
22
= 0,
podemos eliminar de manera similar x
2
de las n 2 restantes ecuaciones.
Si llamamos m
i2
=
a
(2)
i2
a
(2)
22
, i = 3, ..., n, los coecientes del sistema resultante
son:
a
(3)
ij
= a
(2)
ij
m
i2
a
(2)
2j
b
(3)
i
= b
(2)
i
m
i2
b
(2)
2
i = 3, ..., n
Los elementos a
11
, a
(2)
22
, a
(3)
33
, ... se denominan elementos de pivotaje o
pivotes. Si todos ellos son no nulos, podemos continuar la eliminaci on y
despues de (n 1) pasos llegar a:
a
(n)
nn
x
n
= b
(n)
n
Si escribimos el sistema triangular resultante:
a
(1)
11
x
1
+ + + a
(1)
1n
x
n
= b
(1)
1
a
(2)
22
x
2
+ + a
(2)
2n
x
n
= b
(2)
2

a
(n1)
n1,n1
x
n1
+ a
(n1)
n1,n
x
n
= b
(n1)
n1
a
(n)
nn
x
n
= b
(n)
n
Se nalar que el termino independiente b es transformado de la misma
forma que las columnas de A.
Ademas la descripcion de la eliminaci on se simplica si expresamos b
como la ultima columna de A
a
(k)
i,n+1
= b
(k)
i
, i, k = 1, 2..., n
10.2 Eliminaci on gaussiana 181
Entonces las f ormulas se pueden resumir como sigue: La eliminaci on se
realiza en (n 1) pasos, k = 1, ..., n 1. En el paso k los elementos a
(k)
ij
con
i, j > k, son transformados de acuerdo con:
m
ik
=
a
(k)
ik
a
(k)
kk
,
a
(k+1)
ij
= a
(k)
ij
m
ik
a
(k)
kj
(10.1)
i = k + 1, ..., n j = k + 1, ..., n + 1
Por otra parte si tenemos distintos sistemas de ecuaciones con la misma
matriz A:
Ax
1
= b
1
, Ax
2
= b
2
, , Ax
p
= b
p
dichos sistemas pueden ser tratados simultaneamente, adjuntando b
j
como
la n + j-esima columna de A. La unica diferencia con el algoritmo dado en
(10.1), es que el ndice j toma los valores k + 1, ..., n, ..., n + p. Despues de
la eliminaci on tendremos p sistemas triangulares para resolver.
182 10 Soluci on de sistemas de ecuaciones lineales
Coste operativo
Vamos a calcular el n umero de operaciones aritmeticas necesario para
reducir mediante la eliminaci on gaussiana, un sistema con p terminos inde-
pendientes, a su forma triangular. De las ecuaciones (10.1) se sigue que en
el paso k se realizan (nk) divisiones y (nk)(nk+p) multiplicaciones y
sumas. Si estamos interesados en ver el coste operativo para valores grandes
de n, podemos eliminar el n umero de divisiones, de manera que el n umero
total de operaciones es aproximadamente:
n1

k=1
(n k)(n k +p) =
n1

k=1
(n k)
2
+p
n1

k=1
(n k) =
=
(n 1)n(2n 1)
6
+p
(n 1)n
2
=
=
n
2
(n 1)
3

n(n 1)
6
+
p
2
n(n 1) =
=
n
2
(n 1)
3
+ (3p 1)
n(n 1)
6

1
3
n
3
+
(3p 1)
6
n
2
donde cada operaci on es en realidad, una suma y una multiplicaci on. Si se
separan las sumas de las multiplicaciones el coste operativo vara. Como el
n umero de operaciones necesario para resolver un sistema triangular es
n
2
2
,
tenemos que cuando p = 1 y n sucientemente grande, el trabajo principal
del algoritmo recae en la reduccion del sistema a una forma triangular.
10.3. Estabilidad numerica y su mejora. Pivotado
total y parcial. Complejidad algortmica
Hemos visto que el algoritmo de eliminaci on gaussiana es inoperativo si
alg un elemento a
(k)
kk
= 0.
Vamos a ver como remodelar el metodo anterior para hacer el pro-
cedimiento operativo. La idea es buscar en este caso otro elemento a
(k)
ik
,
i = k + 1, ..., n, en la columna k que sea distinto de 0. Dicho elemento tie-
ne que existir, puesto que en otro caso, las k primeras las de A seran
linealmente dependientes y recordemos que A es no singular. Sea a
(k)
rk
= 0 el
elemento buscado, entonces podemos intercambiar las las k y r y proceder
con la eliminaci on. De esto se sigue que cualquier sistema no singular de
ecuaciones puede ser reducido a una forma triangular mediante la elimina-
cion gaussiana combinada con un intercambio de las.
Para asegurar la estabilidad numerica, a menudo es necesario realizar el
10.3 Estabilidad numerica y su mejora. Pivotado total y parcial. Complejidad algortmica183
intercambio de las incluso cuando el pivote no es ex actamente 0, pero es
cercano a 0.
Ejemplo 10.1
x
1
+ x
2
+ x
3
= 1
x
1
+ 1,0001x
2
+ 2x
3
= 2
x
1
+ 2x
2
+ 2x
3
= 1
El sistema triangular que resulta es:
x
1
+ x
2
+ x
3
= 1
0,0001x
2
+ x
3
= 1
9999x
3
= 10000
Si realizamos las sustitucion hacia atr as y permitimos tres decimales:
x
3
= 1, x
2
= 0, x
1
= 0
mientras que si permitimos cuatro decimales:
x
3
= 1,0001, x
2
= 1,0001, x
1
= 1
Si resolvemos el sistema con pivotaje, cambiando las las 2 y 3, el sistema
triangular que obtenemos es:
x
1
+ x
2
+ x
3
= 1
x
1
+ 2x
2
+ 2x
3
= 1
x
1
+ 1,0001x
2
+ 2x
3
= 2

x
1
+ x
2
+ x
3
= 1
x
2
+ x
3
= 0
0,9999x
3
= 1
Resolviendo con tres decimales:
x
3
= 1, x
2
= 1, x
1
= 1
y si permitimos cuatro decimales:
x
3
= 1,0001, x
2
= 1,0001, x
1
= 1
De este modo para prevenir posibles errores, es necesario elegir el pivote
en el paso k, de una de las siguientes maneras:
Pivotaje parcial
184 10 Soluci on de sistemas de ecuaciones lineales
Elegir r como el menor entero para el que
[a
(k)
rk
[ = max [a
(k)
ik
[, k i n
k = 1, ..., n 1 e intercambiar las k y r
a
(k+1)
ij
=

a
(k)
ij

i = 1, ..., k, j
i = k + 1, ..., n, j = 1, ..., k 1
0 i = k + 1, ..., n, j = k
a
(k)
ij
m
ik
a
(k)
kj
j = k + 1, ..., n + 1
donde m
ik
=
a
(k)
ik
a
(k)
kk
, i = k + 1, ..., n
10.3 Estabilidad numerica y su mejora. Pivotado total y parcial. Complejidad algortmica185
Pivotaje total
Elegir r y s como los menores enteros para los que
[a
(k)
rs
[ = max [a
(k)
ij
[, k i, j n
k = 1, ..., n 1 e intercambiar las k y r y las columnas k y s
a
(k+1)
ij
=

a
(k)
ij

i = 1, ..., k, j
i = k + 1, ..., n, j = 1, ..., k 1
0 i = k + 1, ..., n, j = k
a
(k)
ij
m
ik
a
(k)
kj
j = k + 1, ..., n + 1
donde m
ik
=
a
(k)
ik
a
(k)
kk
, i = k + 1, ..., n
En cuanto al coste operativo, hemos visto que en el metodo de elimina-
cion gaussiana sin pivotaje para un sistema de ecuaciones, necesita en torno
a
n
3
3
operaciones.
En el caso de pivotaje parcial, en cada paso k hay que a nadir un nuevo
tipo de operaci on: b usqueda del m aximo de (n k + 1) valores.
Una forma de determinar el m aximo de r elementos (v
1
, ..., v
r
) es:
Comparar v
1
con v
2
: obtenemos maxv
1
, v
2
.
Comparar m axv
1
, v
2
con v
3
: obtenemos maxv
1
, v
2
, v
3
.

Comparar m axv
1
, ..., v
r1
con v
r
: obtenemos maxv
1
, ..., v
r
.
El n umero de comparaciones necesarias es r1. De manera que el n umero
total de operaciones es aproximadamente:
n
3
3
+
n1

k=1
(n k)
n
3
3
+
n
2
2
En el caso del pivotaje total, en cada paso k hay que realizar la b usqueda
del maximo de (n k + 1)
2
elementos, de manera que el n umero total de
operaciones es:
n
3
3
+
n1

k=1
[(n k + 1)
2
1] =
n
3
3
+ [n
2
+ (n 1)
2
+... + 2
2
] (n 1) =

n
3
3
+
n
3
3
=
2n
3
3
es decir el n umero de operaciones se duplica.
186 10 Soluci on de sistemas de ecuaciones lineales
10.4. La variante de Gauss-Jordan
Se trata de un metodo tan eciente como la eliminaci on gaussiana y m as
caro en cuanto a coste operativo. Se eliminan inc ognitas transformando el
sistema en diagonal. Dado el sistema Ax = b, A no singular, existe una unica
soluci on.
a
11
x
1
+ +a
1n
x
n
= b
1

a
n1
x
1
+ +a
nn
x
n
= b
n
Si a
11
= 0, podemos eliminar x
1
de las (n 1) restantes ecuaciones
restando de la i-esima ecuacion la primera multiplicada por m
i1
=
a
i1
a
11
,
i = 2, ..., n. De manera que el sistema resultante es:
a
11
x
1
+ a
12
x
2
+ +a
1n
x
n
= b
1
a
(2)
22
x
2
+ +a
(2)
2n
x
n
= b
(2)
2

a
(2)
n2
x
2
+ +a
(2)
nn
x
n
= b
(2)
n
Si a
(2)
22
= 0, eliminamos x
2
de las (n 1) restantes ecuaciones.
-. Para eliminarla de la primera ecuaci on, restamos de dicha ecuaci on la
segunda multiplicada por:
m
12
=
a
12
a
(2)
22
-. Para eliminarla de las (n 2) ultimas, restamos de cada ecuacion la
segunda multiplicada por:
m
i2
=
a
(2)
i2
a
(2)
22
, i = 3, 4, ..., n
De nuevo a los elementos a
11
, a
(2)
22
, ..., a
(n)
nn
se les denomina pivotes. Si
todos ellos son no nulos, se realiza la eliminaci on, llegando despues de n
pasos al sistema:
a
(n+1)
11
x
1
= b
(n+1)
1
a
(n+1)
22
x
2
= b
(n+1)
2

a
(n+1)
nn
x
n
= b
(n+1)
n
10.4 La variante de Gauss-Jordan 187
que puede resolverse:
x
k
=
b
(n+1)
k
a
(n+1)
kk
, k = 1, ..., n
Es decir, la eliminaci on se realiza en n pasos, k = 1, ..., n. Si A
(1)
= A y
b
(1)
= b.
A
(1)
= A, b
(1)
= b
k = 1, ..., n a
(k+1)
ij
=

a
(k)
ij

j = 1, ..., k 1, i
j = k, ..., n, i = k
0 j = k, i = k
a
(k)
ij
m
ik
a
(k)
kj
j = k + 1, ..., n, i = k
donde m
ik
=
a
(k)
ik
a
(k)
kk
b
(k+1)
i
=

b
(k)
i
i = k
b
(k)
i
m
ik
b
(k)
k
i = k
En cuanto al pivotaje, tanto el parcial como el total se realizan de manera
similar al realizado en la eliminaci on gaussiana. El pivote se busca entre los
elementos de la columna posteriores al elemento considerado, no entre los
anteriores.
Coste operativo
En cada paso k:
-. Divisiones: 1 por cada la para calcular el multiplicador m
ik
, i = k.
(n 1) las.
-. Multiplicaciones y sumas (contadas como un sola operaci on): 1 por
cada uno de los (nk+1) elementos de cada la y hay (n1) las. Adem as
1 por cada termino independiente y hay (n 1).
En total:
n

k=1
[1 + (n k + 1) + 1](n 1) =
= (n 1)
n

k=1
[2 + (n k + 1)] =
= (n 1)[2n +
n

k=1
(n k + 1) =
188 10 Soluci on de sistemas de ecuaciones lineales
= 2n(n 1) + (n 1)
n(n + 1)
2

n
3
2
Faltaran las n divisiones necesarias para resolver el sistema diagonal.
Observaci on:
Eliminaci on gaussiana:
n
3
3
+
n
2
2

n
3
2
+n en la variante de Gauss-Jordan.
Captulo 11
Soluci on de sistemas de ecuaciones
lineales sin inversi on de la matriz de
coecientes
11.1. La descomposici on LU
Hemos visto que el metodo de eliminaci on gaussiana permite tratar va-
rios sistemas con distintos terminos independientes. Sin embargo en muchas
ocasiones, todos los terminos independientes no se conocen al principio. Po-
demos querer resolver por ejemplo:
Ax
1
= b
1
, Ax
2
= b
2
, donde b
2
es funci on de x
1
Parece entonces que habra que repetir el proceso de eliminaci on desde
el principio, con un coste considerable en cuanto al n umero de operaciones.
Veremos como evitar esto.
Supongamos que tenemos una descomposicion de A en el producto de
una matriz triangular inferior y otra triangular superior. A = LU.
Entonces el sistema Ax = b LUx = b, que se puede descomponer en
dos sistemas triangulares:
Ux = y, Ly = b
De manera que si conocieramos L y U podramos resolver Ax = b, con
2
1
2
n
2
= n
2
operaciones.
Teorema 11.1 Sea A una matriz nxn, y denotemos por A
k
la matriz
de orden kxk formada por la intersecci on de las k primeras las y columnas
189
19011 Soluci on de sistemas de ecuaciones lineales sin inversion de la matriz de coecientes
de A. Si det(A
k
) = 0, k = 1, ..., n 1, entonces existe una unica matriz
triangular inferior L = (l
ij
) con l
ii
= 1, i = 1, ..., n y una unica matriz
triangular superior U = (u
ij
), tal que LU = A.
11.1 La descomposici on LU 191
Demostracion:
Por inducci on sobre n. Para n = 1, la descomposicion a
11
= 1u
11
es
unica. Supongamos que el resultado es cierto para n = k 1 y lo vamos a
probar para n = k.
Descomponemos A
k
, L
k
y U
k
de la siguiente forma:
A
k
=

A
k1
b
c
T
a
kk

, L
k
=

L
k1
0
l
T
1

, U
k
=

U
k1
u
0 u
kk

donde b, c, l y u son vectores columna con n1 componentes: si formaramos


el producto L
k
U
k
y lo identicamos con A
k
:
L
k1
U
k1
= A
k1
, L
k1
u = b
l
T
U
k1
= c
T
, l
T
u +u
kk
= a
kk
Por hip otesis de inducci on, L
k1
y U
k1
estan determinadas de manera
unica y como det(A
k
) = det(L
k
)det(U
k
) = 0, son no singulares.
Se sigue que u y l quedan unicamente determinadas por los sistemas
triangulares: L
k1
u = b, l
T
U
k1
= c
T
, s y solo si U
T
k1
l = c. Finalmente
u
kk
= a
kk
l
T
u. Entonces L
k
y U
k
estan unicamente determinadas, lo cual
concluye la demostraci on.
Ejemplo 1: Si para alg un k, det(A
k
) = 0, puede no existir la descom-
posici on LU de A. Por ejemplo si
A =

0 1
1 1

y suponemos que A = LU, donde


LU =

l
11
0
l
21
l
22

u
11
u
12
0 u
22

l
11
u
11
l
11
u
12
l
21
u
11
l
21
u
12
+l
22
u
22

0 1
1 1

Entonces forz osamente l


11
= 0 o u
11
= 0, pero entonces la primera la
de L o la primera columna de U son 0, por lo tanto A = LU.
Sin embargo si las las de A se intercambian, entonces la matriz se con-
vierte en triangular (superior), y la descomposici on LU existe trivialmente.
En efecto, para cualquier matriz no singular A, las las pueden ser reor-
denadas, de manera que exista una transformaci on LU. Esto se sigue de
la equivalencia entre la eliminaci on gaussiana y la descomposicion LU que
veremos ahora.
19211 Soluci on de sistemas de ecuaciones lineales sin inversion de la matriz de coecientes
Supongamos primero que la matriz A es tal que la eliminaci on gaussiana
puede ser llevada a cabo sin intercambio de las ni columnas. Podemos
pensar en la eliminaci on como en la generacion de una sucesi on de matrices,
A = A
(1)
, A
(2)
, ..., A
(n)
, donde A
(k)
= (a
(k)
ij
) es la matriz que se obtiene antes
de realizar la eliminaci on de los coecientes correspondientes a la variable
x
k
:

a
(1)
11
a
(1)
12
a
(1)
1k
a
(1)
1n
a
(2)
22
a
(2)
2k
a
(2)
2n

a
(k)
kk
a
(k)
kn

a
(k)
nk
a
(k)
nn

Consideremos ahora un cierto elemento a


ij
de A durante la eliminaci on.
Si a
ij
esta en la diagonal principal o sobre esta (es decir i j), entonces:
a
(n)
ij
= ... = a
(i+1)
ij
= a
(i)
ij
, i j
Si a
ij
esta bajo la diagonal principal, (i > j), entonces:
a
(n)
ij
= ... = a
(j+1)
ij
= 0, i > j
Ademas los elementos a
ij
son transformados en cada iteraci on k =
1, ..., r, donde r = mn(i 1, j), de manera que
a
(k+1)
ij
= a
(k)
ij
m
ik
a
(k)
kj
Si sumamos estas ecuaciones para k = 1, ..., r, obtenemos:
r

k=1
a
(k+1)
ij
=
r

k=1
a
(k)
ij

r

k=1
m
ik
a
(k)
kj

r

k=1
a
(k+1)
ij

r

k=1
a
(k)
ij
=
r

k=1
m
ik
a
(k)
kj

a
(r+1)
ij
a
(1)
ij
=
r

k=1
m
ik
a
(k)
kj
expresion que puede ser escrita: (a
(1)
ij
= a
ij
)
a
ij
=

a
(i)
ij
+

i1
k=1
m
ik
a
(k)
kj
, si i j, (r = i 1)
0 +

j
k=1
m
ik
a
(k)
kj
, si i > j, (r = j)
11.1 La descomposici on LU 193
o, si denimos m
ii
= 1, i = 1, ..., n:
a
ij
=
p

k=1
m
ik
a
(k)
kj
, p = mni, j
Sin embargo, estas ecuaciones son equivalentes a la ecuacion A = LU,
donde los elementos no nulos de L y U son:
(L)
ik
= m
ik
, i k
(U)
kj
= a
(k)
kj
, j k
Conclumos entonces que los elementos de L, son los multiplicadores
calculados en la eliminaci on gaussiana y la matriz U, la descomposicion
nal de A obtenida mediante eliminaci on gaussiana.
Entonces para obtener la matriz L, debemos guardar los multiplicadores,
m
ik
=
a
(k)
ik
a
(k)
kk
, en los lugares correspondientes a los elementos a
(k)
ik
. Ademas los
elementos de la diagonal de L no necesitan ser almacenados.
Gr acamente;
A =

a
11
a
1n


a
n1
a
nn

u
11
u
1n
m
21
u
22
u
2n

m
n1
m
nn1
u
nn

= LU
Ademas, como detL = 1, detA = a
(1)
11
a
(n)
nn
. En general:
det(L
k
) = 1 det(A
k
) = a
(1)
11
a
(k)
kk
, k = 1, ..., n
Entonces, la eliminaci on gaussiana puede ser realizada sin intercambio de
las o columnas, s y solo si det(A
k
) = 0, k = 1, ..., n1, que es la condici on
para la descomposicion unica en el Teorema 11.1.
Nota: Sabemos que la eliminaci on gaussiana puede resultar ines-
table sin pivotaje parcial, de manera que es usual el intercambio
de las durante la eliminaci on. Es importante se nalar que en este
19411 Soluci on de sistemas de ecuaciones lineales sin inversion de la matriz de coecientes
caso, despues de los correspondientes intercambios obtendremos
una descomposicion LU = A

, donde A

es la matriz que resul-


tara si los intercambios realizados en la eliminaci on, se realizan
en el mismo orden en la matriz A.
11.2. Escenas compactas en la eliminaci on gaus-
siana: metodos de Doolittle, Crout y Cho-
leski
Cuando resolvemos un sistema de ecuaciones lineales mediante elimina-
cion gaussiana tenemos que obtener aproximadamente
n
3
3
resultados inter-
medios, uno para cada operaci on. Incluso para valores peque nos de n esto
resulta bastante tedioso, y en ocasiones da lugar a errores. Sin embargo,
es posible ordenar los calculos de manera que los elementos de L y U se
obtengan directamente. Como en la eliminaci on gaussiana, en el paso k se
determinan los elementos de la la k-esima de U y de la columna k-esima
de L, pero en el metodo compacto, los elementos a
ij
, i, j > k estan todava
sin intercambiar. La ecuaci on matricial A = LU es equivalente a
a
ij
=
r

p=1
m
ip
u
pj
, r = mn(i, j), m
ip
= l
ip
Esto puede suponer unas n
2
operaciones para las n(n +1) inc ognitas en
L y U. Para el k-esimo paso utilizamos las siguientes ecuaciones:
a
kj
=
k

p=1
m
kp
u
pj
, j k,
a
ik
=
k

p=1
m
ip
u
pk
, i > k,
Si hacemos m
kk
= 1, entonces los elementos
u
kk
, u
kk+1
, ..., u
kn
, y m
k+1k
, ..., m
nk
pueden ser obtenidos en este orden de las expresiones:
u
kj
= a
kj

k1

p=1
m
kp
u
pj
, j k
m
ik
=
a
ik

k1
p=1
m
ip
u
pk
u
kk
, i > k
11.2 Escenas compactas en la eliminaci on gaussiana: metodos de Doolittle, Crout y Choleski195
Este se denomina metodo de Doolittle y da como resultado los mismos
factores L, U que la eliminaci on gaussiana. El metodo es incluso numeri-
camente equivalente a la eliminaci on gaussiana, aunque para ordenadores
donde las sumas y los productos pueden ser acumuladas en doble precisi on,
este metodo presenta ventajas computacionales sobre el metodo de elimina-
cion gaussiana.
Si en lugar de normalizar de manera que m
kk
= 1, lo hacemos de manera
que los elementos de la diagonal principal de U cumplan u
kk
= 1, k = 1, ..., n,
se obtiene el metodo de Crout. En este caso, las ecuaciones para el paso k
son:
m
ik
= a
ik

k1

p=1
m
ip
u
pk
, i = k, k + 1, ..., n
u
kj
=
a
kj

k1
p=1
m
kp
u
pj
m
kk
, j = k + 1, ..., n
En ambos casos hemos supuesto que no ha habido intercambio de las
(pivotaje). Sin embargo es esencial pensar en ambos metodos como candi-
datos a ser combinados con estrategias de pivotaje (al menos parcial). Mas
difcil resulta, sin embargo, realizar pivotaje total en cualquiera de los dos
casos.
Caso de matrices simetricas denidas positivas
Sea A = (a
ij
) una matriz simetrica i.e. a
ij
= a
ji
, 1 i, j n. Proba-
remos que si la eliminaci on gaussiana se realiza, sin intercambio de las ni
columnas (pivotaje), entonces:
a
(k)
ij
= a
(k)
ji
, k i, j n
es decir, los elementos transformados forman matrices simetricas de orden
(n + 1 k), k = 2, ..., n. Esta armaci on se puede probar por inducci on. Es
cierta para k = 1. En el paso k, los elementos son transformados de acuerdo
con:
a
(k+1)
ij
= a
(k)
ij
m
ik
a
(k)
kj
= a
(k)
ij

a
(k)
ik
a
(k)
kk
a
(k)
kj
Si la hip otesis de simetra es cierta para alg un k: (a
(k)
ij
= a
(k)
ji
)
a
(k+1)
ij
= a
(k+1)
ji
= a
(k)
ji
m
jk
a
(k)
ki
= a
(k)
ji

a
(k)
jk
a
(k)
kk
a
(k)
ki
19611 Soluci on de sistemas de ecuaciones lineales sin inversion de la matriz de coecientes
y se obtiene el resultado.
Hemos visto entonces, que si la eliminaci on gaussiana se realiza sin pi-
votaje, en una matriz simetrica, tenemos que computar unicamente los ele-
mentos de A
(k)
, que estan en la diagonal principal o sobre ella. Esto signica
que el n umero de operaciones realizado es aproximadamente
n
3
6
y se ahorra
aproximadamente la mitad del espacio en memoria.
Sin embargo, no es siempre posible realizar eliminacion gaussiana sin
pivotaje sobre matrices simetricas. La simetra se preserva si elegimos cual-
quier elemento de la diagonal principal como pivote y completamos el pivo-
taje, pero como muestra la matriz

0 1
1

, [[ << 1
a veces esto no resulta estable. Por otro lado, el intercambio de las destruye
la simetra, lo cual se ve en el mismo ejemplo. De manera que la eliminacion
gaussiana anterior no se puede utilizar para cualquier matriz simetrica.
Para matrices simetricas denidas positivas, la eliminaci on gaussiana sin
pivotaje es siempre estable. Una forma de determinar si una matriz es deni-
da positiva es utilizar el siguiente criterio que enunciamos sin demostracion.
Teorema 11.2(Criterio de Sylvester) Una matriz simetrica de orden
nxn es denida positiva s y s olo si det(A
k
) > 0, k = 1, ...n, donde A
k
es la
matriz kxk formada por las k primeras las y columnas de A.
Es decir, todos los pivotes de A son positivos s y s olo si A es denida
positiva y la eliminaci on gaussiana est a bien denida.
11.2 Escenas compactas en la eliminaci on gaussiana: metodos de Doolittle, Crout y Choleski197
Teorema 11.3 Para una matriz simetrica denida positiva A:
[a
ij
[
2
a
ii
a
jj
y por lo tanto los mayores elementos de A estan en la diagonal.
Demostracion:
Si la misma permutaci on se aplica a las las y columnas de A, la ma-
triz resultante es simetrica y denida positiva. Para k = 2 y una cierta
permutaci on, el Terorema 11.2, da
0 < det

a
ii
a
ij
a
ji
a
jj

= a
ii
a
jj
(a
ij
)
2
de donde se obtiene el resultado.
Teorema 11.4 Sea A una matriz simetrica y denida positiva. Entonces
existe una unica matriz triangular superior R con elementos positivos en la
diagonal principal tal que A = R
T
R.
Demostracion:
Del Teorema 11.1, tenemos que A = LU, donde u
11
= a
11
> 0 y
u
kk
=
det(A
k
)
det(A
k1
)
> 0, k = 2, ..., n
Si introducimos la matriz diagonal D = diag(u
11
, ..., u
nn
), podemos es-
cribir:
A = LU = LDD
1
U = LDU

, U

= D
1
U
donde L y U

son matrices triangulares con unos en la diagonal principal y


estan unicamente determinadas.
Como A es simetrica:
A = A
T
= (U

)
T
DL
T
o L
T
= U

= D
1
U
Ahora, si hacemos R = D

1
2
U, donde D

1
2
tiene elementos positivos en
su diagonal (u

1
2
kk
), obtenemos:
R
T
R = U
T
D

1
2
D

1
2
U = U
T
D
1
U = LU = A
19811 Soluci on de sistemas de ecuaciones lineales sin inversion de la matriz de coecientes
Por lo tanto, podemos armar que la eliminaci on gaussiana sin pivotaje
es siempre posible y estable en matrices simetricas denidas positivas y
ademas es posible elegir los elementos de la diagonal de L reales y de manera
que U = L
T
.
As, la eliminaci on compacta en este caso particular, nos dara expresio-
nes en las que u
kk
= m
kk
y u
pk
= m
kp
,
u
kk
= m
kk
= (a
kk

k1

p=1
m
2
kp
)
1
2
u
ki
= m
ik
=
a
ik

k1
p=1
m
ip
m
kp
m
kk
, i = k + 1, ..., n
Este metodo se denomina metodo de Choleski o de la raz cuadrada. En
cuanto a coste operativo:
Son necesarias del orden de
n
2
2
operaciones hasta obtener la descompo-
sicion LL
T
, +n races cuadradas, (1 raz cuadrada 6 operaciones). Luego
n
2
2
+ 6n operaciones.
LL
T
x = b
L
T
x = y sistema triangular inferior (
n
2
2
operaciones).
Ly = b sistema triangular superior (
n
2
2
operaciones).
Total:
n
2
2
+
n
2
2
+
n
2
2
+ 6n =
3n
2
2
(+6n de las races cuadradas).

Potrebbero piacerti anche