Найти период конечной периодической последовательности
краткое объяснение.
у меня есть последовательность чисел [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 ответов
-
я бы начал с построения гистограммы значений в последовательности
поэтому вы просто составляете список всех чисел, используемых в последовательности (или значительной ее части), и подсчитываете их появление. Это
O(n)
здесьn
- размер последовательности. -
сортировка по возрастанию гистограмме
это
O(m.log(m))
здесьm
- количество различных значений. Вы также можете игнорировать низкий вероятные числа (count<treshold
), которые, скорее всего, в смещении или просто неровности дальнейшего сниженияm
. Для периодических последовательностейm <<< n
таким образом, вы можете использовать его в качестве первого маркера, если последовательность периодическая или нет. -
узнайте срок
на гистограмма 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 между наивысшими значениями и низкой счету. Если последовательность имеет много различных чисел, вы также можете сделать гистограмму подсчетов, проверяя только наиболее распространенные значения.чтобы проверить срок действия, просто возьмите любой элемент в конце или середине последовательности (просто используйте вероятную периодическую область). Затем ищите его в непосредственной близости от вероятного периода до (или после) его возникновения. Если найдено несколько раз, вы получили правильный период (или его несколько)
-
вам точный период
просто проверьте найденные фракции периода (
T/2, T/3,
...) или сделайте гистограмму на найденном периоде и наименьшемcount
говорит вам, сколько реальных периодов вы инкапсулировали так разделить на него. -
найти смещение
когда вы знаете период, это легко. Просто сканируйте с начала возьмите первый элемент и посмотрите, есть ли после периода снова. Если не помните положение. Остановиться в конце или в середине последовательность... или на каких-то последующих успехах. Это
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