From d181ab057d3d7054c070b0de71bc3a6d0c41f2e2 Mon Sep 17 00:00:00 2001 From: Nikolai Ponomarev Date: Thu, 20 Jun 2024 23:36:21 +0300 Subject: [PATCH 1/3] Add nearly complete RPQ chapter --- ...ageConstrainedReachabilityLectureNotes.tex | 2 +- tex/RPQ.tex | 1142 ++++++++--------- tex/styles/tikz.tex | 3 +- 3 files changed, 559 insertions(+), 588 deletions(-) diff --git a/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex b/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex index 50a3783..fc204b2 100644 --- a/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex +++ b/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex @@ -51,7 +51,7 @@ % \input{Multiple_Context-Free_Languages} % FIXME: Переписать главу % %\input{ConjunctiveAndBooleanLanguages} \input{FLPQ} -% \input{RPQ} +\input{RPQ} % %\input{CFPQ} % \input{CYK_for_CFPQ} % \input{Matrix-based_CFPQ} diff --git a/tex/RPQ.tex b/tex/RPQ.tex index 7eb1ed1..6af902a 100644 --- a/tex/RPQ.tex +++ b/tex/RPQ.tex @@ -1,4 +1,6 @@ +\setchapterpreamble[u]{\margintoc} \chapter{Поиск путей с регулярными ограничениями} +\tikzsetfigurename{RPQ_} \section{Достижимость между всеми парами вершин} @@ -10,378 +12,373 @@ \section{Достижимость между всеми парами верши \section{Достижимость с несколькими источниками} -Достижимость от нескольких стартовых вершин через обход в ширину, основанный на линейной алгебре~\cite{9286186}. +Достижимость от нескольких стартовых вершин через обход в ширину, основанный на линейной алгебре~\sidecite{9286186}. Идея алгоритма основана на одновременном обходе в ширину графа и конечного автомата, построенного по грамматике. В классической версии обхода в ширину, основанного на линейной алгебре, используется вектор, куда записывается фронт обхода графа. Так, один раз перемножая этот вектор на матрицу смежности графа, можно совершать один шаг в обходе графа. -Покажем на примере, как данный метод может быть использован, когда мы накладываем дополнительные ограничения в виде регулярного языка на путь в графе. +Покажем на примере, как данный метод может быть использован, когда мы накладываем дополнительные ограничения в виде регулярного языка на путь в графе. -Для этого, во-первых, предъявим булевые представления для матриц смежности графа и автомата для регулярного языка. Затем, введем специальную блочно--диагональную матрицу -для синхронизации обхода в ширину по двум матрицам смежности. Далее, попробуем наивно реализовать обход в ширину, и посмотрим, почему наивная реализация -может выдавать некорректный результат. +Для этого, во-первых, предъявим булевые представления для матриц смежности графа и автомата для регулярного языка. +Затем, введем специальную блочно-диагональную матрицу для синхронизации обхода в ширину по двум матрицам смежности. +Далее, попробуем наивно реализовать обход в ширину, и посмотрим, почему наивная реализация может выдавать некорректный результат. После этого перейдем к реализации обхода в ширину более продвинутым методом, который решает проблему наивного подхода. -\begin{example} - Возьмём следующий граф. - \begin{center} - \label{input_rpq} - \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] - \node[state] (q_0) {$1$}; - \node[state] (q_1) [above=of q_0] {$2$}; - \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; - \node[state] (q_3) [right=of q_2] {$3$}; - \path[->] - (q_0) edge node {b} (q_1) - (q_1) edge node[pos=0.3] {a} (q_2) - (q_2) edge node[pos=0.7] {a} (q_0) - (q_2) edge[bend left] node[above] {b} (q_3) - (q_3) edge[bend left] node {b} (q_2); - \end{tikzpicture} +\subsection{Пример работы алгоритма} +\marginnote{TODO: Подумать над разбиением на разделы и подразделы} +Возьмём следующий граф. +\begin{center} + \label{input_rpq} + \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] + \node[state] (q_0) {$1$}; + \node[state] (q_1) [above=of q_0] {$2$}; + \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; + \node[state] (q_3) [right=of q_2] {$3$}; + \path[->] + (q_0) edge node {b} (q_1) + (q_1) edge node[pos=0.3] {a} (q_2) + (q_2) edge node[pos=0.7] {a} (q_0) + (q_2) edge[bend left] node[above] {b} (q_3) + (q_3) edge[bend left] node {b} (q_2); + \end{tikzpicture} \end{center} Его матрица смежности имеет следующий вид. -\[ G_1 = -\begin{pmatrix} -. & \{a\} & . & \{b\} \\ -. & . & \{b\} & . \\ -\{a\} & . & . & . \\ -\{b\} & . & . & . -\end{pmatrix} +\marginnote{TODO: Точка внизу или снизу?} +\[ + G_1 = + \begin{pmatrix} + . & \{a\} & . & \{b\} \\ + . & . & \{b\} & . \\ + \{a\} & . & . & . \\ + \{b\} & . & . & . + \end{pmatrix} + \qquad + G_1 = + \begin{pmatrix} + \cdot & \{a\} & \cdot & \{b\} \\ + \cdot & \cdot & \{b\} & \cdot \\ + \{a\} & \cdot & \cdot & \cdot \\ + \{b\} & \cdot & \cdot & \cdot + \end{pmatrix} \] Её булева декомпозиция по каждому символу выглядит следующим образом. -\begin{alignat*}{7} -& &&G_{0\_a} &&= \begin{pmatrix} -0 & 1 & 0 & 0 \\ -0 & 0 & 0 & 0 \\ -1 & 0 & 0 & 0 \\ -0 & 0 & 0 & 0 \\ -\end{pmatrix} \ \ \ \ &&G_{0\_b} &&= \begin{pmatrix} -0 & 0 & 0 & 1 \\ -0 & 0 & 1 & 0 \\ -0 & 0 & 0 & 0 \\ -1 & 0 & 0 & 0 \\ -\end{pmatrix} -\end{alignat*} +\[ + G_{0\_a} = \begin{pmatrix} + 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 \\ + 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 \\ + \end{pmatrix} \qquad + G_{0\_b} = \begin{pmatrix} + 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 \\ + 1 & 0 & 0 & 0 \\ + \end{pmatrix} +\] Зададим ограничения с помощью регулярного выражения $b^*ab$, которое представляется автоматом из трех последовательных состояний. - \begin{center} - \begin{tikzpicture}[shorten >=1pt,on grid,auto] - \node[state, initial] (q_0) at (0,0) {$0$}; - \node[state] (q_1) at (2,0) {$1$}; - \node[state, accepting] (q_2) at (4,0) {$2$}; - \path[->] - (q_0) edge node {$a$} (q_1) - (q_1) edge node {$b$} (q_2); - \draw (q_0) edge[loop above] node {$b$} (q_0); - \end{tikzpicture} + \begin{tikzpicture}[shorten >=1pt,on grid,auto] + \node[state, initial] (q_0) at (0,0) {$0$}; + \node[state] (q_1) at (2,0) {$1$}; + \node[state, accepting] (q_2) at (4,0) {$2$}; + \path[->] + (q_0) edge node {$a$} (q_1) + (q_1) edge node {$b$} (q_2); + \draw (q_0) edge[loop above] node {$b$} (q_0); + \end{tikzpicture} \end{center} - Автомат может быть задан матрицей смежности (с дополнительной информацией о стартовых и финальных состояниях). -Для регулярного выражения $b^*ab$ матрица смежности выглядит следующим образом (при этом нужно запомнить, что -состояние $0$ является начальным, $2$ --- конечным). - -\[ G_2 = -\begin{pmatrix} -\{b\} & \{a\} & . \\ -. & . & \{b\} \\ -. & . & . -\end{pmatrix} +Для регулярного выражения $b^*ab$ матрица смежности выглядит следующим образом (при этом нужно запомнить, что состояние $0$ является начальным, $2$~--- конечным). +\[ + G_2 = + \begin{pmatrix} + \{b\} & \{a\} & . \\ + . & . & \{b\} \\ + . & . & . + \end{pmatrix} \] Нам будет необходима булева декомпозиция этой матрицы, и она выглядит следующим образом. +\[ + R_{0\_a} = \begin{pmatrix} + 0 & 1 & 0 \\ + 0 & 0 & 0 \\ + 0 & 0 & 0 + \end{pmatrix} \qquad + R_{0\_b} = \begin{pmatrix} + 1 & 0 & 0 \\ + 0 & 0 & 1 \\ + 0 & 0 & 0 + \end{pmatrix} +\] -\begin{alignat*}{7} - & &&R_{0\_a} &&= \begin{pmatrix} - 0 & 1 & 0 \\ - 0 & 0 & 0 \\ - 0 & 0 & 0 - \end{pmatrix} \ \ \ \ &&R_{0\_b} &&= \begin{pmatrix} - 1 & 0 & 0 \\ - 0 & 0 & 1 \\ - 0 & 0 & 0 - \end{pmatrix} -\end{alignat*} - -Для синхронизации обхода составим набор блочно--диагональных матриц, каждая из которых --- это прямая сумма двух матриц: -$D_{0\_a} = R_{0\_a} \oplus G_{0\_a}$ и $D_{0\_a} = R_{0\_b} \oplus G_{0\_b}$. - -\begin{alignat*}{7} - & &&D_{0\_a} &&= - \left(\begin{array}{c c c | c c c c} - 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 - \end{array}\right) - \ \ \ \ &&D_{0\_b} &&= - \left(\begin{array}{c c c | c c c c} - 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 - \end{array}\right) -\end{alignat*} - -Пусть мы решаем частный случай задачи достижимости с несколькими стартовыми вершинами (multiple--source) ---- достижимость с одной стартовой вершиной (single--source). +\NewDocumentCommand{\MDa}{}{\begin{pNiceArray}[margin]{ccc|cccc} + 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + \midrule + 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray}} +\NewDocumentCommand{\MDb}{}{\begin{pNiceArray}[margin]{ccc|cccc} + 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + \midrule + 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ + 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 1 & 0 & 0 & 0 + \end{pNiceArray}} + +Для синхронизации обхода составим набор блочно-диагональных матриц, каждая из которых~--- это прямая сумма двух матриц: $D_{0\_a} = R_{0\_a} \oplus G_{0\_a}$ и $D_{0\_a} = R_{0\_b} \oplus G_{0\_b}$. +\[ + D_{0\_a} = \MDa \quad + D_{0\_b} = \MDb +\] + +Пусть мы решаем частный случай задачи достижимости с несколькими стартовыми вершинами (multiple-source)~--- достижимость с одной стартовой вершиной (single-source). Пусть единственной начальной вершиной в графе будет вершина $0$. -Теперь создадим вектор $v = $ $\fbox{1 0 0} \fbox{1 0 0 0}$, где в первой части стоит единица на месте начального состояния $0$ в автомате. -Во второй части содержится фронт обхода графа, на первом шаге это всегда множество стартовых вершин. В данном случае единица стоит на месте -единственной стартовой вершины --- $0$. +Теперь создадим вектор +$v = \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 0 & 0 + \end{pNiceArray}$, +где в первой части стоит единица на месте начального состояния $0$ в автомате. +Во второй части содержится фронт обхода графа, на первом шаге это всегда множество стартовых вершин. +В данном случае единица стоит на месте единственной стартовой вершины~--- $0$. Совершим один шаг в обходе графа и получим новый фронт обхода графа. -\begin{alignat*}{7} - a:\,\, - & \begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{0 1 0} \fbox{0 1 0 0} - \end{matrix} -\end{alignat*} - -\begin{alignat*}{7} - b:\,\, - & \begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{1 0 0} \fbox{0 0 0 1} - \end{matrix} -\end{alignat*} - -Сложим два полученных вектора, чтобы получить новый фронт обхода графа: $\fbox{0 1 0} \fbox{0 1 0 0}$ + $\fbox{1 0 0} \fbox{0 0 0 1} = \fbox{1 1 0} \fbox{0 1 0 1}$. - -То есть в наш фронт \fbox{0 1 0 1} попали вершины 1 и 3 соотвественно. А именно, мы совершили следующие переходы в графе и автомате. - -\begin{alignat*}{7} - \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] - \node[state, red] (q_0) {$1$}; - \node[state] (q_1) [above=of q_0] {$2$}; - \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; - \node[state, red] (q_3) [right=of q_2] {$3$}; - \path[->, red] - (q_2) edge node[pos=0.7] {a} (q_0) - (q_2) edge[bend left] node[above] {b} (q_3); - \path[->] - (q_0) edge node {b} (q_1) - (q_1) edge node[pos=0.3] {a} (q_2) - (q_3) edge[bend left] node {b} (q_2); - \end{tikzpicture} - &\hspace{20px} - \begin{tikzpicture}[shorten >=1pt,on grid,auto] - \node[state, draw=none] (q_3) at (0,0) {$$}; % empty node for alignment - \node[state, initial, red] (q_0) at (0,1) {$0$}; - \node[state, red] (q_1) at (2,1) {$1$}; - \node[state, accepting] (q_2) at (4,1) {$2$}; - \path[->, red] - (q_0) edge node {$a$} (q_1); - \path[->] - (q_1) edge node {$b$} (q_2); - \draw (q_0) edge[loop above, red] node {$b$} (q_0); - \end{tikzpicture} -\end{alignat*} - -Совершим еще один шаг алгоритма. Теперь вектор $v$, на который мы умножаем матрицы, имеет следующий вид $\fbox{1 1 0} \fbox{0 1 0 1}$. - -\begin{alignat*}{7} - a:\,\, - & \begin{matrix} - \fbox{1 1 0} \fbox{0 1 0 1} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{0 1 0} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} - -\begin{alignat*}{7} - b:\,\, - & \begin{matrix} - \fbox{1 1 0} \fbox{0 1 0 1} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{1 0 1} \fbox{1 0 1 0} - \end{matrix} -\end{alignat*} - -$\fbox{0 1 0} \fbox{0 0 0 0}$ + $\fbox{1 0 1} \fbox{1 0 1 0} = \fbox{1 1 1} \fbox{1 0 1 0}$. -То есть в наш фронт \fbox{1 0 1 0} попали вершины 0 и 2 соотвественно. Мы совершили следующие переходы в графе и автомате. - -\begin{alignat*}{7} - \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] - \node[state, gray] (q_0) {$1$}; - \node[state, teal] (q_1) [above=of q_0] {$2$}; - \node[state, teal] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; - \node[state, gray] (q_3) [right=of q_2] {$3$}; - \path[->, gray] - (q_2) edge node[pos=0.7] {a} (q_0) - (q_2) edge[bend left] node[above] {b} (q_3); - \path[->, red] - (q_0) edge node {b} (q_1) - (q_3) edge[bend left] node {b} (q_2); - \path[->] - (q_1) edge node[pos=0.3] {a} (q_2); - \end{tikzpicture} - &\hspace{20px} - \begin{tikzpicture}[shorten >=1pt,on grid,auto] - \node[state, draw=none] (q_3) at (0,0) {$$}; % empty node for alignment - \node[state, initial, red] (q_0) at (0,1) {$0$}; - \node[state, gray] (q_1) at (2,1) {$1$}; - \node[state, accepting, teal] (q_2) at (4,1) {$2$}; - \path[->, gray] - (q_0) edge node {$a$} (q_1); - \path[->, red] - (q_1) edge node {$b$} (q_2); - \draw (q_0) edge[loop above, red] node {$b$} (q_0); - \end{tikzpicture} -\end{alignat*} - -При этом, можно заметить, что мы достигли конечной вершины в автомате. Последний элемент левой части результирующего -вектора $\fbox{1 1 \textcolor{red}{1}} \fbox{1 0 1 0}$ отвечает за состояние 2, которое является конечным. А значит, обход необходимо остановить, и текущие вершины фронта -обхода графа записать в ответ. - -Таким образом, вершины графа 0 и 2 являются ответом. Однако вершина 0 --- лишняя. Регулярное выражение $b^*ab$ не подразумевает, что -вершина 0 в графе может быть достигнута. Она могла бы быть достигнута по пустой строке в случае, если бы регулярное выражение имело вид $b^*$ или -по строке $aba$ в случае, если бы регулярное выражение имело вид $b^*aba$. - -Это произошло, потому что в векторе $v$ должна кодироваться информация о паре --- вершине графа и состоянии автомата. -Достигнув вершины 0, мы оказались в конечном состоянии автомата, которое было получено с помощью другой вершины --- вершины 2. - -Эту проблему можно решить, закодировав информацию о каждой такой паре в несколько векторов $v$, и ограничив левую -часть вектора $v$ таким образом, чтобы в ней всегда была лишь одна единица. - -Тогда мы получим, что вектор $v$ вида $\fbox{1 0 0} \fbox{1 0 1 0}$ будет хранить информацию о парах (0, 0) и (0, 2), -где первый элемент пары --- состояние автомата, а второй --- вершина графа. - -Аналогично, вектор $v$ вида $\fbox{0 1 0} \fbox{0 1 1 0}$ кодирует информацию о парах (1, 1) и (1, 2). -Вектор $v$ вида $\fbox{0 0 1} \fbox{0 0 1 1}$ кодирует информацию о парах (2, 2) и (2, 3). +\begin{widepar} + \begin{gather*} + a: \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 0 & 0 + \end{pNiceArray} \cdot \MDa = + \begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 1 & 0 & 0 + \end{pNiceArray} + \\ + b: \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 0 & 0 + \end{pNiceArray} \cdot \MDb = + \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 0 & 0 & 0 & 1 + \end{pNiceArray} + \end{gather*} +\end{widepar} + +Сложим два полученных вектора, чтобы получить новый фронт обхода графа: +\begin{widepar} + \[ + \begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 1 & 0 & 0 + \end{pNiceArray} + + \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 0 & 0 & 0 & 1 + \end{pNiceArray} = + \begin{pNiceArray}[]{ccc|cccc} + 1 & 1 & 0 & 0 & 1 & 0 & 1 + \end{pNiceArray}. + \] +\end{widepar} + +То есть в наш фронт +$\begin{pmatrix} + 0 & 1 & 0 & 1 + \end{pmatrix}$ +попали вершины 1 и 3 соотвественно. +А именно, мы совершили следующие переходы в графе и автомате. + +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] + \node[state, red] (q_0) {$1$}; + \node[state] (q_1) [above=of q_0] {$2$}; + \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; + \node[state, red] (q_3) [right=of q_2] {$3$}; + \path[->, red] + (q_2) edge node[pos=0.7] {a} (q_0) + (q_2) edge[bend left] node[above] {b} (q_3); + \path[->] + (q_0) edge node {b} (q_1) + (q_1) edge node[pos=0.3] {a} (q_2) + (q_3) edge[bend left] node {b} (q_2); + \end{tikzpicture} + \end{center} +\end{minipage} +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[shorten >=1pt,on grid,auto] + \node[state, draw=none] (q_3) at (0,0) {$ $}; % empty node for alignment + \node[state, initial, red] (q_0) at (0,1) {$0$}; + \node[state, red] (q_1) at (2,1) {$1$}; + \node[state, accepting] (q_2) at (4,1) {$2$}; + \path[->, red] + (q_0) edge node {$a$} (q_1); + \path[->] + (q_1) edge node {$b$} (q_2); + \draw (q_0) edge[loop above, red] node {$b$} (q_0); + \end{tikzpicture} + \end{center} +\end{minipage} + +Совершим еще один шаг алгоритма. +Теперь вектор $v$, на который мы умножаем матрицы, имеет следующий вид +$\begin{pNiceArray}[]{ccc|cccc} + 1 & 1 & 0 & 0 & 1 & 0 & 1 + \end{pNiceArray}$. +\begin{widepar} + \begin{gather*} + a: \begin{pNiceArray}[]{ccc|cccc} + 1 & 1 & 0 & 0 & 1 & 0 & 1 + \end{pNiceArray} \cdot \MDa = + \begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray} \displaybreak[0] \\ % FIXME: remove \displaybreak[0] + b: \begin{pNiceArray}[]{ccc|cccc} + 1 & 1 & 0 & 0 & 1 & 0 & 1 + \end{pNiceArray} \cdot \MDb = + \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 1 & 1 & 0 & 1 & 0 + \end{pNiceArray} \\ + \begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray} + + \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 1 & 1 & 0 & 1 & 0 + \end{pNiceArray} = + \begin{pNiceArray}[]{ccc|cccc} + 1 & 1 & 1 & 1 & 0 & 1 & 0 + \end{pNiceArray} + \end{gather*} +\end{widepar} +То есть в наш фронт +$\begin{pmatrix} + 1 & 0 & 1 & 0 + \end{pmatrix}$ +попали вершины 0 и 2 соотвественно. +Мы совершили следующие переходы в графе и автомате. + +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] + \node[state, gray] (q_0) {$1$}; + \node[state, teal] (q_1) [above=of q_0] {$2$}; + \node[state, teal] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; + \node[state, gray] (q_3) [right=of q_2] {$3$}; + \path[->, gray] + (q_2) edge node[pos=0.7] {a} (q_0) + (q_2) edge[bend left] node[above] {b} (q_3); + \path[->, red] + (q_0) edge node {b} (q_1) + (q_3) edge[bend left] node {b} (q_2); + \path[->] + (q_1) edge node[pos=0.3] {a} (q_2); + \end{tikzpicture} + \end{center} +\end{minipage} +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[shorten >=1pt,on grid,auto] + \node[state, draw=none] (q_3) at (0,0) {$ $}; % empty node for alignment + \node[state, initial, red] (q_0) at (0,1) {$0$}; + \node[state, gray] (q_1) at (2,1) {$1$}; + \node[state, accepting, teal] (q_2) at (4,1) {$2$}; + \path[->, gray] + (q_0) edge node {$a$} (q_1); + \path[->, red] + (q_1) edge node {$b$} (q_2); + \draw (q_0) edge[loop above, red] node {$b$} (q_0); + \end{tikzpicture} + \end{center} +\end{minipage} + +При этом, можно заметить, что мы достигли конечной вершины в автомате. +Последний элемент левой части результирующего вектора +$\begin{pNiceArray}[]{ccc|cccc} + 1 & 1 & \textcolor{red}{1} & 1 & 0 & 1 & 0 + \end{pNiceArray}$ +отвечает за состояние 2, которое является конечным. +А значит, обход необходимо остановить, и текущие вершины фронта обхода графа записать в ответ. + +Таким образом, вершины графа 0 и 2 являются ответом. +Однако вершина 0~--- лишняя. +Регулярное выражение $b^*ab$ не подразумевает, что вершина 0 в графе может быть достигнута. +Она могла бы быть достигнута по пустой строке в случае, если бы регулярное выражение имело вид $b^*$ или по строке $aba$ в случае, если бы регулярное выражение имело вид $b^*aba$. + +Это произошло, потому что в векторе $v$ должна кодироваться информация о паре~--- вершине графа и состоянии автомата. +Достигнув вершины 0, мы оказались в конечном состоянии автомата, которое было получено с помощью другой вершины~--- вершины 2. + +Эту проблему можно решить, закодировав информацию о каждой такой паре в несколько векторов $v$, и ограничив левую часть вектора $v$ таким образом, чтобы в ней всегда была лишь одна единица. + +Тогда мы получим, что вектор $v$ вида +$\begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 1 & 0 + \end{pNiceArray}$ +будет хранить информацию о парах $(0, 0)$ и $(0, 2)$, где первый элемент пары~--- состояние автомата, а второй~--- вершина графа. + +Аналогично, вектор $v$ вида +$\begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 1 & 1 & 0 + \end{pNiceArray}$ +кодирует информацию о парах $(1, 1)$ и $(1, 2)$. +Вектор $v$ вида +$\begin{pNiceArray}[]{ccc|cccc} + 0 & 0 & 1 & 0 & 0 & 1 & 1 + \end{pNiceArray}$ +кодирует информацию о парах $(2, 2)$ и $(2, 3)$. Таким образом, мы будем понимать, в каком состоянии автомата мы находимся для каждой из вершин фронта обхода графа. Рассмотрим, как это применяется в разработанном алгоритме, который представлен далее. -Предлагается ``расклеить'' $v$ в матрицу $M$, состоящую из трех векторов, добавив два вектора $\fbox{0 1 0} \fbox{0 0 0 0}$ и $\fbox{0 0 1} \fbox{0 0 0 0}$. -Во второй части этих векторов стоят нули, так как \fbox{0 1 0} и \fbox{0 0 1} кодируют состояния автомата 1 и 2, которые не являются начальными. - -\begin{alignat*}{7} - & &&M &&=\begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} \\ - \fbox{0 1 0} \fbox{0 0 0 0} \\ - \fbox{0 0 1} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} +Предлагается \enquote{расклеить} $v$ в матрицу $M$, состоящую из трех векторов, добавив два вектора $\begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray}$ +и $\begin{pNiceArray}[]{ccc|cccc} + 0 & 0 & 1 & 0 & 0 & 0 & 0 + \end{pNiceArray}$. +Во второй части этих векторов стоят нули, так как +$\begin{pmatrix} + 0 & 1 & 0 + \end{pmatrix}$ +и +$\begin{pmatrix} + 0 & 0 & 1 + \end{pmatrix}$ +кодируют состояния автомата 1 и 2, которые не являются начальными. +\NewDocumentCommand{\MMf}{}{\begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 + \end{pNiceArray}} +\[M = \MMf\] И совершать обход тем же самым образом, но сохранив с помощью матрицы $M$ дополнительную информацию о парах (состояние, вершина). - -\begin{alignat*}{7} - a:\,\, - & \begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} \\ - \fbox{0 1 0} \fbox{0 0 0 0} \\ - \fbox{0 0 1} \fbox{0 0 0 0} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{0 1 0} \fbox{0 1 0 0} \\ - \fbox{0 0 0} \fbox{0 0 0 0} \\ - \fbox{0 0 0} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} - -\begin{alignat*}{7} - b:\,\, - & \begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} \\ - \fbox{0 1 0} \fbox{0 0 0 0} \\ - \fbox{0 0 1} \fbox{0 0 0 0} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{1 0 0} \fbox{0 0 0 1} \\ - \fbox{0 0 1} \fbox{0 0 0 0} \\ - \fbox{0 0 0} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} +\begin{widepar} + \begin{gather*} + a: \MMf \cdot \MDa = \begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray} \\ + b: \MMf \cdot \MDb = \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 0 & 0 & 0 & 1 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray} + \end{gather*} +\end{widepar} Для того, чтобы левая часть матрицы $M$ всегда оставалось единичной, нужно трансформировать в ней строчки особым образом. Для этого нужно складывать только те вектора в правой части матрицы $M$, у которых в левой части единицы стоят на одинаковых позициях. @@ -391,279 +388,252 @@ \section{Достижимость с несколькими источникам Тогда правая часть матрицы $M$ будет кодировать текущий фронт обхода графа. В нашем примере матрица $M$ для следующего шага обхода выглядит следующим образом. +\NewDocumentCommand{\MMs}{}{\begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 0 & 0 & 0 & 1 \\ + 0 & 1 & 0 & 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 0 & 0 + \end{pNiceArray}} +\[M = \MMs\] -\begin{alignat*}{7} - & &&M &&=\begin{matrix} - \fbox{1 0 0} \fbox{0 0 0 1} \\ - \fbox{0 1 0} \fbox{0 1 0 0} \\ - \fbox{0 0 1} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} - -Видно, что во фронт обхода графа попали вершины 1 и 3. В вершину 1 мы попали в состоянии 1, в вершину 3 --- в состоянии 0. +Видно, что во фронт обхода графа попали вершины 1 и 3. +В вершину 1 мы попали в состоянии 1, в вершину 3~--- в состоянии 0. Совершаются следующие переходы в графе и автомате. -\begin{alignat*}{7} - \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] - \node[state, red] (q_0) {$1$}; - \node[state] (q_1) [above=of q_0] {$2$}; - \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; - \node[state, red] (q_3) [right=of q_2] {$3$}; - \path[->, red] - (q_2) edge node[pos=0.7] {a} (q_0) - (q_2) edge[bend left] node[above] {b} (q_3); - \path[->] - (q_0) edge node {b} (q_1) - (q_1) edge node[pos=0.3] {a} (q_2) - (q_3) edge[bend left] node {b} (q_2); - \end{tikzpicture} - &\hspace{20px} - \begin{tikzpicture}[shorten >=1pt,on grid,auto] - \node[state, draw=none] (q_3) at (0,0) {$$}; % empty node for alignment - \node[state, initial, red] (q_0) at (0,1) {$0$}; - \node[state, red] (q_1) at (2,1) {$1$}; - \node[state, accepting] (q_2) at (4,1) {$2$}; - \path[->, red] - (q_0) edge node {$a$} (q_1); - \path[->] - (q_1) edge node {$b$} (q_2); - \draw (q_0) edge[loop above, red] node {$b$} (q_0); - \end{tikzpicture} -\end{alignat*} +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] + \node[state, red] (q_0) {$1$}; + \node[state] (q_1) [above=of q_0] {$2$}; + \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; + \node[state, red] (q_3) [right=of q_2] {$3$}; + \path[->, red] + (q_2) edge node[pos=0.7] {a} (q_0) + (q_2) edge[bend left] node[above] {b} (q_3); + \path[->] + (q_0) edge node {b} (q_1) + (q_1) edge node[pos=0.3] {a} (q_2) + (q_3) edge[bend left] node {b} (q_2); + \end{tikzpicture} + \end{center} +\end{minipage} +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[shorten >=1pt,on grid,auto] + \node[state, draw=none] (q_3) at (0,0) {$ $}; % empty node for alignment + \node[state, initial, red] (q_0) at (0,1) {$0$}; + \node[state, red] (q_1) at (2,1) {$1$}; + \node[state, accepting] (q_2) at (4,1) {$2$}; + \path[->, red] + (q_0) edge node {$a$} (q_1); + \path[->] + (q_1) edge node {$b$} (q_2); + \draw (q_0) edge[loop above, red] node {$b$} (q_0); + \end{tikzpicture} + \end{center} +\end{minipage} Сделаем еще один шаг алгоритма и придем к конечному состоянию в автомате. - -\begin{alignat*}{7} - a:\,\, - & \begin{matrix} - \fbox{1 0 0} \fbox{0 0 0 1} \\ - \fbox{0 1 0} \fbox{0 1 0 0} \\ - \fbox{0 0 1} \fbox{0 0 0 0} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 1 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{0 1 0} \fbox{0 0 0 0} \\ - \fbox{0 0 0} \fbox{0 0 0 0} \\ - \fbox{0 0 0} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} - -\begin{alignat*}{7} - b:\,\, - & \begin{matrix} - \fbox{1 0 0} \fbox{0 0 0 1} \\ - \fbox{0 1 0} \fbox{0 1 0 0} \\ - \fbox{0 0 1} \fbox{0 0 0 0} - \end{matrix} && - \times - \left(\begin{array}{c c c | c c c c} - 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - \hline - 0 & 0 & 0 & 0 & 0 & 0 & 1 \\ - 0 & 0 & 0 & 0 & 0 & 1 & 0 \\ - 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ - 0 & 0 & 0 & 1 & 0 & 0 & 0 - \end{array}\right) - &&= \begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} \\ - \fbox{0 0 1} \fbox{0 0 1 0} \\ - \fbox{0 0 0} \fbox{0 0 0 0} - \end{matrix} -\end{alignat*} - -\begin{alignat*}{7} - & &&M &&=\begin{matrix} - \fbox{1 0 0} \fbox{1 0 0 0} \\ - \fbox{0 1 0} \fbox{0 0 0 0} \\ - \fbox{0 0 1} \fbox{0 0 1 0} - \end{matrix} -\end{alignat*} - -Видно, что мы достигли вершины 2 графа в конечном состоянии 2 автомата. При этом вершина 0 графа так же достигнута, как и в наивном варианте алгоритма, но -теперь известно, что это происходит в состоянии 0 автомата. - -\begin{alignat*}{7} - \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] - \node[state, gray] (q_0) {$1$}; - \node[state, teal] (q_1) [above=of q_0] {$2$}; - \node[state, red] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; - \node[state, gray] (q_3) [right=of q_2] {$3$}; - \path[->, gray] - (q_2) edge node[pos=0.7] {a} (q_0) - (q_2) edge[bend left] node[above] {b} (q_3); - \path[->, red] - (q_0) edge node {b} (q_1) - (q_3) edge[bend left] node {b} (q_2); - \path[->] - (q_1) edge node[pos=0.3] {a} (q_2); - \end{tikzpicture} - &\hspace{20px} - \begin{tikzpicture}[shorten >=1pt,on grid,auto] - \node[state, draw=none] (q_3) at (0,0) {$$}; % empty node for alignment - \node[state, initial, red] (q_0) at (0,1) {$0$}; - \node[state, gray] (q_1) at (2,1) {$1$}; - \node[state, accepting, teal] (q_2) at (4,1) {$2$}; - \path[->, gray] - (q_0) edge node {$a$} (q_1); - \path[->, red] - (q_1) edge node {$b$} (q_2); - \draw (q_0) edge[loop above, red] node {$b$} (q_0); - \end{tikzpicture} -\end{alignat*} +\NewDocumentCommand{\MMt}{}{\begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 1 & 0 + \end{pNiceArray}} + +\begin{widepar} + \begin{gather*} + \MMs \cdot \MDa = \begin{pNiceArray}[]{ccc|cccc} + 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray} \\ + \MMs \cdot \MDb = \begin{pNiceArray}[]{ccc|cccc} + 1 & 0 & 0 & 1 & 0 & 0 & 0 \\ + 0 & 0 & 1 & 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & 0 & 0 & 0 & 0 + \end{pNiceArray} \\ + M = \MMt + \end{gather*} +\end{widepar} + +Видно, что мы достигли вершины 2 графа в конечном состоянии 2 автомата. +При этом вершина 0 графа так же достигнута, как и в наивном варианте алгоритма, но теперь известно, что это происходит в состоянии 0 автомата. + +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] + \node[state, gray] (q_0) {$1$}; + \node[state, teal] (q_1) [above=of q_0] {$2$}; + \node[state, red] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; + \node[state, gray] (q_3) [right=of q_2] {$3$}; + \path[->, gray] + (q_2) edge node[pos=0.7] {a} (q_0) + (q_2) edge[bend left] node[above] {b} (q_3); + \path[->, red] + (q_0) edge node {b} (q_1) + (q_3) edge[bend left] node {b} (q_2); + \path[->] + (q_1) edge node[pos=0.3] {a} (q_2); + \end{tikzpicture} + \end{center} +\end{minipage} +\begin{minipage}{0.45\textwidth} + \begin{center} + \begin{tikzpicture}[shorten >=1pt,on grid,auto] + \node[state, draw=none] (q_3) at (0,0) {$ $}; % empty node for alignment + \node[state, initial, red] (q_0) at (0,1) {$0$}; + \node[state, gray] (q_1) at (2,1) {$1$}; + \node[state, accepting, teal] (q_2) at (4,1) {$2$}; + \path[->, gray] + (q_0) edge node {$a$} (q_1); + \path[->, red] + (q_1) edge node {$b$} (q_2); + \draw (q_0) edge[loop above, red] node {$b$} (q_0); + \end{tikzpicture} + \end{center} +\end{minipage} Таким образом, в ответ попадает вершина 2. -\end{example} Перейдем к формальному описанию алгоритма. +\subsection{Формальное описание алгоритма} -Алгоритм принимает на вход граф $\mathcal{G}$, детерминированный конечный автомат $\mathcal{R}$, описывающий регулярную грамматику, и множество начальных вершин $V_{src}$ графа. - -Граф $\mathcal{G}$ и автомат $\mathcal{R}$ можно представить в виде булевых матриц смежности. Так, в виде словаря для каждой метки графа заводится булева матрица смежности, на месте $(i, j)$ ячейки которой стоит 1, если $i$ и $j$ вершины графа соединены ребром данной метки. Такая же операция проводится для автомата грамматики $\mathcal{R}$. +Алгоритм принимает на вход граф $\mscrG$, детерминированный конечный автомат $\mscrR$, описывающий регулярную грамматику, и множество начальных вершин $V_{\mathrm{src}}$ графа. -Далее, мы оперируем с двумя словарями, где ключом является символ метки ребра графа или символ алфавита автомата, а значением --- соответствующая им булевая матрица. +Граф $\mscrG$ и автомат $\mscrR$ можно представить в виде булевых матриц смежности. +Так, в виде словаря для каждой метки графа заводится булева матрица смежности, на месте $(i, j)$ ячейки которой стоит 1, если $i$ и $j$ вершины графа соединены ребром данной метки. +Такая же операция проводится для автомата грамматики $\mscrR$. -Для каждого символа из пересечения этих множеств строится матрица $\mathfrak{D}$, как прямая сумма булевых матриц. То есть, строится матрица $\mathfrak{D} = Bool_{\mathcal{R}_a} \bigoplus Bool_{\mathcal{G}_a}$, которая определяется как +Далее, мы оперируем с двумя словарями, где ключом является символ метки ребра графа или символ алфавита автомата, а значением~--- соответствующая им булевая матрица. -\begin{equation} -\mathfrak{D} = - \left[ - \begin{matrix} - Bool_{\mathcal{R}_a} & 0\\ - 0 & Bool_{\mathcal{G}_a} - \end{matrix} - \right] -\end{equation} - -Где $\mathcal{R}_{a}$ и $\mathcal{G}_{a}$ матрицы смежности соответствующих символов автомата грамматики $\mathcal{R}$ и графа $\mathcal{G}$ для символа $a \in A_\mathcal{R} \cap A_\mathcal{G}$, $A_\mathcal{R} \cap A_\mathcal{G}$ --- пересечение алфавитов. Такая конструкция позволяет синхронизировать алгоритм обхода в ширину одновременно для графа и грамматики. - -Далее вводится матрица $M$, хранящая информацию о фронте обхода графа. Она нужна для выделения множества пройденных вершин и не допускает зацикливание алгоритма. -\begin{equation} -M^{k \times (k + n)} = - \left[ - \begin{matrix} +Для каждого символа из пересечения этих множеств строится матрица $\mfrakD$, как прямая сумма булевых матриц. +То есть, строится матрица $\mfrakD = \mathrm{Bool}_{\mscrR_a} \oplus \mathrm{Bool}_{\mscrG_a}$, которая определяется как +\[ + \mfrakD = \begin{pmatrix} + \mathrm{Bool}_{\mscrR_a} & 0 \\ + 0 & \mathrm{Bool}_{\mscrG_a} + \end{pmatrix}, +\] +где $\mscrR_{a}$ и $\mscrG_{a}$ матрицы смежности соответствующих символов автомата грамматики $\mscrR$ и графа $\mscrG$ для символа $a \in A_\mscrR \cap A_\mscrG$, $A_\mscrR \cap A_\mscrG$~--- пересечение алфавитов. +Такая конструкция позволяет синхронизировать алгоритм обхода в ширину одновременно для графа и грамматики. + +Далее вводится матрица $M$, хранящая информацию о фронте обхода графа. +\marginnote{TODO: Надо подумать над обозначениями. Почему $Id$, а не $E$? Может быть вообще надо эти обозначения вводить сразу в примере.} +Она нужна для выделения множества пройденных вершин и не допускает зацикливание алгоритма. +\[ + M^{k \times (k + n)} = \begin{pNiceArray}[]{c|c} Id_k & Matrix_{k \times n } - \end{matrix} - \right] -\end{equation} - -Где $Id_k$ --- единичная матрица размера $k$, $k$ --- количество вершин в автомате $\mathcal{R}$, $Matrix_{k \times n }$ --- матрица, хранящая в себе маску пройденных вершин в автомате графа, $n$ --- количество вершин в графе $\mathcal{G}$. + \end{pNiceArray}, +\] +где $Id_k$~--- единичная матрица размера $k$, $k$~--- количество вершин в автомате $\mscrR$, $Matrix_{k \times n }$~--- матрица, хранящая в себе маску пройденных вершин в автомате графа, $n$~--- количество вершин в графе $\mscrG$. \subsection{Выходные данные} -На выходе строится множество $\mathcal{P}$ пар вершин $(v, w)$ графа $\mathcal{G}$ таких, что вершина $w$ достижима из множества начальных вершин, при этом $v \in V_{src}$, $w \not\in V_{src}$. Это множество представляется в виде матрицы размера $|V|\times|V|$, где $(i,j)$ ячейка содержит 1, если пара вершин с индексами $(i, j) \in \mathcal{P}$. +На выходе строится множество $\mscrP$ пар вершин $(v, w)$ графа $\mscrG$ таких, что вершина $w$ достижима из множества начальных вершин, при этом $v \in V_{\mathrm{src}}$, $w \not\in V_{\mathrm{src}}$. +Это множество представляется в виде матрицы размера $|V| \times |V|$, где $(i,j)$ ячейка содержит 1, если пара вершин с индексами $(i, j) \in \mscrP$. \subsection{Процесс обхода графа} -Алгоритм обхода заключается в последовательном умножении матрицы $M$ текущего фронта на матрицу $\mathfrak{D}$. В результате чего, находится матрица $M'$ содержащая информацию о вершинах, достижимых на следующем шаге. Далее, с помощью операций перестановки и сложения векторов $M'$ преобразуется к виду матрицы $M$ и присваивается ей. Итерации продолжаются пока $M'$ содержит новые вершины, не содержащиеся в $M$. На листинге~\ref{BFSRPQ1} представлен этот алгоритм. - -\begin{algorithm}[t] - \caption{Алгоритм достижимости в графе с регулярными ограничениями на основе поиска в ширину, выраженный с помощью операций матричного умножения}\label{BFSRPQ1} - \begin{algorithmic}[1] - \Procedure{BFSBasedRPQ}{$\mathcal{R}=\langle Q, \Sigma, P, F, q \rangle,\mathcal{G}=\langle V, E, L \rangle, V_{src}$} - \State $\mathcal{P}\gets~${Матрица смежности графа} - \State $\mathfrak{D}\gets Bool_\mathcal{R} \bigoplus Bool_\mathcal{G}$\Comment{Построение матриц $\mathfrak{D}$} - \State $M\gets CreateMasks(|Q|,|V|)$ \Comment{Построение матрицы $M$} - \State $M'\gets SetStartVerts(M, V_{src})$ \Comment{Заполнение нач. вершин} - - \While{Матрица~$M$~меняется}{} - \State $M\gets M'\langle\neg M\rangle$\Comment{Применение комплементарной маски} - \ForAll{$a\in (\Sigma \cap L)$} - \State $M'\gets M~$any.pair$~\mathfrak{D}$ - \Comment{Матр. умножение в полукольце} - \State $M'\gets TransformRows(M')$\label{TransformRows} - \Comment{Приведение $M'$ к виду $M$} - \EndFor - \State {$Matrix\gets extractRightSubMatrix(M')$} - \State $V\gets Matrix.reduceVector()$ \Comment{Сложение по столбцам} - \For{$k \in 0\dots|V_{src}|-1$} - \State $W\gets\mathcal{P}.getRow(k)$ - \State $\mathcal{P}.setRow(k, V+W)$ - \EndFor - \EndWhile - \State \textbf{return} $\mathcal{P}$ - \EndProcedure - \end{algorithmic} -\end{algorithm} - -В алгоритме~\ref{BFSRPQ1}, в~\ref{TransformRows} строке происходит трансформация строчек в матрице $M'$. Это делается для того, чтобы представить полученную во время обхода матрицу $M'$, содержащую новый фронт, в виде матрицы $M$. Для этого требуется так переставить строчки $M'$, чтобы она содержала корректные по своему определению значения. То есть, имела единицы на главной диагонали, а все остальные значения в первых $k$ столбцах были нулями. Подробнее эта процедура описана в листинге~\ref{AlgoTransformRows}. - -\begin{algorithm}[H] - \caption{Алгоритм трансформации строчек}\label{AlgoTransformRows} - \begin{algorithmic}[1] - \Procedure{TransformRows}{$M$} - \State{$T \gets extractLeftSubMatrix(M)$} - \State{$Ix, Iy \gets$ итераторы по индексам ненулевых элементов $T$} - \For{$i \in 0\dots|Iy|$} - \State{$R\gets M.getRow(Ix[i])$} - \State{$M'.setRow(Iy[i], R + M'.getRow(Iy[i]))$} - \EndFor - \EndProcedure - \end{algorithmic} -\end{algorithm} - -\pagebreak +Алгоритм обхода заключается в последовательном умножении матрицы $M$ текущего фронта на матрицу $\mfrakD$. +В результате чего, находится матрица $M'$ содержащая информацию о вершинах, достижимых на следующем шаге. +Далее, с помощью операций перестановки и сложения векторов $M'$ преобразуется к виду матрицы $M$ и присваивается ей. +Итерации продолжаются пока $M'$ содержит новые вершины, не содержащиеся в $M$. На листинге~\ref{BFSRPQ1} представлен этот алгоритм. + +% \begin{algorithm}[t] +% \caption{Алгоритм достижимости в графе с регулярными ограничениями на основе поиска в ширину, выраженный с помощью операций матричного умножения}\label{BFSRPQ1} +% \begin{algorithmic}[1] +% \Procedure{BFSBasedRPQ}{$\mscrR=\langle Q, \Sigma, P, F, q \rangle,\mscrG=\langle V, E, L \rangle, V_{\mathrm{src}}$} +% \State $\mscrP\gets~${Матрица смежности графа} +% \State $\mfrakD\gets \mathrm{Bool}_\mscrR \bigoplus \mathrm{Bool}_\mscrG$\Comment{Построение матриц $\mfrakD$} +% \State $M\gets CreateMasks(|Q|,|V|)$ \Comment{Построение матрицы $M$} +% \State $M'\gets SetStartVerts(M, V_{\mathrm{src}})$ \Comment{Заполнение нач. вершин} + +% \While{Матрица~$M$~меняется}{} +% \State $M\gets M'\langle\neg M\rangle$\Comment{Применение комплементарной маски} +% \ForAll{$a\in (\Sigma \cap L)$} +% \State $M'\gets M~$any.pair$~\mfrakD$ +% \Comment{Матр. умножение в полукольце} +% \State $M'\gets TransformRows(M')$\label{TransformRows} +% \Comment{Приведение $M'$ к виду $M$} +% \EndFor +% \State {$Matrix\gets extractRightSubMatrix(M')$} +% \State $V\gets Matrix.reduceVector()$ \Comment{Сложение по столбцам} +% \For{$k \in 0\dots|V_{\mathrm{src}}|-1$} +% \State $W\gets\mscrP.getRow(k)$ +% \State $\mscrP.setRow(k, V+W)$ +% \EndFor +% \EndWhile +% \State \textbf{return} $\mscrP$ +% \EndProcedure +% \end{algorithmic} +% \end{algorithm} + +В алгоритме~\ref{BFSRPQ1}, в~\ref{TransformRows} строке происходит трансформация строчек в матрице $M'$. +Это делается для того, чтобы представить полученную во время обхода матрицу $M'$, содержащую новый фронт, в виде матрицы $M$. +Для этого требуется так переставить строчки $M'$, чтобы она содержала корректные по своему определению значения. +То есть, имела единицы на главной диагонали, а все остальные значения в первых $k$ столбцах были нулями. +Подробнее эта процедура описана в листинге~\ref{AlgoTransformRows}. + +% \begin{algorithm}[H] +% \caption{Алгоритм трансформации строчек}\label{AlgoTransformRows} +% \begin{algorithmic}[1] +% \Procedure{TransformRows}{$M$} +% \State{$T \gets extractLeftSubMatrix(M)$} +% \State{$Ix, Iy \gets$ итераторы по индексам ненулевых элементов $T$} +% \For{$i \in 0\dots|Iy|$} +% \State{$R\gets M.getRow(Ix[i])$} +% \State{$M'.setRow(Iy[i], R + M'.getRow(Iy[i]))$} +% \EndFor +% \EndProcedure +% \end{algorithmic} +% \end{algorithm} \subsection{Модификации алгоритма} -Рассмотрим $V_{src}$ --- множество начальных вершин, состоящее из $r$ элементов. Для каждой начальной вершины $v_{src}^i \in V_{src}$ отметим соответствующие индексы в матрице $M$ единицами, получив матрицу $M(v_{src}^i)$, и построим матрицу $\mathfrak{M}$ следующим образом. - -\begin{equation} -\mathfrak{M}^{(k*r) \times (k + n)} = - \left[ - \begin{matrix} - M(v_{src}^1) \\ - M(v_{src}^2) \\ - M(\dots) \\ - M(v_{src}^r) \\ - \end{matrix} - \right] -\end{equation} - -Матрица $\mathfrak{M}$ собирается из множества матриц $M(v_{src}^i)$ и позволяет хранить информацию о том, из какой начальной вершины достигаются новые вершины во время обхода. - -\begin{algorithm}[t] - \caption{Модификация алгоритма для поиска конкретной исходной вершины}\label{BFSRPQ2} - \begin{algorithmic}[1] - \Procedure{BFSBasedRPQ}{$\mathcal{R}=\langle Q, \Sigma, P, F, q \rangle,\mathcal{G}=\langle V, E, L \rangle, V_{src}$} - \State $\mathcal{P}\gets~${Матрица смежности графа} - \State $\mathfrak{D}\gets Bool_\mathcal{R} \bigoplus Bool_\mathcal{G}$ - \State $\mathfrak{M}\gets CreateMasks(|Q|,|V|)$ - \State $\mathfrak{M}'\gets SetStartVerts(\mathfrak{M}, V_{src})$ - - \While{Матрица~$\mathfrak{M}$~меняется}{} - \State $\mathfrak{M}\gets \mathfrak{M}'\langle\neg\mathfrak{M}\rangle$ - \ForAll{$a\in (\Sigma \cap L)$} - \State $\mathfrak{M}'\gets \mathfrak{M}~$any.pair$~\mathfrak{D}$ - \ForAll{$M \in \mathfrak{M}'$} - \State $M\gets TransformRows(M)$ - \EndFor - \EndFor - \ForAll{$M_k \in \mathfrak{M}'$} - \State $Matrix\gets extractSubMatrix(M)$ - \State $V\gets Matrix.reduceVector()$ - \State $W\gets\mathcal{P}.getRow(k)$ - \State $\mathcal{P}.setRow(k, V+W)$ - \EndFor - \EndWhile - \State \textbf{return} $\mathcal{P}$ - \EndProcedure - \end{algorithmic} -\end{algorithm} - -В листинге~\ref{BFSRPQ2} представлен модифицированный алгоритм. Основное его отличие заключается в том, что для каждой достижимой вершины находится конкретная исходная вершина, из которой начинался обход. - -Таким образом, алгоритмы~\ref{BFSRPQ1}~и~\ref{BFSRPQ2} решают сформулированные в пункте \ref{sec:3.3} задачи достижимости. \ No newline at end of file +Рассмотрим $V_{\mathrm{src}}$~--- множество начальных вершин, состоящее из $r$ элементов. +Для каждой начальной вершины $V_{\mathrm{src}}^i \in V_{\mathrm{src}}$ отметим соответствующие индексы в матрице $M$ единицами, получив матрицу $M(V_{\mathrm{src}}^i)$, и построим матрицу $\mfrakM$ следующим образом. +\[ + \mfrakM^{(k*r) \times (k + n)} = \begin{pmatrix} + M(V_{\mathrm{src}}^1) \\ + M(V_{\mathrm{src}}^2) \\ + M(\dots) \\ + M(V_{\mathrm{src}}^r) + \end{pmatrix} +\] + +Матрица $\mfrakM$ собирается из множества матриц $M(V_{\mathrm{src}}^i)$ и позволяет хранить информацию о том, из какой начальной вершины достигаются новые вершины во время обхода. + +% \begin{algorithm}[t] +% \caption{Модификация алгоритма для поиска конкретной исходной вершины}\label{BFSRPQ2} +% \begin{algorithmic}[1] +% \Procedure{BFSBasedRPQ}{$\mscrR=\langle Q, \Sigma, P, F, q \rangle,\mscrG=\langle V, E, L \rangle, V_{\mathrm{src}}$} +% \State $\mscrP\gets~${Матрица смежности графа} +% \State $\mfrakD\gets \mathrm{Bool}_\mscrR \bigoplus \mathrm{Bool}_\mscrG$ +% \State $\mfrakM\gets CreateMasks(|Q|,|V|)$ +% \State $\mfrakM'\gets SetStartVerts(\mfrakM, V_{\mathrm{src}})$ + +% \While{Матрица~$\mfrakM$~меняется}{} +% \State $\mfrakM\gets \mfrakM'\langle\neg\mfrakM\rangle$ +% \ForAll{$a\in (\Sigma \cap L)$} +% \State $\mfrakM'\gets \mfrakM~$any.pair$~\mfrakD$ +% \ForAll{$M \in \mfrakM'$} +% \State $M\gets TransformRows(M)$ +% \EndFor +% \EndFor +% \ForAll{$M_k \in \mfrakM'$} +% \State $Matrix\gets extractSubMatrix(M)$ +% \State $V\gets Matrix.reduceVector()$ +% \State $W\gets\mscrP.getRow(k)$ +% \State $\mscrP.setRow(k, V+W)$ +% \EndFor +% \EndWhile +% \State \textbf{return} $\mscrP$ +% \EndProcedure +% \end{algorithmic} +% \end{algorithm} + +В листинге~\ref{BFSRPQ2} представлен модифицированный алгоритм. +Основное его отличие заключается в том, что для каждой достижимой вершины находится конкретная исходная вершина, из которой начинался обход. + +Таким образом, алгоритмы~\ref{BFSRPQ1} и~\ref{BFSRPQ2} решают сформулированные в пункте~\ref{sec:3.3} задачи достижимости. diff --git a/tex/styles/tikz.tex b/tex/styles/tikz.tex index e91a454..bf76317 100644 --- a/tex/styles/tikz.tex +++ b/tex/styles/tikz.tex @@ -5,6 +5,7 @@ \usetikzlibrary{shapes.geometric} \usetikzlibrary{automata} \usetikzlibrary{decorations.pathmorphing} +\usetikzlibrary{calc} \tikzsetexternalprefix{figures/externalized/} @@ -20,4 +21,4 @@ } } -% \usetikzlibrary{fit, calc} +% \usetikzlibrary{fit} From 06133c7fb2ab076fdfa723cafa9aa1e056f9cd0d Mon Sep 17 00:00:00 2001 From: Nikolai Ponomarev Date: Mon, 24 Jun 2024 20:49:09 +0300 Subject: [PATCH 2/3] Misc changes in RPQ --- tex/RPQ.tex | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/tex/RPQ.tex b/tex/RPQ.tex index 6af902a..7e072db 100644 --- a/tex/RPQ.tex +++ b/tex/RPQ.tex @@ -21,28 +21,35 @@ \section{Достижимость с несколькими источникам Покажем на примере, как данный метод может быть использован, когда мы накладываем дополнительные ограничения в виде регулярного языка на путь в графе. Для этого, во-первых, предъявим булевые представления для матриц смежности графа и автомата для регулярного языка. +\marginnote{TODO: Подумать над разбиением на разделы и подразделы} Затем, введем специальную блочно-диагональную матрицу для синхронизации обхода в ширину по двум матрицам смежности. Далее, попробуем наивно реализовать обход в ширину, и посмотрим, почему наивная реализация может выдавать некорректный результат. После этого перейдем к реализации обхода в ширину более продвинутым методом, который решает проблему наивного подхода. \subsection{Пример работы алгоритма} -\marginnote{TODO: Подумать над разбиением на разделы и подразделы} -Возьмём следующий граф. -\begin{center} - \label{input_rpq} - \begin{tikzpicture}[node distance=2cm,shorten >=1pt,on grid,auto] - \node[state] (q_0) {$1$}; - \node[state] (q_1) [above=of q_0] {$2$}; - \node[state] (q_2) [right=of $(q_0)!0.5!(q_1)$] {$0$}; - \node[state] (q_3) [right=of q_2] {$3$}; - \path[->] - (q_0) edge node {b} (q_1) - (q_1) edge node[pos=0.3] {a} (q_2) - (q_2) edge node[pos=0.7] {a} (q_0) - (q_2) edge[bend left] node[above] {b} (q_3) - (q_3) edge[bend left] node {b} (q_2); - \end{tikzpicture} -\end{center} +\begin{marginfigure} + \caption{Входной граф.} + \label{fig:ms_rpq_graph} + \begin{center} + \begin{tikzpicture}[ + shorten >=1pt, + auto] + \node[state] (q_0) {$0$}; + \node[state] (q_1) [below left = of q_0] {$1$}; + \node[state] (q_2) [above left = of q_0] {$2$}; + \node[state] (q_3) [right = of q_0]{$3$}; + + \path[->] + (q_0) edge [bend left] node {$b$} (q_3) + (q_3) edge [bend left] node {$b$} (q_0) + (q_0) edge node {$a$} (q_1) + (q_1) edge node {$b$} (q_2) + (q_2) edge node {$a$} (q_0); + \end{tikzpicture} + \end{center} +\end{marginfigure} +Возьмём граф изображенный на рисунке~\ref{fig:ms_rpq_graph}. + Его матрица смежности имеет следующий вид. \marginnote{TODO: Точка внизу или снизу?} @@ -540,6 +547,7 @@ \subsection{Процесс обхода графа} Далее, с помощью операций перестановки и сложения векторов $M'$ преобразуется к виду матрицы $M$ и присваивается ей. Итерации продолжаются пока $M'$ содержит новые вершины, не содержащиеся в $M$. На листинге~\ref{BFSRPQ1} представлен этот алгоритм. +%% FIXME: Переверстать алгоритм % \begin{algorithm}[t] % \caption{Алгоритм достижимости в графе с регулярными ограничениями на основе поиска в ширину, выраженный с помощью операций матричного умножения}\label{BFSRPQ1} % \begin{algorithmic}[1] @@ -575,6 +583,7 @@ \subsection{Процесс обхода графа} То есть, имела единицы на главной диагонали, а все остальные значения в первых $k$ столбцах были нулями. Подробнее эта процедура описана в листинге~\ref{AlgoTransformRows}. +%% FIXME: Переверстать алгоритм % \begin{algorithm}[H] % \caption{Алгоритм трансформации строчек}\label{AlgoTransformRows} % \begin{algorithmic}[1] @@ -604,6 +613,7 @@ \subsection{Модификации алгоритма} Матрица $\mfrakM$ собирается из множества матриц $M(V_{\mathrm{src}}^i)$ и позволяет хранить информацию о том, из какой начальной вершины достигаются новые вершины во время обхода. +%% FIXME: Переверстать алгоритм % \begin{algorithm}[t] % \caption{Модификация алгоритма для поиска конкретной исходной вершины}\label{BFSRPQ2} % \begin{algorithmic}[1] From 4a77c0f082470e9140946844d1b8f442300e3b4d Mon Sep 17 00:00:00 2001 From: Nikolai Ponomarev Date: Mon, 24 Jun 2024 22:06:10 +0300 Subject: [PATCH 3/3] Add CYK chapter --- tex/CYK_for_CFPQ.tex | 1194 ++++++++--------- ...ageConstrainedReachabilityLectureNotes.tex | 2 +- 2 files changed, 578 insertions(+), 618 deletions(-) diff --git a/tex/CYK_for_CFPQ.tex b/tex/CYK_for_CFPQ.tex index a4da827..1957e01 100644 --- a/tex/CYK_for_CFPQ.tex +++ b/tex/CYK_for_CFPQ.tex @@ -1,684 +1,644 @@ -\chapter{CYK для вычисления КС запросов}\label{chpt:CFPQ_CYK} +\setchapterpreamble[u]{\margintoc} +\chapter{CYK для вычисления КС запросов} +\label{chpt:CFPQ_CYK} +\tikzsetfigurename{CFPQ_CYK_} В данной главе мы рассмотрим алгоритм CYK, позволяющий установить принадлежность слова грамматике и предоставить его вывод, если таковой имеется. -Наш главный интерес заключается в возможности применения данного алгоритма для решения описанной в предыдущей главе задачи --- поиска путей с ограничениями в терминах формальных языков. Как уже было указано выше, будем рассматривать случай контекстно-свободных языков. +Наш главный интерес заключается в возможности применения данного алгоритма для решения описанной в предыдущей главе задачи~--- поиска путей с ограничениями в терминах формальных языков. +Как уже было указано выше, будем рассматривать случай контекстно-свободных языков. -\section{Алгоритм CYK}\label{sect:lin_CYK} +\section{Алгоритм CYK} +\label{sect:lin_CYK} -Алгоритм CYK (Cocke-Younger-Kasami) --- один из классических алгоритмов синтаксического анализа. Его асимптотическая сложность в худшем случае --- $O(n^3 * |N|)$, где $n$ --- длина входной строки, а $N$ --- количество нетерминалов во входной грамматике~\cite{Hopcroft+Ullman/79/Introduction}. +Алгоритм CYK (Cocke-Younger-Kasami)~--- один из классических алгоритмов синтаксического анализа. +Его асимптотическая сложность в худшем случае~--- $O(n^3 \cdot |N|)$, где $n$~--- длина входной строки, а $N$~--- количество нетерминалов во входной грамматике~\sidecite{Hopcroft+Ullman/79/Introduction}. -Для его применения необходимо, чтобы подаваемая на вход грамматика находилась в Нормальной Форме Хомского (НФХ)~\ref{section:CNF}. Других ограничений нет и, следовательно, данный алгоритм применим для работы с произвольными контекстно-своболными языками. - -В основе алгоритма лежит принцип динамического программирования. Используются два соображения: +Для его применения необходимо, чтобы подаваемая на вход грамматика находилась в Нормальной Форме Хомского (НФХ)~\ref{section:CNF}. +Других ограничений нет и, следовательно, данный алгоритм применим для работы с произвольными контекстно-свободными языками. +В основе алгоритма лежит принцип динамического программирования. +Используются два соображения: \begin{enumerate} -\item Из нетерминала $A$ выводится цепочка $\omega$ при помощи правила $A \to a$ тогда и только тогда, когда $a= \omega$: -\[ - A \derives \omega \iff \omega = a -\] - -\item Из нетерминала $A$ выводится цепочка $\omega$ при помощи правила $A \to B C$ тогда и только тогда, когда существуют две цепочки $\omega_1$ и $\omega_2$ такие, что $\omega_1$ выводима из $B$, $\omega_2$ выводима из $C$ и при этом $\omega = \omega_1 \omega_2$: -\[ -A \derives[] B C \derives \omega \iff \exists \omega_1, \omega_2 : \omega = \omega_1 \omega_2, B \derives \omega_1, C \derives \omega_2 -\] - -Переформулируем эти утверждения в терминах позиций в строке: -\[ -A \derives[] B C \derives \omega \iff \exists k \in [1 \dots |\omega|] : B \derives \omega[1 \dots k], C \derives \omega[k+1 \dots |\omega|] -\] + \item Из нетерминала $A$ выводится цепочка $\omega$ при помощи правила $A \to a$ тогда и только тогда, когда $a = \omega$: + \[ A \derives \omega \iff \omega = a \] + \item Из нетерминала $A$ выводится цепочка $\omega$ при помощи правила $A \to B C$ тогда и только тогда, когда существуют две цепочки $\omega_1$ и $\omega_2$ такие, что $\omega_1$ выводима из $B$, $\omega_2$ выводима из $C$ и при этом $\omega = \omega_1 \omega_2$: + \[ A \derives[] B C \derives \omega \iff \exists \omega_1, \omega_2 : \omega = \omega_1 \omega_2, B \derives \omega_1, C \derives \omega_2 \] + Переформулируем эти утверждения в терминах позиций в строке: + \[ A \derives[] B C \derives \omega \iff \exists k \in [1 \rng |\omega|] : B \derives \omega[1 \rng k], C \derives \omega[k+1 \rng |\omega|] \] \end{enumerate} В процессе работы алгоритма заполняется булева трехмерная матрица $M$ размера $n \times n \times |N|$ таким образом, что -\[M[i, j, A] = true \iff A \derives \omega[i \dots j]\]. +\[ M[i, j, A] = true \iff A \derives \omega[i \rng j]. \] Первым шагом инициализируем матрицу, заполнив значения $M[i, i, A]$: - \begin{itemize} - \item $M[i, i, A] = true \text{, если в грамматике есть правило } A \to \omega[i]$. - \item $M[i, i, A] = false$, иначе. + \item $M[i, i, A] = true$, если в грамматике есть правило $A \to \omega[i]$.\marginnote{TODO: Может надо заменить на 0 и 1?} + \item $M[i, i, A] = false$, иначе. \end{itemize} Далее используем динамику: на шаге $m > 1$ предполагаем, что ячейки матрицы $M[i', j', A]$ заполнены для всех нетерминалов $A$ и пар $i', j': j' - i' < m$. -Тогда можно заполнить ячейки матрицы $M[i, j, A] \text{, где } j - i = m$ следующим образом: -\[ M[i, j, A] = \bigvee_{A \to B C}^{}{\bigvee_{k=i}^{j-1}{M[i, k, B] \wedge M[k, j, C]}} \] +Тогда можно заполнить ячейки матрицы $M[i, j, A]$, где $j - i = m$ следующим образом: +\[ M[i, j, A] = \bigvee_{A \to B C} \bigvee_{k = i}^{j-1} M[i, k, B] \wedge M[k, j, C] \] -По итогу работы алгоритма значение в ячейке $M[0, |\omega|, S]$, где $S$ --- стартовый нетерминал грамматики, отвечает на вопрос о выводимости цепочки $\omega$ в грамматике. +По итогу работы алгоритма значение в ячейке $M[0, |\omega|, S]$, где $S$~--- стартовый нетерминал грамматики, отвечает на вопрос о выводимости цепочки $\omega$ в грамматике. \begin{example}\label{exampl:CYK} - Рассмотрим пример работы алгоритма CYK на грамматике правильных скобочных последовательностей в Нормальной Форме Хомского. - - -\begin{align*} -S &\to A S_2 \mid \varepsilon & S_2 &\to b \mid B S_1 \mid S_1 S_3 & A &\to a \\ -S_1 &\to A S_2 & S_3 &\to b \mid B S_1 & B &\to b\\ -\end{align*} - -Проверим выводимость цепочки $\omega = a a b b a b$. - -Так как трехмерные матрицы рисовать на двумерной бумаге не очень удобно, мы будем иллюстрировать работу алгоритма двумерными матрицами размера $n \times n$, где в ячейках указано множество нетерминалов, выводящих соответствующую подстроку. - -Шаг 1. Инициализируем матрицу элементами на главной диагонали: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ -\varnothing & \{A\} & \varnothing & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\end{pmatrix} -\] - -Шаг 2. Заполняем диагональ, находящуюся над главной: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ -\varnothing & \{A\} & \cellcolor{lightgray}\{S_1\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \cellcolor{lightgray}\{S_1\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\end{pmatrix} -\] - -В двух ячейках появилисб нетерминалы $S_1$ благодяря присутствиб в грамматике правила $S_1 \to A S_2$. - -Шаг 3. Заполняем следующую диагональ: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ -\varnothing & \{A\} & \{S_1\} & \cellcolor{lightgray}\{S_2\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \cellcolor{lightgray}\{S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\end{pmatrix} -\] - -Шаг 4. И следующую за ней: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \cellcolor{lightgray}\{S_1, S\} & \varnothing & \varnothing \\ -\varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\end{pmatrix} -\] - -Шаг 5 Заполняем предпоследнюю диагональ: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \{S_1, S\} & \varnothing & \varnothing \\ -\varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \cellcolor{lightgray}\{S_2\} \\ -\varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\end{pmatrix} -\] - -\bigbreak -Шаг 6. Завершаем выполнение алгоритма: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \{S_1, S\} & \varnothing & \cellcolor{lightgray}\{S_1, S\} \\ -\varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \{S_2\} \\ -\varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\end{pmatrix} -\] - - -Стартовый нетерминал находится в верхней правой ячейке, а значит цепочка $a a b b a b$ выводима в нашей грамматике. + \marginnote{TODO: Я бы сделал это подразделом, потому что пример всего один} + Рассмотрим пример работы алгоритма CYK на грамматике правильных скобочных последовательностей в Нормальной Форме Хомского. + \begin{align*} + S & \to A S_2 \mid \varepsilon & S_2 & \to b \mid B S_1 \mid S_1 S_3 & A & \to a \\ + S_1 & \to A S_2 & S_3 & \to b \mid B S_1 & B & \to b + \end{align*} + + Проверим выводимость цепочки $\omega = a a b b a b$. + + Так как трехмерные матрицы рисовать на двумерной бумаге не очень удобно, мы будем иллюстрировать работу алгоритма двумерными матрицами размера $n \times n$, где в ячейках указано множество нетерминалов, выводящих соответствующую подстроку. + + Шаг 1. + Инициализируем матрицу элементами на главной диагонали: + \marginnote{TODO: Я бы здесь написал снаружи нетерминалы, хотя бы в одной матричке} + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ + \varnothing & \{A\} & \varnothing & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} + \end{pmatrix} + \] + + Шаг 2. + Заполняем диагональ, находящуюся над главной: + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ + \varnothing & \{A\} & \cellcolor{lightgray}\{S_1\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \cellcolor{lightgray}\{S_1\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} + \end{pmatrix} + \] + В двух ячейках появились нетерминалы $S_1$ благодаря присутствие в грамматике правила $S_1 \to A S_2$. + + Шаг 3. + Заполняем следующую диагональ: + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ + \varnothing & \{A\} & \{S_1\} & \cellcolor{lightgray}\{S_2\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \cellcolor{lightgray}\{S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} + \end{pmatrix} + \] + + Шаг 4. + И следующую за ней: + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \cellcolor{lightgray}\{S_1, S\} & \varnothing & \varnothing \\ + \varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} + \end{pmatrix} + \] + + Шаг 5. + Заполняем предпоследнюю диагональ: + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \{S_1, S\} & \varnothing & \varnothing \\ + \varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \cellcolor{lightgray}\{S_2\} \\ + \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} + \end{pmatrix} + \] + + Шаг 6. + Завершаем выполнение алгоритма: + \marginnote{TODO: Надо подумать как сделать так, чтобы не вылезало} + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \{S_1, S\} & \varnothing & \cellcolor{lightgray}\{S_1, S\} \\ + \varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \{S_2\} \\ + \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} + \end{pmatrix} + \] + + Стартовый нетерминал находится в верхней правой ячейке, а значит цепочка $a a b b a b$ выводима в нашей грамматике. \end{example} \begin{example} -Теперь выполним алгоритм на цепочке $\omega=abaa$. - -Шаг 1. Инициализируем таблицу: - -\[ -\begin{pmatrix} -\{A\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{A\} & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{A\} \\ -\end{pmatrix} -\] - -Шаг 2. Заполняем следующую диагональ: - -\[ -\begin{pmatrix} -\{A\} & \cellcolor{lightgray}\{S_1, S\} & \varnothing & \varnothing \\ -\varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{A\} & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{A\} \\ -\end{pmatrix} -\] - -Больше ни одну ячейку в таблице заполнить нельзя и при этом стартовый нетерминал отсутствует в правой верхней ячейке, а значит эта строка не выводится в грамматике правильных скобочных последовательностей. - + Теперь выполним алгоритм на цепочке $\omega=abaa$. + + Шаг 1. + Инициализируем таблицу: + \[ + \begin{pmatrix} + \{A\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{A\} & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{A\} + \end{pmatrix} + \] + + Шаг 2. + Заполняем следующую диагональ: + \[ + \begin{pmatrix} + \{A\} & \cellcolor{lightgray}\{S_1, S\} & \varnothing & \varnothing \\ + \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{A\} & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{A\} + \end{pmatrix} + \] + + Больше ни одну ячейку в таблице заполнить нельзя и при этом стартовый нетерминал отсутствует в правой верхней ячейке, а значит эта строка не выводится в грамматике правильных скобочных последовательностей. \end{example} \section{Алгоритм для графов на основе CYK} \label{graph:CYK} -Первым шагом на пути к решению задачи достижимости с использованием CYK является модификация представления входа. Прежде мы сопоставляли каждому символу слова его позицию во входной цепочке, поэтому при инициализации заполняли главную диагональ матрицы. Теперь вместо этого обозначим числами позиции между символами. В результате слово можно представить в виде линейного графа следующим образом(в качестве примера рассмотрим слово $a a b b a b$ из предыдущей главы~\ref{sect:lin_CYK}): +Первым шагом на пути к решению задачи достижимости с использованием CYK является модификация представления входа. +Прежде мы сопоставляли каждому символу слова его позицию во входной цепочке, поэтому при инициализации заполняли главную диагональ матрицы. +Теперь вместо этого обозначим числами позиции между символами. +В результате слово можно представить в виде линейного графа следующим образом (в качестве примера рассмотрим слово $a a b b a b$ из предыдущей главы~\ref{sect:lin_CYK}): \begin{center} \begin{tikzpicture}[shorten >=1pt,on grid,auto] - \node[state] (q_0) at (0,0) {$0$}; - \node[state] (q_1) at (2,0) {$1$}; - \node[state] (q_2) at (4,0) {$2$}; - \node[state] (q_3) at (6,0) {$3$}; - \node[state] (q_4) at (8,0) {$4$}; - \node[state] (q_5) at (10,0) {$5$}; - \node[state] (q_6) at (12,0) {$6$}; - \path[->] - (q_0) edge node {$a$} (q_1) - (q_1) edge node {$a$} (q_2) - (q_2) edge node {$b$} (q_3) - (q_3) edge node {$b$} (q_4) - (q_4) edge node {$a$} (q_5) - (q_5) edge node {$b$} (q_6); + \node[state] (q_0) at (0,0) {$0$}; + \node[state] (q_1) at (2,0) {$1$}; + \node[state] (q_2) at (4,0) {$2$}; + \node[state] (q_3) at (6,0) {$3$}; + \node[state] (q_4) at (8,0) {$4$}; + \node[state] (q_5) at (10,0) {$5$}; + \node[state] (q_6) at (12,0) {$6$}; + \path[->] + (q_0) edge node {$a$} (q_1) + (q_1) edge node {$a$} (q_2) + (q_2) edge node {$b$} (q_3) + (q_3) edge node {$b$} (q_4) + (q_4) edge node {$a$} (q_5) + (q_5) edge node {$b$} (q_6); \end{tikzpicture} \end{center} -Что нужно изменить в описании алгоритма, чтобы он продолжал работать при подобной нумерации? Каждая буква теперь идентифицируется не одним числом, а парой --- номера слева и справа от нее. При этом чисел стало на одно больше, чем при прежнем способе нумерации. +Что нужно изменить в описании алгоритма, чтобы он продолжал работать при подобной нумерации? +Каждая буква теперь идентифицируется не одним числом, а парой~--- номера слева и справа от нее. +При этом чисел стало на одно больше, чем при прежнем способе нумерации. -Возьмем матрицу $(n + 1) \times (n + 1) \times |N|$ и при инициализации будем заполнять не главную диагональ, а диагональ прямо над ней. Таким образом, мы начинаем наш алгоритм с определения значений $M[i, j, A] \text{, где } j = i + 1$. При этом наши дальнейшие действия в рамках алгоритма не изменятся. +Возьмем матрицу $(n + 1) \times (n + 1) \times |N|$ и при инициализации будем заполнять не главную диагональ, а диагональ прямо над ней. +Таким образом, мы начинаем наш алгоритм с определения значений $M[i, j, A]$, где $j = i + 1$. +При этом наши дальнейшие действия в рамках алгоритма не изменятся. Для примера~\ref{exampl:CYK} на шаге инициализации матрица выглядит следующим образом: - \[ -\begin{pmatrix} -\varnothing & \{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{A\} & \varnothing & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ - -\end{pmatrix} + \begin{pmatrix} + \varnothing & \{A\} & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{A\} & \varnothing & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing + \end{pmatrix} \] -А в результате работы алгоритма имеем: - +А в результате работы алгоритма имеем \[ -\begin{pmatrix} -\varnothing & \{A\} & \varnothing & \varnothing & \{S_1, S\} & \varnothing & \{S_1, S\} \\ -\varnothing & \varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \{S_2\} \\ -\varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ -\varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ -\varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing \\ -\end{pmatrix} + \begin{pmatrix} + \varnothing & \{A\} & \varnothing & \varnothing & \{S_1, S\} & \varnothing & \{S_1, S\} \\ + \varnothing & \varnothing & \{A\} & \{S_1\} & \{S_2\} & \varnothing & \{S_2\} \\ + \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \varnothing & \varnothing \\ + \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} & \varnothing & \{S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{A\} & \{S_1\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \{B, S_2, S_3\} \\ + \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing & \varnothing + \end{pmatrix} \] -Мы представили входную строку в виде линейного графа, а на шаге инициализации получили его матрицу смежности. Добавление нового нетерминала в язейку матрицы можно рассматривать как нахождение нового пути между соответствующими вершинами, выводимого из добавленного нетерминала. Таким образом, шаги алгоритма напоминают построение транзитивного замыкания графа. Различие заключается в том, что мы добавляем новые ребра только для тех пар нетерминалов, для которых существует соответстующее правило в грамматике. +Мы представили входную строку в виде линейного графа, а на шаге инициализации получили его матрицу смежности. +Добавление нового нетерминала в ячейку матрицы можно рассматривать как нахождение нового пути между соответствующими вершинами, выводимого из добавленного нетерминала. +Таким образом, шаги алгоритма напоминают построение транзитивного замыкания графа. +Различие заключается в том, что мы добавляем новые ребра только для тех пар нетерминалов, для которых существует соответствующее правило в грамматике. -Алгоритм можно обобщить и на произвольные графы с метками, рассматриваемые в этом курсе. При этом можно ослабить ограничение на форму входной грамматики: она должна находиться в ослабленной Нормальной Форме Хомского~(\ref{defn:wCNF}). +Алгоритм можно обобщить и на произвольные графы с метками, рассматриваемые в этом курсе. +При этом можно ослабить ограничение на форму входной грамматики: она должна находиться в ослабленной Нормальной Форме Хомского~(\ref{defn:wCNF}). Шаг инициализации в алгоритме теперь состоит из двух пунктов. \begin{itemize} -\item Как и раньше, с помощью продукций вида \[A \to a \text{, где } A \in N, a \in \Sigma\] -заменяем терминалы на ребрах входного графа на множества нетерминалов, из которых они выводятся. -\item Добавляем в каждую вершину петлю, помеченную множеством нетерминалов для которых в данной грамматике есть правила вида $$A \to \varepsilon\text{, где } A \in N.$$ + \item Как и раньше, с помощью продукций вида $A \to a$, где $A \in N, a \in \Sigma$ заменяем терминалы на ребрах входного графа на множества нетерминалов, из которых они выводятся. + \item Добавляем в каждую вершину петлю, помеченную множеством нетерминалов для которых в данной грамматике есть правила вида $A \to \varepsilon$, где $A \in N$. \end{itemize} - Затем используем матрицу смежности получившегося графа (обозначим ее $M$) в качестве начального значения. Дальнейший ход алгоритма можно описать псевдокодом, представленным в листинге~\ref{alg:graphParseCYK}. - -\begin{algorithm}[H] - \begin{algorithmic}[1] - \caption{Алгоритм КС достижимости на основе CYK} - \label{alg:graphParseCYK} - \Function{contextFreePathQuerying}{G, $\mathcal{G}$} - - \State{$n \gets$ the number of nodes in $\mathcal{G}$} - \State{$M \gets$ the modified adjacency matrix of $\mathcal{G}$} - \State{$P \gets$ the set of production rules in $G$} - \While{$M$ is changing} - \For {$k \in 0..n$} - \For {$i \in 0..n$} - \For {$j \in 0..n$} - \ForAll {$N_1 \in M[i, k]$, $N_2 \in M[k, j]$} - \If {$N_3 \to N_1 N_2 \in P$ } - \State{$M[i, j] \mathrel{+}= \{N_3\}$} - \EndIf - \EndFor - \EndFor - \EndFor - \EndFor - \EndWhile - \State \Return $M$ - \EndFunction - \end{algorithmic} -\end{algorithm} - -После завершения алгоритма, если в некоторой ячейке результируюшей матрицы с номером $(i, j)$ находятся стартовый нетерминал, то это означает, что существует путь из вершины $i$ в вершину $j$, удовлетворяющий данной грамматике. Таким образом, полученная матрица является ответом для задачи достижимости для заданных графа и грамматики. +Затем используем матрицу смежности получившегося графа (обозначим ее $M$) в качестве начального значения. +Дальнейший ход алгоритма можно описать псевдокодом, представленным в листинге~\ref{alg:graphParseCYK}. + +%% FIXME: Переверстать алгоритм +% \begin{algorithm}[H] +% \begin{algorithmic}[1] +% \caption{Алгоритм КС достижимости на основе CYK} +% \label{alg:graphParseCYK} +% \Function{contextFreePathQuerying}{G, $\mathcal{G}$} + +% \State{$n \gets$ the number of nodes in $\mathcal{G}$} +% \State{$M \gets$ the modified adjacency matrix of $\mathcal{G}$} +% \State{$P \gets$ the set of production rules in $G$} +% \While{$M$ is changing} +% \For {$k \in 0..n$} +% \For {$i \in 0..n$} +% \For {$j \in 0..n$} +% \ForAll {$N_1 \in M[i, k]$, $N_2 \in M[k, j]$} +% \If {$N_3 \to N_1 N_2 \in P$ } +% \State{$M[i, j] \mathrel{+}= \{N_3\}$} +% \EndIf +% \EndFor +% \EndFor +% \EndFor +% \EndFor +% \EndWhile +% \State \Return $M$ +% \EndFunction +% \end{algorithmic} +% \end{algorithm} + +После завершения алгоритма, если в некоторой ячейке результируюшей матрицы с номером $(i, j)$ находятся стартовый нетерминал, то это означает, что существует путь из вершины $i$ в вершину $j$, удовлетворяющий данной грамматике. +Таким образом, полученная матрица является ответом для задачи достижимости для заданных графа и грамматики. \begin{example} -\label{CYK_algorithm_ex} -Рассмотрим работу алгоритма на графе - -\begin{center} - \input{figures/graph/graph0.tex} -\end{center} - -и грамматике: - -\begin{align*} -S & \to A B & A & \to a \\ -S & \to A S_1 & B & \to b\\ -S_1 & \to S B &&\\ -\end{align*} - -Данный пример является классическим и еще не раз будет использоваться в рамках данного курса. \\ - -\textbf{Инициализация.} -Заменяем терминалы на ребрах графа на нетерминалы, из которых они выводятся, и строим матрицу смежности получившегося графа: - -\begin{center} - \input{figures/cyk/graph1.tex} -\end{center} - -\[ -\begin{pmatrix} -\varnothing & \{A\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \{A\} & \varnothing \\ -\{A\} & \varnothing & \varnothing & \{B\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -\textbf{Итерация 1.} -Итерируемся по $k$, $i$ и $j$, пытаясь найти пары нетерминалов, для которых существуют правила вывода, их выводящие. Нам интересны следующие случаи: - -\begin{itemize} - \item $k = 2, i = 1, j = 3: A \in M[1, 2], B \in M[2, 3]$, так как в грамматике присутствует правило $S \to A B$, добавляем нетерминал $S$ в ячейку $M[1, 3]$. - \item $k = 3, i = 1, j = 2: S \in M[1, 3], B \in M[3, 2]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[1, 2]$. -\end{itemize} - -В остальных случаях либо какая-то из клеток пуста, либо не существует продукции в грамматике, выводящей данные два нетерминала. - -Матрица после данной итерации: - -\[ -\begin{pmatrix} -\varnothing & \{A\} & \varnothing & \varnothing \\ -\varnothing & \varnothing & \cellcolor{lightgray}\{A, \boldsymbol{S_1}\} & \cellcolor{lightgray}\{S\} \\ -\{A\} & \varnothing & \varnothing & \{B\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -\textbf{Итерация 2.} -Снова итерируемся по $k$, $i$, $j$. Рассмотрим случаи: - -\begin{itemize} - \setlength\itemsep{1em} - \item $k = 1, i = 0, j = 2: A \in M[0, 1], S_1 \in M[1, 2]$, так как в грамматике присутствует правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[0, 2]$. - \item $k = 2, i = 0, j = 3: S \in M[0, 2], B \in M[2, 3]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[0, 3]$. -\end{itemize} - -Матрица на данном шаге: - -\[ -\begin{pmatrix} -\varnothing & \{A\} & \cellcolor{lightgray}\{S\} & \cellcolor{lightgray}\{S_1\} \\ -\varnothing & \varnothing & \{A, S_1\} & \{S\} \\ -\{A\} & \varnothing & \varnothing & \{B\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -\textbf{Итерация 3.} -Рассматриваемые на данном шаге случаи: - -\begin{itemize} - \setlength\itemsep{1em} - \item $k = 0, i = 2, j = 3: A \in M[2, 0], S_1 \in M[0, 3]$, так как в грамматике присутствует правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[2, 3]$. - \item $k = 3, i = 2, j = 2: S \in M[2, 3], B \in M[3, 2]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[2, 2]$. -\end{itemize} - -Матрица после этой итерации: - -\[ -\begin{pmatrix} -\varnothing & \{A\} & \{S\} & \{S_1\} \\ -\varnothing & \varnothing & \{A, S_1\} & \{S\} \\ -\{A\} & \varnothing & \cellcolor{lightgray}\{S_1\} & \cellcolor{lightgray}\{B, \boldsymbol{S}\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -\textbf{Итерация 4.} -Рассмариваемые случаи: - -\begin{itemize} - \setlength\itemsep{1em} - \item $k = 2, i = 1, j = 2: A \in M[1, 2], S_1 \in M[2, 2]$, так как в грамматике присутствует правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[1, 2]$. - \item $k = 2, i = 1, j = 3: S \in M[1, 2], B \in M[2, 3]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[1, 3]$. -\end{itemize} - -Матрица: - -\[ -\begin{pmatrix} -\varnothing & \{A\} & \{S\} & \{S_1\} \\ -\varnothing & \varnothing & \cellcolor{lightgray}\{A, \boldsymbol{S}, S_1\} & \cellcolor{lightgray}\{S, \boldsymbol{S_1}\} \\ -\{A\} & \varnothing & \{S_1\} & \{B, S\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -\textbf{Итерация 5.} -Рассмотрим на это шаге: - -\begin{itemize} - \setlength\itemsep{1em} - \item $k = 1, i = 0, j = 3: A \in M[0, 1], S_1 \in M[1, 3]$, поскольку в грамматике есть правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[0, 3]$. - \item $k = 3, i = 0, j = 2: S \in M[0, 3], B \in M[3, 2]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[0, 2]$. -\end{itemize} - -Матрица на этой итерации: -\[ -\begin{pmatrix} -\varnothing & \{A\} & \cellcolor{lightgray}\{S, \boldsymbol{S_1}\} & \cellcolor{lightgray}\{\boldsymbol{S}, S_1\} \\ -\varnothing & \varnothing & \{A, S, S_1\} & \{S, S_1\} \\ -\{A\} & \varnothing & \{S_1\} & \{B, S\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -\textbf{Итерация 6.} -Интересующие нас на этом шаге случаи: - -\begin{itemize} - \setlength\itemsep{1em} - \item $k = 0, i = 2, j = 2: A \in M[2, 0], S_1 \in M[0, 2]$, поскольку в грамматике есть правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[2, 2]$. - \item $k = 2, i = 2, j = 3: S \in M[2, 2], B \in M[2, 3]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[2, 3]$. -\end{itemize} - -Матрица после данного шага: - -\[ -\begin{pmatrix} -\varnothing & \{A\} & \{S, S_1\} & \{S, S_1\} \\ -\varnothing & \varnothing & \{A, S, S_1\} & \{S, S_1\} \\ -\{A\} & \varnothing & \cellcolor{lightgray}\{\boldsymbol{S}, S_1\} & \cellcolor{lightgray}\{B, S, \boldsymbol{S_1}\} \\ -\varnothing & \varnothing & \{B\} & \varnothing \\ -\end{pmatrix} -\] - -На следующей итерации матрица не изменяется, поэтому заканчиваем работу алгоритма. В результате, если ячейка $M[i, j]$ содержит стартовый нетерминал $S$, то существует путь из $i$ в $j$, удовлетворяющий ограничениям, заданным грамматикой. + \label{CYK_algorithm_ex} + Рассмотрим работу алгоритма на графе + + \begin{center} + \input{figures/graph/graph0.tex} + \end{center} + + и грамматике: + \begin{align*} + S & \to A B & A & \to a \\ + S & \to A S_1 & B & \to b \\ + S_1 & \to S B & & + \end{align*} + + Данный пример является классическим и еще не раз будет использоваться в рамках данного курса. + \marginnote{TODO: Явно уже использовался\\ + TODO: Здесь тоже наверное на подразделы лучше} + + \textbf{Инициализация.} + Заменяем терминалы на ребрах графа на нетерминалы, из которых они выводятся, и строим матрицу смежности получившегося графа: + + \begin{center} + \input{figures/cyk/graph1.tex} + \end{center} + + \[ + \begin{pmatrix} + \varnothing & \{A\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \{A\} & \varnothing \\ + \{A\} & \varnothing & \varnothing & \{B\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + \textbf{Итерация 1.} + Итерируемся по $k$, $i$ и $j$, пытаясь найти пары нетерминалов, для которых существуют правила вывода, их выводящие. + Нам интересны следующие случаи: + \begin{itemize} + \item $k = 2$, $i = 1$, $j = 3$: $A \in M[1, 2]$, $B \in M[2, 3]$, так как в грамматике присутствует правило $S \to A B$, добавляем нетерминал $S$ в ячейку $M[1, 3]$. + \item $k = 3$, $i = 1$, $j = 2$: $S \in M[1, 3]$, $B \in M[3, 2]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[1, 2]$. + \end{itemize} + + В остальных случаях либо какая-то из клеток пуста, либо не существует продукции в грамматике, выводящей данные два нетерминала. + + Матрица после данной итерации: + \[ + \begin{pmatrix} + \varnothing & \{A\} & \varnothing & \varnothing \\ + \varnothing & \varnothing & \cellcolor{lightgray}\{A, \symbf{S_1}\} & \cellcolor{lightgray}\{S\} \\ + \{A\} & \varnothing & \varnothing & \{B\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + \textbf{Итерация 2.} + Снова итерируемся по $k$, $i$, $j$. + Рассмотрим случаи: + \begin{itemize} + \item $k = 1$, $i = 0$, $j = 2$: $A \in M[0, 1]$, $S_1 \in M[1, 2]$, так как в грамматике присутствует правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[0, 2]$. + \item $k = 2$, $i = 0$, $j = 3$: $S \in M[0, 2]$, $B \in M[2, 3]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[0, 3]$. + \end{itemize} + + Матрица на данном шаге: + \[ + \begin{pmatrix} + \varnothing & \{A\} & \cellcolor{lightgray}\{S\} & \cellcolor{lightgray}\{S_1\} \\ + \varnothing & \varnothing & \{A, S_1\} & \{S\} \\ + \{A\} & \varnothing & \varnothing & \{B\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + \textbf{Итерация 3.} + Рассматриваемые на данном шаге случаи: + \begin{itemize} + \item $k = 0$, $i = 2$, $j = 3$: $A \in M[2, 0]$, $S_1 \in M[0, 3]$, так как в грамматике присутствует правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[2, 3]$. + \item $k = 3$, $i = 2$, $j = 2$: $S \in M[2, 3]$, $B \in M[3, 2]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[2, 2]$. + \end{itemize} + + Матрица после этой итерации: + \marginnote{TODO: Жирный и серый сливаются} + \[ + \begin{pmatrix} + \varnothing & \{A\} & \{S\} & \{S_1\} \\ + \varnothing & \varnothing & \{A, S_1\} & \{S\} \\ + \{A\} & \varnothing & \cellcolor{lightgray}\{S_1\} & \cellcolor{lightgray}\{B, \symbf{S}\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + \textbf{Итерация 4.} + Рассматриваемые случаи: + \begin{itemize} + \item $k = 2$, $i = 1$, $j = 2$: $A \in M[1, 2]$, $S_1 \in M[2, 2]$, так как в грамматике присутствует правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[1, 2]$. + \item $k = 2$, $i = 1$, $j = 3$: $S \in M[1, 2]$, $B \in M[2, 3]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[1, 3]$. + \end{itemize} + + Матрица: + \[ + \begin{pmatrix} + \varnothing & \{A\} & \{S\} & \{S_1\} \\ + \varnothing & \varnothing & \cellcolor{lightgray}\{A, \symbf{S}, S_1\} & \cellcolor{lightgray}\{S, \symbf{S_1}\} \\ + \{A\} & \varnothing & \{S_1\} & \{B, S\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + \textbf{Итерация 5.} + Рассмотрим на это шаге: + \begin{itemize} + \item $k = 1$, $i = 0$, $j = 3$: $A \in M[0, 1]$, $S_1 \in M[1, 3]$, поскольку в грамматике есть правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[0, 3]$. + \item $k = 3$, $i = 0$, $j = 2$: $S \in M[0, 3]$, $B \in M[3, 2]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[0, 2]$. + \end{itemize} + + Матрица на этой итерации: + \[ + \begin{pmatrix} + \varnothing & \{A\} & \cellcolor{lightgray}\{S, \symbf{S_1}\} & \cellcolor{lightgray}\{\symbf{S}, S_1\} \\ + \varnothing & \varnothing & \{A, S, S_1\} & \{S, S_1\} \\ + \{A\} & \varnothing & \{S_1\} & \{B, S\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + \textbf{Итерация 6.} + Интересующие нас на этом шаге случаи + \begin{itemize} + \item $k = 0$, i = 2$, j = 2$: $A \in M[2, 0]$, $S_1 \in M[0, 2]$, поскольку в грамматике есть правило $S \to A S_1$, добавляем нетерминал $S$ в ячейку $M[2, 2]$. + \item $k = 2$, $i = 2$, $j = 3$: $S \in M[2, 2]$, $B \in M[2, 3]$, поскольку в грамматике есть правило $S_1 \to S B$, добавляем нетерминал $S_1$ в ячейку $M[2, 3]$. + \end{itemize} + + Матрица после данного шага: + \[ + \begin{pmatrix} + \varnothing & \{A\} & \{S, S_1\} & \{S, S_1\} \\ + \varnothing & \varnothing & \{A, S, S_1\} & \{S, S_1\} \\ + \{A\} & \varnothing & \cellcolor{lightgray}\{\symbf{S}, S_1\} & \cellcolor{lightgray}\{B, S, \symbf{S_1}\} \\ + \varnothing & \varnothing & \{B\} & \varnothing + \end{pmatrix} + \] + + На следующей итерации матрица не изменяется, поэтому заканчиваем работу алгоритма. + В результате, если ячейка $M[i, j]$ содержит стартовый нетерминал $S$, то существует путь из $i$ в $j$, удовлетворяющий ограничениям, заданным грамматикой. \end{example} Можно заметить, что мы делаем много лишних итераций. Можно переписать алгоритм так, чтобы он не просматривал заведомо пустые ячейки. -Данную модификацию предложил Й.Хеллингс в работе~\cite{hellingsRelational}, также она реализована в работе~\cite{10.1007/978-3-319-46523-4_38}. +Данную модификацию предложил Й.Хеллингс в работе~\sidecite{hellingsRelational}, также она реализована в работе~\sidecite{10.1007/978-3-319-46523-4_38}. Псевдокод алгоритма Хеллингса представлен в листинге~\ref{alg:graphParseHellings}. -\begin{algorithm}[H] - \begin{algorithmic}[1] - \caption{Алгоритм Хеллингса} - \label{alg:graphParseHellings} - \Function{contextFreePathQuerying}{$G= \langle \Sigma, N, P, S \rangle$, $\mathcal{G} = \langle V,E,L \rangle$} - - \State{$r \gets \{(N_i,v,v) \mid v \in V \wedge N_i \to \varepsilon \in P \} \cup \{(N_i,v,u) \mid (v,t,u) \in E \wedge N_i \to t \in P \}$} - \State{$m \gets r$} - \While{$m \neq \varnothing$} - \State{$(N_i,v,u) \gets$ m.pick()} - \For {$(N_j,v',v) \in r$} - \For {$N_k \to N_j N_i \in P$ таких что $((N_k, v',u) \notin r)$} - \State{$m \gets m \cup \{(N_k, v',u)\}$} - \State{$r \gets r \cup \{(N_k, v',u)\}$} - \EndFor - \EndFor - \For {$(N_j,u,v') \in r$} - \For {$N_k \to N_i N_j \in P$ таких что $((N_k, v, v') \notin r)$} - \State{$m \gets m \cup \{(N_k, v, v')\}$} - \State{$r \gets r \cup \{(N_k, v, v')\}$} - \EndFor - \EndFor - - \EndWhile - \State \Return $r$ - \EndFunction - \end{algorithmic} -\end{algorithm} +%% FIXME: Переверстать алгоритм +% \begin{algorithm}[H] +% \begin{algorithmic}[1] +% \caption{Алгоритм Хеллингса} +% \label{alg:graphParseHellings} +% \Function{contextFreePathQuerying}{$G= \langle \Sigma, N, P, S \rangle$, $\mathcal{G} = \langle V,E,L \rangle$} + +% \State{$r \gets \{(N_i,v,v) \mid v \in V \wedge N_i \to \varepsilon \in P \} \cup \{(N_i,v,u) \mid (v,t,u) \in E \wedge N_i \to t \in P \}$} +% \State{$m \gets r$} +% \While{$m \neq \varnothing$} +% \State{$(N_i,v,u) \gets$ m.pick()} +% \For {$(N_j,v',v) \in r$} +% \For {$N_k \to N_j N_i \in P$ таких что $((N_k, v',u) \notin r)$} +% \State{$m \gets m \cup \{(N_k, v',u)\}$} +% \State{$r \gets r \cup \{(N_k, v',u)\}$} +% \EndFor +% \EndFor +% \For {$(N_j,u,v') \in r$} +% \For {$N_k \to N_i N_j \in P$ таких что $((N_k, v, v') \notin r)$} +% \State{$m \gets m \cup \{(N_k, v, v')\}$} +% \State{$r \gets r \cup \{(N_k, v, v')\}$} +% \EndFor +% \EndFor + +% \EndWhile +% \State \Return $r$ +% \EndFunction +% \end{algorithmic} +% \end{algorithm} \begin{example} - Запустим алгоритм Хеллингса на нашем примере. - - \textbf{Инициализация} - $$ - m = r = \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2)\} - $$ - - \textbf{Итерации внешнего цикла.} Будем считеть, что $r$ и $m$ --- упорядоченные списки и $pick$ возвращает его голову, оставляя хвост. - Новые элементы добавляются в конец. - \begin{enumerate} - \item Обрабатываем $(A,0,1)$. - Ни один из вложенных циклов не найдёт новых путей, так как для рассматриваемого ребра есть только две возможности достроить путь: $2 \xrightarrow{A} 0 \xrightarrow{A} 1$ и $0 \xrightarrow{A} 1 \xrightarrow{A} 2$ - и ни одна из соответствующих строк не выводтся в заданной грамматике. - \item Перед началом итерации - $$ - m = \{(A,1,2),(A,2,0),(B,2,3),(B,3,2)\}, - $$ $r$ не изменилось. - Обрабатываем $(A,1,2)$. - В данной ситуации второй цикл найдёт тройку $(B,2,3)$ и соответсвующее правило $S \to A \ B$. - Это значит, что и в $m$ и в $r$ добавится тройка $(S, 1, 3)$. - \item - Перед началом итерации - $$ - m = \{(A,2,0),(B,2,3),(B,3,2),(S,1,3)\}, - $$ - $$ - r= \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3)\}. - $$ - Обрабатываем $(A,2,0)$. - Внутринние циклы ничего не найдут, новых путей н появится. - \item - Перед началом итерации - $$ - m = \{(B,2,3),(B,3,2),(S,1,3)\}, - $$ - $$ - r= \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3)\}. - $$ - Обрабатываем $(B,2,3)$. - Первый цикл мог бы найти $(A,1,2)$, однако при проверке во вложенном цикле выяснится, что $(S, 1, 3)$ уже найдена. - В итоге, на данной итерации новых путей н появится. - \item - Перед началом итерации - $$ - m = \{(B,3,2),(S,1,3)\}, - $$ - $$ - r= \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3)\}. - $$ - Обрабатываем $(B,3,2)$. - Первый цикл найдёт $(S,1,3)$ и соответствующее правило $S_1 \to S \ B$. - Это значит, что и в $m$ и в $r$ добавится тройка $(S_1, 1, 2)$. - \item - Перед началом итерации - $$ - m = \{(S,1,3),(S_1, 1, 2)\}, - $$ - $$ - r= \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2)\}. - $$ - Обрабатываем $(S,1,3)$. - Второй цикл мог бы найти $(B,3,2)$, однако при проверке во вложенном цикле выяснится, что $(S_1, 1, 2)$ уже найдена. - В итоге, на данной итерации новых путей н появится. - \item - Перед началом итерации - $$ - m = \{(S_1, 1, 2)\}, - $$ - $$ - r= \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2)\}. - $$ - Обрабатываем $(S_1,1,2)$. - Первый цикл найдёт $(A,0,1)$ и соответствующее правило $S \to A \ S_1$. - Это значит, что и в $m$ и в $r$ добавится тройка $(S, 0, 2)$. - - \item - Перед началом итерации - $$ - m = \{(S, 0, 2)\}, - $$ - $$ - r= \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2)\}. - $$ - Обрабатываем $(S, 0, 2)$. - Найдено: $(B,2,3)$ и соответствующее правило $S_1 \to S \ B$. - B $m$ и в $r$ добавится тройка $(S_1, 0, 3)$. - - \item - Перед началом итерации - $$ - m = \{(S_1, 0, 3)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3)\}. - \end{align*} - Обрабатываем $(S_1, 0, 3)$. - Найдено: $(A,2,0)$ и соответствующее правило $S \to A \ S_1$. - B $m$ и в $r$ добавится тройка $(S, 2, 3)$. - - \item - Перед началом итерации - $$ - m = \{(S, 2, 3)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3)\}. - \end{align*} - - Обрабатываем $(S, 2, 3)$. - Найдено: $(B,3,2)$ и соответствующее правило $S_1 \to S \ B$. - B $m$ и в $r$ добавится тройка $(S_1, 2, 2)$. - - \item - Перед началом итерации - $$ - m = \{(S_1, 2, 2)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2)\}. - \end{align*} - Обрабатываем $(S_1, 2, 2)$. - Найдено: $(A,1,2)$ и соответствующее правило $S \to A \ S_1$. - B $m$ и в $r$ добавится тройка $(S, 1, 2)$. - - \item - Перед началом итерации - $$ - m = \{(S, 1, 2)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2),(S, 1, 2)\}. - \end{align*} - Обрабатываем $(S, 1, 2)$. - Найдено: $(B,2,3)$ и соответствующее правило $S_1 \to S \ B$. - B $m$ и в $r$ добавится тройка $(S_1, 1, 3)$. - - \item - Перед началом итерации - $$ - m = \{(S_1, 1, 3)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2),(S, 1, 2),(S_1, 1, 3)\}. - \end{align*} - Обрабатываем $(S_1, 1, 3)$. - Найдено: $(A,0,1)$ и соответствующее правило $S \to A \ S_1$. - B $m$ и в $r$ добавится тройка $(S, 0, 3)$. - - \item - Перед началом итерации - $$ - m = \{(S, 0, 3)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2),(S, 1, 2),(S_1, 1, 3),(S, 0, 3)\}. - \end{align*} - Обрабатываем $(S, 0, 3)$. - Найдено: $(B,3,2)$ и соответствующее правило $S_1 \to S \ B$. - B $m$ и в $r$ добавится тройка $(S_1, 0, 2)$. - - \item - Перед началом итерации - $$ - m = \{(S_1, 0, 2)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2),(S, 1, 2),(S_1, 1, 3),(S, 0, 3),(S_1, 0, 2)\}. - \end{align*} - Обрабатываем $(S_1, 0, 2)$. - Найдено: $(A,2,0)$ и соответствующее правило $S \to A \ S_1$. - B $m$ и в $r$ добавится тройка $(S, 2, 2)$. - - \item - Перед началом итерации - $$ - m = \{(S, 2, 2)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2),(S, 1, 2),(S_1, 1, 3),(S, 0, 3),(S_1, 0, 2),\\ - &(S, 2, 2)\}. - \end{align*} - Обрабатываем $(S, 2, 2)$. - Найдено: $(B,2,3)$ и соответствующее правило $S_1 \to S \ B$. - B $m$ и в $r$ добавится тройка $(S_1, 2, 3)$. - - \item - Перед началом итерации - $$ - m = \{(S_1, 2, 3)\}, - $$ - \begin{align*} - r= \{&(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2),(S,1,3),(S_1, 1, 2),(S, 0, 2),\\ - &(S_1, 0, 3),(S, 2, 3),(S_1, 2, 2),(S, 1, 2),(S_1, 1, 3),(S, 0, 3),(S_1, 0, 2),\\ - &(S, 2, 2),(S_1, 2, 3)\}. - \end{align*} - Обрабатываем $(S_1, 2, 3)$. - Могло бы быть найдено: $(A,1,2)$ и соответствующее правило $S \to A \ S_1$, однако тройка $(S, 1, 3)$ уже есть в $r$. - А значит никаких новых троек найдено не будет и $m$ становится пустым. - Это была последняя итерация внешнего цикла, в $r$ на текущий момент уже содержится всё ршение. - - \end{enumerate} - + Запустим алгоритм Хеллингса на нашем примере. + + \textbf{Инициализация} + \[ m = r = \{(A,0,1),(A,1,2),(A,2,0),(B,2,3),(B,3,2)\} \] + + \textbf{Итерации внешнего цикла.} + Будем считеть, что $r$ и $m$~--- упорядоченные списки и $pick$ возвращает его голову, оставляя хвост. + \marginnote{TODO: pick по сути не используется} + Новые элементы добавляются в конец. + \begin{enumerate} + \item Обрабатываем $(A, 0, 1)$. + Ни один из вложенных циклов не найдёт новых путей, так как для рассматриваемого ребра есть только две возможности достроить путь: $2 \xrightarrow{A} 0 \xrightarrow{A} 1$ и $0 \xrightarrow{A} 1 \xrightarrow{A} 2$ + и ни одна из соответствующих строк не выводтся в заданной грамматике. + \item Перед началом итерации + \[ + m = \{(A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2)\}, + \] + $r$ не изменилось. + Обрабатываем $(A, 1, 2)$. + В данной ситуации второй цикл найдёт тройку $(B, 2, 3)$ и соответствующее правило $S \to A B$. + Это значит, что и в $m$ и в $r$ добавится тройка $(S, 1, 3)$. + \item Перед началом итерации + \begin{gather*} + m = \{(A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3)\}, \\ + r = \{(A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3)\}. + \end{gather*} + Обрабатываем $(A, 2, 0)$. + Внутренние циклы ничего не найдут, новых путей не появится. + \item Перед началом итерации + \begin{gather*} + m = \{(B, 2, 3), (B, 3, 2), (S, 1, 3)\},\\ + r = \{(A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3)\}. + \end{gather*} + Обрабатываем $(B, 2, 3)$. + Первый цикл мог бы найти $(A, 1, 2)$, однако при проверке во вложенном цикле выяснится, что $(S, 1, 3)$ уже найдена. + В итоге, на данной итерации новых путей не появится. + \item Перед началом итерации + \begin{gather*} + m = \{(B, 3, 2), (S, 1, 3)\},\\ + r = \{(A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3)\}. + \end{gather*} + Обрабатываем $(B, 3, 2)$. + Первый цикл найдёт $(S, 1, 3)$ и соответствующее правило $S_1 \to S B$. + Это значит, что и в $m$ и в $r$ добавится тройка $(S_1, 1, 2)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S, 1, 3), (S_1, 1, 2)\},\\ + r = \{(A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2)\}. + \end{gather*} + Обрабатываем $(S, 1, 3)$. + Второй цикл мог бы найти $(B, 3, 2)$, однако при проверке во вложенном цикле выяснится, что $(S_1, 1, 2)$ уже найдена. + В итоге, на данной итерации новых путей не появится. + \item Перед началом итерации + \begin{gather*} + m = \{(S_1, 1, 2)\}, \\ + r = \{(A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2)\}. + \end{gather*} + Обрабатываем $(S_1, 1, 2)$. + Первый цикл найдёт $(A, 0, 1)$ и соответствующее правило $S \to A S_1$. + Это значит, что и в $m$ и в $r$ добавится тройка $(S, 0, 2)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S, 0, 2)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S, 0, 2)$. + Найдено: $(B, 2, 3)$ и соответствующее правило $S_1 \to S B$. + B $m$ и в $r$ добавится тройка $(S_1, 0, 3)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S_1, 0, 3)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S_1, 0, 3)$. + Найдено: $(A, 2, 0)$ и соответствующее правило $S \to A S_1$. + B $m$ и в $r$ добавится тройка $(S, 2, 3)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S, 2, 3)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S, 2, 3)$. + Найдено: $(B, 3, 2)$ и соответствующее правило $S_1 \to S B$. + B $m$ и в $r$ добавится тройка $(S_1, 2, 2)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S_1, 2, 2)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S_1, 2, 2)$. + Найдено: $(A, 1, 2)$ и соответствующее правило $S \to A S_1$. + B $m$ и в $r$ добавится тройка $(S, 1, 2)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S, 1, 2)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2), (S, 1, 2)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S, 1, 2)$. + Найдено: $(B, 2, 3)$ и соответствующее правило $S_1 \to S B$. + B $m$ и в $r$ добавится тройка $(S_1, 1, 3)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S_1, 1, 3)\},\\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2), (S, 1, 2), (S_1, 1, 3)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S_1, 1, 3)$. + Найдено: $(A, 0, 1)$ и соответствующее правило $S \to A S_1$. + B $m$ и в $r$ добавится тройка $(S, 0, 3)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S, 0, 3)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2), (S, 1, 2), (S_1, 1, 3), (S, 0, 3)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S, 0, 3)$. + Найдено: $(B, 3, 2)$ и соответствующее правило $S_1 \to S B$. + B $m$ и в $r$ добавится тройка $(S_1, 0, 2)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S_1, 0, 2)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2), (S, 1, 2), (S_1, 1, 3), (S, 0, 3) \\ + & (S_1, 0, 2)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S_1, 0, 2)$. + Найдено: $(A, 2, 0)$ и соответствующее правило $S \to A S_1$. + B $m$ и в $r$ добавится тройка $(S, 2, 2)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S, 2, 2)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2), (S, 1, 2), (S_1, 1, 3), (S, 0, 3) \\ + & (S_1, 0, 2), (S, 2, 2)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S, 2, 2)$. + Найдено: $(B, 2, 3)$ и соответствующее правило $S_1 \to S B$. + B $m$ и в $r$ добавится тройка $(S_1, 2, 3)$. + \item Перед началом итерации + \begin{gather*} + m = \{(S_1, 2, 3)\}, \\ + \begin{aligned} + r = \{ & (A, 0, 1), (A, 1, 2), (A, 2, 0), (B, 2, 3), (B, 3, 2), (S, 1, 3), (S_1, 1, 2), \\ + & (S, 0, 2), (S_1, 0, 3), (S, 2, 3), (S_1, 2, 2), (S, 1, 2), (S_1, 1, 3), (S, 0, 3) \\ + & (S_1, 0, 2), (S, 2, 2), (S_1, 2, 3)\}. + \end{aligned} + \end{gather*} + Обрабатываем $(S_1, 2, 3)$. + Могло бы быть найдено: $(A,1,2)$ и соответствующее правило $S \to A S_1$, однако тройка $(S, 1, 3)$ уже есть в $r$. + А значит никаких новых троек найдено не будет и $m$ становится пустым. + Это была последняя итерация внешнего цикла, в $r$ на текущий момент уже содержится всё решение. + \end{enumerate} \end{example} Как можно заметить, количество итераций внешнего цикла также получилось достаточно большим. Проверьте, зависит ли оно от порядка обработки элементов из $m$. -При этом внутренние циклы в нашем случае достаточно короткие, так как просматриваются только ``существенные'' элементы и избегается дублирование. +При этом внутренние циклы в нашем случае достаточно короткие, так как просматриваются только \enquote{существенные} элементы и избегается дублирование. %\section{Вопросы и задачи} %\begin{enumerate} diff --git a/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex b/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex index fc204b2..e12725a 100644 --- a/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex +++ b/tex/FormalLanguageConstrainedReachabilityLectureNotes.tex @@ -53,7 +53,7 @@ \input{FLPQ} \input{RPQ} % %\input{CFPQ} -% \input{CYK_for_CFPQ} +\input{CYK_for_CFPQ} % \input{Matrix-based_CFPQ} % \input{TensorProduct} % \input{SPPF}