Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Unidad 4
Unidad 4
UNIDAD 4: COMPUTABILIDAD
1 INTRODUCCIÓN
Sin embargo, esta definición carece de elementos formales necesarios para desarrollar
una teoría acorde. Alan Turing (1912-1954) fue uno de los precursores en el desarrollo de los
aspectos teóricos de las Ciencias de la Computación, aunque hizo otros aportes igualmente
importantes en IA, criptografía y biología. El definió en los años 30 lo que sería la primera
formalización de la noción intuitiva de algoritmo. El modelo propuesto es conocido como
Máquina de Turing, teniendo la propiedad de ser el autómata o máquina abstracta más general.
Uno de los resultados más importantes obtenidos por Turing establece límites sobre lo
que puede y no puede ser efectivamente computado (resuelto o decidido) mediante
“algoritmo”. Existen otros Modelos Formales como por ejemplo: los Sistemas de Post,
Funciones Recursivas, Funciones Lambda definibles, Máquinas RAM, etc. Todos equivalentes,
según la Tesis de Church-Turing. Se estudiaran en esta unidad Máquina de Turing y las
Funciones Recursivas como modelos para decidir que es computable o No.
Una computadora solo puede ejecutar directamente solo un pequeño conjunto de
operaciones elementales a gran velocidad. ¿Como hace entonces para resolver tal variedad de
problemas? La respuesta yace en el concepto de algoritmo, que es el método que describe
como resolver un problema.
verá de la complejidad intrínseca del problema. Este recorrido por el mundo de los algoritmos
pretende incorporar una visión general de las técnicas y estructuras de datos usadas para
diseñar algoritmos eficientes. Adicionalmente, se podrá conocer los límites actuales de la
computadora digital, lo que permite usar mejor sus recursos.
2 COMPLEJIDAD DE ALGORITMOS
formar un equipo de futbol (con titulares y suplentes. Es decir, 20 jugadores) de “amigos”. Así
que se quiere formar una lista de 20 estudiantes que se lleven bien entre todos ellos. Para
resolver un problema de manera computacional se diseña un algoritmo. Se puede considerar un
algoritmo como una serie de instrucciones que se codifican a través de un lenguaje de
computación y que, al ser interpretado por la computadora, lleva a la solución del problema.
Así, para el problema planteado se puede diseñar un algoritmo que construya todos los
conjuntos de estudiantes compatibles de la manera siguiente: se enumera a los estudiantes del 1
al 70. Se inicia formando dos conjuntos de estudiantes compatibles; los que se llevan con el
estudiante 1, y por otro lado, los que no son amigos del estudiante 1. En cada uno de estos dos
casos, en el siguiente paso se considera que el estudiante 2 puede o no aparecer, generándose
cuatro posibilidades. En el tercer paso se considera para cada uno de los cuatro conjuntos que
se están formando si el estudiante 3 está o no. Así se tendrá que después de n = 70 pasos se han
construido del orden de 1.180.591.620.717.411.303.424 (270) diferentes trayectorias que
representan todos los posibles conjuntos de estudiantes compatibles.
Un algoritmo que busque las soluciones exactas tratará de construir todas estas
trayectorias y después revisar cuáles de estos conjuntos contienen al menos 20 estudiantes
compatibles. Usando una computadora de alta velocidad que construye una trayectoria en, por
ejemplo, un 1 microsegundo, se requeriría de más de 374 millones de años para construir el
total de trayectorias y así, después de esto, determinar si se puede formar el equipo de futbol de
20 amigos.
Entonces, puede verse que el proceso de comprobación está acotado por una función
polinomial, por ejemplo 20 * 19 < n2 (recuerde que n =70, el total de estudiantes). A este tipo
de algoritmos que trabajan dentro de cotas polinomiales de tiempo de acuerdo al tamaño de su
entrada, se le conoce como de algoritmos eficientes. Así, el comprobar si un conjunto contiene
miembros compatibles es un proceso “eficiente”, mientras que construir un conjunto con al
menos 20 estudiantes compatibles requiere de tiempos de computación de orden exponencial,
lo que lo hace un problema “intratable”.
cálculo sin preocuparse por los detalles de implementación; en este caso se dice que la función
es computable o calculable. Puede verificarse si una función es computable utilizando una
máquina de Turing como un modelo de máquina isomorfa a cualquier otro sistema
computacional. El interés de Turing acerca de la capacidad de las máquinas para pensar lo
llevó a desempeñar un papel importante en el desarrollo de las computadoras, no solo teóricas
sino reales. Así, se refuerza la tesis de Church-Turing que dice que si una máquina de Turing
no puede resolver un problema, entonces ninguna computadora puede hacerlo, ya que no existe
algoritmo para obtener una solución. Por tanto, las limitaciones corresponden a procesos
computacionales y no a la tecnología.
1- Uno que proporciona una medida teórica (a priori), que consiste en obtener una
función que acote (por arriba o por abajo) el tiempo de ejecución del algoritmo para
unos valores de entrada dados.
2- Otro que ofrece una medida real (a posteriori), consistente en medir el tiempo de
ejecución del algoritmo para unos valores de entrada dados y en una computadora
concreta.
Ambas medidas son importantes puesto que, si bien la primera ofrece estimaciones del
comportamiento de los algoritmos de forma independiente de la computadora en donde serán
implementados y sin necesidad de ejecutarlos, la segunda representa las medidas reales del
comportamiento del algoritmo. Estas medidas son funciones temporales de los datos de
entradas.
Se entiende por tamaño de la entrada el número de componentes sobre los que se va a
ejecutar el algoritmo. Por ejemplo, la dimensión del vector a ordenar o el tamaño de las
matrices a multiplicar.
La unidad de tiempo a la que debe hacer referencia estas medidas de eficiencia no
puede ser expresada en segundos o en otra unidad de tiempo concreta, pues no existe una
computadora estándar al que puedan hacer referencia todas las medidas. Se denotará por T(n) el
tiempo de ejecución de un algoritmo para una entrada de tamaño n.
Teóricamente T(n) debe indicar el número de instrucciones ejecutadas por una
computadora idealizada. Se debe buscar por tanto medidas simples y abstractas, independientes
de la computadora a utilizar. Para ello es necesario acotar de alguna forma la diferencia que se
puede producir entre distintas implementaciones de un mismo algoritmo, ya sea del mismo
código ejecutado por dos máquinas de distinta velocidad, como de dos códigos que
implementen el mismo método. Esta diferencia es la que acota el siguiente principio:
Principio de Invariancia
Dado un algoritmo y dos implementaciones suyas I1 e I2, que tardan T1(n) y T2(n)
segundos respectivamente, el Principio de Invariancia afirma que existe una constante real c
>0 y un número natural no tales que para todo n no se verifica que T1(n) T2(n).
Es decir, el tiempo de ejecución de dos implementaciones distintas de un algoritmo
dado no va a diferir más que en una constante multiplicativa.
Con esto se puede definir sin problemas que un algoritmo tarda un tiempo del orden de
T(n) si existen una constante real c > 0 y una implementación I del algoritmo que tarda menos
que cT(n), para todo n tamaño de la entrada.
Dos factores a tener muy en cuenta son la constante multiplicativa y el no para los que
se verifican las condiciones, pues si bien a priori un algoritmo de orden cuadrático es mejor que
uno de orden cúbico, en el caso de tener dos algoritmos cuyos tiempos de ejecución son 106n2 y
5n3 el primero sólo será mejor que el segundo para tamaños de la entrada superiores a 200.000.
También es importante hacer notar que el comportamiento de un algoritmo puede
cambiar notablemente para diferentes entradas (por ejemplo, que tan ordenados se encuentran
los datos en el vector). De hecho, para muchos programas el tiempo de ejecución es en realidad
una función de la entrada específica, y no sólo del tamaño de ésta. Así suelen estudiarse tres
casos para un mismo algoritmo: caso peor, caso mejor y caso medio.
El caso mejor corresponde a la traza (secuencia de sentencias) del algoritmo que realiza
menos instrucciones. Análogamente, el caso peor corresponde a la traza del algoritmo que
realiza más instrucciones. Respecto al caso medio, corresponde a la traza del algoritmo que
realiza un número de instrucciones igual a la esperanza matemática de la variable aleatoria
definida por todas las posibles trazas del algoritmo para un tamaño de la entrada dado, con las
probabilidades de que éstas ocurran para esa entrada.
Es muy importante destacar que esos casos corresponden a un tamaño de la entrada
dado, puesto que es un error común confundir el caso mejor con el que menos instrucciones
realiza en cualquier caso, y por lo tanto contabilizar las instrucciones que hace para n = 1.
A la hora de medir el tiempo, siempre se hará en función del número de operaciones
elementales que realiza dicho algoritmo, entendiendo por operaciones elementales (en adelante
OE) aquellas que la computadora realiza en tiempo acotado por una constante. Así, se
considera OE las operaciones aritméticas básicas, asignaciones a variables de tipo predefinido
por el compilador, los saltos (llamadas a funciones y procedimientos, retomo desde ellos, etc.),
las comparaciones lógicas y el acceso a estructuras indexadas básicas, como son los vectores y
matrices. Cada una de ellas contabilizará como l OE.
Resumiendo, el tiempo de ejecución de un algoritmo va a ser una función que mide el
número de operaciones elementales que realiza el algoritmo para un tamaño de entrada dado.
En general, es posible realizar el estudio de la complejidad de un algoritmo sólo en base
a un conjunto reducido de sentencias, aquellas que caracterizan que el algoritmo sea lento o
rápido en el sentido que interese. También es posible distinguir entre los tiempos de ejecución
de las diferentes operaciones elementales, lo cual es necesario a veces por las características
específicas de la computadora (por ejemplo, se podría considerar que las operaciones + y
presentan complejidades diferentes debido a su implementación). Sin embargo, en este texto se
tendrá en cuenta, a menos que se indique lo contrario, todas las operaciones elementales del
lenguaje, y se supondrá que sus tiempos de ejecución son todos iguales.
Para hacer un estudio del tiempo de ejecución de un algoritmo para los tres casos
citados se comenzará con un ejemplo concreto. Se supone entonces que se dispone de la
definición de los siguientes tipos y constantes:
programa, pues se pasa por referencia y no por valor (está declarado como un argumento VAR,
aunque no se modifique dentro de la función). En caso de pasarlo por valor, se necesitaría tener
en cuenta el coste que esto supone (un incremento de n OE). Con esto:
- En el caso mejor para el algoritmo, se efectuará la línea (1) y de la línea (2) sólo la
primera mitad de la condición, que supone 2 OE (se supone que las expresiones se
evalúan de izquierda a derecha, y con "cortocircuito", es decir, una expresión lógica
deja de ser evaluada en el momento que se conoce su valor, aunque no hayan sido
evaluados todos sus términos). Tras ellas la función acaba ejecutando las líneas (5)
a (7). En consecuencia, T(n)=1 + 2 + 3 = 6.
- En el caso peor, se efectúa la línea (1), el bucle se repite n-l veces hasta que se
cumple la segunda condición, después se efectúa la condición de la línea (5) y la
función acaba al ejecutarse la línea (7). Cada iteración del bucle está compuesta por
las líneas (2) y (3), junto con una ejecución adicional de la línea (2) que es la que
ocasiona la salida del bucle. Por lo tanto:
Tenemos pues:
consideramos equiprobables cada una de las posiciones en las que puede encontrarse el
elemento dentro del vector (incluyendo la posición especial 0, que indica que el elemento a
buscar no se encuentra en el vector).
Dada una función f, se quiere estudiar aquellas funciones g que a lo sumo crecen tan
deprisa como f Al conjunto de tales funciones se le llama cota superior de f y se lo denomina
0(f). Conociendo la cota superior de un algoritmo donde se puede asegurar que, en ningún caso,
el tiempo empleado será de un orden superior al de la cota.
Definición:
Dada una función, queremos estudiar aquellas funciones g que a lo sumo crecen tan
lentamente como f Al conjunto de tales funciones se le llama cota inferior de f y lo
denominamos (f). Conociendo la cota inferior de un algoritmo podemos asegurar que, en
ningún caso, el tiempo empleado será de un orden inferior al de la cota.
Definición
Intuitivamente, t (f) indica que t está acotada inferiormente por algún múltiplo de f
Normalmente estaremos interesados en la mayor función f tal que t pertenezca a (f), a la que
se denomina cota inferior.
Como última cota asintótica, definiremos los conjuntos de funciones que crecen
asintóticamente de la misma forma:
Definición
Intuitivamente , t (f) indica que t está acotada tanto superior como inferiormente por
múltiplos de f, es decir, que t y f crecen de la misma forma.
Los algoritmos de complejidad O(n) y O(n log n) son los que muestran un
comportamiento más "natural": prácticamente a doble de tiempo, doble de datos procesables.
Los algoritmos de complejidad logarítmica son un descubrimiento fenomenal, pues en
el doble de tiempo permiten atacar problemas notablemente mayores, y para resolver un
problema el doble de grande sólo hace falta un poco más de tiempo .
Los algoritmos de tipo polinómico no son una maravilla, y se enfrentan con dificultad a
problemas de tamaño creciente. En la práctica se dice que son el límite de lo "tratable". Sobre
la tratabilidad de los algoritmos de complejidad polinómica habría mucho que hablar, y a veces
semejante calificativo es puro eufemismo. Mientras complejidades del orden O(n2) y O(n3)
suelen ser efectivamente abordables, prácticamente nadie acepta algoritmos de orden O(n100),
por muy polinómicos que sean. La frontera es imprecisa.
Cualquier algoritmo por encima de una complejidad polinómica se dice "intratable" y
sólo será aplicable a problemas muy pequeños. A la vista de lo anterior se comprende que los
programadores busquen algoritmos de complejidad lineal. Es un golpe de suerte encontrar algo
de complejidad logarítmica. Si se encuentran soluciones polinomiales, se puede vivir con ellas;
pero ante soluciones de complejidad exponencial, más vale seguir buscando.
No obstante lo anterior ...
... si un programa se va a ejecutar muy pocas veces, los costes de codificación y
depuración son los que más importan, relegando la complejidad a un papel secundario.
... si a un programa se le prevé larga vida, hay que pensar que le tocará mantenerlo a
otra persona y, por tanto, conviene tener en cuenta su legibilidad, incluso a costa de la
complejidad de los algoritmos empleados.
... si se puede garantizar que un programa sólo va a trabajar sobre datos pequeños
(valores bajos de N), el orden de complejidad del algoritmo que usemos suele ser
irrelevante, pudiendo llegar a ser incluso contraproducente.
Por ejemplo, si se dispone de dos algoritmos para el mismo problema, con tiempos de
ejecución respectivos:
algoritmo tiempo complejidad
f 100 n O(n)
2
g n O(n2)
Asintóticamente, "f" es mejor algoritmo que "g"; pero esto es cierto a partir de N > 100.
Si el problema no va a tratar jamás problemas de tamaño mayor que 100, es mejor solución
usar el algoritmo "g".
El ejemplo anterior muestra que las constantes que aparecen en las fórmulas para T(n),
y que desaparecen al calcular las funciones de complejidad, pueden ser decisivas desde el
punto de vista de ingeniería. Pueden darse incluso ejemplos más dramáticos:
Aún siendo dos algoritmos con idéntico comportamiento asintótico, es obvio que el
algoritmo "f" es siempre 100 veces más rápido que el "g" y candidato primero a ser utilizado.
... usualmente un programa de baja complejidad en cuanto a tiempo de ejecución, suele
conllevar un alto consumo de memoria; y viceversa. A veces hay que sopesar ambos
factores, quedándonos en algún punto de compromiso.
... en problemas de cálculo numérico hay que tener en cuenta más factores que su
complejidad pura y dura, o incluso que su tiempo de ejecución: queda por considerar la
precisión del cálculo, el máximo error introducido en cálculos intermedios, la
estabilidad del algoritmo, etc. etc.
Antes de comenzar vale mencionar los elementos que caracterizan a las definiciones recursivas,
ya que nos serán de utilidad.
.
Esta puede redefinirse en forma recursiva como sigue:
① f(0) = 0 y f(1) = 1
② f(n+2) = f(n) + f(n+1)
combinadas con otras nuevas, que servirán de reglas de construcción, nos permiten formar
otras cuya computabilidad se desprenda de las originales.
Nota :
- A los elementos de N0n, los notaremos con las letras x, y,… (en negrita) , son n-uplas de
números naturales.
- En el caso particular que el dominio de la función numérica f cumple: D= N0n, diremos
que f es una función total (definida para toda n-upla de N0n). Mientras que si D N0n
diremos que f es una función parcial (puede o no estar definida para toda n-upla de N0
n
). Es claro que el conjunto de todas las funciones numéricas parciales incluye a todas
las totales.
- Desde aquí en adelante todos los números que consideraremos serán elementos de N0.
- Para simplificar, notaremos f(m) a una función numérica con dominio incluido en N0 m.
- Por convención f: N00 → N0, con D N00 (función con cero variables) representa un
elemento de su codominio. De esta manera podremos referirnos a elementos de N0,
haciendo referencia a funciones con cero variables.
Es decir, s hace corresponder a cada número, x, su respectivo sucesor, x+1. Viéndolo de esta
manera, s es una función computable, ya que conocemos desde hace tiempo un proceso
efectivo para la suma de naturales.
Aceptamos con facilidad que debemos clasificar a z como computable, ya que el proceso no es
más que reemplazar cualquier valor de entrada por 0, y éste (el proceso) es efectivo.
La composición
Notación: f= (h,g1,g2,…,gm) se lee “f se obtiene de componer h con las funciones g1, g2,…, gm”.
Árbol de evaluación:
Ejemplo3: Intentemos definir la función numérica uno: N0 → N0 tal que uno(x)=1, x N0.
Vemos que: uno(1)= (s,z)
Luego: dos(1)= (s, (s,z))= (s,uno)
Realizando un proceso similar, toda función constante se puede expresar utilizando las
funciones bases y la composición, un número finito de veces.
La recursión (o recursión primitiva)
Por último veremos el “constructor” llamado “recursión”. Éste combinado con lo dado hasta
ahora nos dará la posibilidad de representar muchas funciones computables.
Observamos que la definición admite la posibilidad de que k sea igual a cero. En ese caso nos
encontraremos que g es una función de cero variables. Recordemos nuestra convención de
representar las funciones de ese tipo con elementos de N0.
Para concluir observemos que:
toda función construida por recursión, a partir de funciones computables , es computable.
Pues si f se define por recursión sobre dos funciones g y h computables, podemos calcular
f(x,y) calculando primero f(x,0)=g(x) ( la cual es computable), luego f(x,1), después f(x,2)
hasta llegar a f(x,y).
Ejemplo 4: Definamos la función suma (suma de números naturales) tal que suma(x,y)=x+y, a
través de la regla de recursión:
Tenemos que: suma(x,0)= x=I(x)
y además: suma(x, y+1)= x+(y+1)=(x+y)+1=s(x+y)=s(suma(x,y))=s(I(3)3(x,y,suma(x,y)).
Formalmente:
1) suma(x,0)= g(x)
2) suma(x, y+1)= h(x,y,suma(x,y))
Proposición 1: Dada una función numérica f(1) definimos una nueva función numérica F(2),
llamada potencia de f, de manera que:
x N0 : F(x,0)=x , y
x N0 y N0: F(x,y+1)=f(F(x,y)).
Entonces, si f FRP, resulta que F FRP. Notaremos f y(x)=F(x,y).
Demostración: Sea f FRP y como F=R(I, (f, I(3)3)) entonces podemos concluir que
F FRP. ◙
4.2.4 Ejemplos de funciones recursivas primitivas
Las siguientes funciones numéricas pertenecen al conjunto de las FRP:
i)Función predecesor:
vii) Igualdad:
A= {1} (x) = .
A= {0,2} (x) = .
Es importante notar que existe una correspondencia biunívoca entre funciones características y
Sea k=1, x=a 0 . Debemos mostrar que A={a} 0 es conjunto recursivo primitivo. Para
esto sólo hay que verificar que = (E,I,a ) lo cual es trivial. ( recordemos que a(1) es la
(1)
función constante de una variable cuyo valor es a, y que E está definida en la sección vii).
- Hipótesis Inductiva:
Supongamos ahora, para , que todos los subconjuntos unitarios de son recursivos
primitivos.
- Mostremos que esto también vale para todo subconjunto de un elemento de .
Sea x = (a1, a2,… , ak+1) N0k+1. Pongamos y = (a1, a2,… , ak ). Entonces sabemos que {y}(k)
es FRP por hipótesis inductiva. Pero
(k+1)
{x} = (mutl; ( {y},I1(k+1),…, Ik(k+1)); (E; I(k+1)k+1 ; ak+1))
lo que nos dice que {x} es FRP.
Luego si cada conjunto de A= es CRP, luego ( por propiedad 4) la unión finita
de CRP es CRP. ◙
Enunciaremos ahora una proposición, la cual en muchos casos facilitara la demostración de que
una función es recursiva primitiva.
Proposición 6.
(a) Sea m N, y A;B Nm0 conjuntos recursivos primitivos tales que =Nm0 y =ϕ ,
m (m) (m)
es decir, A y B representan una partición de N 0 . Si además, f y g son ambas funciones
recursivas primitivas, entonces la función h(m) definida por: h(x) = es
recursiva primitiva.
(b) Si {Ai }i=l l P (Nm0) es una partición finita de N0k , donde Ai CRP, i, y además {fi(m)}
l
i=l FRP es una familia de funciones recursivas primitivas, entonces la función F(m) definida
por
F(x) = fi(x); si x Ai
es una función recursiva primitiva.
Demostración: Para el primer apartado, se tiene
h = f. A + g . B = (suma; (mult; f, A); (mult; g, B))
Ya que A y B son CRP, podemos asegurar que A y B son FRP, por lo que h es FRP.
La segunda parte del enunciando se deja como ejercicio.
◙
Corolario: Sea f(m) una función numérica tal que el conjunto { x N0m : f(x) ≠ 0} resulte finito.
Entonces f FRP.
Por último, presentamos las relaciones recursivas primitivas. Antes, vale recordar la siguiente
definición.
Se puede deducir de la definición anterior, que una relación es recursiva primitiva si la función
característica del conjunto que define es recursiva primitiva.
Ejemplo 6. Mostraremos como ejemplo que la relación '=' (igualdad) es recursiva primitiva.
La relación igual define el conjunto R= = {(x; x)/ x N0}.
La idea será encontrar una función recursiva primitiva f que caracterice al conjunto R=.
Observemos que la función de igualdad E(x; y) definida anteriormente es la función que
estamos buscando.
Por lo tanto:
=E
y resulta la relación '=' recursiva primitiva.
Proposición 8: Si f1, f2, …, fn son funciones recursivas primitivas de N0n en N0.; y P1, P2,
…,Pn son predicados recursivos primitivos de n-lugares y, para cada xϵ N0n exactamente una y
sólo una de las condiciones P1(x), P2(x), …,Pn(x) es verdadera. Entonces la función numérica f:
N0n→ N0 definida por:
es recursiva primitiva.
Demostración: Ejercicio.
A continuación se demuestra sólo la primer propiedad (la segunda queda como ejercicio).
Pues f es total.
Por lo tanto: y N0: y = f(x)
Para la recursión se utiliza un razonamiento similar. Esto nos permite afirmar que toda FRP es
total. ◙
Por otro lado se puede demostrar la incompletitud de la clase de FRP para representar todas las
funciones totales computables. Existe una demostración constructiva (función de Wilhelm
Ackermann, 1928) y otra no constructiva (argumento de diagonalización de Cantor).
Nosotros veremos a continuación la demostración no constructiva. Esta se basa en mostrar que
existe una enumeración de las funciones recursivas primitivas. Sobre la enumeración se utiliza
el método de demostración por diagonalización, y se construye una función total que no está en
la enumeración. Es decir, se obtiene una función total que no es recursiva primitiva.
o Las cadenas sobre el alfabeto ∑ que representan a FRP (es decir que corresponden a
una codificación válida de una FRP) se ordenan por longitud, y cadenas de igual
longitud se ordenan alfabéticamente (ordenamiento canónico), pudiendo ser colocadas
en las filas de una matriz.
f0
f1
f2
FRP f3
Año 2013 Página 23
Teoría de la Computación – Apuntes de Cátedra
…
…
0 1 2 …
f0
f1
f2
FRP f3
…
…
N0
0 1 2 …
f0 f0(0) f0(1) f0(2) …
f1 f1(0) f1(1) f1(2) …
f2 f2(0) f2(1) f2(2) …
FRP f3 f3(0) f3(1) f3(2) …
… … … … …
… … … … …
En principio, podemos ver que para cada n≥0 existe la imagen de la función recursiva
primitiva n-ésima , del listado, fn(n). Por lo tanto se puede computar fn(n). Se define una
nueva función g(n) = fn(n) + 1, para cada n≥0.
Es decir que g se define sumándoles 1 a cada elemento de la diagonal de la matriz.
N0
0 1 2 …
f0 f0(0) f0(1) f0(2) …
f1 f1(0) f1(1) f1(2) …
f2 f2(0) f2(1) f2(2) …
FRP f3 f3(0) f3(1) f3(2) …
… … … … …
… … … … …
El absurdo surge de haber supuesto que g estaba en la enumeración, con lo cual g es total y no
es FRP.
Por lo tanto existen funciones totales computables que no son FRP. ◙
Observación:
- En base a la definición de minimización de una función g podemos notar que en
general no existe un método claro para el cálculo de los valores imágenes, inclusive aún
si sabemos cómo calcular g. Obviamente el procedimiento a aplicar es:
m:=0;
while g(x,m)≠0 do m:=m+1; (I)
output m
que puede no ser un algoritmo, en el caso en que no termine.
cierta, implica que si una función no puede ser expresada como una función recursiva entonces
no es calculable.
Esta tesis es más que nada una expresión de deseo. Lo que se trata de decir es que se considera
casi imposible que podamos encontrar alguna función de las que consideramos calculables, de
acuerdo a nuestra idea intuitiva, y que ésta no sea una función recursiva.
A pesar de esto, el hecho de que en búsqueda de otros modelos del cálculo (Máquinas de
Turing, Funciones de Listas) no se haya podido ampliar el campo, ayuda a fortalecer la
credibilidad de la afirmación hecha por Church.
La tesis de Church es equivalente a la tesis de Turing.
Problema de la detención: Sería deseable poder determinar para cada programa si éste
termina o no para todas las posibles entradas. Si existiera tal procedimiento, uno podría decidir
por adelantado si existe el riesgo de que un programa no termine nunca. Desafortunadamente,
ese procedimiento no existe. Ese es el famoso problema de la detención. Concretamente, el
problema de la detención implica el diseño de un procedimiento que pueda predecir, para un
programa arbitrario, si éste llegará alguna vez a una conclusión. Desafortunadamente, vamos a
demostrar que el problema de la detención es indecidible.
Considere una función, digamos finaliza(x), que tiene como argumento un programa, y que
vale 0 si el programa se detiene, o bien 1 en caso contrario. Por supuesto, los programas se
pueden considerar como cadenas, y todo carácter de una cadena posee una representación
numérica. En este sentido, se puede decir que x es un entero dado por la representación interna
(en la computadora) del programa x.
Para demostrar que finaliza(x) es indecidible, supondremos que finaliza siempre concluye, y
derivaremos una contradicción. Para esto considere el siguiente esqueleto de programa, que
denotaremos con B.
read(x);
Podemos dar la entrada x cualquier valor, incluyendo x=B. Por tanto, ejecutamos B tomando
como entrada al propio B.
• Si B concluye entonces finaliza(B) tiene que proporcionar el valor 1, lo cual significa que la
sentencia while se ejecuta indefinidamente. Consiguientemente, la finalización de B implica
que B no llega a terminar. Contradicción, por lo cual concluimos que la premisa que B termina
es falsa.
• Ahora si consideramos que finaliza(B)=0, entonces la sentencia while de B concluye. Una vez
más se deriva en una contradicción.
Por tanto si B termina como si no, termina en una contradicción. Esto significa de que la
suposición de que finaliza(B) concluye tiene que ser falsa, y por tanto el problema es
indecidible.
Año 2013 Página 27
Teoría de la Computación – Apuntes de Cátedra
Cuadros Resumen
Formalmente, las funciones recursivas primitivas se pueden definir teniendo en cuenta un conjunto de
funciones bases y un conjunto de reglas de construcción:
• Funciones Bases
• Reglas de Construcción
- Composición:
- Recursión:
• Interpretación: Las funciones recursivas primitivas son todas las funciones bases y todas aquellas que
se pueden obtener a partir de las funciones bases por la aplicación sucesiva de un número finito de
veces de las reglas de construcción: composición y recursión.
Cuadro 1
Funciones μ-Recursivas
Se definen utilizando el operador de minimización y extienden el conjunto de funciones recursivas primitivas.
Minimización:
Observación: la secuencia de computaciones puede continuar infinitamente sin obtener un resultado para f(x) .
Cuadro 2
Q: conjunto de estados
Σ: alfabeto de entrada
Γ: alfabeto de la cinta, con Σ⊂Γ
δ: función de transición, δ: Q×Γ → Q×Γ×{I,D,N}
q0: estado inicial
qf: estado final
Gráficamente:
Definición:
es una función total. Es decir que para un lenguaje (conjunto) recursivo siempre es posible
determinar la pertenencia de un elemento dado.
Un problema está definido por la especificación genérica del mismo, por ejemplo “dado un
vector, calcular el promedio de sus componentes”, mientras que una instancia de un problema
está definido por los argumentos de cada parámetro que determina el problema por ejemplo
5 7 6 6 promedio = 6
Los problemas se modelan mediante algoritmos, es decir, todo problema puede tratar de
implementarse por medio de un algoritmo.
Un algoritmo puede definirse como un procedimiento de cómputo que siempre entrega
una respuesta o resultado, cualquiera sea la entrada. En este sentido, se puede decir que un
algoritmo computa una función total.
Si se codifica instancia de un problema, estos se transforman en cadenas que pueden
alimentar una maquina de Turing para determinar si es decidible o No decidible.
Un lenguaje decidible es que para toda instancia del problema (cadenas) hay una MT
que decide por Si o por NO y siempre se detiene, es decir es un lenguaje recursivo.
Un lenguaje No decidible es que para toda instancia del problema (cadenas) hay una
MT que decide por Si o por NO o no se detiene (posee un lazo infinito), es decir es un lenguaje
recursivo enumerable.
o Por otro lado, no siempre se cumple, que un lenguaje r.e. sea recursivo. Esto se
visualiza a través de un contra ejemplo:
o L = {n ∈ N |n = p − q, con p, q primos }
o L es r.e. pero no recursivo.
p/q 1 2 3 4 .....
...
...
...
...
......
A esta altura, pueden surgir las siguientes preguntas: ¿qué puede ser computable y que
no? Es decir, que es decidible y que no es decidible? En lo referente a decidible, se verá la
complejidad. En el análisis de cardinalidad de conjunto, se tiene:
∑ = {0,1} alfabeto
2 ∑* representa un conjunto, y corresponde a todos los posibles lenguajes que se pueden formar
con ∑*
Preguntas asociadas:
= 0
=?
¿Se pueden contar los enteros con los naturales? Se puede asignar un número natural a
cada número entero?
Ambos conjuntos, son infinitamente contables, pues por ejemplo, para cada elemento
de los , se le hace corresponder un natural. La correspondencia se hace de la siguiente forma:
= 0
Ejemplo:
Recordar que entre dos número racionales, existen infinitos número racionales, no
cubre la recta de los número reales. Ahora, se puede hacer corresponder a cada racional un
número natural? Es decir, dado un natural, se puede encontrar un racional y hacerlo
corresponder? Lo anterior se lleva a cabo mediante una matriz, como la que sigue:
a/b 1 2 3 4 .....
...
...
...
...
......
Cualquier número racional está dentro de la matriz y el sentido de las flechas indica el
sentido del barrido o de asignación. En la matriz anterior, notar que faltan los números
negativos, para ello se realizan los círculos en otros cuadrantes, como sigue:
Se pude llegar a ver la cardinalidad de los números reales, es decir, R , para demostrar
esto, se puede utilizar la Diagonal de Canto.
R= 2 0
Por lo tanto, L ∑ *
Ejemplo:
Para el ejemplo:
G: cuyas producciones son:
1. S aS
2. S Sa
3. S a
Gráfico de una M. T.
S # aS ## S # Sa ## S # a ∑* es una cadena
Resumiendo gráficamente:
Toda MT se puede codificar como una secuencia binaria finita, es decir una secuencia
finita de ceros y unos. Para simplificar la codificación, se supone que toda MT tiene un único
estado inicial denotado por q1, y un único estado final denotado q2. Se tendrá que para una MT
Año 2013 Página 36
Teoría de la Computación – Apuntes de Cátedra
Los estados de una MT, q1,q2,q3,...,qn, se codifican también con secuencias de unos:
Símbolo Codificación
q1(inicial) 1
q2(final) 11
. .
. .
. .
qn 1n
Ejemplo:
S0
A 00
B 000 Se asocia un orden, con un índice.
Se va a codificar a como #
a1
b 11
c 111
S AB
Se concatena con , (con comas) y para marcar el final, se coloca
0#00#000, ##. Por lo tanto la codificación completa de G es:
A aA
00#1#000,
0#00#000,00#1#00,00#1,000#11##
Aa
00#1,
Bb
000#11,
Un lenguaje decidible es que para toda instancia del problema o cadena, existe una M.
T. que decida por sí o por no. Es decir, un lenguaje recursivo es decidible, pues para cada
cadena de entrada, la M. T. decide por un sí o por un no. Un lenguaje recursivamente
enumerable, no es decidible, pues la M. T. puede decidir por un si, por un no o nunca
detenerse, es decir, entra en un loop infinito. Notar que la familia de lenguajes recursivos es un
subconjunto de la familia de lenguajes recursivamente enumerables.
6. REDUCCIÓN DE PROBLEMAS
Dado dos problemas A y B, reducir A á B, significa:
T(x) si o no
x
Algoritmo
T
para B
IA IB
La función T es total, para toda instancias de A, hay alguna IB, pero no es subyectiva, porque
B es más general, es decir, efectivamente computable.
Ejemplo:
Problema A: Dado un grafo G, donde G = (V, E), obtener el tamaño del máximo clique para
G.
Problema B: Dado un grafo G, donde G = (V, E), obtener el tamaño del máximo conjunto
independiente en G
Se observa que:
Gráficamente problema A:
2 3
4 5
Gráficamente problema B:
Conjunto independiente:
1 2 Tamaño 2 Tamaño 3
{1,3} {1,3,5}
{1,5} {2,4,5}
3 {1,4}
{1,6}
4 5
Se nota que:
Gráficamente:
T T(x) B n
x
4 5
T B
x n
T (G=(V,E)) = G´= (V, Ē) T(x)
Por lo anterior:
x T B
T(x)
RB RA
Definición:
Un problema a se reduce a un problema B, si existe una función de Transformación T:
IA IB total, donde los IA, IB conjuntos son conjuntos de instancias de los problemas
A y B respectivamente.
Gráficamente:
IA IB
Existe un algoritmo de ambigüedad si se introduce una codificación de una gramática del Tipo
2, donde la codificación de la M. T., contesta, sí o no. Como se observa en el gráfico:
AMB
si
G
no
A B
xi wi
X1 w1
X2 w2
X3 w3
… …
xn wn
Año 2013 Página 42
Teoría de la Computación – Apuntes de Cátedra
Notar que xi y wi *
Ejemplo: Problema:
Se debe encontrar un conjunto de índices i1, … im; ik {1,…,n} tal que cumple que x1 x2 x3
…xim = wi1 wi2 wi3 … wim existe al menos una lista de índices.
Ejemplo:
A B
ai (índices)
xi wi
1 1 111
2 1011 10
3 10 0
Se parte de la hipótesis que PCP es no decidible. Se debe demostrar que PCP AMB, luego
AMB de Libre de Contexto es decidible.
Gráficamente:
PCP
No No
Notar que:
A B
Xi Wi
X1 W1
X2 W2
… ..
Xn Wn
ai A B
1 1 111
2 1011 10
3 10 0
10111110=101111110
X2 X1X1X3 = W2 W1 W1 W3