找到最终周期序列的一段时间

时间:2016-05-23 05:18:48

标签: algorithm math sequence

简短说明。

我有一系列数字[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]

还有什么更有效的吗?

3 个答案:

答案 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)

  1. 我将从构建序列中值的直方图开始

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

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

    这是O(m.log(m)),其中m是不同值的数量。您还可以忽略最有可能出现偏移的低概率数字(count<treshold)或仅进一步降低m的不规则性。对于周期序列m <<< 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告诉您封装了多少实际句点,然后除以它。< / p>

  5. 查找偏移量

    当你知道这段时间这很容易。只需从开始扫描第一项,看看是否再次出现。如果不记得位置。停止在序列的结尾或中间......或者在某些阈值上取得成功。这最多为O(n),最后记住的位置是offset中的最后一项。

  6. [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)。

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

您可能会注意到准确性取决于变量 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(序列)