Найти период конечной периодической последовательности

краткое объяснение.

у меня есть последовательность чисел [0, 1, 4, 0, 0, 1, 1, 2, 3, 7, 0, 0, 1, 1, 2, 3, 7, 0, 0, 1, 1, 2, 3, 7, 0, 0, 1, 1, 2, 3, 7]. Как видите, из 3-го значения последовательность периодическая с периодом [0, 0, 1, 1, 2, 3, 7].

Я пытаюсь автоматически извлечь этот период из этой последовательности. Проблема в том, что я не знаю ни длины периода, ни положения, из которого последовательность становится периодической.

полное описание (может потребоваться math)

Я изучаю комбинаторную теорию игр, и краеугольный камень этой теории требует расчета Grundy values игры графика. Это порождает бесконечную последовательность, которая во многих случаях становится в конце концов периодические.

я нашел способ эффективно вычислять значения grundy (он возвращает мне последовательность). Я хотел бы автоматически извлечь смещение и период этой последовательности. Я знаю, что вижу часть последовательности [1, 2, 3, 1, 2, 3] вы не можете быть уверены в том, что [1, 2, 3] - это точка (кто знает, может быть, следующее число -4, что нарушает предположение), но меня не интересуют такие тонкости (я предполагаю, что последовательности достаточно, чтобы найти реальный период). Также проблема в том, что последовательность может остановиться в середине периода:[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, ...] (период по-прежнему 1, 2, 3).

мне нужно найти наименьшее смещение и период. Например, для исходной последовательности, смещение может быть [0, 1, 4, 0, 0] и период [1, 1, 2, 3, 7, 0, 0], а самый маленький -[0, 1, 4] и [0, 0, 1, 1, 2, 3, 7].


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

вот мой быстрый код python (не протестировали его должным образом):

def getPeriod(arr):
    min_offset, min_period, n = len(arr), len(arr), len(arr)
    best_offset, best_period = [], []
    for offset in xrange(n):
        start = arr[:offset]
        for period_len in xrange(1, (n - offset) / 2):
            period = arr[offset: offset+period_len]
            attempt = (start + period * (n / period_len + 1))[:n]

            if attempt == arr:
                if period_len < min_period:
                    best_offset, best_period = start[::], period[::]
                    min_offset, min_period = len(start), period_len
                elif period_len == min_period and len(start) < min_offset:
                    best_offset, best_period = start[::], period[::]
                    min_offset, min_period = len(start), period_len

    return best_offset, best_period

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

offset [0, 1, 4]
period [0, 0, 1, 1, 2, 3, 7]

есть ли что-нибудь более эффективное?

3 ответов


  1. я бы начал с построения гистограммы значений в последовательности

    поэтому вы просто составляете список всех чисел, используемых в последовательности (или значительной ее части), и подсчитываете их появление. Это O(n) здесь n - размер последовательности.

  2. сортировка по возрастанию гистограмме

    это O(m.log(m)) здесь m - количество различных значений. Вы также можете игнорировать низкий вероятные числа (count<treshold), которые, скорее всего, в смещении или просто неровности дальнейшего снижения m. Для периодических последовательностей m <<< n таким образом, вы можете использовать его в качестве первого маркера, если последовательность периодическая или нет.

  3. узнайте срок

    на гистограмма the counts должно быть вокруг кратных n/period. Так приблизительно / найти GCD гистограммы отсчетов. Проблема в том, что ты необходимо учитывать, что в графах, а также в n (смещенная часть), поэтому вам нужно вычислить GCD приблизительно. например:

    sequence  = { 1,1,2,3,3,1,2,3,3,1,2,3,3 }
    

    заказал гистограммы:

    item,count
    2    3
    1    4
    3    6
    

    the GCD(6,4)=2 и GCD(6,3)=3 вы должны проверить, по крайней мере +/-1 вокруг GCD результаты так что возможные периоды вокруг:

    T = ~n/2 = 13/2 = 6
    T = ~n/3 = 13/3 = 4
    

    так что проверьте T={3,4,5,6,7} просто чтобы быть уверенным. Использовать всегда GCD между наивысшими значениями и низкой счету. Если последовательность имеет много различных чисел, вы также можете сделать гистограмму подсчетов, проверяя только наиболее распространенные значения.

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

  4. вам точный период

    просто проверьте найденные фракции периода (T/2, T/3, ...) или сделайте гистограмму на найденном периоде и наименьшем count говорит вам, сколько реальных периодов вы инкапсулировали так разделить на него.

  5. найти смещение

    когда вы знаете период, это легко. Просто сканируйте с начала возьмите первый элемент и посмотрите, есть ли после периода снова. Если не помните положение. Остановиться в конце или в середине последовательность... или на каких-то последующих успехах. Это O(n) и последнее запоминающееся положение-это последний элемент в offset.

[edit1] было любопытно, поэтому я пытаюсь закодировать его на C++

я упростил / пропустил несколько вещей (предполагая, что по крайней мере половина массива является периодической), чтобы проверить, не сделал ли я какую-то глупую ошибку в своем алгоритме, и здесь результат (работает как ожидалось):

const int p=10;         // min periods for testing
const int n=500;        // generated sequence size
int seq[n];             // generated sequence
int offset,period;      // generated properties
int i,j,k,e,t0,T;
int hval[n],hcnt[n],hs; // histogram

// generate periodic sequence
Randomize();
offset=Random(n/5);
period=5+Random(n/5);
for (i=0;i<offset+period;i++) seq[i]=Random(n);
for (i=offset,j=i+period;j<n;i++,j++) seq[j]=seq[i];
if ((offset)&&(seq[offset-1]==seq[offset-1+period])) seq[offset-1]++;

// compute histogram O(n) on last half of it
for (hs=0,i=n>>1;i<n;i++)
    {
    for (e=seq[i],j=0;j<hs;j++)
     if (hval[j]==e) { hcnt[j]++; j=-1; break; }
    if (j>=0) { hval[hs]=e; hcnt[hs]=1; hs++; }
    }
// bubble sort histogram asc O(m^2)
for (e=1,j=hs;e;j--)
 for (e=0,i=1;i<j;i++)
  if (hcnt[i-1]>hcnt[i])
  { e=hval[i-1]; hval[i-1]=hval[i]; hval[i]=e;
    e=hcnt[i-1]; hcnt[i-1]=hcnt[i]; hcnt[i]=e; e=1; }
// test possible periods
for (j=0;j<hs;j++)
 if ((!j)||(hcnt[j]!=hcnt[j-1]))    // distinct counts only
  if (hcnt[j]>1)                    // more then 1 occurence
   for (T=(n>>1)/(hcnt[j]+1);T<=(n>>1)/(hcnt[j]-1);T++)
    {
    for (i=n-1,e=seq[i],i-=T,k=0;(i>=(n>>1))&&(k<p)&&(e==seq[i]);i-=T,k++);
    if ((k>=p)||(i<n>>1)) { j=hs; break; }
    }

// compute histogram O(T) on last multiple of period
for (hs=0,i=n-T;i<n;i++)
    {
    for (e=seq[i],j=0;j<hs;j++)
     if (hval[j]==e) { hcnt[j]++; j=-1; break; }
    if (j>=0) { hval[hs]=e; hcnt[hs]=1; hs++; }
    }
// least count is the period multiple O(m)
for (e=hcnt[0],i=0;i<hs;i++) if (e>hcnt[i]) e=hcnt[i];
if (e) T/=e;

// check/handle error
if (T!=period)
    {
    return;
    }

// search offset size O(n)
for (t0=-1,i=0;i<n-T;i++)
 if (seq[i]!=seq[i+T]) t0=i;
t0++;

// check/handle error
if (t0!=offset)
    {
    return;
    }

код все еще не оптимизированный. Для n=10000 к 5ms настройки мои. Результат в t0 (смещение) и T (период). возможно, вам придется немного поиграть с константами treshold


Примечание: если есть срок P1 длиной L, тогда есть также Точка P2 С такая же длина, L, так что входная последовательность заканчивается точно на P2 (то есть у нас нет частичного периода в конце).

действительно, другой период той же длины всегда может быть получен путем изменения смещения. Новый период быть ротацией начального периода.

например, следующая последовательность имеет период длины 4 и смещение 3:

0 0 0 (1 2 3 4) (1 2 3 4) (1 2 3 4) (1 2 3 4) (1 2 3 4) (1 2

но он также имеет период с той же длиной 4 и смещением 5, без частичного периода в конце:

0 0 0 1 2 (3 4 1 2) (3 4 1 2) (3 4 1 2) (3 4 1 2) (3 4 1 2)


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

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


однажды мне пришлось сделать что-то подобное. Я использовал грубую силу и здравый смысл, решение не очень элегантное, но оно работает. Решение всегда работает,но вы должны установить правильные параметры (k, j, con) в функции.

  • последовательность сохраняется в виде списка в переменной seq.
  • k размер массива последовательности, если вы думаете, что ваша последовательность займет много времени, чтобы стать периодической, то установите это k большой число.
  • переменная нашел сообщит нам, прошел ли массив периодический тест с периодом j
  • j - это период.
  • если вы ожидаете огромный период, то вы должны установить j в большом количестве.
  • мы проверяем периодичность, проверяя последний J в+30 чисел последовательности.
  • чем больше период (j) еще надо проверить.
  • As как только один из тест пройден, мы выходим из функции и возвращаем меньший период.

Как вы можете заметить, что точность зависит от переменных j и k но если вы установите их на очень большие числа, это всегда будет правильно.

def some_sequence(s0, a, b, m):
    try:    
        seq=[s0]
        snext=s0
        findseq=True
        k=0
        while findseq:     
            snext= (a*snext+b)%m
            seq.append(snext)

#UNTIL THIS PART IS JUST TO CREATE THE SEQUENCE (seq) SO IS NOT IMPORTANT
            k=k+1
            if k>20000:
                # I IS OUR LIST INDEX
                for i in range(1,len(seq)):
                    for j in range(1,1000):
                        found =True
                        for con in range(j+30):
                          #THE TRICK IS TO START FROM BEHIND                   
                          if not (seq[-i-con]==seq[-i-j-con]):
                              found = False
                        if found:
                            minT=j
                            findseq=False
                            return minT

except:

    return None

упрощенная версия

def get_min_period(sequence,max_period,test_numb):
    seq=sequence
    if max_period+test_numb > len(sequence):
        print("max_period+test_numb cannot be bigger than the seq length")
        return 1
    for i in range(1,len(seq)):       
        for j in range(1,max_period):
            found =True
            for con in range(j+test_numb):                                       
                if not (seq[-i-con]==seq[-i-j-con]):
                    found = False
            if found:           
                minT=j
                return minT

здесь max_period это максимальный период, который вы хотите найти, и test_numb сколько номеров последовательности вы хотите проверить, чем больше, тем лучше, но вы должны сделать max_period+test_numb