Суффикс-автомат - Suffix automaton

Суффикс-автомат
Суффикс-автомат bold.svg
ТипИндекс подстроки
Изобрел1983
ИзобретенныйАнсельм Блумер; Джанет Блумер; Анджей Эренфойхт; Дэвид Хаусслер; Росс МакКоннелл
Сложность времени в нотация большой O
АлгоритмСреднийХудший случай
Космос

В Информатика, а суффиксный автомат эффективный структура данных для представления индекс подстроки заданной строки, которая позволяет хранить, обрабатывать и извлекать сжатую информацию обо всех ее подстроки. Суффиксный автомат строки самый маленький ориентированный ациклический граф с выделенной начальной вершиной и набором «конечных» вершин, так что пути от начальной вершины к конечным вершинам представляют суффиксы строки. Формально суффиксный автомат определяется следующим набором свойств:

  1. Его дуги помечены буквы;
  2. ни один из его узлы иметь две исходящие дуги, помеченные одной и той же буквой;
  3. для каждого суффикса существует путь от начальной вершины до некоторой конечной вершины такой, что конкатенация букв на пути образует этот суффикс;
  4. у него наименьшее количество вершин среди всех графов, определенных указанными выше свойствами.

Говоря с точки зрения теория автоматов, суффиксный автомат - это минимальный частичный детерминированный конечный автомат который распознает набор суффиксы данного нить . В граф состояний суффиксного автомата называется ориентированным ациклическим графом слов (DAWG), термин, который также иногда используется для обозначения любого детерминированный ациклический конечный автомат.

Суффикс-автоматы были представлены в 1983 году группой ученых из Денверский университет и Университет Колорадо в Боулдере. Они предложили линейное время онлайн алгоритм для его построения и показал, что суффиксный автомат строки длина не менее двух символов не более государства и самое большее переходы. Дальнейшие работы показали тесную связь между суффиксными автоматами и суффиксные деревья, и обрисовали в общих чертах несколько обобщений суффиксных автоматов, таких как сжатый суффиксный автомат, полученный сжатием узлов с одной исходящей дугой.

Суффикс-автоматы обеспечивают эффективные решения таких проблем, как поиск подстроки и вычисление наибольшая общая подстрока из двух и более струн.

История

Ансельм Блюмер с рисунком обобщенного CDAWG для струнных ababc и abcab

Понятие суффиксного автомата было введено в 1983 году.[1] группой ученых из Денверский университет и Университет Колорадо в Боулдере состоящий из Ансельма Блумера, Джанет Блумер, Анджей Эренфойхт, Дэвид Хаусслер и Росс МакКоннелл, хотя подобные концепции ранее изучались вместе с суффиксными деревьями в работах Питера Вайнера,[2] Воан Пратт[3] и Анатолий Слисенко.[4] В своей первоначальной работе Блюмер и другие. показал суффиксный автомат, построенный для строки длины больше чем имеет самое большее государства и самое большее переходов и предложил линейный алгоритм для построения автоматов.[5]

В 1983 году Му-Тиан Чен и Джоэл Сейферас независимо показали, что алгоритм построения суффиксного дерева Вейнера 1973 года[2] при построении суффиксного дерева строки строит суффиксный автомат перевернутой строки как вспомогательная конструкция.[6] В 1987 году Блюмер и другие. применил технику сжатия, используемую в суффиксных деревьях, к суффиксному автомату и изобрел сжатый суффиксный автомат, который также называют сжатым направленным ациклическим графом слов (CDAWG).[7] В 1997 г. Максим Крочмор и Рено Верен разработали линейный алгоритм для прямого построения CDAWG.[1] В 2001 году Сюнсуке Иненага и другие. разработал алгоритм построения CDAWG для набора слов, заданных три.[8]

Определения

Обычно, говоря о суффиксных автоматах и ​​связанных с ними понятиях, некоторые понятия из формальная теория языка и теория автоматов используются, в частности:[9]

  • "Алфавит" конечный набор который используется для построения слов. Его элементы называются «персонажами»;
  • "Слово" конечная последовательность символов . «Длина» слова обозначается как ;
  • "Формальный язык "- набор слов в заданном алфавите;
  • «Язык всех слов» обозначается как (где символ "*" означает Клини звезда ), «пустое слово» (слово нулевой длины) обозначается символом ;
  • "Конкатенация слов " и обозначается как или же и соответствует слову, полученному записью справа от , то есть, ;
  • «Конкатенация языков» и обозначается как или же и соответствует набору попарных конкатенаций ;
  • Если слово может быть представлен как , куда , затем слова , и называются "префикс", "суффикс" и "подслово "(подстрока) слова соответственно;
  • Если тогда говорят, что "происходит" в как подслово. Здесь и называются левая и правая позиции вхождения в соответственно.

Структура автомата

Формально, детерминированный конечный автомат определяется 5-кратный , куда:[10]

  • это «алфавит», который используется для построения слов,
  • это набор автоматов "состояния ",
  • - «начальное» состояние автомата,
  • набор «конечных» состояний автомата,
  • это частичный «переходная» функция автомата, такая, что за и либо не определено, либо определяет переход от над характером .

Чаще всего детерминированный конечный автомат представляется в виде ориентированный граф («диаграмма») такая, что:[10]

  • Набор графа вершины соответствует состоянию состояний ,
  • График имеет конкретную отмеченную вершину, соответствующую начальному состоянию ,
  • График имеет несколько отмеченных вершин, соответствующих множеству конечных состояний ,
  • Набор графика дуги соответствует набору переходов ,
  • В частности, каждый переход представлен дугой из к отмечен символом . Этот переход также можно обозначить как .

По диаграмме автомат распознает слово только если есть путь из начальной вершины в какую-то последнюю вершину так что объединение символов на этом пути образует . Набор слов, распознаваемых автоматом, образует язык, который должен распознаваться автоматом. В этих терминах язык, распознаваемый суффиксным автоматом это язык его (возможно, пустых) суффиксов.[9]

Состояния автоматов

«Правильный контекст» слова относительно языка это набор это набор слов так что их соединение с образует слово из . Правильный контекст вызывает естественное отношение эквивалентности по набору всех слов. Если язык распознается некоторым детерминированным конечным автоматом, существует единственное с точностью до изоморфизм автомат, который распознает тот же язык и имеет минимально возможное количество состояний. Такой автомат называется минимальный автомат для данного языка . Теорема Майхилла – Нероде позволяет ему явно определять его с точки зрения правильного контекста:[11][12]

Теорема — Минимальный язык распознавания автоматов по алфавиту может быть явно определен следующим образом:

  • Алфавит остается такой же,
  • состояния соответствовать правильному контексту из всех возможных слов ,
  • Начальное состояние соответствует правильному контексту пустого слова ,
  • Конечные состояния соответствовать правильному контексту слов из ,
  • Переходы даны , куда и .

В этих терминах «суффиксный автомат» - это минимальный детерминированный конечный автомат, распознающий язык суффиксов слова . Правильный контекст слова по отношению к этому языку состоит из слов , так что суффикс . Это позволяет сформулировать следующую лемму, определяющую биекция между правым контекстом слова и набором правильных позиций его вхождений в :[13][14]

Теорема — Позволять - набор правильных позиций вхождений в .

Между и :

  • Если , тогда ;
  • Если , тогда .

Например, для слова и его подслово , он держит и . Неофициально состоит из слов, следующих за вхождениями до конца и формируется правильными позициями этих вхождений. В этом примере элемент соответствует слову в то время как слово соответствует элементу .

Из него следует несколько структурных свойств суффиксных состояний автомата. Позволять , тогда:[14]

  • Если и иметь хотя бы один общий элемент , тогда и также имеют общий элемент. Это подразумевает суффикс и поэтому и . В вышеупомянутом примере , так суффикс и поэтому и ;
  • Если , тогда , таким образом происходит в только как суффикс . Например, для и он считает, что и ;
  • Если и суффикс такой, что , тогда . В приведенном выше примере и это справедливо для "промежуточного" суффикса который .

Любой штат суффиксного автомата распознает некоторую непрерывную цепь вложенных суффиксов самого длинного слова, распознаваемого этим состоянием.[14]

«Левое расширение» строки самая длинная строка который имеет тот же правильный контекст, что и . Длина самой длинной строки, распознанной обозначается . Он содержит:[15]

Теорема — Левое расширение может быть представлен как , куда это самое длинное слово, такое что любое вхождение в предшествует .

«Суффиксная ссылка» государства указатель на состояние который содержит самый большой суффикс что не признается .

В этом смысле можно сказать распознает точно все суффиксы это дольше, чем и не дольше . Он также содержит:[15]

Теорема — Суффиксные ссылки образуют дерево который может быть определен явно следующим образом:

  1. Вершины дерева соответствуют левым расширениям из всех подстроки,
  2. Края дерева соединяют пары вершин , так что и .

Связь с суффиксными деревьями

Взаимосвязь дерева суффиксов, дерева суффиксов, DAWG и CDAWG

А "префиксное дерево "(или" дерево ") - корневое ориентированное дерево, в котором дуги помечены символами таким образом, чтобы не было вершин такого дерева имеет две выходящие дуги, отмеченные одним и тем же символом. Некоторые вершины в trie помечаются как конечные. Говорят, что Trie распознает набор слов, определяемых путями от его корня до конечных вершин. Таким образом, префиксные деревья представляют собой особый вид детерминированных конечных автоматов, если вы воспринимаете их корень как начальную вершину.[16] "Суффикс трие" слова - это префиксное дерево, распознающее набор своих суффиксов. "А суффиксное дерево "представляет собой дерево, полученное из дерева суффиксов с помощью процедуры сжатия, во время которой последующие ребра объединяются, если степень вершины между ними равно двум.[15]

По его определению, суффиксный автомат может быть получен через минимизация суффикса trie. Можно показать, что уплотненный суффиксный автомат получается как минимизацией суффиксного дерева (если предположить, что каждая строка на краю суффиксного дерева является сплошным символом из алфавита), так и уплотнением суффиксного автомата.[17] Помимо этой связи между суффиксным деревом и суффиксным автоматом той же строки, существует также связь между суффиксным автоматом строки и суффиксное дерево перевернутой строки .[18]

Аналогично правым контекстам можно ввести «левые контексты». , "правильные расширения" соответствует самой длинной строке, имеющей тот же левый контекст, что и и отношение эквивалентности . Если учесть правильные расширения по отношению к языку "префиксов" строки можно получить:[15]

Теорема — Суффиксное дерево строки может быть определен явно следующим образом:

  • Вершины дерева соответствуют правым расширениям из всех подстроки,
  • Края соответствуют тройкам такой, что и .

Здесь тройня означает, что есть преимущество от к со строкой написано на нем

, что подразумевает дерево суффиксных ссылок строки и суффиксное дерево строки изоморфны:[18]

Суффиксные структуры слов abbcbc и cbcbba

Как и в случае левых расширений, для правых расширений верна следующая лемма:[15]

Теорема — Правое продолжение строки может быть представлен как , куда это самое длинное слово такое, что каждое вхождение в сменяется .

Размер

Суффиксный автомат строки длины имеет самое большее государства и самое большее переходы. Эти границы достигаются на строках и соответственно.[13] Это можно сформулировать более строго как куда и - количество переходов и состояний в автомате соответственно.[14]

Максимальные суффиксные автоматы

Строительство

Первоначально автомат состоит только из одного состояния, соответствующего пустому слову, затем символы строки добавляются один за другим, и автомат постепенно перестраивается на каждом шаге.[19]

Обновления состояния

После добавления нового символа к строке некоторые классы эквивалентности изменяются. Позволять быть правильным контекстом по отношению к языку суффиксы. Тогда переход от к после добавляется к определяется леммой:[14]

Теорема — Позволять быть несколькими словами и быть некоторым персонажем из этого алфавита. Тогда существует следующее соответствие между и :

  • если суффикс ;
  • иначе.

После добавления к текущему слову правильный контекст может существенно измениться, только если суффикс . Отсюда следует отношение эквивалентности это уточнение из . Другими словами, если , тогда . После добавления нового символа не более двух классов эквивалентности будут разделены, и каждый из них может разделиться максимум на два новых класса. Во-первых, класс эквивалентности, соответствующий пустой правый контекст всегда разбивается на два класса эквивалентности, один из которых соответствует сам и имея как правильный контекст. Этот новый класс эквивалентности содержит ровно и все его суффиксы, которых не было в , поскольку правый контекст таких слов раньше был пуст, а теперь содержит только пустое слово.[14]

Зная соответствие между состояниями суффиксного автомата и вершинами суффиксного дерева, можно определить второе состояние, которое может расщепиться после добавления нового символа. Переход от к соответствует переходу от к в перевернутой строке. В терминах суффиксных деревьев это соответствует вставке нового самого длинного суффикса в суффиксное дерево . После такой вставки могут образоваться не более двух новых вершин: одна из них соответствует , а другой соответствует своему прямому предку, если было ветвление. Возвращаясь к суффиксным автоматам, это означает, что первое новое состояние распознает а второй (если есть второе новое состояние) - его суффиксная ссылка. Это можно сформулировать в виде леммы:[14]

Теорема — Позволять , быть немного словом и символом . Также позвольте быть самым длинным суффиксом , что происходит в , и разреши . Тогда для любых подстрок из он содержит:

  • Если и , тогда ;
  • Если и , тогда ;
  • Если и , тогда .

Это означает, что если (например, когда не произошло в вообще и ), то разбивается только класс эквивалентности, соответствующий пустому правому контексту.[14]

Помимо суффиксных ссылок необходимо также определить конечные состояния автомата. Из свойств структуры следует, что все суффиксы слова признанный распознаются некоторой вершиной на суффикс путь из . А именно, суффиксы длиной больше, чем роды , суффиксы длиной больше, чем но не больше чем роды и так далее. Таким образом, если государство, признающее обозначается , то все конечные состояния (то есть распознавание суффиксов ) образуют последовательность .[19]

Обновления переходов и суффиксных ссылок

После персонажа добавляется к возможные новые состояния суффиксного автомата и . Суффиксная ссылка от идет в и из это идет к . Слова из происходить в только в качестве его суффиксов, поэтому вообще не должно быть переходов от а переходы к нему должны идти от суффиксов иметь длину не менее и быть отмеченным символом . Состояние формируется подмножеством , таким образом, переход от должно быть таким же, как от . Между тем переходы, ведущие к должен идти от суффиксов имеющий длину меньше чем и по крайней мере , поскольку такие переходы привели к раньше и соответствовал отделившейся части этого государства. Состояния, соответствующие этим суффиксам, могут быть определены путем обхода пути ссылки суффикса для .[19]

Построение суффиксного автомата для слова abbcbc 
∅ → а
Одна буква SA.svgОдна буква SA.svg
После добавления первого символа в автомате суффикса создается только одно состояние.Точно так же к суффиксному дереву добавляется только один лист.
а → ab
Ab SA.svgBa ST.svg
Новые переходы выводятся из всех предыдущих конечных состояний как б не появлялся раньше.По той же причине к корню суффиксного дерева добавляется еще один лист.
ab → abb
Abb SA.svgBba ST.svg
Состояние 2 распознает слова ab и б, но только б - новый суффикс, поэтому это слово разделяется на состояние 4.В суффиксном дереве это соответствует расщеплению ребра, ведущего в вершину 2.
abb → abbc
Abbc SA.svgCbba ST.svg
Характер c происходит впервые, поэтому переходы выполняются из всех предыдущих конечных состояний.Дерево суффиксов обратной строки имеет еще один лист, добавленный к корню.
abbc → abbcb
Abbcb SA.svgBcbba ST.svg
Состояние 4 состоит из единственного слова б, который является суффиксом, поэтому состояние не разбивается.Соответственно, новый лист навешивается на вершину 4 суффиксного дерева.
abbcb → abbcbc
Суффикс-автомат extension.svgСуффиксное дерево extension.svg
Состояние 5 распознает слова abbc, BBC, до н.э и c, но только последние два являются суффиксами нового слова, поэтому они разделяются на новое состояние 8.Соответственно, ребро, ведущее к вершине 5, разбивается, а вершина 8 помещается в середину ребра.

Алгоритм построения

Теоретические результаты, приведенные выше, приводят к следующему алгоритму, который принимает характер и восстанавливает суффиксный автомат в суффиксный автомат :[19]

  1. Состояние, соответствующее слову хранится как ;
  2. После добавляется предыдущее значение хранится в переменной и сам переназначен в новое состояние, соответствующее ;
  3. Состояния, соответствующие суффиксам обновляются переходами на . Для этого нужно пройти , пока не появится состояние, в котором уже есть переход на ;
  4. По окончании вышеупомянутого цикла возможны 3 случая:
    1. Если ни одно из состояний на пути суффикса не имело перехода на , тогда никогда не происходило в до и суффиксная ссылка из должен привести к ;
    2. Если переход по находится и ведет от государства государству , так что , тогда не нужно разделять, и это суффиксная ссылка ;
    3. Если переход найден, но , затем слова из иметь длину не более следует выделить в новое состояние «клон» ;
  5. Если предыдущий шаг был завершен созданием , переходы от него и его суффиксная ссылка должны копировать переходы от , в то же время назначается как общее суффиксное звено обоих и ;
  6. Переходы, которые привели к раньше, но соответствовало словам длины не более перенаправлены на . Для этого нужно пройти суффиксный путь пока не будет найдено состояние, при котором переход по от этого не ведет к .

Вся процедура описывается следующим псевдокодом:[19]

функция add_letter (x):    определять p = последний    назначать last = new_state ()    назначать len (последняя) = len (p) + 1    пока δ (р, х) не определено: назначать δ (p, x) = последняя, ​​p = ссылка (p)    определять д = δ (р, х)    если q = последний:        назначать ссылка (последняя) = q0    иначе если len (q) = len (p) + 1:        назначать ссылка (последняя) = q    еще:        определять cl = new_state ()        назначать len (cl) = len (p) + 1        назначать δ (cl) = δ (q), ссылка (cl) = ссылка (q)        назначать ссылка (последняя) = ссылка (q) = cl        пока δ (p, x) = q:            назначать δ (p, x) = cl, p = ссылка (p)

Здесь - начальное состояние автомата и это функция, создающая для него новое состояние. Предполагается , , и хранятся как глобальные переменные.[19]

Сложность

Сложность алгоритма может варьироваться в зависимости от базовой структуры, используемой для хранения переходов автомата. Это может быть реализовано в с накладные расходы памяти или в с накладные расходы на память, если предполагается, что выделение памяти выполняется в . Чтобы получить такую ​​сложность, необходимо использовать методы амортизированный анализ. Значение строго уменьшается с каждой итерацией цикла, в то время как он может увеличиваться только на единицу после первой итерации цикла на следующей add_letter вызов. Общая стоимость никогда не превышает и он увеличивается только на единицу между итерациями добавления новых букв, что предполагает, что общая сложность также является не более чем линейной. Аналогично демонстрируется линейность второго цикла.[19]

Обобщения

Суффиксный автомат тесно связан с другими суффиксными структурами и индексы подстроки. Имея суффиксный автомат конкретной строки, можно построить ее суффиксное дерево с помощью сжатия и рекурсивного обхода за линейное время.[20] Подобные преобразования возможны в обоих направлениях для переключения между суффиксным автоматом и суффиксное дерево перевернутой строки .[18] Помимо этого, было разработано несколько обобщений для построения автомата для набора строк, заданных trie,[8] компактная суффиксная автоматизация (CDAWG),[7] сохранить структуру автомата на скользящем окне,[21] и строить его двунаправленным способом, поддерживая вставку символов как в начало, так и в конец строки.[22]

Сжатый суффикс-автомат

Как уже упоминалось выше, уплотненный суффиксный автомат получается как посредством уплотнения регулярного суффиксного автомата (путем удаления состояний, которые не являются конечными и имеют ровно одну выходящую дугу), так и посредством минимизации суффиксного дерева. Как и в случае обычного суффиксного автомата, состояния уплотненного суффиксного автомата могут быть определены явно. Двустороннее расширение слова это самое длинное слово , так что каждое вхождение в предшествует и ему удалось . В терминах левого и правого расширений это означает, что двустороннее расширение - это левое расширение правого расширения или, что эквивалентно, правое расширение левого расширения, то есть . В терминах двустороннего расширения компактный автомат определяется следующим образом:[15]

Теорема — Сжатый суффикс-автомат слова определяется парой , куда:

  • - набор состояний автомата;
  • представляет собой набор автоматных переходов.

Двусторонние расширения индуцируют отношение эквивалентности который определяет набор слов, распознаваемых одним и тем же состоянием уплотненного автомата. Это отношение эквивалентности является переходное закрытие отношения, определяемого , что подчеркивает тот факт, что компактный автомат может быть получен склейкой эквивалентных вершин суффиксного дерева через отношения (минимизация суффиксного дерева) и состояний суффиксного автомата склейки эквивалентны через отношение (уплотнение суффиксного автомата).[23] Если слова и имеют такие же правильные расширения и слова и имеют одинаковые левые расширения, затем все строки в совокупности , и имеют такие же двусторонние расширения. В то же время может случиться так, что ни левое, ни правое продолжение и совпадают. В качестве примера можно взять , и , для которых левое и правое расширение следующие: , но и . При этом, в то время как отношения эквивалентности односторонних расширений были сформированы некоторой непрерывной цепочкой вложенных префиксов или суффиксов, отношения эквивалентности двусторонних расширений более сложны, и единственное, что можно сделать с уверенностью, это то, что строки с одинаковым двусторонним расширением находятся подстроки самой длинной строки с одинаковым двусторонним расширением, но может даже случиться так, что у них нет общей непустой подстроки. Общее количество классов эквивалентности для этого отношения не превышает откуда следует, что сжатый суффикс-автомат строки длины имеет самое большее состояния. Количество переходов в таком автомате не более .[15]

Суффиксный автомат из нескольких строк

Рассмотрим набор слов . Можно построить обобщение суффиксного автомата, которое распознало бы язык, образованный суффиксами всех слов из набора. Ограничения на количество состояний и переходов в таком автомате останутся такими же, как и для однословного автомата, если вы положите .[23] Алгоритм аналогичен построению однословного автомата, но вместо состояние, функция add_letter будет работать с состоянием, соответствующим слову предполагая переход от набора слов к набору .[24][25]

This idea is further generalized to the case when is not given explicitly but instead is given by a prefix tree с вершины. Mohri и другие. showed such an automaton would have at most and may be constructed in linear time from its size. At the same time, the number of transitions in such automaton may reach , for example for the set of words over the alphabet the total length of workds is equal to , the number of vertices in corresponding suffix trie is equal to and corresponding suffix automaton is formed of государства и transitions. Algorithm suggested by Mohri mainly repeats the generic algorithm for building automaton of several strings but instead of growing words one by one, it traverses the trie in a breadth-first search order and append new characters as it meet them in the traversal, which guarantees amortized linear complexity.[26]

Sliding window

Немного алгоритмы сжатия, Такие как LZ77 и RLE may benefit from storing suffix automaton or similar structure not for the whole string but for only last its characters while the string is updated. This is because compressing data is usually expressively large and using memory is undesirable. In 1985, Janet Blumer developed an algorithm to maintain a suffix automaton on a sliding window of size в worst-case and on average, assuming characters are distributed independently and равномерно. She also showed complexity cannot be improved: if one considers words construed as a concatenation of several words, where , then the number of states for the window of size would frequently change with jumps of order , which renders even theoretical improvement of for regular suffix automata impossible.[27]

The same should be true for the suffix tree because its vertices correspond to states of the suffix automaton of the reversed string but this problem may be resolved by not explicitly storing every vertex corresponding to the suffix of the whole string, thus only storing vertices with at least two out-going edges. A variation of McCreight's suffix tree construction algorithm for this task was suggested in 1989 by Edward Fiala and Daniel Greene;[28] several years later a similar result was obtained with the variation of Ukkonen's algorithm by Jesper Larsson.[29][30] The existence of such an algorithm, for compacted suffix automaton that absorbs some properties of both suffix trees and suffix automata, was an open question for a long time until it was discovered by Martin Senft and Tomasz Dvorak in 2008, that it is impossible if the alphabet's size is at least two.[31]

One way to overcome this obstacle is to allow window width to vary a bit while staying . It may be achieved by an approximate algorithm suggested by Inenaga et al. in 2004. The window for which suffix automaton is built in this algorithm is not guaranteed to be of length but it is guaranteed to be at least and at most while providing linear overall complexity of the algorithm.[32]

Приложения

Suffix automaton of the string may be used to solve such problems as:[33][34]

  • Counting the number of distinct substrings of в on-line,
  • Finding the longest substring of occurring at least twice in ,
  • Finding the longest common substring of и в ,
  • Counting the number of occurrences of в в ,
  • Finding all occurrences of в в , куда is the number of occurrences.

Здесь предполагается, что is given on the input after suffix automaton of is constructed.[33]

Suffix automata are also used in data compression,[35] music retrieval[36][37] and matching on genome sequences.[38]

Рекомендации

Библиография

внешняя ссылка