Lenguajes Decidibles

Buscar

Los lenguajes decidibles son cadenas de palabras calculables mediante funciones recursivas por lo cual también se les llamas lenguajes recursivos. Un posible alfabeto sería, digamos, {a, b} , y una cadena cualquiera sobre este alfabeto sería, por ejemplo, ababba . Un lenguaje sobre este alfabeto, que incluyera esta cadena, sería: el conjunto de todas las cadenas que contienen el mismo número de símbolos que , por ejemplo La palabra vacía (esto es, la cadena de longitud cero) se permite en este tipo de lenguajes, notándose frecuentemente A diferencia de que ocurre con el alfabeto (que es un conjunto finito) y con cada palabra (que tiene una longitud también finita), un lenguaje puede estar compuesto por un número infinito de palabras. Esos son algunos ejemplos de problemas de decisión expresados como lenguajes: Las frases sobre el alfabeto {a, b} que contienen alternadas las letras a y b.  Las frases sobre el alfabeto {a, b, c} que contienen igual número de letras a y b.  Las frases que describen un grafo con aristas etiquetadas con números naturales que indican su longitud, dos vértices del grafo y un camino en el grafo que es el camino más corto entre esos dos vértices.  Las frases que describen una máquina de Turing y una cinta de entrada para esta máquina tal que la máquina se para en un tiempo finito al procesar esa entrada.

Existen problemas que no pueden ser resueltos por una computadora, dado que las computadoras solamente pueden ejecutar algoritmos, esto es secuencia de instrucciones universalmente precisas y entendibles que resuelven cualquier instancia de problemas computacionales definidos rigurosamente.

El correspondiente modelo matemático se llama máquina de Turing (Alan Turing. En el contexto de complejidad computacional. Los problemas se pueden clasificar desde el punto de vista de la teoría de computabilidad en resolubles y no resolubles.Lenguaje aceptable: es aquel lenguaje L para el cual no existe ninguna máquina de Turing que puede aceptar cualquier cadena wÎL y rechazar cualquier cadena wÏL. el interés está centrado en establecer una medida de la cantidad de recursos computacionales (en términos de tiempo y/o espacio) necesarios para resolver un determinado problema o equivalentemente reconocer un lenguaje LOS PROBLEMAS RESOLUBLES SE SUBDIVIDEN EN TRATABLES E INTRATABLES Los problemas tratables son: Aquellos para los cuales existe un algoritmo eficiente que los resuelve. .Lenguaje decidible: es aquel lenguaje L para el cual existe una máquina de Turing que puede aceptar cualquier cadena wÎL y rechazar cualquier cadena wÏL. 1936). con el fin de determinar si los mismos son teóricamente decidibles. La teoría de computabilidad tiene como objetivo el estudio de problemas de decisión.No es una sorpresa que esta idea intuitiva de algoritmo pueda ser definida formalmente. Los intratables son: Aquellos para los cuales no se conoce (o tal vez no exista) un algoritmo eficiente que los resuelva Lenguajes aceptables y decidibles . Los problemas resolubles son objeto de estudio de la teoría de complejidad computacional. .Lenguajes recursivamente innumerables: lenguajes estructurados por frases. .Lenguajes recursivos: lenguajes decidibles por una máquina de Turing .

existe un conjunto al que no le podemos asignar una respuesta. Dentro de estos problemas. PROBLEMA DE LA PARADA El problema de parada para máquinas de Turing es el ejemplo de problema irresoluble más conocido. Fue además el primer problema que se demostró formalmente que no tenía solución. ni afirmativa ni negativa: no existe un algoritmo que nos permita determinar si el problema tiene solución. problemas a los que podemos decir si tienen solución o no. es que nos permite decidir si otros problemas son resolubles o no definicion .Lenguajes aceptables vs. Una de las razones por la que es importante conocer que el problema de la parada no tiene solución. Lenguajes decidibles Lenguaje aceptable La máquina separa al reconocer una cadena del lenguaje Lenguaje decidible La máquina dice si una cadena pertenece al lenguaje o no Implica reconocer el complemento del lenguaje ¡Existen lenguajes aceptables que no son decidibles! Un lenguaje es aceptable pero su complemento no. es decir. El concepto de problema indecidible o irresoluble se aplica a problemas de decisión.

llegamos a una contradicción. Con esto intentamos decidir si la máquina M para con la entrada M. tomaremos una máquina D (denominaremos SD). Ahora crearemos una máquina D. Por lo tanto. y parará si M no para con w. esta máquina en principio parará si D no para con entrada D. si M para con la entrada M. y parará y rechazará la entrada si M no para con w. por que aplicar D a SD debería dar como resultado lo mismo que aplicar D sobre D. creando una máquina P’ equivalente. supondremos que existe una máquina de Turing que es capaz de determinar si otra máquina de Turing para con una entrada determinada. . Por lo tanto. entonces D para. Modificamos la máquina P. la cinta contendrá MM (la codificación de la máquina repetida). Consideremos una máquina de Turing P. Demostración Para demostrarlo. SD aplica como entrada a la máquina que recibe. Del mismo modo para el otro caso. Sea . Por lo tanto. entonces D no para. A continuación. a la salida de la máquina copia. No se puede determinar si una máquina de Turing se detiene con una entrada arbitraria. la misma máquina. es decir. Es decir. y si M no para con la entrada M.Sea M una máquina de Turing arbitraria con un alfabeto de entrada Σ. sabiendo que D=SD. ¿Puede decidirse si la máquina M se detendrá con la entrada w? Solución La respuesta a esta pregunta es negativa. Esta máquina no parará si M para con w. Pero si SD no para y si D para con entrada D. La máquina P parará y aceptará la entrada si M para con w. Recibe una máquina M. cuya función es la siguiente. y no parará si D para con entrada D. Por último. D coge este resultado y lo pasa a través de P’. y que se encarga de ejecutar M sobre la cadena w. la pasa por una máquina que se encarga de copiar la máquina M a continuación. supongamos que el problema de la parada tiene solución. que recibe como entrada una máquina de Turing M y una cadena w codificadas en la cinta y una a continuación de la otra (Mw). el problema de la parada no tiene solución. y le aplicaremos como entrada una máquina D.

o de la subtarea a es imposible llegar a la subtaréa b y viceversa. Porque en el caso de no encontrar una solución en una subtarea se retrocede a la subtarea original y se prueba otra cosa distinta (una nueva subtarea distinta a las probadas anteriormente). ¿Por qué se llaman algoritmos de vuelta atrás?. por lo que el tratamiento general de estos algoritmos es de naturaleza recursiva. El caso es que el grafo no está definido de forma explícita (como lista o matriz de adyacencia). imposible llegar a una misma solución xpartiendo de dos subtareas distintas a y b. algoritmos). es decir. que se irá creando según avance el recorrido. que más o menos viene a significar que hay que probar todo lo posible hasta encontrar la solución o encontrar que no existe solución al problema. estructuras de datos. o no contiene ciclos. A menudo dicho grafo es un árbol. y recorrido de grafos. Puesto que a veces nos interesa conocer múltiples soluciones de un problema. se separa la búsqueda en varias búsquedas parciales o subtareas. sistemas computacionales troducción Los algoritmos de vuelta atrás se utilizan para encontrar soluciones a un problema. Gráficamente se puede ver así: . estos algoritmos se pueden modificar fácilmente para obtener una única solución (si existe) o todas las soluciones posibles (si existe más de una) al problema dado. siendo cada subtarea un nodo del grafo. Para conseguir este propósito. Estos algoritmos se asemejan al recorrido en profundidad dentro de un grafo (ver sección de grafos. es decir. simplemente una búsqueda sistemática.Informacion por Karina Sosa Ramos instituto tecnologico de villahermosa ing. estas subtareas suelen incluir más subtareas. al buscar una solución es. sino de forma implícita. No siguen unas reglas para la búsqueda de la solución. Asimismo. en general.

equivalentes a los corchetes de C. Tal vez se pierda la mejor solución. es decir. Los bloques se agrupan con begin y end. sino una que sea razonablemente buena y cuyo coste computacional sea bastante reducido. A continuación se exponen estos esquemas. . Sin embargo. es imposible demostrar que al hacer una poda no se esté ocultando una buena solución. y puede adaptarse fácilmente según las necesidades de cada problema. de tal forma que se puedan podar algunas de las ramas. Los algoritmos de vuelta atrás tienen un esquema genérico. la poda no impide encontrar la mejor solución. extraídos de Wirth (ver Bibliografía). A veces. además están tabulados. Esto es posible si llegado a un punto se puede demostrar que la solución que se obtendrá a partir de ese punto no será mejor que la mejor solución obtenida hasta el momento. el problema quizás no pida la mejor solución. no recorrer ciertas subtareas.esquema para una solución: procedimiento ensayar (paso : TipoPaso) repetir | seleccionar_candidato | if aceptable then | begin | anotar_candidato | if solucion_incompleta then . pero se encontrará una aceptable en un tiempo reducido. Si se hace correctamente. Esa es una buena razón para aumentar las restricciones a la hora de recorrer un nodo.A menudo ocurre que el árbol o grafo que se genera es tan grande que encontrar una solución o encontrar la mejor solución entre varias posibles es computacionalmente muy costoso. según se busque una o todas las soluciones. En estos casos suelen aplicarse una serie de restricciones.

por ejemplo el tablero de ajedrez. | end hasta que (acertado = cierto) o (candidatos_agotados) fin procedimiento . Además. Por último se estudiará como encontrar todas las soluciones posibles partiendo de una misma casilla. es necesario determinar la organización de los datos para implementar el algoritmo. y de un caballo. se exponen una serie de problemas típicos que se pueden resolver fácilmente con las técnicas de vuelta atrás. La vuelta del caballo Se dispone de un tablero rectangular. en cuyo caso hay que dar marcha atrás. Para resolver el problema hay que realizar todos los movimientos posibles hasta que ya no se pueda avanzar. Muchos problemas de los pasatiempos de los periódicos pueden resolverse con la ayuda de un ordenador y en esta web se muestran algunos de ellos.cierto.| begin | ensayar(paso_siguiente) | if no acertado then borrar_candidato | end | else begin | anotar_solucion | acertado <. que se mueve según las reglas de este juego. El primero que se expone es muy conocido.esquema para todas las soluciones: procedimiento ensayar (paso : TipoPaso) para cada candidato hacer | seleccionar candidato | if aceptable then | begin | anotar_candidato | if solucion_incompleta then | ensayar(paso_siguiente) | else | almacenar_solucion | borrar_candidato | end hasta que candidatos_agotados fin procedimiento Por último. . o bien hasta que se cubra el tablero. de tal forma que el caballo pase una sola vez por cada casilla. Una variante es obligar al caballo a volver a la posición de partida en el último movimiento. El objetivo es encontrar una manera de recorrer todo el tablero partiendo de una casilla determinada. Se trata de la vuelta del caballo.

2. A continuación se expone un código completo en C. que recubre un tablero cuadrado de lado N partiendo de la posición (0. A continuación se muestra un gráfico con un tablero de tamaño 5x5 con todo el recorrido partiendo de la esquina superior izquierda. Las coordenas relativas se guardan en dos arrays: ejex = [2. 1. Se determina que hay solución cuando ya no hay más casillas que recorrer. Con las coordenadas en las que se encuentre el caballo y las ocho coordenadas relativas se determina el siguiente movimiento. 2. 2] ejey = [1. -2. -1] El tablero. Estos movimientos serán los ocho candidatos. una variable que se pasa por referencia es puesta a 1 (cierto).. #include <stdio. del tamaño que sea. 1. entre otras cosas no salirse de los límites del tablero y no pisar una casilla ya cubierta (selección del candidato).¿Cómo se mueve un caballo?.0). Cuando se encuentra una solución. 1. -1. pero esto no siempre es recomendable. -1. Puede hacerse una variable de alcance global y simplificar un poco el código. Para aquellos que no sepan jugar al ajedrez se muestra un gráfico con los ocho movimientos que puede realizar. -2. es necesario considerar algunos aspectos más. -1. -2. Para codificar el programa. -2. se representará mediante un array bidimensional de números enteros.h> #define N 5 #define ncuad N*N .

j++) tablero[i][j] = 0. return 0. /* tablero del caballo.&q). } } else printf("\nNo existe solucion\n"). i++) for (j = 0. int *q). j++) printf("%3d ".int i. int main(void) { int tablero[N][N]. /* inicializa el tablero a cero */ for (i = 0.0.0. 1. 2.-2. } void mover(int tablero[][N]. } while (!*q && k < 8). u.2.j.-1. v = pos_y + ejey[k]. /* pone el primer movimiento */ tablero[0][0] = 1. tablero[i][j]). int pos_x. putchar('\n'). 2. const int ejex[8] = { -1. /* anota el candidato */ if (i < ncuad) { /* llega al final del recorrido? */ mover(tablero. i < N. int *q) { int k. 1. v. ejey[8] = { -2. int pos_y. do { u = pos_x + ejex[k]. 2. } Cambiando el valor de N puede obtenerse una solución para un tablero cuadrado de tamaño N.v. int i. 2. if (q) { /* hay solucion: la muestra. if (!*q) tablero[u][v] = 0. j < N. /* seleccionar candidato */ if (u >= 0 && u < N && v >= 0 && v < N) { /* esta dentro de los limites? */ if (tablero[u][v] == 0) { /* es valido? */ tablero[u][v] = i. *q = 0. i < N. */ for (i = 0. .void mover(int tablero[][N].i+1. j < N.-1. /* hay solucion */ } } k++. 1 }. mover(tablero. /* borra el candidato */ } else *q = 1. k = 0.-2 }.q). */ int i.-2.-1.q. i++) { for (j = 0.u. 1. int pos_x. int pos_y.

La dificultad que plantea este problema es la representación de los datos. Para los que no sepan ajedrez deben saber que una reina amenaza a otra pieza que esté en la misma columna. else imprimir_solucion(tablero). se muestra una adaptación del procedimiento que muestra todas las soluciones. Ejemplo: si en la tercera fila hay una reina situada en la quinta columna. y se llamarán diagb y diagc. Para poner una reina se utiliza esta instrucción: col[i] = j . if (u >= 0 && u < N && v >= 0 && v < N) { /* esta dentro de los limites */ if (tablero[u][v] == 0) { tablero[u][v] = i. int pos_x.u. k < 8. A este array se le llamará fila. fila o cualquiera de las cuatro diagonales. El problema que ahora se plantea es claramente. void mover(int tablero[][N]. Se puede utilizar un array bidimensional de tamaño 8x8. k++) { u = pos_x + ejex[k]. for (k = 0.A continuación. Por último se utilizan dos arrays más para determinar las diagonales libres. if (i < ncuad) mover(tablero.i+1. Por tanto. Se trata de colocar ocho reinas sobre un tablero de ajedrez. Si se ejecuta para N = 5 se encuentra que hay 304 soluciones partiendo de la esquina superior izquierda. en un array se guarda la posición de cada reina en la columna que se encuentre. de vuelta atrás. Cuando se encuentra una solución se llama a un procedimiento (no se ha codificado aquí) que imprime todo el tablero. v = pos_y + ejey[k]. como se verá. Es lógico que cada reina debe ir en una fila distinta. Hace falta otro array que determine si hay puesta una reina en la fila j-ésima. . u. pero las operaciones para encontrar una reina que amenace a otra son algo engorrosas y hay un truco para evitarlas. de tal forma que ninguna amenace (pueda comerse) a otra. A este array se le llamará col. } } } } El problema de las ocho reinas Continuamos con problemas relacionados con el ajedrez. v. tablero[u][v] = 0.int i. int pos_y) { int k. fila[j] = diagb[i+j] = diagc[7+i-j] = FALSE.v). Para quitar una reina esta otra: fila[j] = diagb[i+j] = diagc[7+i-j] = TRUE. entonces la tercera posición del array guardará un 5. La solución aquí expuesta vuelve a ser tomada de Wirth (ver Bibliografía). Se recomienda intentar resolverlo a mano.

col[i]). boolean *q.&q. *q = FALSE. typedef enum bool boolean. } void ensayar(int i. boolean fila[8]. Se han utilizado tipos enumerados para representar los valores booleanos.Se considera válida la posición para este caso: if (fila[j] && diagb[i+j] && diagc[7+i-j]) entonces proceder . if (!*q) fila[j] = diagb[i+j] = diagc[7+i-j] = TRUE. for (i = 0. int col[8].diagb.col. for (i = 0. fila[j] = diagb[i+j] = diagc[7+i-j] = FALSE. return 0. aquellas que no sean rotaciones o inversiones de otras soluciones. se deja al lector que implemente un procedimiento que encuentre todas las soluciones. boolean *q. ensayar(0. boolean diagc[]) { int j. A continuación se expone el código completo en C. for (i = 0.diagb[15]. } while (!*q && j < 8). #include <stdio. j = 0. int col[]. int main(void) { int i. } Por último. int col[].q. } else printf("\nNo hay solucion"). /* encuentra la solucion */ } j++.diagc).diagb.col. boolean fila[]. boolean fila[].fila. boolean q.h> enum bool {FALSE. es decir. diagc[15]. if (q) { printf("\nSolucion:"). if (i < 7) { /* encuentra solucion? */ ensayar(i+1..fila. boolean diagb[]. i++) printf(" %d". boolean diagb[]. TRUE}.diagc).. do { if (fila[j] && diagb[i+j] && diagc[7+i-j]) { col[i] = j. i++) fila[i] = TRUE. i < 8. void ensayar(int i. i < 8. . boolean diagc[]). i++) diagb[i] = diagc[i] = TRUE. i < 15. Si se desea complicar más entonces se puede pedir que encuentre todas las soluciones distintas. } else *q = TRUE.

3. void mochila(int i. seleccionada entre todas las soluciones) si se modifica la instrucción almacenar_solucion por esta otra: si f(solucion) > f(optimo) entonces optimo <. variante del esquema para obtener todas las soluciones. puede hacerse extensible a múltiples piezas simultáneamente. se dispone de n tipos de objetos y que no hay un número limitado de cada tipo de objeto (si fuera limitado no cambia mucho el problema). Ejemplo: se supondrá: n=4 W=8 w() = 2. siendo n. if (solucion + valor[k] > *optimo) *optimo = solucion+valor[k]. } } } Dicho procedimiento puede ser ejecutado de esta manera. hay 4 tipos de objetos y la mochila tiene una capacidad de 8.Ahora que se conoce el método general. Otra solución no óptima de valor 13 se obtiene introduciendo 2 objetos de peso 3 y 1 objeto de peso 2. A continuación se muestra una solución al problema. solucion + valor[k]. y solucion es una solucion que se está probando. Cada tipo i de objeto tiene un peso wi positivo y un valor vi positivo asociados. La mochila tiene una capacidad de peso igual a W. Partiendo del esquema que genera todas las soluciones expuesto anteriormente se puede obtener la mejor solución (la solución óptima. int solucion. de entre todas las soluciones. ¿Cuál es la solución óptima?. k < n. Se trata de llenar la mochila de tal manera que se maximice el valor de los objetos incluidos pero respetando al mismo tiempo la restricción de capacidad. El problema de la mochila consiste en llenar una mochila con una serie de objetos que tienen una serie de pesos con un valor asociado. optimo). Una solución no óptima de valor 12 se obtiene introduciendo cuatro objetos de peso 2. la solución óptima. o 2 de peso 4. Notar que no es obligatorio que una solución óptima llegue al límite de capacidad de la mochila. int r. 5. Pues bien. r . Es decir.peso[k]. int *optimo) { int k. 5 v() = 3. y los valores relacionados varían entre 3 y 10. 4. ahora se trata de encontrar la mejor solución.solucion siendo f(s) función positiva. 10 Es decir. 6. El Problema de la mochila (selección óptima) Con anterioridad se ha estudiado la posibilidad de encontrar una única solución a un problema y la posibilidad de encontrarlas todas. Los pesos varían entre 2 y 5. W. for (k = i. optimo es la mejor solucion encontrada hasta el momento. peso y valor variables globales para simplificar el programa: . k++) { if (peso[k] <= r) { mochila(k.

3. mochila(0. OIE 2001.5.Muchos otros problemas requieren vuelta atrás.Tom y Jerry (solamente el apartado 3). . Enunciado (en inglés: The Primes) .Solución .Números primos.El salto del caballo. OIE 97.Islas en el mar. . los problemas de laberintos se resuelven mucho mejor mediante exploración en anchura (ver grafos). Observar que la solución óptima se obtiene independientemente de la forma en que se ordenen los objetos. OIE 99. peso[] = {2. OIE 97. Enunciado .n = 4. Enunciado .. valor[] = {3. . OIE 98. W = 8.10}. optimo = 0.Buque. en general se trata de algoritmos auxiliares para resolver una pequeña tarea. Enunciado . Enunciado .5}.También los algoritmos de vuelta atrás permiten la resolución del problema del laberinto. 0.Solución .4.6. Enunciado .. De todas formas. IOI 92. IOI 94. W. &optimo).Sellos (El correo del zar). Enunciado (en inglés: Islands in the sea) . puesto que la vuelta atrás no es más que el recorrido sobre un grafo implícito finito o infinito. Problemas propuestos .Caminos.

Sign up to vote on this title
UsefulNot useful