Нахождение повторяющейся последовательности в конце последовательности чисел

Моя проблема заключается в следующем: у меня есть большая последовательность чисел. Я знаю, что через какой-то момент она становится периодической, то есть в начале последовательности есть k чисел, а затем есть еще m чисел, которые повторяются до конца последовательности. В качестве примера, чтобы сделать это более понятным, последовательность может выглядеть так: [1, 2, 5, 3, 4, 2, 1, 1, 3, 2, 1, 1, 3, 2, 1, 1, 3 , ...], где k равно 5, а m равно 4, и тогда повторяющийся блок будет [2, 1, 1, 3]. Как видно из этого примера, у меня могут быть повторяющиеся биты внутри большего блока, поэтому просто искать первые экземпляры повторения не помогает.

Однако я не знаю, что такое k или m — моя цель — взять последовательность [a_1, a_2, ..., a_n] в качестве входных данных и вывести последовательность [a_1, ..., a_k, [a_(k +1), ..., a_(k+m)]] - в основном усекая более длинную последовательность, перечисляя большую ее часть как повторяющийся блок.

Есть ли эффективный способ решить эту проблему? Кроме того, вероятно, сложнее, но более идеально в вычислительном отношении - возможно ли сделать это, когда я генерирую рассматриваемую последовательность, чтобы мне нужно было генерировать минимальное количество? Я просмотрел другие похожие вопросы на этом сайте, но все они, похоже, имеют дело с последовательностями без начального неповторяющегося бита и часто без необходимости беспокоиться о внутреннем повторении.

Если это поможет / будет полезно, я также могу объяснить, почему я смотрю на это и для чего я буду его использовать.

Спасибо!

РЕДАКЦИИ: Во-первых, я должен был упомянуть, что я не знаю, заканчивается ли входная последовательность точно в конце повторяющегося блока.

Реальная проблема, над которой я пытаюсь работать, состоит в том, чтобы написать красивое выражение в закрытой форме для разложения непрерывных дробей (CFE) квадратичных иррациональных чисел (на самом деле, отрицательного CFE). Очень просто сгенерировать частичные частные* для этих CFE с любой степенью точности, однако в какой-то момент хвост CFE для квадратичного иррационального числа становится повторяющимся блоком. Мне нужно работать с частичными частными в этом повторяющемся блоке.

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

* Если я записываю разложение цепной дроби как [a_0, a_1, ...], я называю a_i частичными частными.

Некоторая справочная информация может быть найдена здесь для тех, кто заинтересован: /а>


person Istarion    schedule 04.05.2012    source источник
comment
Если ваша последовательность бесконечна, эта проблема вообще неразрешима: вы никогда не можете знать, находитесь ли вы просто во внутреннем сегменте повторения, который остановится в какой-то момент позже. Есть ли у вас какие-либо ограничения на k/m или вам просто нужен алгоритм наилучшего предположения, который работает по пути (для раздела по мере создания)?   -  person Danica    schedule 04.05.2012
comment
Гарантировано ли, что у вас есть как минимум 2 повторения последней периодической подпоследовательности в конце заданного ввода [a_1...a_n]?   -  person Alexey Frunze    schedule 04.05.2012
comment
@Dougal У меня есть некоторые верхние границы для m, хотя они не очень хорошие оценки - последовательность технически бесконечна, но я смотрел на подачу ее конечной части, которая гарантированно будет иметь несколько повторений повторяющейся части. Что, я полагаю, отвечает на вопрос Алекса — да, у меня гарантированно минимум два повторения.   -  person Istarion    schedule 04.05.2012


Ответы (5)


Вы можете использовать скользящий хеш для достичь линейной временной сложности и пространственной сложности O (1) (я думаю, что это так, поскольку я не верю, что у вас может быть бесконечная повторяющаяся последовательность с двумя частотами, которые не кратны друг другу).

Алгоритм: вы просто сохраняете два скользящих хэша, которые расширяются следующим образом:

                       _______  _______  _______
                      /       \/       \/       \
...2038975623895769874883301010883301010883301010
                      .        .        .      ||
                      .        .        .    [][]
                      .        .        .  [ ][ ]
                      .        .        .[  ][  ]
                      .        .       [.  ][   ]
                      .        .     [  . ][    ]
                      .        .   [    .][     ]
                      .        . [      ][      ]
                      .        [       ][       ]

Продолжайте делать это для всей последовательности. Первый проход будет обнаруживать только повторения, повторяющиеся 2*n раз для некоторого значения n. Однако это не наша цель: наша цель при первом проходе — обнаружить все возможные периоды, что и происходит. По мере прохождения последовательности, выполняющей этот процесс, мы также отслеживаем все относительно простые периоды, которые нам нужно будет проверить позже:

periods = Set(int)
periodsToFurthestReach = Map(int -> int)

for hash1,hash2 in expandedPairOfRollingHashes(sequence):
    L = hash.length
    if hash1==hash2:
        if L is not a multiple of any period:
            periods.add(L)
            periodsToFurthestReach[L] = 2*L
        else L is a multiple of some periods:
            for all periods P for which L is a multiple:
                periodsToFurthestReach[P] = 2*L

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

В конце мы еще раз проверяем результат, чтобы убедиться, что не было коллизии хэшей (вряд ли она есть, заносим в черный список и повторяем).

Здесь я предположил, что ваша цель состояла в том, чтобы минимизировать неповторяющуюся длину, а не давать повторяющийся элемент, который можно было бы дополнительно учесть; вы можете изменить этот алгоритм, чтобы найти все другие сжатия, если они существуют.

person ninjagecko    schedule 04.05.2012
comment
Это выглядит очень полезным - спасибо! К сожалению, я не могу гарантировать, что мы закончим нашу последовательность в конце повторяющегося блока, но кажется вероятным, что приведенные здесь идеи все равно можно адаптировать для работы. - person Istarion; 05.05.2012
comment
@Istarion: если вы не остановились на границе повторяющегося блока, вы все равно обнаружите ту же повторяющуюся последовательность (тот же период), но можете прикрепить часть повторяющегося блока, например. ...4697 123 (45123)(45123)... Чтобы адаптироваться, возьмите каждое решение, предложенное вышеописанным методом, и попытайтесь сдвинуть повторяющиеся блоки потенциального решения влево, цифра за цифрой , сравнивая с (например) 3=seq[-1], 2=seq[-2], 1=seq[-3], 7!=seq[-4] -> сдвиг на 3. - person ninjagecko; 05.05.2012

Итак, ninjagecko дал хороший рабочий ответ на поставленный мною вопрос. Большое спасибо! Тем не менее, я нашел более эффективный, математически обоснованный способ сделать конкретный случай, который я рассматриваю, то есть написать выражение в закрытой форме для разложения непрерывной дроби квадратичного иррационального числа. Очевидно, что это решение будет работать только для этого конкретного случая, а не для общего случая, о котором я спрашивал, но я подумал, что было бы полезно разместить его здесь, если у других возникнет аналогичный вопрос.

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

Когда вы работаете над разложением числа x в непрерывную дробь, вы в основном устанавливаете x_0 равным x, а затем формируете свою последовательность [a_0; a_1, a_2, a_3, ...] путем определения a_n = floor(x_n) и x_(n+1) = 1/(x_n - a_n). Обычно вы просто продолжаете это, пока не достигнете желаемой точности. Однако для наших целей мы просто запускаем этот метод до тех пор, пока x_k не станет приведенным квадратичным иррациональным числом (что происходит, если оно больше 1, а его сопряженное число находится в диапазоне от -1 до 0). Как только это происходит, мы знаем, что a_k — это первый член нашего повторяющегося блока. Затем, когда мы находим x_(k+m+1) равным x_k, мы знаем, что a_(k+m) — это последний член в нашем повторяющемся блоке.

person Istarion    schedule 04.05.2012

Поиск справа:

  • делает a_n == a_n-1
  • делает (a_n,a_n-1) == (a_n-2,a_n-3)
  • ...

Это явно O (m ^ 2). Единственная доступная граница, по-видимому, заключается в том, что m ‹ n/2, так что это O (n ^ 2)

Это приемлемо для вашего приложения? (Мы делаем вашу домашнюю работу за вас, или здесь действительно существует реальная проблема?)

person Roland Turner    schedule 04.05.2012
comment
К сожалению, я не могу гарантировать, что наш ввод заканчивается в конце повторяющейся последовательности, но, возможно, мы все еще можем работать справа. Реальная проблема, над которой я работаю, связана с разложением непрерывных дробей (в частности, разложением отрицательных непрерывных дробей). Квадратичные числа являются периодическими в своем разложении на непрерывные дроби (но не обязательно чисто периодическими, отсюда и начальная часть последовательности), и я хочу иметь возможность написать красивый закрытый список, который представляет всю полноту разложения на отрицательные непрерывные дроби числа. задано квадратное число. - person Istarion; 04.05.2012

На этой странице перечислены несколько хороших алгоритмов обнаружения циклов и представлена ​​реализация алгоритма на C.

person Apocalisp    schedule 04.05.2012
comment
Эти алгоритмы (например, алгоритм Флойда и приведенный выше) неприменимы, так как они работают только либо с графами с указателями, либо со списком, где каждое число зависит только от предыдущего числа. - person ninjagecko; 04.05.2012
comment
Алгоритм обнаружения циклов, применяемый «из коробки», будет иметь ложные срабатывания повсюду. Однако, если вы увеличите размер алфавита, рассматривая каждую часть из k цифр как одно число или каждое скользящее окно из k цифр как одно число, это может оказаться практичным для некоторых последовательностей и может быть довольно быстрым. - person mcdowella; 04.05.2012

Рассмотрим последовательность после того, как она повторилась несколько раз. Это закончится, например. ...12341234123412341234. Если вы возьмете повторяющуюся часть строки непосредственно перед последним циклом повторений, а затем продвинете ее на длину этого цикла, вы обнаружите, что у вас есть длинное совпадение между подстрокой в ​​конце последовательности и та же подстрока сдвинулась влево на расстояние, малое по сравнению с ее длиной.

И наоборот, если у вас есть строка, в которой a[x] = a[x + k] для большого числа x, то у вас также есть a[x] = a[x + k] = a[x + 2k] = a [x + 3k]... поэтому строка, совпадающая с самой собой при перемещении на короткое расстояние по сравнению с ее длиной, должна содержать повторы.

Если вы посмотрите на http://en.wikipedia.org/wiki/Suffix_array, вы увидите что вы можете построить список всех суффиксов строки в отсортированном порядке за линейное время, а также массив, который сообщает вам, сколько символов каждый суффикс имеет общего с предыдущим суффиксом в отсортированном порядке. Если вы ищете запись с наибольшим значением this, это будет мой кандидат на строку, идущую ..1234123412341234, а расстояние между начальными точками двух суффиксов скажет вам длину, на которой последовательность повторяется. (но на практике какой-то скользящий хеш-поиск, например http://en.wikipedia.org/wiki/Rabin-Karp может быть быстрее и проще, хотя существуют вполне программируемые алгоритмы массива суффиксов линейного времени, такие как «Простое построение массива суффиксов линейной работы» Карккайнена и Сандерса).

Предположим, вы применяете этот алгоритм, когда количество доступных символов равно 8, 16, 32, 64, .... 2 ^ n, и вы, наконец, находите повтор в 2 ^ p. Сколько времени вы потратили впустую на предыдущих этапах? 2 ^ (p-1) + 2 ^ (p-2) + ..., что в сумме составляет около 2 ^ p, поэтому повторные поиски являются только постоянными накладными расходами.

person mcdowella    schedule 04.05.2012
comment
Еще один полезный ответ, спасибо! Как я уже упоминал выше, это, кажется, все еще сталкивается с проблемами, если последовательность заканчивается на полпути через повторяющийся блок, но это может быть более преодолимой проблемой. - person Istarion; 05.05.2012