Está en la página 1de 12

Notación Algorítmica y Números Naturales

(Extraído de los apuntes del Dr. Hibbard y otros apuntes)

Wirth dice: “Programa = Algoritmo + Estructura de datos”.


ALGORITMOS: Definiciones:
Conjunto de instrucciones perfectamente expresadas de tal forma que pueden ser
ejecutadas por una máquina o persona, sin apelar a conocimientos adicionales a
los que requieren las mismas instrucciones
Especificación paso a paso del planteamiento de la solución de un problema en
forma clara, precisa, de modo que para una entrada dada en un número finito de
pasos arroje una salida.
Conjunto finito y no ambiguo de etapas expresadas en un cierto orden, para unas
condiciones iniciales, permite resolver un problema en un tiempo finito.

Definicion de algoritmo basado en el concepto de programa (Wirth):


Todo programa describe una sucesión de transformaciones sobre el conjunto de sus
variables. Si el mismo programa se ejecuta dos veces con valores iniciales
diferentes, sería un error afirmar que los dos procesos o cálculos son iguales. Sin
embargo, ambos exhibirán el mismo comportamiento. La descripción de ese
comportamiento, sin hacer referencia a un procesador en particular, se llama
usualmente algoritmo

Un algoritmo explica como realizar o calcular algo determinado.


Cuando a los algoritmos los relaciones con las estructuras de datos, particulares a
un determinado lenguaje de programación, entonces construyo un programa.

Un algoritmo es un objeto matemático, es una función que debe poderse definir


simplemente en notación matemática.
Diferencia entre:
Notación: está dirigida a la mente humana. Cada mente hace el análisis sintáctico a
su modo.
Lenguaje Formal: está dirigido a la máquina, por lo tanto requiere de una rigurosa
sintaxis.

Aquí vamos a abocarnos a los algoritmos puros. Si bien, tenemos que tener en
cuenta las estructuras de datos con lo que trabajarán los algoritmos. No lo
relacionados con ningún lenguaje en particular, eliminando detalles propios de
implementadción de un determinado lenguaje de programación.

Por ejemplo, Deseamos realizar un algorimo para calcular la raiz cuadrada de un


número A. Utilizamos para ello la siguiente expresión:
(x=a1/2; x2=a; x.x=a; x=a/x; x+x=x+a/x; 2x=x+a/x; x=(x+a/x)/2)

Empezamos con X=3, luego X= (X + A / X) / 2


Hasta que X tenga la precisión necesaria.

Aquí hay una instrucción (no una ecuación X 2 = A, sino no hace falta el algoritmo)
“Para obtener el nuevo valor de X ponga el promedio entre el viejo valor de X y el
cociente de A y el viejo valor de X”.
Página1
Esto es una de las características de la notación algorítmica, lo que parece una
ecuación no lo es, sino una instrucción.

Otra característica en la notación algorítmica es la frase “La precisión necesaria”.


Depende del uso que se va a hacer del resultado. Puede ser que se requiera 2,
4, 12 o 20 cifras; o que el X sea tal que X2 este dentro de 0.0001 de A.
Empezamos con X=3, luego X= (X + A / X) / 2
Hasta que |X2 – A| < 
ó hasta que X no cambie

Por ejemplo sea A=2. Calcular la raiz cuadrada de A.


Aplico el Algoritmo:
a) X = 3
b) X = ( 3 + 2 / 3 ) / 2 = 1.8333333333...
c) X = ( 1.8333333333... + 2 / 1.8333333333... ) / 2 = 1.46212121212
d) X = ( 1.46212121212 + 2 / 1.46212121212 ) / 2 = 1.41499842989
e) X = ( 1.41499842989 + 2 / 1.41499842989 ) / 2 = 1.41421378005
f) X = ( 1.41421378005 + 2 / 1.41421378005 ) / 2 = 1.41421356237
g) X = ( 1.41421356237 + 2 / 1.41421356237 ) / 2 = 1.41421356237
Observe que las X son iguales en 11 dígitos decimales.

Si expresamos el problema en términos de números naturales, podemos


solucionar el problema de la Precisión de la computadora y el de la fiel
representación matemática.
Hagamos un algoritmo que produzca un número natural X tal que X 2 < A < (X+1)2
es decir la parte entera de raiz cuadrada de A.

Empezando con X=3 ponga


X= [ (X + [A / X] ) / 2 ] Los corchetes significan
parte entera
Hasta que: X = [A / X] ó X+1 = [A / X] ó [A / X]+1 = X

Ejemplo: raiz cuadrada de 9 X=3


a) X= [ (3 + [9 / 3] ) / 2 ] = 3 dos X consecutivos iguales o X = [A / X] ya que 3= [9 /3].

Ejemplo: raiz cuadrada de 2 X=3


a) X= [ (3 + [2 / 3] ) / 2 ] = 1
b) X= [ (1 + [2 / 1] ) / 2 ] = 1

Vamos a definir un algoritmo que nos aproxima a la raiz cuadrada de un número


natural “sin salir del dominio de ellos” y así evitar las aproximaciones que son
necesarias cada vez que necesitamos un número como 1/3 por ejemplo.
2 2
Es decir, dado un argumento a natural, devuelve el natural X tal que X < a < (X+1)
que lo expresa mencionando solo números naturales.

Página2
Raiz2(a) = iter(a,3)
donde
iter (a,x)=
sea c = cociente(a,x)
si (x=c ó x+1=c) x
sino
si (c+1=x) c
sino iter(a, cociente (x+cociente(a,x),2))

Note que cada invocación de iter es automática, funciona con ignorancia total del
contexto con que fue invocado.
La función cociente nos devuelve la parte entera de a / b, o sea un natural x tal que
x b <= a < (x+1) b (iter es local, cociente y raiz2 global)

Ejemplo: raiz2 (49) = iter (49,3)


iter(49,3)=
sea c = [49 / 3] = 16
si (3 = 16 o 3+1=16 o 16+1 = 3) x
sino
si (16+1 =3) c
sino iter (49,[(x +[a / x]) / 2])
________________________
iter(49, [(3 +[49 / 3]) / 2]) =
iter(49, 9)=
c=[49 / 9]= 5
iter(49,7)
________________________
iter(49,7)=
c=[49 / 7] = 7 retorna x=7

Queda por resolver cociente (a,b)

Página3
Programación Funcional

Todo programa de computadora es una función, y = f(x) donde x es el argumento;


(todos los datos que el usuario entrega sea desde el teclado, mouse, disquete
etc.) dichos datos se transforman mediante f(x) y devuelven valores (toda la
información que el programa entrega al usuario). Por eso la notación matemática
para describir funciones es útil para la programación. Hay lenguajes formales
orientados a la expresión de programas en su naturaleza de función. Pero no son
muy populares porque:

a) Algunos problemas parecen dificiles de interpretar.


b) En otros problemas la “expresión” en base de funciones queda muchísimo mas
corta, pero para también nos interesa la “eficiencia” y la máquina por si sola no
maneja eficientemente la invocación de una función. Hay rutinas muy ineficientes
en lenguaje funcional, ya que ocupan muchísimos recursos y el tiempo de
proceso es elevado. (ejemplo Fibonacci)

Si lo que hace un programa es realizar una función , lo primero que tiene que
entender el programador es saber ¿Cuál es la función?. Independientemente de
como van a ser los mecanismos para invocar la función y a las subfunciones que
de ella dependen.

Entonces queremos tratar el problema de llevar a cabo en una computadora una


función definida con precisión matemática.
No podemos con un lenguaje formal definir con presición matemática una función.
Pero la idea es que a cualquiera que sepa leer, no le queden dudas de cuál es la
función. (es decir una vez que uno comprende la definición de una función –
notación - las demás son comprendibles).

Entender una función y saber calcularla para cualquier argumento son dos cosas
muy distintas.
En un extremo tenemos funciones difíciles de calcular y fáciles de definir y en el otro
extremo están las funciones fáciles de calcular y difíciles de definir.
Trabajaremos con funciones que no son muy difíciles de definir y no son triviales
para calcularlas. La mayoría se definen con notación matemática estandard
aumentada con unos cuantos artificios especiales para expresar algoritmos.

Como ejemplo veremos la función máximo común divisor. Dado a y b con a ? b el


máximo comun divisor (mcd) es menor igual que a, pues tiene que dividir a “a” y a
“b” respectivamente. Podemos ir probando a, a-1, a-2 etc. El primero que
encontremos es el mcd (el 1 divide a “a” y a “b”, por lo que siempre habrá un
mcd).

Supongamos que a y b son distintos de cero, iterativamente sería:


para p= min(a,b) disminuyendo hasta 2 hacer
si resto (a,p) = 0 & resto(b,p)=0 contestar p
contestar 1

Note que en cada iteración de la “para” estamos evaluando una función de tres
argumentos a,b,y p. Esta función, cada vez que inicia una nueva iteración, se
Página4
está llamando a sí misma. Si llamamos a esta función mcd1 se la puede definir
así:

mcd1(a,b,p)=
si (p=1) 1
sino
si (resto(a,p) = resto(b,p)=0) p
sino mcd1(a,b,p-1)

Si p=1 el mcd vale 1 sino evaluamos los dos restos de a y b dividido p. Si son = 0 la
función vale p, sino vale lo que que da ella misma aplicada en los argumentos a,
b, p-1.

La función máximo común divisor puede definirse:


mcd(a,b)=mcd1(a,b,max(min(a,b),1))

Ejemplo: mcd(16,12)
Algoritmo iterativo: Para p=12 ..........(-1)..............2
resto (a,p)=0 & resto(b,p)=0 luego p
luego 1
p a b resto(a,p) resto(b,p)
12 16 12 ≠0 =0
11 ≠0 ≠0
10 ≠0 ≠0
9 ≠0 ≠0
8 =0 ≠0
7 ≠0 ≠0
6 ≠0 =0
5 ≠0 ≠0
4 =0 =0 luego 4

Algoritmo funcional: mcd(16,12)=mcd1(16,12,max(min(16,12),1))


a b p
16 12 12
16 12 11
16 12 10
16 12 4 luego 4

Lo que se desea traslucir no es que un procedimiento iterativo puede expresarse


como una función, sino que es una función, que como programadores no dejamos
de crear funciones por el mero hecho de dejar la sintaxis de ellas. En el ejemplo
del mcd, el mismo se nos presenta como un problema nétamente iterativo cuando
en su esencia es funcional.

Veamos otro algoritmo para encontrar el mcd que no es “netamente iterativo”. El


algoritmo de Euclides.
mcd(a,b)= si (b=0) a sino mcd(b, resto(a,b)).

Ejemplo: mcd (12,16)= mcd (16,4)= mcd (4,4) = mcd (4,0)=4


La superioridad es notable (*para simplificar diremos mcd(0,0) es 0. Euclides no conocía el 0)
Página5
ESQUEMA

Dos principios claves para la construcción de funciones:

1º) COMPOSICIÓN: Dadas funciones f y g de un (o más) argumento, puedo definir


h(x)=f(g(x)), o invocar la expresión f(g(x)) sin darle un nombre (en el ejemplo, la
función resto fue usada en composición) f o g puede invocar a h de nuevo
(directa o indirectamente).

2º) SELECCIÓN CONDICIONAL: Es cuando decimos que si tal cosa ocurre


entonces tal valor, sino tal otro valor. La sintaxis que usamos aquí es:
si (alguna condición) alguna expresión sino otra expresión.

En el ejemplo de Euclides “alguna condición” es “b=0”; “alguna expresión” es “a”; y


“otra expresión” es “mcd(b,resto(a,b))”.
Una condición formalmente es una función cuyo valor es siempre verdadero o falso.

El esquema general es escribir una lista de definiciones de la forma:


f(lista de argumentos)=expresión.

Donde expresión se forma mediante composición y selección condicional de las


funciones en la lista (posiblemente incluida la que se está definiendo), los
argumentos de la lista de argumentos y funciones dadas por conocidas.

O sea:
f (x1, x2, …, xn) = si C luego E1 sino E2

En este esquema no hay ninguna garantía que convocando una de las funciones
vamos a conseguir un valor.

Este esquema no es un lenguaje de programación; es un esquema de notación para


comunicarnos entre nosotros sobre algoritmos y es libre de las restricciones que
necesariamente tiene un lenguaje de programación.

Ya que está dirigido al ser humano (no a una máquina), el único requerimiento
sintáctico es la claridad matemática. De hecho, es notación matemática estándar,
con unos pocos agregados que facilitan la expresión de algoritmos.

SINTAXIS

No tenemos reglas formales de sintaxis porque usamos notación matemática


estándar, con la excepción de la selección condicional.

Por ejemplo: “mcd(a,b)= si (b=0) a sino mcd(b, resto(a,b))”


Suele escribirse como
a, si b=0
mcd(a,b) =
mcd(b, resto(a,b)), sino
Página6
Parece torpe para algoritmos.
La sintaxis es cuestión de gustos, el único requerimiento que vamos a pedir es que
sea clara, con la claridad matemática.

La definición del algoritmo de Euclides no deja lugar a dudas de cómo proceder:


mcd(36,15)= mcd(15,6) = mcd(6,3) = mcd(3,0) = 3.

Si colocamos mucho de estos nos empieza a cansar el escribir “mcd”; ya sabemos


que estamos calculando un mcd, así que basta escribir (24,14) (14,10) (10,4) (4,2
(2,0). Aún los paréntesis no son necesarios y si tenemos un pizarrón y tiza
ponemos ( , ) y vamos llenando los espacios con los sucesivos argumentos,
hasta que el 2º sea 0.

Hemos diseñado un sistema de “manejo de memoria” para el cálculo del algoritmo


de Euclides, que no está incluida en la definición.

Mientras ( b0)
temp = b
Euclides (en forma imperativa) b=resto(a,b)
a= temp

Aquí nos preocupamos por el manejo de memoria “En la forma funcional se puede
dejar de lado todo detalle del manejo de la memoria”
La cuestión de cómo se va a manejar la memoria, y manejarla eficientemente,
pertenece al área de estructura de datos, que no estamos considerando en este
momento.
La forma imperativa que hemos visto es más eficiente y rápida de ejecutar en la
máquina.

CARACTERÍSTICAS DE LA PROGRAMACIÓN FUNCIONAL

• El valor de una expresión depende sólo de los valores de sus subexpresiones, si


las tiene.

• La programación puramente funcional puede caracterizarse como una


programación sin asignaciones, sin embargo la mayoría de los lenguajes
funcionales son impuros ya que proporcionan asignaciones.

• El manejo de los almacenamientos es implícito. Se asigna en el momento de ser


necesario y se libera automáticamente cuando se vuelve inaccesible. Esto lleva a
programas mas cortos y simples.

• La programación funcional trata a las funciones como ciudadanos de primera


clase. Las funciones tienen la misma jerarquía que cualquier otro valor. Una
función puede ser el valor de una expresión, puede pasarse como argumento y
puede colocarse en una estructura de datos.

Página7
TIPOS DE DATOS
Los números naturales forman un tipo de datos y vienen con ciertas operaciones,
básicamente sumar y multiplicar. La sustracción y división no siempre son
posibles. Con esto queremos decir que un tipo de datos es un conjunto con unas
funciones asociadas. A veces hay que especificar el tipo de datos.

Por ejemplo “natural mcd(natural a, natural b) = ...................”


Veremos la función que obtiene (en números naturales) el cociente y el resto de dos
argumentos.

COCIENTE:
cociente(a, b) = si ( a<b) 0 sino cociente(a-b, b)+1
cociente es el número de veces que podemos restar b de a.

RESTO:
resto(a, b) = si(a<b) a sino resto(a-b, b)
resto es lo que queda después de restar b todas las veces
posibles
.
Si deseamos los dos resultados (dos números enteros: cociente y resto) tenemos:
OTRO TIPO DE DATOS: ”El par ordenado” de números naturales.

El conjunto de este tipo de datos es NxN y las funciones son primero y segundo. Si
z es un par ordenado de datos, entonces primero(z) y segundo(z) son números
naturales.
Veamos la función Cociente – Resto que contesta un par ordenado z

cociente_resto (a, b)=


si (a<b) (0,a)
sino
sea (c, r)= cociente_resto (a-b, b)
(c+1, r).

Ejemplo: cociente (a,b) = si (a<b) 0 sino cociente (a-b, b)+1



 “3+1”
retorna
cociente (31, 7)  “4”
(resultado final)

invoca
 “2+1”
retorna
cociente (24, 7)

 invoca

 “1+1”
retorna
cociente (17, 7)

 invoca

cociente (10, 7) continua desde donde fue llamado  retorna  “0+1”



invoca
cociente (3, 7) como (a < b) retorna  “0”

Página8
Ejemplo: resto(a, b) = si(a<b) a sino resto(a-b, b)

resto(31, 7) S/R

invoca
resto(24, 7) S/R

 invoca

resto(17, 7) S/R

 invoca

resto(10, 7) continua desde donde fue llamado  (fin) S/R (sin retorno)

invoca
resto(3, 7) como (a<b)  retorna  “3” (resultado final)

Ejemplo: cociente_resto (31, 7)= div (a, b)


div (a, b) = si (b>a) (0,a) sino (c+1, r) en donde (c, r)= div (a-
b, b)

Si sabemos multiplicar por 10 rápidamente, el siguiente algoritmo lo aprovecha:


div10(a, b) = si (10b>a) div (a, b)
sino (10c1+c, r)
en donde (c1, r1) = div10(a, 10b) y (c, r) = div(r1,
b)

div10(310, 7) (10c1+c, r) = 10*4+4,2 = (44, 2)


resultado
invoca
div10(310, 70) (c1, r1) = (4, 30) (c, r) = (4, 2)
  
 

  (4, 30)
retorna
div(310, 70)

 ........................
 (4, 2)
retorna
div(30, 7)
............................................

El que lleva a cabo fielmente los ejercicios de div10 encuentra que si no tiene
cuidado puede perderse.
Otro ejemplo complicado es el de dados dos números a y b distintos de cero,
encontrar dos números enteros s y t tales que sa+tb=mcd(a,b).
Por ejemplo para a=6 y b=4 una solución es s=1 y t=-1 o s=3 y t=-4
Pues 1*6+(-1)*4=mcd(6,4) Rta 2.
st(a,b) = si (b=0) luego (1,0)
Sino sea (c,r) = cr(a, b)
(s,t) = st((b, r)
(t, s-ct).

TIPO DE DATOS CARACTER Y STRING (cadena de caracteres).

El próximo ejemplo trabaja con sucesiones de caracteres. Un texto es una sucesión


de caracteres. Cuando tenemos tres tipos de paréntesis “()”, “[]”, “{}”, y una
función aparear contesta con la sucesión nula si los paréntesis están bien, o la
Página9
subsucesión terminal que empieza con el paréntesis culpable.
Notación: Una sucesión s consiste de s1 y resto(s).

aparear(s) = si (s=nula) luego nulo


sino si (s1 es paréntesis)
2
sea t = aparear(resto(s))
si (t=nulo) luego s (no se encontró compañero de s1)
sino
si (t1 es paréntesis y hace juego con s1) aparear(resto(t))
sino s
sino
si (s1 es paréntesis cerrado) luego s
1
sino aparear(resto(s))

Aparear (“a(a)”)
1
aparear(“(a)”)
aparear(“a)”) s1 = ”(”  t = “)”
2


 

1
aparear (“)”)
aparear (nulo)
fin (ok)

TIPO DE DATOS LISTA.

Sea el problema: Encontrar el mayor elemento de una lista no ordenada.


Para poder resolver este problema debemos considerar lo siguiente:

•La lista es un tipo de datos que no importa cual es su estructura. Sólo


importa pensarla como una colección de datos.

•Las funciones primitivas que actúan sobre este tipo de datos son:
Primero(): retorna el primero de la lista, sin sacarlo de ella.
Resto(): retorna una lista con los mismos elementos que la lista
pasada por parámetro, pero sin el primero de ella.

•Vacío es una constante asociada a este tipo de datos


•Las funciones primitivas que actúan sobre el tipo de datos lista son:
Primero(): retorna el primero de la lista pasada como
argumento, sin sacarlo de ella.
Resto(): retorna una lista con los mismos elementos que la lista
pasada como argumento, pero sin el primero de sus elementos.

•Vacío es una constante asociada a este tipo de datos que tendrá una lista
vacía.
Volviendo al problema propuesto, para conocer el mayor de una lista se debe
recorrer secuencialmente y por completo la lista.
Cuando se inspeccionó un elemento de la lista, el primero, falta inspeccionar
Página10
el resto de la lista, a menos que sea el último, es decir, que el resto de la
lista sea una lista vacía.
Sobre una lista vacía, sería un error buscar el mayor elemento.
Sobre una lista de un solo elemento, el mayor es justamente ese, que está
ubicado como primero.

Por todo lo expuesto, la función mayor se la puede expresar como:


1 Mayor(Lista) = si (Lista = Vacío)
luego ERROR
sino
2.........................si Resto(Lista) = Vacío
luego Primero (Lista)
sino
3............................si Primero(Lista) > Mayor(Resto(Lista))
luego Primero(Lista)
sino
4..........................................Mayor(Resto(Lista))
Pruebas:

• Si la Lista = [5]
Por línea 2 la función tomará el valor de Primero([5]), el valor 5.

• Si la lista = [ ]
Por la línea 1 la función tomará el valor de ERROR.
Pruebas:

• Si la lista = [9, 6]

• Por la línea 3 se debe resolver Primero([9, 6]) y Mayor(Resto([9, 6])) para


luego resolver la condición.
Primero([9, 6]) toma valor 9.
Resto([9, 6]) toma el valor [6], por lo que Mayor(Resto([9, 6])) es
equivalente a Mayor([6])
Por la línea 2 Mayor([6]) toma el valor 6.
Volviendo a línea 3, como 9 > 6, la función retorna Primero([9, 6]), el valor 9.
Pruebas:

• Si la lista = [2, 6]

• Por la línea 3 se debe resolver Mayor(Resto([2,6])) para luego resolver la


condición.

• Por la línea 2 Mayor([6]) toma el valor 6.

• Volviendo a línea 3, como resulta que no es Primero([2, 6]) > Mayor(Resto([2,


6])), la función retorna el valor que toma Mayor(Resto([2,6])), es decir, 6.

Ejercitación: Probar Mayor para el caso en que la lista sea [4, 5, 2]

Otro ejemplo:
Sumar los elementos de una lista de enteros.

Página11
La función suma se la puede expresar como:

SUMA(Lista) = si (Lista = Vacío)


luego 0
sino
Primero (Lista) + SUMA(Resto(Lista))
Prueba:
Si la lista = [9, 6]
SUMA([9, 6]) = Primero([9, 6]) + SUMA (Resto ([9, 6])
Es decir, SUMA ([9, 6]) = 9 + SUMA([6])
Entonces, SUMA([9,6])= 9+6=15
SUMA([6]) = Primero([6]) + SUMA (Resto ([6])
Es decir, SUMA ([6]) = 6 + SUMA([ ]),
Entonces, SUMA([6])=6+0=6
SUMA([ ]) = 0

Página12

También podría gustarte