Академический Документы
Профессиональный Документы
Культура Документы
HolaMundo.pascal(online):Captulo8
Ms Siguienteblog
Crearblog Acceder
.c
.java
4 DE MARZ O DE 2 0 0 8
Captulo 8
.
Arboles y Recursividad
Decimos que una definicin es recursiva cuando "define en funcin de si misma". Un algoritmo
es recursivo cuando para resolver el problema se invoca a si mismo una y otra vez hasta
resolverlo.
Para comprender esto lo mejor ser analizar algunos casos tpicos como la funcin matemtica
factorial.
Definimos la funcin factorial de la siguiente manera: Sea x perteneciente al conjunto de los
nmeros naturales (incluyendo al cero) entonces:
factorial(x) = x*factorial(x1)
factorial(0) = 1
La definicin recurre a si misma para expresar lo que define. Es una definicin recurrente o
recursiva.
Desde el punto de vista de la programacin, programar esta funcin recursiva no implica ni
ms ni menos que seguir al pie de la letra su definicin matemtica.
Recursos / Download
SZDelphi (por JDownloader)
Instalacin y uso de Delphi
Plantilla de Diagramas p/Visio
Contenidos
1 Introduccin
2 Operadores
3 Procedimientos y Funciones
4 Corte de Control
5 Arrays
6 Archivos de Registros
7 Estructuras Dinmicas
Como vemos, la funcin recibe un parmetro x. Si x es igual a 0 entonces retorna 1. Si x es
mayor que cero entonces retorna el producto de x por "lo que retorna la misma funcin"
invocndola con el parmetro (x1).
A continuacin vemos un programa principal que lee un valor n y muestra el factorial de n
utilizando la funcin recursiva.
8 Arboles y Recursividad
9 Teora de Objetos (intro)
EjemplosResueltos
Ahora en Video!
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
1/15
15/10/2015
HolaMundo.pascal(online):Captulo8
No al blog de Java
//funcionfactorialrecursiva
functionfactorial(x:integer):longint;
begin
if(x=0)thenbegin
factorial:=1;
endelsebegin
factorial:=x*factorial(x1);
end;
end;
Super Baterista
//programaprincipal
varn:integer;
begin
write('Ingreseunvalor:');
read(n);
write('Elfactorialde',n,'es:');
writeln(factorial(n));
end.
Sitios Relacionados
PascAlgo (por Lic. Hugo Cuello)
Notemos que la funcin factorial puede resolverse tambin sin necesidad de utilizar
recursividad. De hecho, ese fue uno de los primeros ejemplos que analizamos en este este
trabajo.
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
Algoritmia.net
Java Algorithm Framework
2/15
15/10/2015
HolaMundo.pascal(online):Captulo8
Si bien el algoritmo es bastante simple, esta versin resulta ms compleja que la versin
recursiva.
Pila de Llamadas
Para comprender bien el funcionamiento de los procedimientos y las funciones recursivas es
fundamental notar que cada llamada (o invocacin) a un procedimiento o funcin queda
apilada en una "pila de llamadas" de forma tal que cada procedimiento o funcin que se
encuentre apilado no podr finalizar su ejecucin si antes no finalizaron los procedimientos
y/o funciones que fueron llamados (apilados) con posterioridad.
Para hacerlo ms simple, si en la funcin f invocamos a la funcin g y en la funcin g
invocamos a la funcin h entonces la primer funcin en finalizar su ejecucin ser h, luego g y
por ltimo f. Y cuando finalice h, la funcin g continuar su ejecucin exactamente en la
lnea siguiente a la llamada a la funcin h. Y cuando finalice g, f continuar su ejecucin en la
lnea siguiente a la llamada a g.
Cuando tenemos el caso de una funcin recursiva, cada invocacin recursiva (a si misma) ser
apilada en la pila de llamadas, as que la invocacin a la funcin no terminar hasta que no
terminen las invocacines que fueron apiladas posteriormente.
Esto lo podemos probar con el siguiente ejemplo:
testRecursivo.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
//procedimientorecursivo
proceduremiProcRecursivo(n:integer);
begin
writeln('comienza(n=',n,')');
if(n<4)thenbegin
miProcRecursivo(n+1);
end;
writeln('finaliza(n=',n,')');
end;
//programaprincipal
begin
miProcRecursivo(1);
end.
La salida muestra la pila de llamadas: se llam al procedimiento con el parmetro 1, luego con
2, luego con 3, luego con 4. El primero en finalizar fue el que recibi el parmetro 4, luego el
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
3/15
15/10/2015
HolaMundo.pascal(online):Captulo8
3, luego el 2, luego el 1. "El ltimo que se llam fue el primero que finaliz".
Es muy importante notar que la funcin recursiva debe tener una condicin de corte. En este
ejemplo la condicin de corte
es if( n < 4 ). Si no estuviese esta condicin entonces el procedimiento se llamara a si mismo
permanentemente trayendo aparejadas dos consecuencias: primero el programa se colgara ya
que nunca retornar el control al programa principal y, segundo, dado que la pila de llamadas
se implementa en memoria, en algn momento la memoria libre se acabar y tendremos un
error llamado "stack overflow" que har finalizar abruptamente la ejecucin del programa.
Vamos a probarlo:
testRecursivo2.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
//procedimientorecursivo
proceduremiProcRecursivo(n:integer);
begin
writeln('comienza(n=',n,')');
//invocamosrecursivosiempre,sincondicion
miProcRecursivo(n+1);
//nuncasellegaaestepunto
writeln('finaliza(n=',n,')');
end;
//programaprincipal
begin
miProcRecursivo(1);
end.
El resultado es:
El programa corta abruptamente tirando un "Runtime error 202". El cdigo de error 202
indica que ocurri un "Stack Overflow" (desbordamiento de la pila). Y, de hecho, lo que
muestra en la consola (pantalla) es el "Stack Trace": estado de la pila de llamadas al momento
de ocurrir el error.
Volviendo a la funcin recursiva factorial ahora podemos notar que la condicin de corte es if(
x=0 ).
Problema 8.1
Se tiene el archivo EMPLEADOS.dat con la informacin de cada empleado de la compaia.
EMPLEADOS.dat
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
4/15
15/10/2015
HolaMundo.pascal(online):Captulo8
id (integer)
nombre (string[40])
idJefe (integer, hace referencia al id del jefe del empleado)
El archivo est ordenado por id de forma tal que cada id coincide con el nmero de registro
del archivo (menos 1, ya que los registros se numeran desde cero).
El "gerente general" de la compaia, tambin figura como empleado y su idJefe ser 1.
Se pide desarrollar un programa interactivo que permita ingresar el id de un empleado y luego
imprima toda la lnea jerrquica a la cual responde el empleado cuyo id se ingres.
Anlisis
El problema se resuelve muy facilmente utilizando un procedimiento recursivo que llamaremos
imprimirJefe.
Comencemos por definir la seccin type para luego analizar el programa principal en el que
haremos la invocacin al procedimiento recursivo.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
type
REmpleado=record
id:integer;
nombre:string[40];
idJefe:integer;
end;
FEmpleados=fileofREmpleado;
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
5/15
15/10/2015
HolaMundo.pascal(online):Captulo8
Problema 8.2
Se tiene el archivo FAMILIAS.dat con la informacin de un conjunto de familias, con el
siguiente formato.
FAMILIAS.dat
nombre string[30]
cantHijos integer
El archivo est ordenado de forma tal que "para cada persona, sus hijos se encuentran a
continuacin". Veamos un ejemplo:
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
6/15
15/10/2015
HolaMundo.pascal(online):Captulo8
Anlisis
Para resolver este problema vamos a pensar en una funcin que llamaremos leerFamilia. Esta
funcin retornar true o false segn haya podido procesar correctamente los datos de una
familia del archivo. Si retorna true, en los parmetros de salida retornar el nombre y la
cantidad de integrantes de la familia que proces.
Con esta funcin (que luego analizaremos en detalle) el programa principal queda resuelto de
la siguiente manera.
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
7/15
15/10/2015
HolaMundo.pascal(online):Captulo8
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
8/15
15/10/2015
HolaMundo.pascal(online):Captulo8
La funcin se ocupa de recorrer el archivo por lo tanto debe controlar que no llegue el eof.
Retorna true o false si ley un registro y (en ese caso) asign valores a los parmetros suma y
nomFlia. Recordemos que al leer el ltimo registro de un archivo la funcin eof retorna true
por este motivo el if( eof(arch) ) lo hacemos al comienzo de la funcin y si retorna true
entonces finalizamos la funcin retornando false (no hay ms registros por leer).
Basicamente la funcin lee un registro (una persona), obtiene la cantidad de hijos de esta
persona y luego entra en un for donde se llama a si misma tantas veces como hijos tiene la
persona que ley.
Dentro del for se incrementa la variable suma (parmetro que recibe por referencia) por lo
tanto, si la persona leida tiene tres hijos, el for iterar tres veces y la suma se incrementar
en 3, pero a su vez, dentro del for se invoca a la funcin pasndole la misma variable suma
por lo que si uno de los hijos de la persona leida tiene 5 hijos ms, la llamada recursiva
incrementar la (misma) variable suma otras cinco veces, y as.
Notemos tambin que antes de ingresar al for preguntamos si suma es cero. En este caso
estaremos ante la presencia de un "jefe de familia", entonces asignamos su nombre al
parmetro nomFlia. Justamente la variable suma (que en el programa principal es
totMiembros) se inicializa en cero antes de invocar a la funcin.
familias.pas
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
//
//secciontype
//
type
RFamilia=record
nombre:string[30];
cantHijos:integer;
end;
FFamilias=fileofRFamilia;
//
//funcionleerFamilia
//
functionleerFamilia(vararch:FFamilias
;varnomFlia:string[30]
;varsuma:integer):boolean;
var
reg:RFamilia;
nomFliaAux:string[30];
i:integer;
begin
//sinohaymasdatosenelarchivoretornafalse
if(eof(arch))thenbegin
leerFamilia:=false;
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
9/15
15/10/2015
HolaMundo.pascal(online):Captulo8
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
exit;//lafuncionfinalizaaqui...
end;
//leounregistro
read(arch,reg);
//sisumaesceroentoncesesun"jefe"deflia
if(suma=0)thenbegin
nomFlia:=reg.nombre;
end;
//iterotantasvecescomohijostienelapersona
fori:=1toreg.cantHijosdobegin
suma:=suma+1;//incrementounavezporhijo
//invocorecursivamentealafuncionpara
//procesarloshijosdeloshijos
leerFamilia(arch,nomFlia,suma);
end;
//lafuncionreturnatrueporquepudimosasignar
//datosalosparametrosnomFliaysuma
leerFamilia:=true;
end;
//
//programaprincipal
//
var
arch:FFamilias;reg:RFamilia;
nomFlia:string[30];
totMiembros:integer;
begin
//abroelarchivo
assign(arch,'FAMILIAS.dat');
reset(arch);
//inicializolavariableenlacuallafuncion
//sumaralosmiembrosdecadafamilia
totMiembros:=0;
//cadaiteracioncorrespondeaunanuevafamilia
while(leerFamilia(arch,nomFlia,totMiembros))
dobegin
//imprimounalineadellistado
writeln(nomFlia,'',totMiembros);
//reseteolavariable
totMiembros:=0;
end;
//cierroelarchivo
close(arch);
end.
Arboles
Los rboles son estructuras de datos recursivas. Esto es: nodos que tienen 2 o ms punteros a
nodos de su mismo tipo. Al representarlos graficamente vemos que tienen forma de rbol.
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
10/15
15/10/2015
HolaMundo.pascal(online):Captulo8
El rbol tiene una raz la cual tiene dos o ms ramas. Cada rama es en s misma una raz con
ms ramas y as, por lo tanto para recorrer un rbol necesitaremos un algoritmo recursivo.
Segn la cantidad de hijos que tengan sus nodos, los rboles se clasifican en rboles binarios
(2 hijos), rboles ternarios (3 hijos) o rboles narios (n hijos, con n variable).
El diagrama anterior representa un rbol binario.
En este captulo trabajaremos con rboles binarios. Luego, en los ejercicios explicaremos
otros casos a medida que se vayan planteando.
type
PNArbol=^NArbol;
NArbol=record
info:integer;
izq:PNArbol;//punteroalhijoizquierdo
der:PNArbol;//punteroalhijoderecho
end;
Ahora consideraremos un conjunto de valores numricos, sin ningn orden como podra ser el
conjunto A que veremos a continuacin:
A = { 5, 2, 8, 9, 1, 4, 6, 3, 7 }
Con los valores de este conjunto analizaremos las diferentes operaciones que podemos aplicar
a los rboles.
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
11/15
15/10/2015
HolaMundo.pascal(online):Captulo8
El primer valor es el 5, por lo tanto ser la raz del rbol. Luego ingresa un 2. Como es menor
que 5 lo ubicamos en el nodo izquierdo. Luego ingresa 8, que es mayor que 5 as que lo
ubicamos en su nodo derecho. A continuacin ingresa 9, es mayor que 5, pasamos a su nodo
derecho que tiene 8, como 9 es mayor que 8 lo ubicamos en su nodo derecho. Luego llega el 1
que es menor que 5. Vemos su nodo izquierdo y encontramos al 2. Como 1 es menor que 2 lo
ubicamos en su nodo izquierdo, y as hasta cargar todos los datos del conjunto.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
//programaprincipal
varraiz:PNArbol;
begin
agregarValor(raiz,5);
agregarValor(raiz,2);
agregarValor(raiz,8);
agregarValor(raiz,9);
agregarValor(raiz,1);
agregarValor(raiz,4);
agregarValor(raiz,6);
agregarValor(raiz,3);
agregarValor(raiz,7);
//:
end.
procedureagregarValor(varraiz:PNArbol;v:integer);
begin
//silaraizesnulaentoncesleasignomemoria
//guardolainformacion(v)yfinalizoelproc.
if(raiz=NIL)thenbegin
new(raiz);
raiz^.info:=v;
raiz^.der:=NIL;
raiz^.izq:=NIL;
exit;
end;
//siestoyacaesporquelaraiznoesNIL
//entoncessielvalorvquevamosaagregares
//menorqueelvalordelaraizvuelvoainvocar
//alprocedimientoconsiderandocomoraiz
//alhijoizquierdo.Siesmayoroiguallo
//invococonsiderandoalhijoderecho
if(v<raiz^.info)thenbegin
agregarValor(raiz^.izq,v);
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
12/15
15/10/2015
HolaMundo.pascal(online):Captulo8
22:
23:
24:
25:
26:
endelsebegin
agregarValor(raiz^.der,v);
end;
end;
Al cargar el rbol siguiendo este criterio decimos que el rbol es un arbol binario de
bsqueda. Es decir que podemos utilizar un rbol binario para realizar bsquedas con la
misma eficiencia que una bsqueda binaria y tambin podemos utilizarlo para ordenar
conjuntos.
Veremos que segn el recorrido que realicemos sobre sus nodos, los valores (en este caso
numricos) resultarn ordenados ascendentemente.
procedureinOrden(raiz:PNArbol);
begin
if(raiz<>NIL)thenbegin
inOrden(raiz^.izq);
writeln(raiz^.info);
inOrden(raiz^.der);
end;
end;
Basicamente lo que hace este procedimiento es llamarse a si mismo con el hijo izquierdo y
terminar si es NIL.
Hagamos un seguimiento con los datos del ejemplo que estamos analizando.
Invocamos al procedimiento pasndole un puntero a la raz del rbol: 5. Como es distinto de
NIL se llama a si mismo con su hijo izquierdo: 2, que como es distinto de NIL se llama a si
mismo con su hijo izquierdo: 1, que como es distinto de NIL se llama a si mismo con su hijo
izquierdo: NIL. Esta tima llamada finaliza sin hacer nada ya que no ingresa al if. As que
regresamos a la llamada anterior (la del 1). Imprime su valor (1) y se llama a si mismo con su
hijo derecho quien es NIL por lo tanto finaliza. En este momento finaliza la invocacin al
procedimiento con el valor 1, as que regresamos a la llamada anterior: imprime el (2) y se
llama a si misma con su hijo derecho: 4 que como es distinto de NIL se llama a si misma con su
hijo izquierdo: 3, y as sucesivamente.
Recorrido de PreOrden: Este recorrido considera primero la raz, luego el arbol izquierdo y
por ltimo el arbol derecho.
En preOrden, los nodos de nuestro rbol se listaran de la siguiente manera:
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
13/15
15/10/2015
HolaMundo.pascal(online):Captulo8
5, 2, 1, 4, 3, 8, 6, 7, 9
procedurepreOrden(raiz:PNArbol);
begin
if(raiz<>NIL)thenbegin
writeln(raiz^.info);
preOrden(raiz^.izq);
preOrden(raiz^.der);
end;
end;
Primero imprimimos, luego invocamos con el hijo izquierdo y luego con el hijo derecho.
Recorrido de Orden Posterior (o postOrden): este recorrido implica recorrer primero la rama
izquierda, luego la derecha y por ltimo procesar la raz.
En nuestro ejemplo, el recorrido postOrden arrojar el siguiente resultado:
1, 3, 4, 2, 7, 6, 9, 8, 5
y el algoritmos para procesarlo es el siguiente:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedurepostOrden(raiz:PNArbol);
begin
if(raiz<>NIL)thenbegin
postOrden(raiz^.izq);
postOrden(raiz^.der);
writeln(raiz^.info);
end;
end;
14/15
15/10/2015
HolaMundo.pascal(online):Captulo8
.
Publicado por PabloSZ
Entrada ms reciente
Pgina principal
Entrada antigua
http://holamundopascal.blogspot.mx/2008/03/capitulo8.html
15/15