【问题标题】:Find a period of eventually periodic sequence找到一个最终周期序列的周期
【发布时间】:2016-05-23 05:18:48
【问题描述】:

简短说明。

我有一个数字序列[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]。如您所见,从第三个值开始,序列是周期性的,周期为 [0, 0, 1, 1, 2, 3, 7]

我正在尝试从这个序列中自动提取这段时间。问题是我既不知道周期的长度,也不知道序列从哪个位置变为周期。

完整解释(可能需要一些数学知识)

我正在学习组合博弈论,该理论的基石需要计算博弈图的Grundy values。这会产生无限序列,在许多情况下会变成eventually periodic

我找到了一种有效计算粗略值的方法(它返回给我一个序列)。我想自动提取这个序列的偏移量和周期。我知道看到序列的一部分[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]

还有什么更高效的吗?

【问题讨论】:

  • 除非有一个已知的周期长度上限,否则您不能这样做。一个看似周期性的序列可能会在十亿个元素之后偏离模式。
  • @Henry,谢谢,但我知道并在我的问题中解释了它:I am aware that seeing a part of the sequence [1, 2, 3, 1, 2, 3] you can't be sure that [1, 2, 3] is a period (who knows may be the next number is 4, which breaks the assumption), but I am not interested in such intricacies (I assume that the sequence is enough to find the real period)
  • @SalvadorDali dfa 代表“确定性有限自动机”。无论如何,这里已经问过这个问题:stackoverflow.com/questions/18620942/…
  • 你考虑过自相关吗?
  • @MBo 不,我什至不知道这个词。乍一看,它看起来很有希望。会看看。

标签: algorithm math sequence


【解决方案1】:
  1. 我将从构建序列中值的直方图开始

    因此,您只需列出按顺序使用的所有数字(或其中的重要部分)并计算它们的出现次数。这是O(n),其中n 是序列大小。

  2. 对直方图进行升序排序

    这是O(m.log(m)),其中m 是不同值的数量。您也可以忽略低概率数字 (count&lt;treshold),它们最有可能出现在偏移中,或者只是进一步降低 m 的不规则性。对于周期性序列m &lt;&lt;&lt; n,因此如果序列是否周期性,您可以将其用作第一个标记。

  3. 找出期间

    柱状图中,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)=2GCD(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(句点)中。 您可能需要稍微调整一下阈值常量

【讨论】:

  • 听起来很有趣 +1,谢谢。明天再仔细看看。
【解决方案2】:

备注:如果有一个长度为L的句号P1,那么也有一个句号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)


这意味着我们可以通过对序列进行倒序处理来找到一个周期的最小长度,并使用距离末尾的零偏移来搜索最小周期。一种可能的方法是简单地在反向列表上使用您当前的算法,而不需要对偏移量进行循环。

现在我们知道了所需周期的长度,我们还可以找到它的最小偏移量。一种可能的方法是尝试所有不同的偏移量(优点是不需要在长度上循环,因为长度是已知的),但是,如果需要,可以进行进一步优化,例如通过尽可能多地推进从结尾处处理列表时可能会允许最后的句点重复(最接近未反转序列开头的那个)。

【讨论】:

    【解决方案3】:

    我不得不做一次类似的事情。我使用了蛮力和一些​​常识,解决方案不是很优雅,但它有效。该解决方案始终有效,但您必须在函数中设置正确的参数 (k,j, con)。

    • 序列保存为变量seq中的列表。
    • k 是序列数组的大小,如果您认为您的序列需要很长时间才能变为周期性,则将此 k 设置为一个大数字。
    • 变量 found 将告诉我们数组是否通过了周期为 j 的周期性测试
    • j 是句号。
    • 如果您期望一个很大的周期,那么您必须将 j 设置为一个很大的数字。
    • 我们通过检查序列的最后 j+30 个数字来测试周期性。
    • 句号 (j) 越大,我们必须检查的越多。
    • 一旦通过其中一项测试,我们就会退出函数并返回较小的句点。

    您可能会注意到,准确性取决于变量 jk,但如果您将它们设置为非常大的数字,它始终是正确的。

    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
    

    其中 ma​​x_period 是您要查找的最大周期,test_numb 是您要测试的序列数,越大越好,但您有使 ma​​x_period+test_numb

    【讨论】:

      猜你喜欢
      • 2017-09-26
      • 1970-01-01
      • 2012-10-19
      • 1970-01-01
      • 2014-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多