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