Está en la página 1de 44

Teoría de la Computación – Apuntes de Cátedra

UNIDAD 4: COMPUTABILIDAD

1 INTRODUCCIÓN

La teoría de los lenguajes clasifica a los conjuntos según su complejidad estructural.


Por lo tanto los conjuntos regulares se consideran como más simples que los LLC, por que el
autómata finito tiene una estructura menos compleja que un APD. Otra clasificación, conocida
como la complejidad computacional, está basada en la cantidad de tiempo, espacio u otro
recurso que se necesita para decidir un lenguaje en algún dispositivo de cómputo universal.

Aunque la complejidad computacional se refiere principalmente al tiempo y al espacio,


existen muchas otras medidas posibles, como el número de inversiones de la dirección de
movimiento de la cabeza de la cinta de una Máquina de Turing (MT) de una sola cinta. De
hecho, se puede definir una medida de la complejidad de manera abstracta y demostrar
muchos de los resultados en un marco general.

¿Todo problema que se presenta es factible de ser resuelto mediante algoritmo? y si es


posible resolverlo, ¿Admite una solución eficiente? es decir, ¿cuál es el costo computacional
del algoritmo hipotético? Se tratará de responder a estas preguntas. Sin embargo, se debe
intentar formalizar la idea de algoritmo a fin de presentar algunos resultados relacionados a la
Teoría de Computabilidad, como así también el concepto de algoritmo eficiente.

A continuación se presenta, una idea intuitiva de algoritmo:

Un conjunto de pasos que ejecutados en cierto orden permiten resolver un


determinado problema.

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.

Es estudio de los algoritmos es el corazón de la ciencia de la computación. Se intenta en


esta unidad mostrar como analizarlos y no como diseñarlos. Ciertos problemas pueden ser
resueltos rápidamente y otros no. Eso depende del algoritmo usado, pero también, como se
Año 2013 Página 1
Teoría de la Computación – Apuntes de Cátedra

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.

La resolución práctica de un problema exige por una parte un algoritmo o método de


resolución y por otra un programa o codificación de aquel en una computadora real. Ambos
componentes tienen su importancia; pero la del algoritmo es absolutamente esencial, mientras
que la codificación puede muchas veces pasar a segundo plano. A efectos prácticos o
ingenieriles, nos deben preocupar los recursos físicos necesarios para que un programa se
ejecute. Aunque puede haber muchos parámetros, los más usuales son el tiempo de ejecución y
la cantidad de memoria (espacio). Ocurre con frecuencia que ambos parámetros están fijados
por otras razones y se plantea la pregunta inversa: ¿cuál es el tamaño del mayor problema que
se puede resolver en T segundos y/o con M bytes de memoria? En lo que sigue se centrará casi
siempre en el parámetro tiempo de ejecución, si bien las ideas desarrolladas son fácilmente
aplicables a otro tipo de recursos.

La Teoría de la Complejidad Computacional es la parte de la teoría de la computación


que estudia los recursos requeridos durante el cálculo para resolver un problema. Un cálculo
resulta complejo si es difícil de realizar. En este contexto se puede definir la complejidad de
cálculo como la cantidad de recursos necesarios para efectuar un cálculo. Así, un cálculo
difícil requerirá más recursos que uno de menor dificultad. Un algoritmo que resuelve un
problema pero que tarda mucho en hacerlo, difícilmente será de utilidad. Igualmente un
algoritmo que necesite varios gigabytes de memoria no será probablemente utilizado. A estos
recursos se les puede añadir otros, tales como el número de procesadores necesarios para
resolver el problema en paralelo. Si un cálculo requiere más tiempo que otro se dice que es más
complejo y lo se llama complejidad temporal. Por otro lado, si un cálculo requiere más espacio
de almacenamiento que otro se dice que es más complejo, en este caso se dice de una
complejidad espacial. Es claro que el tiempo que se requiere para efectuar un cálculo en una
computadora moderna es menor que el requerido para hacer el mismo cálculo con las máquinas
de las década pasadas, y seguramente que el tiempo en una máquina de la próxima generación
será mucho menor

2 COMPLEJIDAD DE ALGORITMOS

Un algoritmo es un procedimiento no necesariamente secuencial, que toma un conjunto


de valores de entrada y los transforma para producir un conjunto de valores de salida. El
análisis de un algoritmo consiste en predecir la cantidad de recursos (tiempo, memoria,
comunicación) que un algoritmo requerirá para cualquier entrada. Para cada problema se
determina una medida N de su tamaño (por número de datos) y se intentará hallar respuestas en
función de dicho N. El concepto exacto que mide N depende de la naturaleza del problema.
Así, para un vector se suele utilizar como N su longitud; para una matriz, el número de
elementos que la componen; para un grafo, puede ser el número de nodos (a veces es más
importante considerar el número de arcos, dependiendo del tipo de problema a resolver); en un
archivo se suele usar el número de registros, etc. Es imposible dar una regla general, pues cada
problema tiene su propia lógica de coste.
Por ejemplo
Se supone que en un grupo de 70 estudiantes (el tamaño de la entrada de este problema
será, entonces, n=70), cada uno de los estudiantes entrega una lista de sus compañeros que son
compatibles con él y otra lista con los compañeros que son incompatibles con él. Se quiere

Año 2013 Página 2


Teoría de la Computación – Apuntes de Cátedra

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.

La proporción de tiempo necesario para realizar este proceso exhaustivo es lo que ha


alejado a cualquier programador y usuario de resolver esta clase de problemas de manera
exacta. El que se requiera tiempos de computación de orden exponencial (por ejemplo, 2 n
pasos) es lo que define un problema intratable. Estos problemas intratables tienen
inherentemente un número exponencial de soluciones factibles.

En cambio, la acción de comprobar si 20 estudiantes de un conjunto dado son


compatibles o no, es una tarea sencilla, ya que sólo se tendría que revisar para cada miembro
del conjunto si los restantes (19) miembros son compatibles con él o no. Lo que requiere de a
lo más 20*19 (380) operaciones de comparación. Al utilizar el mismo equipo de cómputo para
revisar si un conjunto contiene 20 estudiantes que sean compatibles o no requiere del orden de
.0049 segundos.

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”.

3 RELACIÓN ENTRE LA TEORÍA DE COMPUTABILIDAD Y LA DE


COMPLEJIDAD

La Teoría de la Complejidad estudia la eficiencia de los algoritmos en función de los


recursos que utiliza en un sistema computacional. La Teoría de la Computabilidad pretende
abstraer los detalles de los sistemas computacionales y busca un algoritmo para efectuar un

Año 2013 Página 3


Teoría de la Computación – Apuntes de Cátedra

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.

3.1 LA COMPLEJIDAD DE LOS ALGORITMOS

En un sentido amplio, dado un problema y un dispositivo donde resolverlo, es necesario


proporcionar un método preciso que lo resuelva, adecuado al dispositivo. A tal método se lo
denomina algoritmo.
El estudio de los algoritmos se centrar en dos aspectos muy importantes de los
algoritmos, como son su diseño y el estudio de su eficiencia.
El primero se refiere a la búsqueda de métodos o procedimientos, secuencias finitas de
instrucciones adecuadas al dispositivo que se dispone, y que permitan resolver el problema. Por
otra parte, el segundo permite medir de alguna forma el coste que consume un algoritmo para
encontrar la solución y ofrece la posibilidad de comparar distintos algoritmos que resuelven un
mismo problema. Esta Unidad, está dedicada al segundo de estos aspectos: la eficiencia.

3.2 EFICIENCIA Y COMPLEJIDAD

Una vez que se dispone de un algoritmo que funciona correctamente, es necesario


definir criterios para medir su rendimiento o comportamiento. Estos criterios se centran
principalmente en su simplicidad y en el uso eficiente de los recursos.
A menudo se piensa que un algoritmo sencillo no es muy eficiente. Sin embargo, la
sencillez es una característica muy interesante a la hora de diseñar un algoritmo, pues facilita
su verificación, el estudio de su eficiencia y su mantenimiento. De ahí que muchas veces prime
la simplicidad y legibilidad del código frente a alternativas más crípticas y eficientes del
algoritmo.
Respecto al uso eficiente de los recursos, éste suele medirse en función de dos
parámetros: el espacio, es decir, memoria que utiliza, y el tiempo, lo que tarda en ejecutarse.
Ambos representan los costes que supone encontrar la solución al problema planteado
mediante un algoritmo. Dichos parámetros van a servir además para comparar algoritmos entre
sí, permitiendo determinar el más adecuado de entre varios que solucionan un mismo
problema. El análisis se va a centrar solamente en la eficiencia temporal.
El tiempo de ejecución de un algoritmo va a depender de diversos factores como son:
los datos de entrada que le se suministran, la calidad del código generado por el compilador
para crear el programa objeto, la naturaleza y rapidez de las instrucciones máquina del
procesador concreto que ejecute el programa, y la complejidad intrínseca del algoritmo. Hay
dos estudios posibles sobre el tiempo:

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

Año 2013 Página 4


Teoría de la Computación – Apuntes de Cátedra

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

Año 2013 Página 5


Teoría de la Computación – Apuntes de Cátedra

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:

CONSTANTE n = ... ; (* num. máximo de elementos de un vector *);


TIP0 vector = Arreglo [1 .. n] de entero

y de un algoritmo cuya implementación en Pseudocódigo es:

Funcion Buscar (a: vector, c: entero): entero


Variables j:entero
Comienzo
J:=1; (*1*)
Mientras(a[j]<c) y j<n hacer (*2*)
J:=j+1; (*3*)
FinMeintras (*4*)
Si a[j]==c Entonces (*5*)
Retorna j (*6*)
Sino Retorna 0 (*7*)
FinSi (*8*)
Fin

Para determinar el tiempo de ejecución, se calculará primero el número de operaciones


elementales (OE) que se realizan:
- En la la línea (1) se ejecuta 1 OE (una asignación).
- En la línea (2) se efectúa la condición del bucle, con un total de 4 OE (dos
comparaciones, un acceso al vector, y un Y).
- La línea (3) está compuesta por un incremento y una asignación (2 OE).
- La línea (5) está formada por una condición y un acceso al vector (2 OE).
- La línea (6) contiene un Retorna (1 OE) si la condición se cumple.
- La línea (7) contiene un Retorna (1 OE), cuando la condición del SI anterior es
falsa.

Obsérvese cómo no se contabiliza la copia del vector a la pila de ejecución del

Año 2013 Página 6


Teoría de la Computación – Apuntes de Cátedra

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:

- En el caso medio, el bucle se ejecutará un número de veces entre 0 y n-l, y se


supone que cada una de ellas tiene la misma probabilidad de suceder. Como existen
n posibilidades (puede que el número buscado no esté) se supone a priori que son
equiprobables y por tanto cada una tendrá una probabilidad asociada de l/n. Con
esto, el número medio de veces que se efectuará el bucle es de:

Tenemos pues:

Es importante observar que no es necesario conocer el propósito del algoritmo para


analizar su tiempo de ejecución y determinar sus casos mejor, peor y medio, sino que basta con
estudiar su código. Suele ser un error muy frecuente el determinar tales casos basándose sólo
en la funcionalidad para la que el algoritmo fue concebido, olvidando que es el código
implementado el que los determina.
En este caso, un examen más detallado de la función (¡y no de su nombre!) nos muestra
que tras su ejecución, la función devuelve la posición de un entero dado e dentro de un vector
ordenado de enteros, devolviendo si el elemento no está en el vector. Lo que acabamos de
probar es que su caso mejor se da cuando el elemento está en la primera posición del vector. El
caso peor se produce cuando el elemento no está en el vector, y el caso medio ocurre cuando
Año 2013 Página 7
Teoría de la Computación – Apuntes de Cátedra

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).

3.3 COTAS DE COMPLEJIDAD. MEDIDAS ASINTÓTICAS.

Una vez vista la forma de calcular el tiempo de ejecución T de un algoritmo, nuestro


propósito es intentar clasificar dichas funciones de forma que se pueda compararlas. Para ello,
se define clases de equivalencia, correspondientes a las funciones que "crecen de la misma
forma”.
En las siguientes definiciones N denotará el conjunto de los números naturales y R el de
los reales.

3.3.1 Cota Superior. Notación O

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:

Sea f: N  [0, ) se define el conjunto de funciones de orden O (omicron) de f como:

Diremos que una función t: N [0,∞) es de orden O de f si t  0(f)

En el ejemplo del algoritmo Buscar analizado anteriormente obtenemos que su tiempo


de ejecución en el mejor caso es 0(1), mientras que sus tiempos de ejecución para los casos
peor y medio son O(n)

3.3.2 Cota Inferior. Notació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

Sea f: N  [0, ) se define el conjunto de funciones de orden  (omega) de f como:

Diremos que una función t: N [0,∞) es de orden  de f si t  (f)

Año 2013 Página 8


Teoría de la Computación – Apuntes de Cátedra

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.

3.3.3 Orden Exacto. Notación 

Como última cota asintótica, definiremos los conjuntos de funciones que crecen
asintóticamente de la misma forma:

Definición

Sea f: N  [0, ) se define el conjunto de funciones de orden ( Theta) de f como:


(f) = O(f)  (f)
O lo que es igual:

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.

Observaciones de las Observaciones sobre las cotas asintóticas:

1- La utilización de las cotas asintóticas para comparar funciones de tiempo de


ejecución se basa en la hipótesis de que son suficientes para decidir el mejor
algoritmo, prescindiendo de las constantes de proporcionalidad. Sin embargo, esta
hipótesis puede no ser cierta cuando el tamaño de la entrada es pequeño.
2- Para un algoritmo dado se pueden obtener tres funciones que miden su tiempo de
ejecución, que corresponden a sus casos mejor, medio y peor, y que denominaremos
respectivamente Tm(n), Tl/2(n) y Tp(n). Para cada una de ellas podemos dar tres cotas
asintóticas de crecimiento, por lo que se obtiene un total de nueve cotas para el
algoritmo.
3- simplificar, dado un algoritmo diremos que su orden de complejidad es O(f) si su
tiempo de ejecución para el peor caso es de orden O de f, es decir, Tm(n) es de orden
O(f). De forma análoga diremos que su orden de complejidad para el mejor caso es
Q(g) si su tiempo de ejecución para el mejor caso es de orden Q de g, es decir, Tm(n)
es de orden Q(g).
4- Por último, diremos que un algoritmo es de orden exacto 0(f) si su tiempo de
ejecución en el caso medio Tl!2(n) es de este orden.

3.4 ÓRDENES DE COMPLEJIDAD

Se dice que O(f(n)) define un "orden de complejidad". Se escogerá como


representante de este orden a la función f(n) más sencilla del mismo. Así se tendrá:

O(1) orden constante


O(log n) orden logarítmico
O(n) orden lineal
O(n log n) orden n*logarítmico

Año 2013 Página 9


Teoría de la Computación – Apuntes de Cátedra

O(n2) orden cuadrático


a
O(n ) orden polinomial (a >= 2)
n
O(a ) orden exponencial (a >= 2)
O(n!) orden factorial
Es más, se puede identificar una jerarquía de órdenes de complejidad que coincide con
el orden de la tabla anterior; jerarquía en el sentido de que cada orden de complejidad superior
tiene a los inferiores como subconjuntos. Si un algoritmo A se puede demostrar de un cierto
orden O1, es cierto que también pertenece a todos los órdenes superiores (la relación de orden
cota superior es transitiva); pero en la práctica lo útil es encontrar la "menor cota superior", es
decir el menor orden de complejidad que lo cubra.

3.5 Impacto Práctico

Para captar la importancia relativa de los órdenes de complejidad conviene hacer


algunas cuentas. Sea un problema que se sabe resolver con algoritmos de diferentes
complejidades. Para compararlos entre sí, se supone que todos ellos requieren 1 hora de
computadora para resolver un problema de tamaño N=100.
¿Qué ocurre si se dispone del doble de tiempo? Nótese que esto es lo mismo que
disponer del mismo tiempo en una computadora el doble de potente, y que el ritmo actual de
progreso del hardware es exactamente ese:
"duplicación del número de instrucciones por segundo".
¿Qué ocurre si queremos resolver un problema de tamaño 2n?
O(f(n)) N=100 t=2h N=200
log n 1h 10000 1.15 h
n 1h 200 2h
n log n 1h 199 2.30 h
2
n 1h 141 4h
3
n 1h 126 8h
2 n
1h 101 1030 h

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

Año 2013 Página 10


Teoría de la Computación – Apuntes de Cátedra

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:

algoritmo tiempo complejidad


f n O(n)
g 100 n O(n)

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.

Año 2013 Página 11


Teoría de la Computación – Apuntes de Cátedra

4. Funciones Recursivas Primitivas (FRP) y Funciones Recursivas


(FR)
4.1 Introducción:
Unas de las cuestiones básicas de las ciencias de la computación es: lo que se puede computar
y lo que no se puede computar. Más concretamente, si se nos da un problema computacional en
particular, la cuestión es si existe o no un método efectivo para resolver este problema
computacional. Con esto queremos decir si existe alguna computadora que pueda resolver el
problema dentro de un período finito de tiempo. A efectos de la presente discusión, es
irrelevante la cantidad de tiempo que se necesite para encontrar la solución, tanto si son días,
años o siglos. Sólo nos concierne la posibilidad de que exista, en principio, una solución.
Es importante distinguir entre un problema en general y un caso particular de un problema.
Por ejemplo, determinar la validez de una expresión lógica arbitraria es un problema general.
Un caso concreto de este problema es la determinación de la validez de una expresión lógica
concreta. Un procedimiento por computadora correspondiente a este problema tendría entonces
un caso concreto del problema como entrada, e indicaría en su salida si ese caso concreto era o
no válido. Ahora la cuestión es si existen o no procedimientos que puedan resolver todos los
casos de un cierto problema en un período de tiempo finito. Para nuestro ejemplo, esta cuestión
pasa a ser; ¿Existe un procedimiento que pueda decidir si es válida o no cualquier expresión
lógica? Para las expresiones lógicas del cálculo de proposiciones, existe ciertamente un
procedimiento así. El problema de la validez en el cálculo de proposiciones es, en este sentido,
decidible. Sin embargo, no existe un procedimiento que pueda determinar si es o no válida una
expresión arbitraria del cálculo de predicados. El problema de demostrar la validez de una
expresión general del cálculo de predicados es indecidible. Es importante ver lo que significa
esto. Es muy posible que se pueda diseñar un procedimiento que indique si son o no válidas la
mayoría de las expresiones lógicas que contienen predicados. Sin embargo, es un hecho que
existen algunas expresiones para las cuales no es posible determinar la validez por ningún
procedimiento. Específicamente, el procedimiento no termina nunca. Obsérvese que en nuestra
discusión hemos empleado la palabra procedimiento y no algoritmo. La diferencia entre un
procedimiento y un algoritmo es que un algoritmo siempre concluye, mientras que es posible
que un procedimiento no llegue a terminar. Por tanto, un problema es indecidible si y sólo si no
existe un algoritmo para él.
Hay varias maneras de enunciar en forma matemática lo que se puede calcular y lo que no se
puede calcular. En 1936, Alan Turing definió una máquina, que ahora se llama máquina de
Turing, y demostró que todo aquello que se pueda computar en un sentido intuitivo se podrá
calcular también en una máquina de Turing. En 1931, Gödel presentó un conjunto de funciones
que denominó recursivas. Estas funciones fueron generalizadas por Kleene en 1934, y esto dio
lugar posteriormente a que Church formulase su famosa tesis en que indica que todo aquello
que se pueda computar en un sentido intuitivo puede expresarse en términos de funciones
recursivas generalizadas.
En la actualidad, el término función recursiva (FR) se utiliza para describir lo que Kleene
denominara funciones recursivas generalizadas, y para las funciones descubiertas por Gödel se
emplea el término funciones recursivas primitivas.
Kleene muestra que existe la equivalencia entre lo que puede ser computado con una MT y lo
que puede expresado por una FR, de modo que:
MT ≣ FR

Año 2013 Página 12


Teoría de la Computación – Apuntes de Cátedra

Antes de comenzar vale mencionar los elementos que caracterizan a las definiciones recursivas,
ya que nos serán de utilidad.

Los elementos de una definición recursiva son:


① Bases (condiciones límites o axiomas).
Valores conocidos (del conjunto, relación ó
función).
② Reglas de construcción recursivas.
La forma de obtener nuevos valores desde valores
previamente conocidos.
③ Interpretación de los valores que puede tomar la
función, relación o conjunto.
Los valores (del conjunto, relación ó función) se
obtienen desde la aplicación de un número finito de
veces de las reglas de construcción a los valores bases.

Desde ①, ② y ③ se puede especificar un procedimiento efectivo para evaluar una función


(siempre que este definida para todo argumento).
Ejemplo1: Definición Recursiva de Conjuntos
¿Cómo definir el conjunto de los números naturales? N = {1, 2, 3, 4, 5, ………}

Definición recursiva de los números naturales:


① 1ϵN
② nϵN → s(n)=n+1ϵN
③ Los únicos elementos de N son los que se obtienen de aplicar las reglas ① y ② un numero
finito de veces.

Ejemplo2: La expresión algebraica de la “Función de Fibonacci” f(n) para algún n∈ N es:

.
Esta puede redefinirse en forma recursiva como sigue:
① f(0) = 0 y f(1) = 1
② f(n+2) = f(n) + f(n+1)

4.2 Funciones Recursivas Primitivas (FRP)


En búsqueda de encontrar un modelo de lo que entendemos por calculable, construiremos un
conjunto de funciones que llamaremos Funciones Recursivas Primitivas.
Comenzaremos con una colección de funciones cuya sencillez sea tal que no haya duda a
acerca de su computabilidad. A este conjunto de funciones que utilizaremos como los ladrillos
de construcción, lo llamaremos Funciones Base. Luego mostraremos que estas funciones base

Año 2013 Página 13


Teoría de la Computación – Apuntes de Cátedra

combinadas con otras nuevas, que servirán de reglas de construcción, nos permiten formar
otras cuya computabilidad se desprenda de las originales.

4.2.1 Funciones Numéricas

Definición: Llamaremos función numérica a toda función


f:N0n→N0 , con dominio D N0n y n N0 , siendo N0 el conjunto de
los números naturales donde el 0(cero) es uno de sus
elementos.

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.

4.2.2 Funciones Base


La base de la jerarquía de funciones computables consiste en una familia de funciones finita,
que serán clave a la hora de definir las FRP.

Definición: Llamaremos función sucesor, y la simbolizaremos


s, a la función s: N0→N0 tal que s(x)=x+1.

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.

Definición: Llamaremos función cero (zero), y la


simbolizaremos z, a la función z: N0→N0 tal que z(x)=0.

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.

Año 2013 Página 14


Teoría de la Computación – Apuntes de Cátedra

Definición: Llamaremos función proyección en la k-ésima


componente, y la simbolizaremos I(n)k, a la función
I(n)k: N0n→N0 tal que I(n)k(x1,x2,…,xk, …,xn)=xk , para 1≤ k
≤n.
Ejemplo2:
I(4)3(x, y+1 , f(x,y+1) ,0)= f(x,y+1)
Al igual que las demás funciones iniciales (z y s) , es fácil establecer que las proyecciones
deben estar en la clase de funciones computables.
Un caso especial es la función proyección de un sólo argumento I (1)1(x)=x. Esta función se
denomina función identidad y la simbolizaremos I (esto es, I(x)=x).

Definición: Llamaremos funciones base a cualquier función


numérica del conjunto conformado por las funciones s , z y
todas las proyecciones I(n)k , con k N y 1≤ k ≤n.

4.2.3 Reglas de Construcción


Las funciones referidas anteriormente, por sí solas, no pueden lograr mucho. Por lo tanto,
estudiaremos como estas pueden emplearse para construir otras funciones más complejas.

La composición

Definición: Dada una función numérica h(m) y una familia


finita de funciones numéricas {gi(n)}mi=1 , definimos una nueva
función f(n) de manera que:
f(n):N0n→N0 y f(n)(x)= h(g1(x),g2(x),…,gm(x)),
con x=( x1,x2,…,xk, …,xn) N0n.
Se puede ver que:
“la composición de funciones computables es computable”,
ya que si h (m) y el conjunto de funciones gi(n) , para i=1,2,…,m son computables, basta con
calcular las diferentes gi(n) y usar sus salidas como entradas de la función h (m) .

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)

Año 2013 Página 15


Teoría de la Computación – Apuntes de Cátedra

Por último veremos el “constructor” llamado “recursión”. Éste combinado con lo dado hasta
ahora nos dará la posibilidad de representar muchas funciones computables.

Definición: Sea k N0 y sean g(k) y h(k+2) dos funciones


numéricas. Definimos una nueva función numérica f(k+1) de la
siguiente manera:
f(xk,0)=g(xk)
f(xk,y+1)=h(xk,y,f(xk,y))
Notación: f= (g,h) se lee “f se obtiene por recursión primitiva por medio de las funciones g y h”.
Árbol de evaluación:

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))

donde: g=I y h(x,y,z)= s(I(3)3(x,y,z)).


Notación: suma=R(g,h)=R(I, (s, I(3)3))

Estamos ahora en condiciones de definir el conjunto de funciones recursivas primitivas:


Definición:
a) Todas las funciones base son recursivas primitivas.
b) Las funciones obtenidas a partir de funciones recursivas
primitivas aplicando un número finito de composiciones y
recursiones son funciones recursivas primitivas.
c) Las funciones obtenidas según a) y b) son todas las
funciones recursivas primitivas.

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:

Año 2013 Página 16


Teoría de la Computación – Apuntes de Cátedra

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:

ii) Función Multiplicación: mult(x,y)=x.y


iii) Función Factorial: Fac(x)=x!
iv) Función exponencial: Exp(x,y)=xy, con la convención de que 00=1.
v) Función iszero (es cero) : .

vi) Función signo : .

vii) Igualdad:

Demostramos el apartado i). (Dejamos como ejercicio el resto.)


Intentemos definir la función Pr(x) que cumpla con lo especificado anteriormente.
Tenemos que: Pr(0) = 0 = z(0)
y además Pr(y + 1) = y = I(2)1 (y; Pr(y))
De esto deducimos que se puede construir Pd por recursión a partir de z(0) y I(2)1 ; Pr =
R(z(0); I(2)1 )) .
Por lo tanto: Pr FRP. ◙

4.2.4 Conjuntos recursivos primitivos (CRP)

Definición: Dado un conjunto X, para cada subconjunto A X


definimos su función característica :X→{0,1} por :

Ejemplo 5: Si consideramos X={0,1,2}, los subconjuntos de X son: ϕ, {0}, {1}, {2},


{0,1},{0,2},{1,2} y X. ( 23=8 subconjuntos que son los elementos de partes de X, P(X).)
Podemos definir 8 funciones características, una para cada subconjunto de X, donde algunas de
ellas se muestran a continuación:
Para A= ϕ (x) = 0.

A= {1} (x) = .

A= {0,2} (x) = .

Es importante notar que existe una correspondencia biunívoca entre funciones características y

Año 2013 Página 17


Teoría de la Computación – Apuntes de Cátedra

el subconjunto que la determina.


Formalizamos esta idea:
Proposición 2: Sea X un conjunto, P(X) su conjunto partes, y F el conjunto de todas las
funciones características :X→{0,1}, con A . Entonces la aplicación : P(X)→ F tal que
A es una función biyectiva, cuya inversa es : F→ P(X) tal que )={x
: .
Demostración: Se da como ejercicio. (Basta probar que: ).
Lo que nos dice esta proposición es que:
“trabajar con subconjuntos de X y con funciones características de F es esencialmente lo
mismo.”
Nuestro interés en las funciones características es que nos permiten expresar las operaciones
booleanas entre conjuntos de forma algebraica. Más precisamente tenemos la siguiente
proposición.
Proposición 3: Sea un conjunto X. Para cada A,B X se tiene que:
a)
b)
c) .
Demostración: Ejercicio.

Esta idea se conecta con las FRP mediante la siguiente definición:


Definición: Sea . Diremos que un subconjunto A de N0 es un
conjunto recursivo primitivo si su función característica
es recursiva primitiva.

Con esta definición y usando la Proposición 3 podemos enunciar la siguiente proposición.


Proposición 4: Sea , y sean A y B dos subconjuntos de N0k. Si A y B son conjuntos
recursivos primitivos, entonces A , son conjuntos recursivos primitivos.
Demostración: Ejercicio.
Para introducir algún ejemplo de conjunto recursivo primitivo, incluiremos la siguiente
proposición.
Proposición 5: Sea . Todos los subconjuntos finitos de son recursivos primitivos.
Demostración: Sea A un subconjunto finito de . Luego: i) A=ϕ ó ii) A= (A es

unión finita de subconjuntos unitarios de A).


Si vale i) sabemos que (x) = 0 = z(I1(k)(x)). Por lo tanto =
(z,I1(k)) la cual es FRP. Luego el conjunto vacio es recursivo primitivo.
Si vale ii) ( trabajamos por inducción sobre la k-upla de elementos de .)
P(k):”
- Paso Base:

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

Año 2013 Página 18


Teoría de la Computación – Apuntes de Cátedra

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.

Demostración: Se desprende de la proposición anterior, y del hecho que los conjuntos de


exactamente un elemento son recursivos primitivos.

4.2.5 Relaciones recursivas primitivas (RRP)

Por último, presentamos las relaciones recursivas primitivas. Antes, vale recordar la siguiente
definición.

Definición: Llamaremos relación R entre dos conjuntos A y B


a todo subconjunto del producto cartesiano AxB.

R relación de A en B sii R AxB.

Año 2013 Página 19


Teoría de la Computación – Apuntes de Cátedra

Ahora estamos en condiciones de dar la siguiente definición.

Definición: Decimos que R N0xN0 es una relación recursiva


primitiva si el conjunto que la define es recursivo
primitivo.

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.

Ejercicio: Mostrar que la relación ≤ (menor o igual que) en N es recursiva primitiva.

4.2.6 Procedimientos y funciones recursivas primitivas


- Procedimientos con sentencias “if”

Frecuentemente, se necesitan funciones que están definidas por distintas expresiones y


en distintos dominios. Para hacer posible esto se define la función if en la siguiente
forma:
if = f. A + g . B
donde f , g , A y B son FRP.
- Procedimientos con sentencias “for” ( procedimiento con bucle anidado con
contador)

Según lo visto anteriormente la sentencia if es recursiva primitiva. Ahora mostraremos


que la sentencia for se puede transformar en una función recursiva primitiva. Para
hacer esto, supongamos que el bucle va desde 1 hasta m, y que se utiliza n como índice.
Además supondremos que todas las variables salvo m se combinan en una sola variable
y. El cuerpo de la sentencia for se puede expresar ahora como una única función,
digamos y=G(y,n), en donde n es el índice del bucle. La función correspondiente a este
bucle es f. En otras palabras f se puede expresar, por ejemplo en Pascal, en la forma:
f=(for n:=1 to m do y:=G(y,n))
Es posible expresar f en términos de una recursividad primitiva. De hecho, se tiene:
f(y,0)=y donde g=I.
f(y,n)=h(y,n-1,f(y,n-1)) donde h(y,n,z)=G(z,n).
- Las llamadas recursivas a procedimientos como las sentencias de bucles sin contador,
tal como las sentencias while y repeat, pueden dar lugar a programas que no terminen,
se concluye que ninguna de estas estructuras se puede expresar en términos de una
recursividad primitiva.

4.2.7 Predicados recursivos primitivos (PRP)


Varias de las funciones definidas en la sección 2.4, como Pr o sg, se han definido por casos

Año 2013 Página 20


Teoría de la Computación – Apuntes de Cátedra

(son funciones definidas a trozos). Estas funciones responden a la forma general:


. (A)
Recordemos que una “condición” P que depende de una variable xϵN0n, de modo que P(X) es
verdadera o falsa se llama predicado. Dicho de otra manera, un predicado P de n-lugares es una
función parcial P:N0n→{ verdadero, falso} que asigna a cada n-upla x , de 0 ó 1, un único valor
de { verdadero, falso}. Se relaciona estrechamente un predicado con su función característica:

: N0n→{0,1} definida por :

De tal forma que (A) es equivalente a:

Nota: En la práctica, en la expresión (A) se sobreentiende la verdad de la condición P(x) y se


escribe:

Definición: Decimos que un predicado P de n-lugares es


computable, si su función característica es computable.

Definición: Decimos que un predicado P de n-lugares es un


predicado recursivo primitivo, si su función característica
es recursiva primitiva.

Proposición 7: Si P1 y P2 son dos predicados recursivos primitivos de n-lugares, entonces


también lo son P1 P2, P1 P2 y ¬P1.
Demostración: Ejercicio.

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.

Proposición 9: Si P es un predicado recursivo primitivo de (n+1) lugares. La cuantificación


existencial limitada:

Año 2013 Página 21


Teoría de la Computación – Apuntes de Cátedra

EP(x,k)=(existe y con 0≤y≤k tal que P(x,y) es verdadero);


y la cuantificación universal limitada:
AP(x,k)=(para cada y que satisface 0≤y≤k; P(x,y) es verdadero)
son predicados recursivos primitivos.
Demostración: (Ver Capitulo 4 -pags. 456 y 457- Libro de John Martin(2004) “Lenguajes
formales y teoría de la computación” (3° Edición) Ed. Mc Graw Hill.)

Ejemplo: El resto y el cociente “div” son funciones recursivas primitivas, pues:


resto(0,x)=0
resto(y+1,x)=
y
div(0,x)=0
div(y+1,x)=

4.3 Más allá de las FRP


4.3.1 Introducción
En nuestra búsqueda por encontrar un modelo de cálculo, hemos definido el conjunto de las
funciones recursivas, y después de haber visto una gran cantidad de ejemplos de estas
podríamos pensar que las FRP son el modelo de cálculo que buscábamos. Pero bien, veamos
¿habrá alguna función que no podemos representar como función recursiva primitiva? Más
adelante podremos ver que sí, existen funciones que no podremos representar como FRP. Para
aclarar mejor esto veamos algunas características que presentan las FRP.

4.3.2 Características de las FRP


La clase de FRP tiene entre sus propiedades:
i) Son todas funciones totales.
ii) Son posibles de caracterizar a través de las derivaciones recursivas
primitivas.

Definición: Una función f tiene una derivación recursiva


primitiva si existe una secuencia finita de funciones f1,f2,
…, fn tal que f=fn y cada fi de la secuencia es una función
base o puede obtenerse de funciones previas de la secuencia
por composición o recursión primitiva.
La definición anterior muestra un proceso para obtener un algoritmo para evaluar una función f
desde una secuencia: f1 f2 . . . fn = f .

A continuación se demuestra sólo la primer propiedad (la segunda queda como ejercicio).

Teorema: Si f es FRP entonces f es total.


Demostración. Observemos que las funciones base están definidas para todo su conjunto de
partida, con lo cual las funciones base son totales. Además, los procesos de composición y
recursión tienen la característica de ser conservativos respecto de la propiedad de ser totales.

Veamos la prueba para la composición.


Sea f(n). Sean g1(m) ; g2(m) ;…; gn(m) n- funciones totales, queremos probar que h(m) = [f(n);

Año 2013 Página 22


Teoría de la Computación – Apuntes de Cátedra

g1(m) ; g2(m) ;…; gn(m) ] es total.


Sea x N0m podemos calcular xi = gi(x); i = 1 ,…, n pues gi es total.
Entonces
y: y = f(x1; x2;…; xn) ,

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. ◙

En nuestra primera aproximación a los procesos de cálculo que deseábamos modelizar,


partíamos de que debían ser capaces de representar algunos algoritmos de cálculo elementales,
como por ejemplo el que determinaba la raíz cuadrada de un número natural. Claramente, el
cálculo de la raíz cuadrada de un número no es una función total, por lo que nos encontramos
con que hay funciones calculables que no pueden ser representadas por una función recursiva
primitiva.

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.

Teorema: Existe una función computable total de N0 en N0 que no es recursiva primitiva.


D/:
o Para enumerar las funciones recursivas primitivas se necesita codificar sobre un
alfabeto finito (por ejemplo {0,1}) el sistema de ecuaciones que las representa. El
alfabeto debe contener símbolos para la función proyección, sucesor, la función cero,
para la recursión y la composición, además para los paréntesis, y los índices de las
funciones básicas como I71 .
Sea un alfabeto ∑ que contiene todos los símbolos que podrían ser necesarios para
describir funciones numéricas de N0 en N0. Toda función recursiva primitiva puede ser
especificada por medio de las funciones básicas que lo representan, que a su vez puede
ser representada por una cadena en ∑.

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


En la demostración, se consideran sólo funciones recursivas primitivas con un único


argumento. Por ello, en las columnas de la matriz se coloca el conjunto de los números
naturales:
N0

0 1 2 …
f0
f1
f2
FRP f3

o Y cada posición (i,j) de la matriz es el valor resultante de evaluar a fi para el argumento


xj.

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) …
… … … … …
… … … … …

g es una función total , pues está definida para todo natural de N0 .


g es computable, pues acabamos de esbozar como realizar su cómputo.
Probemos ahora que g no es FRP.
Supongamos que la función g es FRP, debería estar en la enumeración ya que están
enumeradas todas las FRP.
Luego, existe un j∈ N tal que g(n) = fj(n) para todo n ∈ N .
En particular la igualdad anterior se cumple para un n=j, y con lo cual :
Año 2013 Página 24
Teoría de la Computación – Apuntes de Cátedra

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. ◙

4. Funciones Recursivas (FR)


En la sección anterior hemos demostrado que existen funciones totales que no son funciones
recursivas primitivas. En esta sección agregaremos un nuevo operador con el objetivo de
definir una nueva familia de funciones, la cual llamaremos funciones recursivas.
4.4.1 El minimizador
A los procedimientos constructivos que teníamos (composición y recursión), agregaremos un
nuevo mecanismo de fabricación de funciones a partir de otras. Lo llamaremos minimización
(minimalization) y se define de la siguiente manera.

Definición: Dada una función numérica total g(n+1), decimos


(n)
que la función numérica parcial f se construye por
minimización de g, si:
f(n)(x) = }
es decir:
f(n)(x): “es el mínimo valor , dentro del conjunto de los
naturales, que verifican g(x,m)=0, en caso de existir”.
Notaremos: f(n)(x) = .

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.

Definición: Diremos que una función numérica g(n+1) es


minimizable si verifica la propiedad:
2013 cada x∈N0 hay un valor m∈N tal que g(x,m)=0”.Página 25
n
“ para
Año
Teoría de la Computación – Apuntes de Cátedra

- Es claro que si en el procedimiento (I) g(x,m) es minimizable, éste (el procedimiento)


es un algoritmo.

- Las funciones numéricas definidas con el operador minimización, minimizando una


función g minimizable, son totales.

Definición: El conjunto de funciones - recursivas son aquellas


que se pueden obtener desde las funciones básicas utilizando las
reglas de composición, recursión primitiva, y minimización de
funciones minimizables, un número finito de veces.
Se puede probar que: “Toda función es computable por Máquina de Turing.”
Ejemplo 7. Intentemos definir la función raíz cuadrada, raiz(x) = , para números naturales.
Vale decir que para valores cuya raíz cuadrada no es un número natural, la función no está
definida.
raiz(x) = = min{m N0 /m2 = x}
Observemos que:
m2 = x ≡ E(m2; x) = 1 ≡ dif(1,E(m2; x)) = 0
g(x,m) = dif(1,E(m2; x)) función total, recursiva primitiva pero no minimizable.
El procedimiento de búsqueda del valor m lo podemos acotar para cada caso particular x, ya
que se sabe que m nunca puede superar al valor de x.
Ahora estamos en condiciones de definir las funciones recursivas.

Definición: Conjunto de Funciones Recursivas (FR)(o funciones -


recursivas parciales)
(i) Si f FRP, entonces f es FR.
(ii) Para cada n≥0 y cada función numérica total g(n+1) de FR, la
función parcial fn:N0n→ N0 definida por
fn(x)=
es una FR.
(iii) Ninguna otra función está en FR.

4.4.2 Tesis de Church


Desde el comienzo, nuestro objetivo fue encontrar una forma de modelar lo que entendemos
por cálculo. Para eso definimos las FRP, luego las FR y ahora nos preguntamos: serán estas
últimas el conjunto de todas las funciones calculables que pretendemos abarcar con nuestro
modelo?.
La respuesta a este interrogante es conocida como la Tesis de Church.
Toda función calculable puede ser expresada como una función recursiva. Si esta tesis es
Año 2013 Página 26
Teoría de la Computación – Apuntes de Cátedra

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.

5. Nota Complementaria Trataremos ahora un problema famoso, el problema de


detención.

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);

while finaliza(x)=1 do print(“sigue en marcha”)

Podemos dar la entrada x cualquier valor, incluyendo x=B. Por tanto, ejecutamos B tomando
como entrada al propio B.

Supongamos ahora que finaliza es una función decidible.

• 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

- Cero: z(x) = 0 (informal 0)


- Sucesor: s(x) = x+1 (∀ x ∈ N) (informal x+1)
- Proyección: Ik(x) = xk ( x = (x1, x2, . . . , xn), 1 ≤ k ≤ n ) (informal xk)
(Si n=1 entonces I(x) es la función identidad)

• 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

Año 2013 Página 28


Teoría de la Computación – Apuntes de Cátedra

Funciones μ-Recursivas
Se definen utilizando el operador de minimización y extienden el conjunto de funciones recursivas primitivas.
Minimización:

El resultado de f(x) se obtiene desde:

Observación: la secuencia de computaciones puede continuar infinitamente sin obtener un resultado para f(x) .

Cuadro 2

Año 2013 Página 29


Teoría de la Computación – Apuntes de Cátedra

5. Máquina de Turing (M. T.)

Es un Modelo que se estudió en la anterior unidad, pero se recuerda, que es un modelo


abstracto de computación.

Definición: Una MT estándar determinística, es una 6-upla M=(Q,Σ,Γ,δ,q0,qf) donde:

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:

5.1 Lenguajes Recursivos y Recursivamente Enumerables

La familia de Lenguajes aceptados por MT es la familia de los lenguajes Tipo 0 o


lenguajes recursivamente enumerables (r.e.) ≡ Lenguajes generados por Gramáticas
Irrestrictas.

Incluidos en la familia de lenguajes r.e. se encuentran los lenguajes recursivos (rec.) y


dentro de ellos, los generados por Gramáticas Dependiente del Contexto ≡ lenguajes aceptados
por ALA (Autómata Linealmente Acotado).

Definición:

Un lenguaje (conjunto) L ⊆ Σ∗ es recursivamente enumerable (r.e.) si existe una función f : N


→ L, total, sobreyectiva y computable. Esto significa que los elementos de L pueden ser
enumerados, es decir, asignarle un índice natural.

Definición de función característica:

Sea L r.e, luego L es recursivo si la función característica asociada a L, c : Σ∗ → {0, 1} y


definida de la siguiente manera:

es una función total. Es decir que para un lenguaje (conjunto) recursivo siempre es posible
determinar la pertenencia de un elemento dado.

Año 2013 Página 30


Teoría de la Computación – Apuntes de Cátedra

Un lenguaje L es enumerable recursivamente si existe una MT que acepte a L, y


recursivo si existe una MT que dcide a L. En algunas ocasiones se les suele llamar lenguajes
aceptables por MT y lenguajes que pueden decidirse con MT, respectivamente.

De lo anterior se desprende que cada lenguaje recursivo es enumerables


recursivamente. Los lenguajes recursivamente enumerables, forman la clase más general entre
los conjuntos de lenguajes computables, para los que se pueden construir MT que los aceptan.
Los lenguajes recursivamente enumerables pueden ser generados por una gramática sin
restricciones, también llamada de Tipo 0, según la Jerarquía de Chomsky. Esta categoría
incluye los lenguajes sensibles al contexto.

Tanto la operación de unión como la intersección conservan la propiedad de ser


enumerables de manera recursiva.

5.2 Relación entre lenguajes recursivos y la noción de algoritmo:

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.

 La familia de lenguajes recursivos es un subconjunto propio de la familia de los


lenguajes r.e., es decir que:
o Si L es un lenguaje recursivo, luego L es r.e.. Para mostrar esta parte, es posible
usar la función característica como hipótesis.

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.

L puede ser enumerado usando una matriz infinita de la siguiente manera:

Año 2013 Página 31


Teoría de la Computación – Apuntes de Cátedra

p/q 1 2 3 4 .....

1 (1,1) (1,2) (1,3) (1,4) ......

2 (2,1) (2,2) (2,3) (2,4) ......

3 (3,1) (3,2) (3,3) (3,4) ......


...

...

...

...

...
......

El algoritmo para computar f : N → L ...


Se obtiene lo valores para:
f (1) =? f (4) =? f (5) =?

L puede ser enumerado, pero la función característica c es total?.

5.3 Cardinalidad de conjuntos

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

∑ * son todas las posibles cadenas a partir de ∑

2 ∑* representa un conjunto, y corresponde a todos los posibles lenguajes que se pueden formar
con ∑*

Preguntas asociadas:

 ¿Qué carnalidad tiene ∑*?, es decir  ∑ * ?


 ¿Qué carnalidad tiene 2 ∑* ?, es decir  2 ∑* ?
 ¿En el infinito serán iguales  ∑ * =  2 ∑* ?

Se verá la cardinalidad de los números Naturales,   .

 = 0

La cardinalidad del conjunto de los números naturales es (aleph cero), no es un número, es un


símbolo para poder diferenciarlo de otras cardinalidades infinitas, este es una cardinalidad
infinita.

Ahora, puede surgir como cuestionamiento:

 =?

Año 2013 Página 32


Teoría de la Computación – Apuntes de Cátedra

¿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:

Los números enteros son contablemente infinitos, por esto sería:

 = 0

Ejemplo:

Puede surgir la pregunta:

Q = 0 ? Qué cardinalidad tendría Q? Siendo;

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 .....

1 1/1 ½ 1/3 1/4 ......

2 2/1 2/2 2/3 2/4 ......

3 3/1 3/2 3/3 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:

Año 2013 Página 33


Teoría de la Computación – Apuntes de Cátedra

Por lo anterior, se tiene que  ∑ * = 0

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.

Con lo anterior se obtiene que:

R= 2 0

Se dice que son enumerables si a cada elemento de un conjunto se le hace corresponder un


número natural.

La cardinalidad de  ∑ *   2 ∑* pues se sobreentiende que ∑ * es menor que 2 ∑*. Se


verá en el siguiente gráfico:

Año 2013 Página 34


Teoría de la Computación – Apuntes de Cátedra

5.4 CODIFICACIÓN DE UNA MAQUINA DE TURING


Lo que sigue es una M. T.:

Cuando se dice que un lenguaje L es decidible, es porque la M. T. puede decidir por sí o


por no, pues la manera de decidir si acepta o no una cadena del lenguaje L.

Por lo tanto, L  ∑ *

Ejemplo:

Sea una Gramática G, Tipo 2, surge la siguiente pregunta G es ambigua? El anterior es


un problema de decisión, pues es por un si o por un no.

Para el ejemplo:
G: cuyas producciones son:

1. S  aS
2. S  Sa
3. S  a

Gráfico de una M. T.

Año 2013 Página 35


Teoría de la Computación – Apuntes de Cátedra

Se va a codificar la gramática G,  G , para realizar lo anterior, se utiliza un alfabeto y


así codificar la Gramática G. Siguiendo con el ejemplo:

S # aS ## S # Sa ## S # a  ∑*  es una cadena

Es decir, que si la codificación es introducida a la M. T. del gráfico, la respuesta sería


Si. Pues la M. T. recibe una cadena como S # aS ## S # Sa ## S # a , y puede decidir si
es o no ambigua, por lo tanto es una codificación válida.

El ∑ es { S, #, a} surge la siguiente pregunta: ¿Qué lenguaje L está decidiendo la M.


T.?

Son todas las


codificaciones de la
Gramática G, tal que
son ambiguas, es decir:
L= {  G   ∑* / G
es ambigua}

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

M de la forma: Γ= {s1,s2,.....,sm,....,sp}, donde s1 representa el símbolo blanco 0, Δ o b (según


se desee denotar), Σ = {s2,.....,sm} es alfabeto de entrada y sm + 1,...,sp son los símbolos
auxiliares utilizados por M(cada MT utiliza su propia colección finito de símbolos auxiliares).
Todos estos símbolos se codifican como secuencias de unos:

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

Las directrices de desplazamiento D (derecha), I (izquierda) y S se codifican con 1, 11,


111, respectivamente. Una transición δ(q, a ) = (p, c, D) se codifica usando ceros como
separadores entre los estados, los símbolos del alfabeto de cinta y la directriz de
desplazamiento D.

Año 2013 Página 37


Teoría de la Computación – Apuntes de Cátedra

Así, la transición δ(q3,s2) = (q5,s3, D) se codifica como 01110110111110111010. En


general, la codificación de una transición cualquiera δ(qi,sk) = (qj,st, D) es 01i01k01j01l01t
donde t= 1 ó 2 ó 3, según la dirección sea derecha(D), izquierda(I), esperar(S).

Una MT se codifica escribiendo consecutivamente las secuencias de las modificaciones


de todas sus transiciones. Mas precisamente, la codificación de una MT M es de la forma
C1C2...Ci donde Ci son las codificaciones de las transiciones de M. Puesto que el orden en que
se representen las transiciones de una MT no es relevante, una misma MT tiene varias
codificaciones diferentes. Esto no representa ninguna desventaja práctica o conceptual ya que
no se pretende que las codificaciones sean únicas.

Ejemplo:

Sea G, una gramática, cuyos elementos son:


N = { S, A, B}
∑= {a, b, c}
P:
S  AB
A  aA | a
Bb
Se realiza la codificación de G:

S0
A  00
B  000 Se asocia un orden, con un índice.
Se va a codificar a  como #
a1
b 11
c  111

Según lo anterior, se tiene:

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##
Aa
00#1,
Bb
000#11,

Año 2013 Página 38


Teoría de la Computación – Apuntes de Cátedra

Es un problema que no tiene un algoritmo asociado, pues demostrar que G no es


ambigua, no es decidible.

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:

 Transformar una instancia de A en una instancia de B


 Resolver el problema B para luego obtener una solución de A

Algoritmo para A donde A reduce a B (es una M. T.)

T(x) si o no
x
Algoritmo
T
para B

Del gráfico anterior se desprende:

 Que x es una instancia de A y T(x) es una instancia de B.


 No es la llamada a un procedimiento. Se transforma la instancia e A que es x y con el
algoritmo de B, se puede dar como respuesta si o no.
 Usamos una solución de B para resolver A. B es más general que A.

Gráficamente, el conjunto de instancias de A (IA ) y un conjunto de instancias de B (IB).

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.

Año 2013 Página 39


Teoría de la Computación – Apuntes de Cátedra

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:

 El problema B es el problema inverso de A


 La respuesta para ambos problemas es un número natural
 Ambos son problemas resolubles

Gráficamente problema A:
2 3

4 5

¿Cuántos subgrafos completos hay?

(1, 2) (2,3) (1,4) (4,5) (2,4) (1,2,4)

El máximo cliqué es (1,2,3), que es la respuesta al Problema A.

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:

 Se quiere resolver A, pero se tiene resuelto B


 La respuesta de B, sirve para responder A

Año 2013 Página 40


Teoría de la Computación – Apuntes de Cátedra

Gráficamente:

T T(x) B n
x

Surge la pregunta, quién es T?


T (G=(V,E)) = G´= (V, Ē)

Por lo anterior quedaría:


2 3 Conjunto máximo independiente es:
{1, 2, 4}

4 5

T B

x n
T (G=(V,E)) = G´= (V, Ē) T(x)

Por lo anterior:

B es un problema resoluble porque B tiene un algoritmo A  B, se lee, A reduce a B,  B es


decidible; luego A es decidible.
A

x T B
T(x)
RB RA

Año 2013 Página 41


Teoría de la Computación – Apuntes de Cátedra

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

Se verá que A  B  A es no decidible, luego B es no decidible, puede notarse que A es más


particular que B y que B es más general.
B

Problema de Correspondencia de Post, se reduce a un problema de ambigüedad (PCP  AMB)

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

Para analizar el Problema de Post, se parte de dos listas:

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 va a demostrar que PCP  AMB

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

<A,B> T <GA,B AMB Si Si


>

No No

Notar que:

<A,B> es la codificación de las dos listas A y B


<GA,B> es la codificación de las gramáticas

PCP Problema de Correspondencia de Post


Se parte de dos listas de n componentes (cadenas), el problema plantea encontrar una secuencia
de índices tal que cumple i1,i2,…,im ik {1..n}  xi1 xi2…xim=wi1 wi2 …wim Existe al menos
una lista de índices con m componentes

A B
Xi Wi
X1 W1
X2 W2
… ..
Xn Wn

Año 2013 Página 43


Teoría de la Computación – Apuntes de Cátedra

Ejemplo: sean las Ay B con n=3

ai A B
1 1 111
2 1011 10
3 10 0

Existe una instancia de solución PCP con m=4


Si , por ejemplo la secuencia 2,11,3

10111110=101111110
X2 X1X1X3 = W2 W1 W1 W3

Año 2013 Página 44

También podría gustarte