Wprowadzenie

Celem niniejszego wykładu jest: Nie są to proste pytania.

W trakcie kolejnych wykładów będziemy poznawać kolejne klasy języków tworzące tzw. hierarchię Chomsky'ego. Hierarchia ta jest owocem prac Noama Chomsky'ego, lingwisty, który próbował sformalizować pojęcia gramatyki i języka. Hierarchia ta składa się z czterech rodzajów gramatyk:

0.
gramatyki ogólne,
1.
gramatyki kontekstowe,
2.
gramatyki bezkontekstowe,
3.
gramatyki liniowe.

W trakcie badań nad pojęciem obliczalności pojawiło się wiele modeli, o różnej sile wyrazu:

  1. automaty skończone, wyrażenia regularne (stała skończona pamięć),
  2. automaty stosowe (stała skończona pamięć + nieograniczony stos),
  3. bez ograniczeń:
Niektóre z nich powstały na długo przed tym zanim powstały komputery i informatyka. Okazało się, że modele te w zadziwiający sposób odpowiadają kolejnym klasom w hierarchii Chomsky'ego:

Nie są to czysto teoretyczne formalizmy. Niektóre z nich mają wiele praktycznych zastosowań. Szczególny nacisk położymy na dwa najważniejsze z tych formalizmów: wyrażenia regularne i gramatyki bezkontekstowe. W szczególności służą do specyfikowania analizatorów leksykalnych i składniowych -- typowych modułów pojawiających się wszędzie tam, gdzie program wczytuje dane o określonej składni. Poznamy też narzędzia wspomagające tworzenie takich analizatorów -- generatory, które same tworzą kod źródłowy analizatorów na podstawie ich specyfikacji.

Języki jako problemy decyzyjne

We wprowadzeniu wspomnieliśmy o obliczeniach i językach. Gdy mówimy o obliczaniu, to zwykle mamy na myśli obliczenie wartości takiej czy innej funkcji. Gdy zaś mówimy o języku, to mamy na myśli (potencjalnie nieskończony) zbiór napisów złożonych ze znaków ustalonego (skończonego) alfabetu. (Napisy takie będziemy nazywać słowami.) Jak połączyć te dwa pojęcia?

Definicja

Problem decyzyjny to funkcja, która możliwym danym wejściowym przyporządkowuje wartości logiczne tak/nie.
Inaczej mówiąc problem decyzyjny, to taki problem, w którym szukany wynik to wartość logiczna. Na problem decyzyjny możemy też patrzeć jak na zbiór danych -- zbiór tych danych, dla których odpowiedź brzmi ,,tak''. I odwrotnie, na dowolny zbiór danych możemy patrzeć jak na problem decyzyjny -- czy dane należą do określonego zbioru?

Jak to jednak ma się do języków? Jeśli interesuje nas tylko składnia języka (a nie jego semantyka, czyli znaczenie), to każdy język możemy przedstawić sobie jako zbiór (być może nieskończony) słów. Tak więc każdy język nie jest niczym więcej niż problemem decyzyjnym określonym dla słów. Powyższe intuicje są przedstawione bardziej formalnie poniżej.

W trakcie tego kursu będziemy zajmować się m.inn. tym, dla jakich języków (gdy spojrzymy na nie jak na problemy decyzyjne) można skonstruować automatyczne mechanizmy odpowiadające na pytanie, czy dane słowo należy do języka.

Podstawowe pojęcia dotyczące słów

Definicja

Alfabet to dowolny niepusty, skończony zbiór. Elementy alfabetu nazywamy znakami.
Alfabetem może to być np. zbiór cyfr dziesiętnych, zbiór znaków ASCII, czy zbiór bitów {0, 1}. Alfabet będziemy zwykle oznaczać przez $\Sigma$, a elementy alfabetu przez $a, b, c, \dots$.

Definicja

Słowo (lub napis) nad alfabetem $\Sigma$, to dowolny skończony ciąg znaków z $\Sigma$. W szczególności, pusty ciąg jest też słowem (i to nad dowolnym alfabetem). Oznaczamy go przez $\varepsilon$.
Słowa będziemy zwykle reprezentować przez zmienne $x, y, z, \dots$.

W przypadku języków programowania mówimy zwykle o napisach. Natomiast w teorii języków używa się raczej terminu słowo. Dodatkowo, w odniesieniu do języków naturalnych bardziej właściwe niż ,,słowo'' wydaje się ,,zdanie''. Tak naprawdę jednak chodzi o to samo pojęcie.

Definicja

Długość słowa oznaczamy przez $\vert\cdot\vert$.
Na przykład |ala|=3, $\vert\varepsilon\vert=0$.

Definicja

Sklejenie (konkatenacja) słów to słowo powstałe z połączenia słów, tak jakby zostały one zapisane kolejno po sobie. Sklejanie zapisujemy pisząc po sobie sklejane słowa.
Na przykład, jeżeli x=ala, y=ma, z=kota, to xyz = alamakota. Sklejanie słów jest łączne, tzn. x(yz)=(xy)z, a $\varepsilon$ jest elementem neutralnym sklejania, tzn. $x\varepsilon=\varepsilon x = x$.

Sposób zapisu sklejania słów przypomina zwyczajowy zapis iloczynów (bez znaku mnożenia). Przez analogię, możemy wprowadzić operacje ,,potęgowania'' słów:

Definicja

Przez an oznaczamy n-krotne powtórzenie znaku a. Podobnie, przez xn oznaczamy n-krotne powtórzenie słowa x.
Na przykład, a5=aaaaa, $a^0=\varepsilon$, an+1=ana, oraz $x^0=\varepsilon$, xn+1=xnx. Jeżeli x=ala, to x3 = alaalaala.

Definicja

Przez #a(x) oznaczamy liczbę wystąpień znaku a w słowie x.
Na przykład, #a(ala) = 2.

Definicja

Prefiksem słowa nazywamy dowolny jego początkowy fragment, tzn. x jest prefiksem y wtw., gdy istnieje takie z, że xz = y. Jeżeli ponadto $x \neq \varepsilon$ i $x \neq y$, to mówimy, że x jest właściwym prefiksem y.

Analogicznie sufiksem słowa nazywamy dowolny jego końcowy fragment, tzn. x jest sufiksem y wtw., gdy istnieje takie z, że zx = y. Jeżeli ponadto $x \neq \varepsilon$ i $x \neq y$, to mówimy, że x jest właściwym sufiksem y.

Podsłowem danego słowa nazywamy dowolny jego spójny fragment, tzn. powiemy, że x jest podsłowem y wtw., gdy istnieją takie v i w, że vxw = y.

Na przykład, kaj i kaja są prefiksami (właściwymi) słowa kajak, natomiast jak jest jego sufiksem (właściwym).

Definicja

Przez $\mathord{\mbox{\rm rev}}(x)$ oznaczamy słowo x czytane wspak. Na przykład $\mathord{\mbox{\rm rev}}{abbab} = babba$.

Jeżeli słowo x czytane wprost i wspak jest takie samo (tzn. $x = \mathord{\mbox{\rm rev}}(x)$), to mówimy, że x jest palindromem.

Podstawowe pojęcia dotyczące języków

Przejdziemy teraz o jeden poziom wyżej i zajmijmy się zbiorami słów czyli językami.

Definicja

Język (nad alfabetem $\Sigma$), to dowolny zbiór słów (nad alfabetem $\Sigma$).
Jeżeli alfabet $\Sigma$ jest znany z kontekstu, to będziemy go czasami pomijać. Języki będziemy zwykle oznaczać przez $A, B, C, \dots$. Język złożony ze wszystkich możliwych słów nad alfabetem $\Sigma$ będziemy oznaczać przez $\Sigma^*$.

Język, to podzbiór zbioru $\Sigma^*$. Będziemy więc (w kolejnych wykładach) utożsamiać język z problemem decyzyjnym, dla zbioru danych $\Sigma^*$.

Skoro języki to zbiory słów (napisów), a więc określone są na nich wszystkie podstawowe operacje na zbiorach:

Na przykład, $(\{ala, ola, ula\} \setint \{abba, ala, ula\})
\setminus \{ula, bula\} = \{ala\}$.

Przyda nam się też kilka operacji charakterystycznych dla języków:

Definicja

$\overline{A}$ to dopełnienie języka A, czyli $\overline{A} = \Sigma^* \setminus A$.
Na przykład $\overline{\Sigma^*} = \emptyset$.

Definicja

AB oznacza sklejenie (konkatenację) języków A i B, czyli język zawierający wszystkie możliwe sklejenia słów z A ze słowami z B, $AB = \{ xy : x \in A, y \in B \}$.
Na przykład {a, ab} {b, bb} = {ab, abb, abbb}.

Zauważmy, że sklejanie języków jeszcze bardziej przypomina mnożenie, niż to miało miejsce w przypadku sklejania słów. Elementem neutralnym (czyli ,,jedynką'') sklejania języków jest język zawierający tylko słowo puste $\{\varepsilon\}$, $\{\varepsilon\}A = A\{\varepsilon\} = A$. Natomiast ,,zerem'' sklejania języków jest język pusty, $\emptyset A = A \emptyset = \emptyset$. W odróżnieniu od operacji mnożenia, sklejanie języków nie jest przemienne.

Sklejanie języków jest rozdzielne względem sumowania języków:

\begin{displaymath}A(B \setsum C) = AB \setsum AC \end{displaymath}

Jest tak dlatego, że każde sklejenie słowa należącego do A ze słowem należącym do $(B \setsum C)$ jest sklejeniem słowa należącego do A ze słowem należącym do B, lub sklejeniem słowa należącego do A ze słowem należącym do C, i vice versa. Analogicznie:

\begin{displaymath}(A \setsum B)C = AC \setsum BC \end{displaymath}

Mając zdefiniowane sklejanie języków, możemy analogicznie do potęgowania słów zdefiniować potęgowanie języków:

Definicja

Język An definiujemy rekurencyjnie: Czyli $A^n = \underbrace{A \dots A}_{n \mbox{ razy}}$.
Inaczej mówiąc, An to język zawierający wszystkie możliwe sklejenia n (niekoniecznie różnych) słów wziętych z A.

Języki, jako zbiory, mogą być nieskończone. Możemy więc rozważać sklejenie dowolnej liczby słów pochodzących z A. Prowadzi to do tzw. domknięcia Kleene'ego:

Definicja

Domknięcie Kleene'ego języka A, oznaczane jako A*, to $A^* = \bigcup_{n \ge 0} A^n$. Inaczej mówiąc, A* to język zawierający wszystkie możliwe sklejenia dowolnej liczby (łącznie z 0) słów należących do A.
Dla każdego języka A mamy $\varepsilon \in A^*$, gdyż $\{\varepsilon\} = A^0 \subseteq A^*$. Jeżeli tylko A zawiera jakieś niepuste słowo, to A* jest zbiorem nieskończonym. Na przykład, $\{ab, aa\}^* =
\{\varepsilon, ab, aa, abab, abaa, aaab, aaaa, \dots \}$.

Jeżeli spojrzymy na $\Sigma$ nie jak na alfabet, ale jak na zbiór słów długości 1, to wyjaśni się dlaczego $\Sigma^*$ jest zbiorem wszystkich słów nad alfabetem $\Sigma$ -- po prostu każde słowo jest sklejeniem pewnej liczby znaków.

Czasami będzie nas interesowało sklejenie dowolnej, ale dodatniej, liczby słów z A:

Definicja

Przez A+ oznaczamy język zawierający wszystkie możliwe sklejenia dowolnej dodatniej liczby słów należących do A, A+ = AA*.

Powtórzenie pojęć dotyczących relacji

Przypomnijmy sobie kilka pojęć dotyczących relacji.

Definicja

Niech X będzie dowolnym zbiorem, a $\rho \subseteq X \times X$ relacją (binarną) określoną na tym zbiorze. Powiemy, że relacja $\rho$ jest:

Relacja równoważności dodatkowo dzieli zbiór X na rozłączne klasy abstrakcji.

Definicja

Jeśli $\rho$ jest relacją równoważności, $x \in X$, to przez $[x]_\rho$ oznaczamy klasę abstrakcji x:

\begin{displaymath}[x]_\rho = \{y \in X : x \rho y \}
\end{displaymath}

Przez $X_{/\rho}$ oznaczamy zbiór klas abstrakcji elementów zbioru X:

\begin{displaymath}
X_{/\rho} = \{[x]_\rho : x \in X\}
\end{displaymath}

Pojęcia te są bardziej intuicyjne, gdy przedstawimy sobie relację jako graf skierowany (z pętelkami). Wyobraźmy sobie, że elementy zbioru X, to wierzchołki grafu. Z wierzchołka x prowadzi krawędź do wierzchołka y wtedy i tylko wtedy, gdy $x \rho y$. (W szczególności, gdy $x \rho x$, to mamy ,,pętelkę'' prowadzącą z x do x.)

Relacja jest zwrotna, gdy w każdym wierzchołku jest ,,pętelka''.

\includegraphics{rys-1-1}

Relacja jest symetryczna, gdy krawędzie między (różnymi) wierzchołkami są dwukierunkowe (tzn. jeżeli jest krawędź w jednym kierunku, to jest i w drugim).

\includegraphics{rys-1-2}

Relacja jest antysymetryczna, gdy krawędzie między (różnymi) wierzchołkami mogą być tylko jednokierunkowe.

\includegraphics{rys-1-4}

Relacja jest przechodnia, jeżeli dla każdej ścieżki w grafie (długości przynajmniej 1) istnieje w nim krawędź łącząca początek ścieżki z jej końcem. Inaczej mówiąc, graf musi zawierać wszystkie możliwe krawędzie idące ,,na skróty''.

\includegraphics{rys-1-3}

Relacja jest relacją równoważności, jeżeli graf jest podzielony na ileś spójnych składowych, każda składowa to klika (tzn. między każdymi dwoma wierzchołkami tej samej składowej mamy krawędź), natomiast między wierzchołkami należącymi do różnych składowych nie mamy krawędzi. Spójne składowe takiego grafu są nazywane klasami abstrakcji.

\includegraphics{rys-1-5}

Relacja jest częściowym porządkiem, jeżeli nie zawiera cykli (z wyjątkiem pętelek, które są we wszystkich wierzchołkach).

\includegraphics{rys-1-6}

Pojęcia zwrotności, przechodniości i symetryczności wszystkie polegają na tym, że pewne pary/krawędzie muszą być obecne. Będziemy mówić o odpowiednim domknięciu relacji, jako o relacji powstałej przez dodanie odpowiednich par/krawędzi, niezbędnych do spełnienia określonej własności. Szczególnie przydatne będzie nam domknięcie zwrotnio-przechodnie.

Definicja

Niech $\rho \subseteq X \times X$ będzie dowolną relacją binarną. Domknięciem zwrotnio-przechodnim relacji $\rho$ nazywamy najmniejszą taką relację $\rho' \subseteq X \times X$, która:
Podobnie definiujemy domknięcie przechodnie, czy symetryczne.

Intuicyjnie, domknięcie zwrotnio-przechodnie polega na dodaniu wszystkich takich krawędzi, które w oryginalnym grafie mogły być realizowane przez ścieżki -- łącznie z pętelkami, które odpowiadają ścieżkom długości 0.

Przykład

Relacja i jej domknięcie zwrotnio-przechodnie:
\includegraphics{rys-1-7}

Podsumowanie

W tym wykładzie poznaliśmy podstawowe pojęcia dotyczące słów i języków. Przypomnieliśmy też podstawowe informacje na temat relacji, które będą nam potrzebne w dalszych wykładach.

Skorowidz

Praca domowa

  1. Jaka jest różnica między $\emptyset$ i $\varepsilon$? Ile słów zawierają te języki?
  2. Podaj które prefiksy słowa ababbbabab są równocześnie jego sufiksami.
  3. Podaj wszystkie słowa należące do języka {aba, ba, a}{ba, baba}.
  4. Podaj wszystkie słowa należące do języka $\{a, ab, ba, baba\} \setint \{ab, ba\}^2$.
  5. Podaj wszystkie słowa należące do języka $\{a, ab, ba\} \setminus \{a\}^*\{bb\}^*\{a\}^*$.

Zadania

  1. Operacje na zbiorach. Niech $A, B \subset X$. Narysować diagram przedstawiający te zbiory. Które są sobie równe? Które z nich można uszeregować zgodnie z zawieraniem.
  2. Wybieramy dowolny numer telefonu i traktujemy go jak słowo złożone z cyfr. Podaj:
  3. Wybieramy zbiór 2-3 elementowy. Jakie są jego podzbiory? Ile podzbiorów ma zbiór n-elementowy?
  4. Które z poniższych tożsamości są prawdziwe? W przypadku prawdziwych uzasadnij, a w przypadku fałszywych podaj przykład słowa, które przeczy tożsamości.
  5. Co to za języki:
  6. Używając symboli i operacji wprowadzonych na pierwszym wykładzie (czyli operacji na słowach, zbiorach, językach i definicji zbiorów) zdefiniować języki:
  7. Podać domknięcie zwrotnio-przechodnie i domknięcie symetryczne relacji {(1,2), (2,3), (3,1), (2,4), (5,3) } określonej na zbiorze $\{1, 2, \dots, 6\}$.
  8. Pokazać, że następujące relacje są relacjami równoważności:
  9. Pokazać, że następujące relacje są częściowymi porządkami:
  10. Relacja $\prec$ jest określona na językach nad ustalonym alfabetem w następujący sposób: $A \prec B$ wtw., gdy istnieje taki język C, że AC = B. Udowodnij, że relacja $\prec$ jest częściowym porządkiem.