简短说明。
我有一系列数字[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]
。
我正在尝试从此序列中自动提取此期间。问题是我既不知道周期的长度,也不知道序列从哪个位置成为周期性的。
完整解释(可能需要一些数学)
我正在学习组合博弈论,这个理论的基石需要人们来计算游戏图的Grundy values。这会产生无限序列,在许多情况下变为eventually periodic。
我找到了一种有效计算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]
还有什么更有效的吗?
答案 0 :(得分:5)
备注 :如果有 P1 的句号 L ,那么还有句号< em> P2 ,相同长度, L ,这样输入序列完全以 P2 结束( ie 我们最后没有涉及部分时间段。)
实际上,通过改变偏移量总是可以获得相同长度的不同周期。新时期将是初始阶段的轮换。
例如,以下序列的长度为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)
这意味着我们可以通过以相反的顺序处理序列来找到周期的最小长度,并使用从末尾开始的零偏移来搜索最小周期。一种可能的方法是在反向列表上简单地使用当前算法,而不需要循环偏移。
现在我们知道了期望周期的长度,我们也可以找到它的最小偏移量。一种可能的方法是尝试所有不同的偏移(具有不需要循环超过长度的优点,因为长度是已知的),但是,如果必要的话,可以进一步优化,例如通过推进尽可能多的当从最后处理列表时,允许最后重复句点(即最接近未反转序列的开头的那个)是部分的。
答案 1 :(得分:4)
我将从构建序列中值的直方图开始
因此,您只需列出序列中使用的所有数字(或其中的重要部分)并计算它们的出现次数。这是O(n)
,其中n
是序列大小。
对直方图进行升序排序
这是O(m.log(m))
,其中m
是不同值的数量。您还可以忽略最有可能出现偏移的低概率数字(count<treshold
)或仅进一步降低m
的不规则性。对于周期序列m <<< n
,如果序列是周期性的,则可以将其用作第一个标记。
找出期限
在直方图中,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
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
告诉您封装了多少实际句点,然后除以它。< / p>
查找偏移量
当你知道这段时间这很容易。只需从开始扫描第一项,看看是否再次出现。如果不记得位置。停止在序列的结尾或中间......或者在某些阈值上取得成功。这最多为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
(句点)。 您可能需要稍微使用阈值常量
答案 2 :(得分:0)
我必须做一次类似的事情。我使用蛮力和一些常识,解决方案不是很优雅,但它的工作原理。解决方案始终有效,但您必须在函数中设置正确的参数(k,j,con)。
您可能会注意到准确性取决于变量 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&lt; LEN(序列)强>