Algorithm 无等长n子序列的最长二进制序列

Algorithm 无等长n子序列的最长二进制序列,algorithm,binary-data,Algorithm,Binary Data,我们正在寻找一种符合以下标准的算法 输入为任意正整数(n),表示比较子序列的长度 我们搜索最长的二进制序列,它不包含等长的n个子序列。匹配的相等序列可以重叠(当匹配必须不相交时,这也是一个有趣的问题)。输出将是这个位序列 例如,如果n=3: 10111010无效,因为重复了101子序列01010也无效01101001有效,但显然不是最长的序列。使用自由约束解算器,可以按如下方式编写给定序列长度的搜索: int: n = 3; int: k = pow(2,n)+n-1; array[1..k]

我们正在寻找一种符合以下标准的算法

输入为任意正整数(
n
),表示比较子序列的长度

我们搜索最长的二进制序列,它不包含等长的n个子序列。匹配的相等序列可以重叠(当匹配必须不相交时,这也是一个有趣的问题)。输出将是这个位序列

例如,如果
n=3

10111010
无效,因为重复了
101
子序列<由于多次出现
010
,代码>01010也无效
01101001
有效,但显然不是最长的序列。

使用自由约束解算器,可以按如下方式编写给定序列长度的搜索:

int: n = 3;
int: k = pow(2,n)+n-1;

array[1..k] of var 0..1: a;

constraint
  forall (i in 1..k-n) (
    forall (j in i+1..k-n+1) (
      exists (x in 0..n-1)(
        a[i+x] != a[j+x]
      )
    )
  );

solve satisfy;

output [show(a[m]) | m in 1..k];
对于
n=3
,最长的序列为

1110100011

k=11
收益率
不可满足


对于子序列长度n=3,查找k=10位上的序列需要71毫秒。对于子序列长度n=9,在6.1s中可以找到520位的总序列。

在谷歌上搜索二进制De Bruijn序列算法,我发现这一个可以告诉你发生了什么。被称为“FKM算法”(以Fredricksen、Kessler和Maiorana的名字命名),它使用“项链前缀”方法来查找词典学上最小的De Bruijn序列。我将使用n=4的示例进行解释

首先,创建长度为n的所有二进制序列,即从0到2n-1的所有数字:

0000、0001、0010、0011、0100、0101、0110、0111、1000、1001、1010、, 1011100110111011111

然后,移除未处于最低旋转位置的序列,例如,
0110
可以旋转到
0011
更小的位置:

0000、0001、0011、0101、01111、1111

(您会注意到,这将删除除
0000
之外的所有偶数,以及除
1111
之外的所有大于
0111
的数字,这有助于简化代码。)

然后将序列减少为其“非周期前缀”,即,如果它们是较短序列的重复,则使用该较短序列;e、 g.
0101
01
的重复,
1111
1
的重复:

0,0001,0011,01,0111,1

加入序列,就有了一个De Bruijn序列:

000010110101111

对于非循环序列,添加n-1个零:

000010110101111000

(进一步信息:和B.Stevens,A.Williams:“二进制字符串的最酷顺序”,摘自《算法的乐趣》,2012年,第327-328页)


我很想看看FKM与我的其他算法相比表现如何,所以我编写了这个相当笨拙的JavaScript实现。它确实要快得多,并且在一秒钟内生成N=20的1048595位序列。在严肃的语言中,这应该是非常快的

函数DeBruijnFKM(n){
var seq=“0”;//从预先计算的0开始
对于(var i=1;ii;j--)零+=“0”//n-i前导零
对于(var k=i>1?max/2+1:1;kzeros.length)返回false;//零多于前导零
如果(len==0.length
&&零+仓>仓子仓(位置)+零+仓子仓(0,位置)){
返回false;//找到较小的旋转
}
len=0;
pos=i+1;
}
else++len;
}
返回true;
}
函数非周期性refix(零,bin){
if(zeros.length>=bin.length)返回零+bin;//前导零太多
bin=零+bin;
对于(var i=2;i一个
n
-位,如果它能在最大周期内运行,必须满足大多数要求。这是因为它的运行状态是测试窗口的大小。如果一个位模式出现一次以上,那么它的状态将恢复到以前的状态,并且它的周期将比预期的短

不幸的是,LFSR不能在零状态下运行。要克服这一问题,只需在位字符串的开头附加零即可

void generate(int n) {
  static const uint64_t polytab[64] = {
    0x2, 0x2, 0x6, 0xc,
    0x18, 0x28, 0x60, 0xc0,
    0x170,0x220, 0x480, 0xa00,
    0x1052, 0x201a, 0x402a, 0xc000,
    /* table can be completed from: 
     * http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
     */
  };
  uint64_t poly = polytab[n];
  uint64_t m = ~(-2ll << (n - 1));
  uint64_t s = 1;
  for (i = 0; i < n; i++) emit(0);
  do {
    emit(s & 1);
    s <<= 1;
    s = (s + parity(s & poly)) & m;
  } while (s != 1);
}
n=3、4和5的输出:

3: 0001011100
4: 0000100110101111000
5: 000001001011001111100011011101010000

在找到FKM算法之前,我尝试了一个简单的递归算法,该算法尝试0和1的每一个组合,并返回(词典)第一个结果。我发现这个方法很快就会耗尽内存(至少在浏览器的JavaScript中),因此我试图根据这些观察结果提出一个改进的非递归版本:

  • 通过运行从0到2N-1的N长度二进制字符串,并检查它们是否已经存在于序列中,如果不存在,则检查它们是否与序列的结尾部分重叠,您可以使用N长度的块而不是每比特构建字典最小的二进制De Bruijn序列

  • 您只需要遍历长度为N的二进制字符串(最多为2N-1-1),然后添加2N-1而不重叠。不需要检查以“1”开头的N长度字符串

  • Y
    3: 0001011100
    4: 0000100110101111000
    5: 000001001011001111100011011101010000
    
     0    00000
     1     00001
     2      00010
     3          00011
     4      (00100)
     5               00101
     6          (00110)
     7                    00111
     8       (01000)
     9                 (01001)
    10               (01010)
    11                         01011
    12           (01100)
    13                           01101
    14                    (01110)
    15                              01111
                                        +10000
    =>    000001000110010100111010110111110000