Está en la página 1de 110

Lenguajes de libre contexto

Gramáticas

Una gramática es otra forma de describir un lenguaje.

Ejemplo:
O  S P
S  A T
P  V
A  el
A  un
T  niño
T  perro
V  corre
V  camina
Gramáticas

Una derivación de "un niño corre":


O  S P
S  A T O  S P
P  V  A T P
A  el  A T V
A  un  un T V
T  niño  un T corre
T  perro  un niño corre
V  corre
V  camina
Gramáticas

Lenguaje descrito por esta


O  S P gramática:
S  A T
P  V L={"el niño corre", "el niño
A  el camina", "un niño corre", "un niño
A  un camina", "el perro corre, el perro
T  niño camina", "un perro corre", "un
T  perro perro camina"}
V  corre
V  camina
Gramáticas

T  niño

Variable o "no- Regla de Terminal


terminal" producción
Gramáticas

Otro ejemplo: Lenguaje:


S  aSb
L  {a b : n  0}
n n
S 
Algunas derivaciones:
S  aSb  ab
S  aSb  aaSbb  aabb
S  aSb  aaSbb  aaaSbbb
Este formalismo
 aaaaSbbbb  aaaabbbb permite describir
algunos lenguajes que
no son regulares.
Gramática: forma general

Una gramática es una tupla G=(V,T,S,P) donde

•T es un conjunto finito de símbolos terminales


(es el alfabeto en que estarán escritas las
palabras).

•V es un conjunto finito de variables (símbolos


que no aparecerán en la palabra final).

•S  V es la variable de inicio.

•P es un conjunto finito de reglas de producción


de la forma pq, donde p es de la forma
(V+T)*V(V+T)* y q es de la forma (V+T)*.
Derivaciones

•T={a,b}
S  aSb •V={S}
•S=S
S  •P={SaSb, S}
S  aSb  ab
Una derivación es la obtención de una palabra u a
partir de una palabra v, ambas pertenecientes a
(V+T)*, aplicando una regla de producción:

•Si la regla es pq, será aplicable sólo si p está


incluida en u, o sea, u=xpy.

•El resultado será v=xqy. Escribimos uv.


Derivaciones

Definimos * como la cerradura transitiva de .

Es decir, u * v ssi u1,u2,...,uk tales que


u  u1  u2  ...  uk  v

Además definimos que u * u para todo u.

Definimos el lenguaje descrito por la gramática


como el conjunto de todas las palabras de
terminales que pueden derivarse a partir de S:
L(G) = { wT*: S * w }

A veces cuando no haya confusión posible, anotaremos *


simplemente como 
Convención sobre notación

Notación: se suelen usar en este contexto

•Letras minúsculas del comienzo del alfabeto para


los terminales.

•Letras minúsculas del final del alfabeto para las


palabras (de terminales, o de terminales mezclados
con variables).

•Letras mayúsculas para las variables. Cuando por


algún motivo esto pueda inducir a confusión,
entonces se usan con corchetes: S. Casos típicos:
cuando es inevitable usar mayúsculas en los
terminales, o cuando una variable tiene un nombre
natural (ejemplo: predicado).
Gramáticas de libre contexto

El formalismo general de gramáticas es demasiado


poderoso :
puede describir lenguajes más complejos que
los que nos interesan (por ahora).

Estudiaremos en este capítulo las gramáticas de


libre contexto y sus lenguajes asociados (lenguajes
de libre contexto).

Nota: también lo traducen como “de contexto


libre”, “independientes del contexto”, etc, etc.
Gramáticas de libre contexto

Def.: una gramática se dice de libre contexto (GLC)


ssi en toda regla de producción pq, se tiene que
pV.

Es decir, las reglas son de la forma


Xw
con w(V+T)*.

¿Por qué interesan?


 Los lenguajes de programación, y (salvo algunas
construcciones particulares) los lenguajes
naturales son de libre contexto!!
(Gramáticas regulares

Un caso aún más particular son las GLC en que se


pide que todas las reglas de producción sean de las
formas
SaT
ó Sa

Se les llama gramáticas lineales por la derecha.


Análogamente se definen las gramáticas lineales por
la izquierda, que tienen reglas de la forma STa, ó
bien Sa.

Una gramática es regular si es lineal por la derecha,


o es lineal por la izquierda.
Gramáticas regulares

L es un lenguaje regular ssi L=L(G) para una


gramática regular G.
S  aA
A
a
SB Nota: en la clase el
estado C estaba anónimo,
a y las producciones que
A  aC incluyen C estaban
resumidas en una sola,
C AaaB. Lo cambio aquí

S C  aB para respetar el formato


definito en la


a VF transparencia previa.
a B  bB
B
Ba
b Ir derivando la palabra corresponde
a ir cambiando de estado interno (la
variable), y escribiendo.
Gramáticas regulares)

Ojo: para que G sea regular debe ser lineal por la


derecha o por la izquierda, pero no puede mezclar
las dos formas :

•SaT
•TSb
•S

genera {anbn, n0}, que es el clásico ejemplo de


lenguaje no regular.
GLC, ejemplos

Volvamos al caso general de GLC. Otro ejemplo:


S  Tb
T  aTb
T 
¿Lenguaje?  {anbn+1, n0}

Una notación conveniente: cuando el lado izquierdo


es el mismo, agrupamos varias reglas de producción
en una sola línea mediante “|”:
S  Tb
T  aTb | 
GLC, ejemplos

Más ejemplos:

S  SS | ( S ) | 
donde T={(,)}, V={S}. ¿Lenguaje?

S  (S)  ()
S  (S)  (SS)  ((S)S)  ((S)(S))  (()(S))  (()())

da el lenguaje de los paréntesis bien balanceados.


GLC, ejemplos

Más ejemplos:

S  aSa | bSb | 
donde T={a,b}, V={S}. ¿Lenguaje?

S  aSa  abSba  abbSbba  abbbba

 es el lenguaje de los palíndromes de largo par:


L={ w=u uR, u{a,b}*}

Ejercicio: modificar la gramática para que también


genere palíndromes de largo impar (p.ej., aba).
GLC, ejercicios

Ejercicios:

•¿Qué lenguajes describen las siguientes gramáticas?

G 1: G2:
S  XaaX S  XY
X  aX | bX |  X  aX | bX | a
Y  Ya | Yb | a

•Escriba una gramática que describa el lenguaje


de todas las expresiones regulares válidas
sobre el alfabeto {a,b}.
GLC "prototípicas"

Algunas formas típicas de LLC (lenguajes de libre


contexto):
•Recursivos
•Por partes
•Anidados

Algunos ejemplos para tener en cuenta (como


“principios de diseño”):

•Recursivo: {anbn, n0}


Lo generamos con algo de la forma SaSb
El de los palíndromes es análogo.
Idea: hay un "surtidor" al medio que emite letras de
manera simétrica.
GLC "prototípicas"

•Por partes: L={anbnambm, n0, m0}

Lo generamos con algo de la forma SXY


Luego a partir de X e Y generamos las dos partes
(aprovechando que no tienen relación).

En este caso L=L1L1, con L1={anbn, n0}, de modo que


X e Y pueden ser el mismo:

SXX
XaXb | 
GLC "prototípicas"

•Anidados: L={anbmambn, n0, m0}

Aquí las “partes independientes” son anbn y bmam


que está dentro de la anterior.

Generamos primero lo exterior, luego lo interior.

En este caso particular, tanto lo interior como lo


exterior es del tipo recursivo.

SaSb | X
XbXa | 
GLC, un ejemplo más complejo

L={w{0,1}*: w tiene dos bloques de 0’s del mismo


tamaño}.

Permitidos: 01011, 001011001, 10010101001


No permitidos: 01001000, 01111

10010011010010110
inicio parte central final
00110100
A B C D

Cantidad de 0’s:
A: , ó termina en 1 •la misma a cada lado
C: , ó comienza con 1 •al menos uno
GLC, un ejemplo más complejo

 De modo que descomponemos por partes, y


luego aplicamos recursividad en B.

10010011010010110
inicio parte central final
A B C S → ABC
A →  | U1
00110100 U → 0U | 1U | 
C →  | 1U
D
B → 0B0 | 0D0
Cantidad de 0’s: D → 1U1 | 1
•la misma a cada lado
•al menos uno
Derivaciones y árboles

Cuando una derivación pasa por algún punto en que


hay más de una variable, significa que habrá varias
derivaciones equivalentes, según cuál sea el orden
en que aplicamos las producciones:

S  XY
X  aaX | 
Y  Yb | 
S XY aaXY aaXYb aaaaXYb aaaaXb aaaab

S XY aaXY aaaaXY aaaaY aaaaYb aaaab


Derivaciones y árboles

Dentro de esta variedad de


S  XY
derivaciones equivalentes,
distinguimos la derivación izquierda X  aaX | 
y la derivación derecha (anotadas
por L y R respectivamente).
Y  Yb | 

S XY aaXY aaaaXY aaaaY aaaaYb aaaab


es una producción izquierda: en cada paso reemplazamos la
variable que está más a la izquierda.

Una derivación derecha sería:


S XY XYb Xb aaXb aaaaXb aaaab
Derivaciones y árboles

Árbol de análisis sintáctico (o “árbol de derivación”):


representa la derivación, sin importar el orden:
S
SL aaaab :

X Y S XY aaXY
aaaaXY aaaaY
aaaaYb aaaab
a a X Y b
SR aaaab :

a a X  S XY XYb
Xb aaXb
aaaaXb aaaab

Derivaciones y árboles
S
•En la raíz va S.
X Y
•En las hojas, terminales o .
a a X Y b •Las derivaciones extremas,
L y R, corresponden a
a a X  hacer recorridos del árbol
en pre-orden y post-orden,
 respectivamente.

•Nótese que en este caso hay más de una derivación, pero


el árbol es único (no hay otra forma de derivar aaaab).
•No siempre será el caso.
GLC: ambigüedad

E  E + E | (E) | V
E Vx|y|z E
E + E E + E
x+y+z
V E + E E + E V
x V V V V z
y z x y

E  E+E  V+E  x+E  E  E+E  E+E+E  V+E+E 


x+E+E  x+V+E  x+y+E  x+E+E  x+V+E  x+y+E  x+y+V
x+y+V  x+y+z  x+y+z

•Aquí hay dos árboles distintos, cada uno con una


derivación izquierda distinta.
GLC: ambigüedad

T o t a l = p r e c i o + i v a ; Analizador
léxico

Total = precio + iva ;

asignación

Parser
Total := Expresión

id + id

precio iva

•Los parseadores construyen árboles de análisis


sintáctico.
•Ese árbol indica cómo se entiende el texto.
GLC: ambigüedad
La existencia de más de un árbol de derivación
puede ser entonces nefasta:

E  E + E | EE | (E) | V
E Vx|y|z E
E  E E + E
V E + E xy+z E  E V
x V V V V z
y z x y

“Primero y+z, “Primero xy,


luego x  eso” luego eso + z”
GLC: ambigüedad

Un caso clásico de ambigüedad en lenguajes de


programación: dos ifs, un else.

S La mayoría de los
lenguajes lo resuelven
if b then S else S
asignando el else al if
más cercano.
if b then S a

a
S

if b then S

if b then S else S

a a
GLC: ambigüedad

Decimos que una gramática G es ambigua, si existe


una palabra en L(G) que admite más de un árbol de
derivación.

Nota: puede haber más de una derivación sin que


indique ambigüedad, siempre y cuando sigan el
mismo árbol.

Otro motivo que hace nefasta la ambigüedad: para


el parser es más fácil encontrar un árbol de
derivación si la solución es única.
GLC: ambigüedad

•La buena noticia: a veces podemos cambiar la


gramática por otra equivalente (i.e., mismo
lenguaje) pero sin ambigüedad:

E  E + E | EE | (E) | V
Vx|y|z
•Genera lo mismo, pero
obliga al árbol a
ET|E+T reconocer la prioridad
TF|TF de la multiplicación.
F  (E) | V •Java o C++ aplican algo
Vx|y|z análogo para resolver
los else ambiguos.
GLC: ambigüedad

Las malas noticias:

•A veces un lenguaje es inherentemente


ambiguo: sólo existen gramáticas ambiguas que
lo describen.

•Si tenemos una gramática ambigua, no existe un


algoritmo general que nos diga acaso es
intrínsecamente ambigua.

•Y aún si no lo fuera, tampoco hay método


infalible para “desambiguarla”.
GLC: ambigüedad

Ejemplo de lenguaje inherentemente ambiguo (sin


demostrar):

L  {a nb nc m }  {a nb mc m }

S  S1 | S2 S1  S1c | A S 2  aS 2 | B
A  aAb |  B  bBc | 

•Una palabra de la forma anbncn tiene dos árboles distintos.


•Lo que no demostraremos es que para cualquier otra
gramática equivalente, pasa lo mismo.
Ver Hopcroft.
GLC: ambigüedad

Ergo:

Se puede tratar de evitar la ambigüedad.

A veces hay que convivir con ella.

Un lenguaje de programación debiera


diseñarse con una gramática que evite la
ambigüedad (tanto para evitar errores de
interpretación, como para facilitar el parseo).
GLC: simplificación

Dada una gramática G, digamos,

S → aSb | bSaSb | T
T→S|

Si además nos dan un string w,

•¿Cómo sabemos acaso wL(G)?

•En caso de que esté, ¿cómo obtenemos un árbol


de derivación?

•Y en caso de obtenerlo, ¿es único?


GLC: simplificación

S → aSb | bSaSb | T w=aabbb


T→S| ¿está?

Por fuerza bruta: podemos intentar generar todas


las posibles derivaciones, buscando alguna que
genere w.

S  aSb  aaSbb 
 abSaSbb  ... Problema:
 aTb 
 bSaSb  baSbaSb  ¿Cuándo
...
parar?
 T  S  ...
  
GLC: simplificación

S → aSb | bSaSb | T w=aabbb


T→S| ¿está?

Una idea: parar cuando la palabra que tenemos


exceda la longitud de w.

Problema #1: si existen reglas de producción que


llevan a , entonces la longitud no necesariamente
va aumentando.
S  aSb  abSaSbb  abSabb  ababb

Para evitar esos "acortamientos", sería bueno que


no hubiera producciones nulas como esa.
GLC: simplificación

S → aSb | bSaSb | T w=aabbb


T→S| ¿está?

Una idea: parar cuando la palabra que tenemos


exceda la longitud de w.

Problema #2: podemos quedarnos pegados en loops,


si se dan casos en que, por ejemplo S→T, T→S.

S  aSb  aTb  aSb  …

Así que sería bueno evitar situaciones de ese tipo


también.
Eliminación de producciones nulas

Decimos que una variable X es anulable si existe una


derivación
X * 

Para determinar las variables anulables, aplicamos un


algoritmo de marcado recursivo:

•Marcamos todas las variables X que tengan una


regla de producción X.

•Mientras exista una regla de producción de la


forma YX1...Xm donde Y no está marcado pero
todos los Xi lo están, marcar Y.
Eliminación de producciones nulas

Para eliminar las producciones nulas,

(1) Determinar todas las variables anulables,


X1,...,Xk

(2) Para cada producción de la forma YuXiv,


agregar una producción Yuv. Más en general: si
el lado derecho incluye más de una variable
anulable, considerar cada combinación de
anulación. Por ejemplo, si YuXabYv, y tanto X
como Y son anulables, agregamos
•YuabYv
•YuXabv
•Yuabv
Eliminación de producciones nulas

Para eliminar las producciones nulas,

(3) Si Xi es una producción, eliminarla.

(4) Si S es anulable, entonces agregar una


producción S (salvo que ya exista).

Esa última salvedad es importante:


•Si  no está en el lenguaje de la gramática,
entonces S no es anulable y no habrá producciones
nulas.
•Si  está en el lenguaje de la gramática, entonces
S es anulable y la única producción nula será S.
Eliminación de producciones nulas

Ejemplo:

S  a | Xb | aYa •Eliminamos X  
XY| •A partir de SXb se agrega
Yb|X Sb, pues X es anulable.
•A partir de SaYa se agrega
Anulables: X e Y. Saa, pues Y es anulable,

S  a | Xb | aYa | b | aa
XY
Yb|X
Eliminación de producciones nulas

Otro ejemplo:

Gramática inicial Gramática final

S  aMb S  aMb
Sustituimos
M  aMb M  S  ab
M  M  aMb
M  ab
Anulables: M
Eliminación de producciones unitarias

Una "producción unitaria" es de la forma XY;


decimos que existe una derivación unitaria entre dos
variables X e Y si se tiene X * Y.

Si no hay producciones nulas (y supondremos aquí que


ya las eliminamos), entonces X * Y sólo es posible
mediante una cadena de producciones unitarias.
Producción
S X -
S  aX | Yb S Y -
XS X S XS
Y  bY | b | X X Y -
Y X YX
Y S YXS
Eliminación de producciones unitarias

Para eliminar derivaciones unitarias:

•Para cada par de variables tales que X * Y,


introducimos nuevas producciones: para cada
producción no-unitaria de Y, Ys1, Ys2, ...,
agregamos Xs1, Xs2, ...

•Se hace simultaneamente para todos los pares X,Y


con derivación unitaria.

•Después se eliminan las producciones unitarias, y


todo lo redundante (que suele aparecer).
Eliminación de producciones unitarias

Ejemplo: S  aX | Yb
XS
Y  bY | b | X
•Como X * S, se agregan XaX , XYb
•Como Y * X... No se agrega nada.
•Como Y * S, se agregan YaX , YYb
•Finalmente, eliminamos las producciones unitarias
XS y YX.

La nueva gramática quedaría

S  aX | Yb
X  aX | Yb
Y  bY | b | aX | Yb
Eliminación de variables inútiles

Al ir transformando las gramáticas (manteniendo,


recordemos, el mismo lenguaje) pueden aparecer
variables o producciones inútiles. Dos formas típicas
son:

S  aSb SX
S  X  aX Y es inútil: no
SX X  hay forma de que
aparezca en una
X  aX Y  bX derivación!

X es inútil: nunca desaparece, así que no puede formar


parte de la derivación de una palabra del lenguaje.
Eliminación de variables inútiles

En general una variable será útil cuando exista una


palabra wL(G) en cuya derivación aparezca:

S    uXv    w
•Si esto nunca ocurre, es inútil.

•Una producción es útil sólo si todas sus variables son


útiles. De lo contrario, es inútil.

•Para determinar las variables inútiles: son aquellas


que no producen strings de terminales, o bien, que no
son accesibles desde S.
Eliminación de variables inútiles

Para encontrar las variables que producen strings de


terminales, definimos un conjunto U=T. Luego
iteramos:
•Si existe una variable XU, pero que tiene una
producción Xu, uU*, entonces agregamos X a U.
•Si no existe ninguna variable así, salimos de la
iteración.

S  aS | A | C U={a,b}
Aa U={a,b,A}
U={a,b,A,S}
B  aa
U={a,b,A,S,B}
C  aCb
Eliminación de variables inútiles

Las variables que hayan quedado fuera de U son las


que no producen strings de terminales, ergo, son
inútiles. Las eliminamos, así como todas las
producciones en que aparezcan.

S  aS | A | C U={a,b} S  aS | A
Aa U={a,b,A}
U={a,b,A,S}
Aa
B  aa
U={a,b,A,S,B} B  aa
C  aCb
Eliminación de variables inútiles

A continuación eliminamos las variables que no se


alcancen desde S. Eso es aplicar recorrido de grafos,
de EDA:

S  aS | A
Generamos
Aa grafo de
dependencia S A B
B  aa

B no es alcanzable
 Es inútil. S  aS | A
Aa
Eliminación de variables inútiles

Simplificación de gramáticas:

(1) Eliminar producciones nulas

(2) Eliminar producciones unitarias

(3) Eliminar variables inútiles

• (1) y (2) son relevantes para que las cosas que


siguen a continuación funcionen.

• (3) es útil para no acarrear lastre, que suele


aparecer como subproducto de (1) y (2).
Aumento de longitud en la derivación

Sea G una gramática sin producciones nulas ni


unitarias.

Entonces, en una derivación, cada paso aumenta la


longitud de la palabra, o bien lo mantiene constante
pero a costa de reducir la # de variables.
¿Motivo? Cada paso de la derivación reemplaza una variable
X por algo, pero ese "algo" no es  ni tampoco es una
variable "desnuda". Alternativas:
•Reemplazar por una expresión con más de un símbolo:
la expresión se alarga.
•Reemplazar por un símbolo terminal: queda con la
misma longitud, pero con una variable menos.
Aumento de longitud en la derivación

En ese caso sí podemos aplicar la fuerza bruta para


evaluar acaso un string w pertenece a una gramática G:

•Probamos todas las derivaciones de largo a lo más


|w| (es decir, todas las combinaciones de a lo más
|w| reglas de producción).

•Esa es ahora una cantidad finita, así que podemos


probarlas en tiempo finito. En el peor de los casos,
probamos |P|+|P|2+...+|P||w| combinaciones.

•wL(G) ssi alguna de esas derivaciones la genera.


La buena y mala noticia

Es buena noticia: existe algoritmo para saber si una


palabra está en el lenguaje.

La mala noticia: es pésimo.

La cantidad de casos crece exponencialmente en


la medida que crece |w|.
Para un código Java de 200 líneas, el tiempo de
ejecución sería astronómico ( 10200).

Existe algoritmo más eficiente, pero requiere


transformar la gramática un poco más.
Forma Normal de Chomsky (FNC)

Una gramática está en la forma normal de Chomsky


(FNC) si cada producción (a excepción de S, si que
existe) es de una de las dos siguientes formas:

XYZ o bien Xa

donde, como de costumbre, X,Y,ZV, aT.

S  AS S  AS
Está en FNC
S a S  AAS
A  SA A  SA
No está en FNC
Ab A  aa
Forma Normal de Chomsky (FNC)

Teorema: Para toda gramática de libre contexto G,


existe una gramática de libre contexto G' en forma
normal de Chomsky que es equivalente a G (es decir,
L(G)=L(G')).

Para demostrarlo, tenemos que ver que podemos


transformar cualquier GLC hasta que quede en FNC.

1. Eliminamos las producciones nulas, unitarias,


inútiles.
2. Eliminamos los lados derechos "mixtos":
Forma Normal de Chomsky (FNC)

Para eliminar los lados derechos mixtos, creamos una


nueva variable Ta por cada terminal a. Reemplazamos
cada a por el Ta respectivo, y agregamos un
producción Taa.
S  ABTa
S  ABa
A  TaTaTb
A  aab
B  ATc
B  Ac
Ta  a
Ojo: si alguna producción ya era de la
forma Xa, la dejamos así (no la
Tb  b
cambiamos a XTa). Así evitamos Tc  c
introducir producciones unitarias.
Forma Normal de Chomsky (FNC)

3. Reemplazamos toda producción de la forma


AC1C2...Cn por una cadena de producciones
AC1V1, V1C2V2, ... , Vn-2Cn-1 Cn
donde los V1,...,Vn-2 son nuevas variables, intermedias.
S  AV1
S  ABTa V1  BTa
A  TaTaTb A  TaV2
V2  TaTb
B  ATc
B  ATc
Ta  a
Ta  a
Tb  b Tb  b
Tc  c Tc  c
Forma Normal de Chomsky (FNC)

Y listo.
S  AV1
•Llevar GLC a la forma normal de V1  BTa
Chomsky es relativamente fácil. A  TaV2
V2  TaTb
•Tener la GLC en FNC sirve para varias
cosas, prácticas y teóricas. B  ATc
Ta  a
•La más importante: para parsear en Tb  b
tiempo polinomial en |w|.
Tc  c
CYK

Algoritmo CYK (Cocke-Younger-Kasami):

Input: una GLC G en FNC, y una palabra w.

Output: acaso wL(G)


[y fácilmente se puede pedir que además dé un árbol
de derivación, en caso de respuesta positiva]

Idea: determino las variables que producen todas las


subpalabras de w de largo 1. Luego las que producen
todas las subpalabras de w de largo 2. Etc...
CYK

•Para una palabra de largo k dada (digamos, u),


consideramos las posibles formas de descomponerla en 2
palabras más cortas.

•Si para una descomposición u=v1v2 se tiene que X * v1,


Y * v2 (esa información ya está en la tabla), y existe
ZXY (eso lo miro en la gramática), entonces Z*u.

wL(G) ssi al final S es una de las variables que


producen w.
CYK
Ejemplo,
con gramática: Subpalabras de largo k,
S  AB partiendo de posición j

A  BB k\j 1 2 3 4 5
Aa 1 a a b b b

B  AB 2 aa ab bb bb
Bb
3 aab abb bbb

y palabra 4 aabb abbb

aabbb
5 aabbb
S  AB
CYK
A  BB
Aa
a a b b b
B  AB
A A B B B
Bb
aa ab bb bb

aab abb bbb


Variables que
generan las
subpalabras de aabb abbb
largo 1

aabbb
S  AB
CYK
A  BB
Aa
a a b b b
B  AB
A A B B B
Bb
aa ab bb bb
S,B A A
aab abb bbb
Variables que
generan las
subpalabras de aabb abbb
largo 2

aabbb
S  AB
CYK
A  BB
a a b b b
Aa
A A B B B
B  AB
Bb aa ab bb bb
S,B A A
aab abb bbb
Variables que S,B A S,B
generan las
subpalabras aabb abbb
de largo 5 (o A S,B
sea, w)
aabbb
S,B
CYK

•Es fácil modificar CYK para que me entregue un árbol


de derivación: cada vez que ponemos una variable en la
tabla, debemos recordar por qué la pusimos.

•Luego con esa información recuperamos el árbol.

•CYK llena una tabla de tamaño  |w|2.

•Para cada item de la tabla hay que hacer algo de


trabajo. Se puede demostrar que el tiempo de ejecución
es  |w|3. Mucho mejor que |P||w|.
PDA (Autómatas de pila)

•Antes teníamos la correspondencia lenguajes regulares


 autómatas finitos.

•También para los LLC existe, no sólo un tipo de


gramática, sino también un tipo de máquina que los
reconoce.

•Como los lenguajes regulares son un subconjunto propio


de los LLC, el nuevo tipo de máquina es una
generalización del que teníamos antes.
PDA (Autómatas de pila)

Input Pila

Estados

•Al AF le agregamos una pila (stack). Memoria


potencialmente infinita, pero de acceso restringido.

•El resultado es un autómata de pila, autómata apilador,


o en inglés pushdown automaton (PDA).
PDA (Autómatas de pila)

•A medida que el PDA lee su input, puede sacar o


guardar símbolos en la pila.

•Sus cambios de estados, y lo que haga con la pila,


dependerán de lo que va leyendo en el input y de lo que
saque de la pila.

•Los PDA que veremos, salvo que se indique lo contrario,


son no deterministas : hay transiciones , más de una
transición para una misma situación, etc...

•Hay más de una forma de definir el lenguaje


reconocido; de momento será "a la antigua", mediante
estados de aceptación.
PDA (Autómatas de pila)

Un poco de notación extra:

•La pila tiene su propio alfabeto,  (que puede Pila


coincidir en parte con el del input).

•Existe un símbolo especial $ que señala "el


fondo" de la pila. Al comienzo es todo lo que la
pila contiene. $
leo del saco guardo
input
•En el grafo de
transiciones tendremos
q1 a, b  c q2 que anotar lo que se saca
y guarda en la pila.
a, b  c
q1 q2

input
 a   a 

pila
b tope c
h Reemplaza h
e e
$ $
a,  c
q1 q2

input
 a   a 

pila c
b tope b
h Guarda h
e ("push")
e
$ $
a,b  
q1 q2

input
 a   a 

pila
b tope
h Saca h
e ("pop")
e
$ $
q1 a,   q2

input
 a   a 

pila
b tope b
h Sin cambio h
e e
$ $
PDA (Autómatas de pila)

q2
a, b  c

q1 q1  ,b  c q2

a, b  c q3
No determinismo Transición 

Recordatorio:  también se escribe


a veces como “” o bien como “”.
PDA (Autómatas de pila)

Formalmente, un PDA es una tupla

M = ( Q,  , q0, F )

•Q es un conjunto finito de estados


• es el alfabeto de entrada
• es el alfabeto de la pila
•q0 es el estado inicial
•FQ son los estados de aceptación
• es la función de transición,

: Q({})({})  2Q({})
PDA, ejemplo

Ejemplo: (ojo, aquí )

a,   a b, a  

q0 ,    q b, a   q  , $  $ q
1 2 3
PDA, ejemplo
Input
a a a b b b
$
Pila

estado a,   a b, a  
actual

q0  ,    q b, a   q  , $  $ q
1 2 3
PDA, ejemplo
Input
a a a b b b
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo
Input
a a a b b b a
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo
Input a
a a a b b b a
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo a
Input
a
a a a b b b a
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo a
Input
a
a a a b b b a
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo
Input a
a a a b b b a
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo
Input
a a a b b b a
$
Pila

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo
Input
a a a b b b
$
Pila

a,   a b, a  
acepta
q0  ,    q1 b, a   q2  , $  $ q3
PDA, ejemplo

En general, el lenguaje que este PDA acepta es

L  {a b : n  0}
n n

a,   a b, a  

q0  ,    q1 b, a   q2  , $  $ q3
PDA, otro ejemplo

L( M )  {ww }R

a,   a a, a  
b,   b b, b  

q0 ,    q1 , $  $ q2
PDA, otro ejemplo
Input
a b b a
$
Pila
a,   a a, a  
b,   b b, b  

q0 ,    q1 , $  $ q2
PDA, otro ejemplo
Input
a b b a a
$
Pila
a,   a a, a  
b,   b b, b  

q0 ,    q1 , $  $ q2
PDA, otro ejemplo
Input
b
a b b a a
$
Pila
a,   a a, a  
b,   b b, b  

q0 ,    q1 , $  $ q2
PDA, otro ejemplo
Input
b
a b b a a
$
Pila
a,   a a, a  
b,   b b, b  

q0 ,    q1 , $  $ q2
PDA, otro ejemplo
Input
b
a b b a a
$
Pila
a,   a a, a  
b,   b b, b  

q0 ,    q1 , $  $ q2
PDA, otro ejemplo
Input
a b b a a
$
Pila
a,   a a, a  
b,   b b, b  

q0 ,    , $  $ q2
q1
PDA, otro ejemplo
Input
a b b a
$
Pila
a,   a a, a  
b,   b b, b  
acepta
q0 ,    q1 , $  $ q2
Guardando strings

Símbolo Símbolo String


leído sacado guardado

q1 a, b  w q2
Guardando strings

a, b  cdf
q1 q2
input
a a

c string
pila d guardado
b top f
h Guardar h
e e
$ $
Y otro ejemplo...

L( M )  {w : na  nb }

a, $  0$ b, $  1$
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b a a b

$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b a a b
0
$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b b a a
0
$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b b a a
1
$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b b a a 1
1
$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b b a a 1
1
$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b b a a
1
$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  

q1
, $  $ q2
Input
a b b b a a

$
a, $  0$ b, $  1$
Pila
a, 0  00 b, 1 11
a, 1   b, 0  
acepta
q1
, $  $ q2