Сложность Лемпеля — Зива
Эта статья должна быть полностью переписана. |
Сложность Лемпеля-Зива (Lempel-Ziv Complexity) - алгоритм для вычисления Колмогоровской сложности, который может быть исполнен на любом языке программирования, поддерживающем операции копирования и вставки в строку. Несмотря на простоту данного алгоритма, он является очень мощным и быстрым.
Сложность Лемпеля-Зива была впервые представлена в статье под названием On the Complexity of Finite Sequences (IEEE Trans. On IT-22,1 1976) двумя израильскими учеными, Авраамом Лемпелем и Яаковом Зивом. Данная мера сложности связана с Колмогоровской сложностью, но единственная функция, которую она использует - рекурсивная копия.
Механизм, лежащий в основе данного измерения сложности, является отправной точкой для некоторых алгоритмов сжатия без потерь, таких как LZ77, LZ78 и LZW. Несмотря на то, что она основана на элементарном принципе копирования слов, эта мера сложности не слишком ограничительна в том смысле, что она удовлетворяет основным качествам, ожидаемым такой мерой: последовательности с определенной регулярностью не имеют слишком большой сложности, и сложность возрастает по мере увеличения длины и неоднородности.
Сложность Лемпеля-Зива можно использовать для измерения повторяемости бинарных последовательностей и текста, например, текстов песен.
Принцип вычисления
[править | править код]Пусть S - двоичная последовательность длины n, для которой мы хотим вычислить сложность Лемпеля-Зива, обозначенную как C(S). Последовательность рассматривается слева направо.
Представим, что у нас имеется разделяющая линия, которую мы можем перемещать в двоичной последовательности по мере вычисления. Вначале эта линия устанавливается сразу после первого символа, в начале последовательности. Обозначим данную позицию - позицией 1, откуда мы должны переместить ее в позицию 2, которая считается начальной позицией для следующего шага, и так далее. Мы должны переместить разделитель (начиная с позиции 1) еще дальше вправо, чтобы слово между позицией 1 и позицией разделителя было подсловом последовательности, которая начинается перед позицией 1 разделителя.
Как только разделитель установлен в позицию, где это условие не выполняется, мы останавливаемся, перемещаем разделитель в эту позицию и начинаем снова, отмечая эту позицию как новую начальную позицию (то есть позицию 1). Продолжайте итерации до конца последовательности. Сложность Лемпеля-Зива соответствует числу итераций, необходимых для завершения этой процедуры.
Иными словами, сложность Лемпеля-Зива - это количество различных подстрок (или подслов), встречающихся при просмотре двоичной последовательности в виде потока (слева направо).
Математическое объяснение
[править | править код]Обозначения
[править | править код]Обозначим S, как двоичную последовательность длины n. Пусть S (i, j) с будет подпоследовательностью S с началом i и концом j (если j <i , S (i, j) - пустая строка). Длина n в S обозначена как l(S), а последовательность Q называется фиксированным префиксом S, если:
Воспроизводимость и производительность
[править | править код]С одной стороны, последовательность S длины n называется воспроизводимой из ее префикса S (1, j), когда S (j + 1, n) является подсловом S (1, n-1). Это обозначается S (1, j) → S.
Иными словами, S воспроизводится из своего префикса S (1, j), если остальная часть последовательности, S (j + 1, n), является не чем иным, как копией другого подслова (начиная с индекса i < j +1) из S (1, n-1).
Чтобы доказать, что последовательность S может быть воспроизведена одним из ее префиксов S (1, j), необходимо показать, что:
С другой стороны, производительность определяется из воспроизводимости: последовательность S получается из ее префикса S (1, j), если S (1, n-1) воспроизводимо из S (1, j). Это обозначается S (1, j) ⇒S. Иными словами, S (j + 1, n-1) должна быть копией другого подслова S (1, n-2). Последний символ S может быть новым символом (но не может быть), что может привести к созданию нового подслово (отсюда и термин «производительность»).
Исчерпывающая история и сложность
[править | править код]Из определения продуктивности, пустая строка Λ = S(1,0) ⇒ S(1,1). Таким образом, с помощью рекурсивного производственного процесса на шаге i мы имеем S (1, ) ⇒ S (1, +1), поэтому мы можем построить S из его префиксов. И так как S (1, i) ⇒ S (1, i+1) (с +1 = +1) всегда верно, этот процесс производства S занимает не более n = l(S) шагов. Пусть m, , будет количеством необходимых шагов для этого процесса S. S может быть записана в разложенном виде, называемом историей S, и обозначена H(S), определяемая как:
называется компонентом H(S).
Компонент S, (S), называется исчерпывающим, если S(1, ) - самая длинная последовательность, создаваемая S (1,-1) (т. e. S (1, -1) ⇒ S(1, )) но так, чтобы S (1, -1) не создавал S (1, ). T.е: Индекс p, который позволяет иметь самую длинную продукцию, называется указателем.
История S называется исчерпывающей, если все ее компоненты являются исчерпывающими, кроме, возможно, последнего. Из определения можно показать, что любая последовательность S имеет только одну исчерпывающую историю, и эта история имеет наименьшее количество компонентов из всех возможных историй S. Наконец, количество компонентов этой уникальной исчерпывающей истории S называется сложностью Лимпеля-Зива и обозначается как С.
Алгоритм
[править | править код]Для вычисления данной сложности существует эффективный алгоритм за линейное количество операций
Формальное описание этого метода задается следующим алгоритмом:
- i = p-1, p-указатель (см. выше)
- u-длина текущего префикса
- v-длина текущего компонента для текущего указателя p
- vmax-конечная длина, используемая для текущего компонента (наибольшая из всех возможных указателей p)
- C-сложность Лемпеля-Зива, увеличивающаяся итеративно.
i := 0
C := 0
u := 0
v := 0
vmax := 0
while u + v <= n do
if S[i + v] = S[u + v] then
v := v + 1
else
vmax := max(v, vmax)
i := i + 1
v := 1
if i = u then
C := C + 1
u := u + vmax
i := 0
end if
end if
end while
if v != 1 then
C := C + 1
end if
Код на JavaScript (Сложность выше линейной)
function isSubArray(subArr, mainArr) {
var length = mainArr.length - subArr.length;
for(var i = 0; i <= length; ++i) {
for(var j = 0; j < subArr.length; ++j) {
if(subArr[j] !== mainArr[i + j]) break;
if(j + 1 === subArr.length) return true;
}
}
return false;
}
function C(arr) {
var start = 0;
var ptr = 1;
var steps = 0;
while(ptr <= arr.length) {
while(ptr <= arr.length && isSubArray(arr.slice(start, ptr), arr.slice(0, start))) ++ptr;
start = ptr; ++ptr;
++steps;
}
return steps;
}
Библиография
[править | править код]- Abraham Lempel and Jacob Ziv, « On the Complexity of Finite Sequences », IEEE Trans. on Information Theory, January 1976, p. 75–81, vol. 22, n°1