В информатике алгоритм Кока–Янгера–Касами (также называемый CYK или CKY ) — это алгоритм синтаксического анализа для контекстно-свободных грамматик, опубликованный Итироо Сакаи в 1961 году. [1] [2] Алгоритм назван в честь некоторых из его переоткрывателей: Джона Кока , Дэниела Янгера, Тадао Касами и Джейкоба Т. Шварца . Он использует синтаксический анализ снизу вверх и динамическое программирование .
Стандартная версия CYK работает только с контекстно-свободными грамматиками, заданными в нормальной форме Хомского (CNF). Однако любая контекстно-свободная грамматика может быть алгоритмически преобразована в грамматику CNF, выражающую тот же язык (Sipser 1997).
Важность алгоритма CYK обусловлена его высокой эффективностью в определенных ситуациях. Используя нотацию O большое , худшее время выполнения CYK равно , где — длина анализируемой строки, а — размер грамматики CNF (Hopcroft & Ullman 1979, стр. 140). Это делает его одним из самых эффективных [ требуется цитирование ] алгоритмов синтаксического анализа с точки зрения асимптотической сложности в худшем случае , хотя существуют и другие алгоритмы с лучшим средним временем выполнения во многих практических сценариях.
Алгоритм динамического программирования требует, чтобы контекстно-свободная грамматика была представлена в нормальной форме Хомского (CNF), поскольку она проверяет возможности разделения текущей последовательности на две меньшие последовательности. Любая контекстно-свободная грамматика, которая не генерирует пустую строку, может быть представлена в CNF с использованием только правил производства форм и ; чтобы разрешить пустую строку, можно явно разрешить , где — начальный символ. [3]
Алгоритм в псевдокоде выглядит следующим образом:
пусть входными данными будет строка I, состоящая из n символов: a 1 ... a n . пусть грамматика содержит r нетерминальных символов R 1 ... R r , с начальным символом R 1 . пусть P [ n , n , r ] будет массивом булевых значений. Инициализируйте все элементы P значением false. пусть back [ n , n , r ] будет массивом списков троек возврата. Инициализируйте все элементы back пустым списком.для каждого s = 1 до n для каждой единицы продукции R v → a s set P [ 1 , s , v ] = trueдля каждого l = 2 до n -- Длина промежутка для каждого s = 1 до n - l +1 -- Начало промежутка для каждого p = 1 до l -1 -- Разбиение промежутка для каждого произведения R a → R b R c если P [ p , s , b ] и P [ l - p , s + p , c ], то устанавливаем P [ l , s , a ] = true, добавить <p,b,c> к задним [ l , s , a ]если P [n, 1 , 1 ] истинно, то I является членом языка вернуться назад — повторяя шаги назад, можно легко построить все возможные деревья разбора строки. в противном случае вернуть «не является членом языка»
Позволяет восстановить наиболее вероятный синтаксический анализ с учетом вероятностей всех продукций.
пусть входными данными будет строка I, состоящая из n символов: a 1 ... a n . пусть грамматика содержит r нетерминальных символов R 1 ... R r , с начальным символом R 1 . пусть P [ n , n , r ] будет массивом действительных чисел. Инициализируем все элементы P нулем. пусть back [ n , n , r ] будет массивом троек обратной точки. для каждого s = 1 до n для каждой единицы продукции R v → as set P [ 1 , s , v ] = Pr( R v → as ) для каждого l = 2 до n -- Длина промежутка для каждого s = 1 до n - l +1 -- Начало промежутка для каждого p = 1 до l -1 -- Разбиение промежутка для каждого производства R a → R b R c prob_splitting = Pr( R a → R b R c ) * P [ p , s , b ] * P [ l - p , s + p , c ] if prob_splitting > P [ l , s , a ] then set P [ l , s , a ] = prob_splitting set back [ l , s , a ] = <p,b,c> если P [n, 1 , 1 ] > 0 , то найти дерево разбора, пройдя по нему в обратном направлении , вернуть дерево разбора, иначе вернуть «не является членом языка»
Неформально этот алгоритм рассматривает каждую возможную подстроку входной строки и устанавливает ее как истинную, если подстрока длины, начинающаяся с , может быть сгенерирована из нетерминала . После того, как он рассмотрел подстроки длины 1, он переходит к подстрокам длины 2 и так далее. Для подстрок длины 2 и больше он рассматривает каждое возможное разбиение подстроки на две части и проверяет, есть ли какая-либо продукция, которая соответствует первой части и соответствует второй части. Если это так, он записывает как соответствующую всей подстроке. После завершения этого процесса входная строка генерируется грамматикой, если подстрока, содержащая всю входную строку, соответствует начальному символу.
Вот пример грамматики:
Теперь предложение she eats a fish with a fork анализируется с помощью алгоритма CYK. В следующей таблице, в , i - это номер строки (начиная снизу с 1), а j - это номер столбца (начиная слева с 1).
Для удобства чтения таблица CYK для P представлена здесь как двумерная матрица M , содержащая набор нетерминальных символов, таких, что R k находится в тогда и только тогда, . В приведенном выше примере, поскольку начальный символ S находится в , предложение может быть сгенерировано грамматикой.
Приведенный выше алгоритм является распознавателем , который будет определять только, находится ли предложение в языке. Его просто расширить до синтаксического анализатора , который также строит дерево синтаксического анализа , сохраняя узлы дерева синтаксического анализа как элементы массива, вместо логического значения 1. Узел связан с элементами массива, которые использовались для его создания, чтобы построить структуру дерева. Если необходимо создать только одно дерево синтаксического анализа, необходим только один такой узел в каждом элементе массива. Однако, если необходимо сохранить все деревья синтаксического анализа неоднозначного предложения, необходимо сохранить в элементе массива список всех способов, которыми соответствующий узел может быть получен в процессе синтаксического анализа. Иногда это делается с помощью второй таблицы B[n,n,r] так называемых обратных указателей . Конечным результатом является общий лес возможных деревьев синтаксического анализа, где общие части деревьев факторизуются между различными синтаксическими анализами. Этот общий лес можно удобно интерпретировать как неоднозначную грамматику, генерирующую только проанализированное предложение, но с той же неоднозначностью, что и исходная грамматика, и те же деревья синтаксического анализа вплоть до очень простого переименования нетерминалов, как показано Лэнгом (1994).
Как отметили Ланге и Лейсс (2009), недостатком всех известных преобразований в нормальную форму Хомского является то, что они могут привести к нежелательному раздуванию размера грамматики. Размер грамматики равен сумме размеров ее правил производства, где размер правила равен единице плюс длина его правой части. Используя для обозначения размера исходной грамматики, раздувание размера в худшем случае может варьироваться от до , в зависимости от используемого алгоритма преобразования. Для использования в обучении Ланге и Лейсс предлагают небольшое обобщение алгоритма CYK, «без ущерба для эффективности алгоритма, ясности его представления или простоты доказательств» (Ланге и Лейсс 2009).
Также возможно расширить алгоритм CYK для разбора строк с использованием взвешенных и стохастических контекстно-свободных грамматик . Веса (вероятности) затем хранятся в таблице P вместо булевых значений, поэтому P[i,j,A] будет содержать минимальный вес (максимальную вероятность) того, что подстрока от i до j может быть получена из A. Дальнейшие расширения алгоритма позволяют перечислять все разборы строки от наименьшего к наибольшему весу (от наибольшей к наименьшей вероятности).
Когда вероятностный алгоритм CYK применяется к длинной строке, вероятность разделения может стать очень маленькой из-за умножения многих вероятностей. С этим можно справиться, суммируя логарифмическую вероятность вместо умножения вероятностей.
Худшее время выполнения CYK составляет , где n — длина анализируемой строки, а | G | — размер грамматики CNF G . Это делает его одним из самых эффективных алгоритмов для распознавания общих контекстно-свободных языков на практике. Валиант (1975) дал расширение алгоритма CYK. Его алгоритм вычисляет ту же таблицу синтаксического анализа, что и алгоритм CYK; однако он показал, что алгоритмы для эффективного умножения матриц с 0-1-элементами могут быть использованы для выполнения этого вычисления.
Используя алгоритм Копперсмита–Винограда для умножения этих матриц, это дает асимптотическое худшее время выполнения . Однако постоянный член, скрытый нотацией Big O, настолько велик, что алгоритм Копперсмита–Винограда имеет смысл только для матриц, которые слишком велики для обработки на современных компьютерах (Knuth 1997), и этот подход требует вычитания и поэтому подходит только для распознавания. Зависимости от эффективного умножения матриц нельзя полностью избежать: Ли (2002) доказал, что любой синтаксический анализатор для контекстно-свободных грамматик, работающий во времени, может быть эффективно преобразован в алгоритм, вычисляющий произведение -матриц с 0-1-элементами за время , и это было расширено Аббудом и др. [4] для применения к грамматике постоянного размера.