我们正在寻找具有以下标准的算法。
输入是一个任意正整数(n
),表示比较子序列的长度。
我们搜索最长的二进制序列,其中不包含相等的n长度子序列。匹配的相等序列可以重叠(当匹配必须是不相交的时候也是一个有趣的问题)。输出将是这个位序列。
例如,如果n = 3
:
10111010
子序列, 101
无效。由于多次出现01010
,010
也无效。 01101001
有效,但显然不是最长的序列。
答案 0 :(得分:2)
谷歌搜索二进制De Bruijn序列算法,我找到了一个你可以实际告诉发生了什么的那个。被称为“FKM算法”(在Fredricksen,Kessler和Maiorana之后),它使用“项链前缀”方法找到字典上最少的De Bruijn序列。我将解释使用n = 4的示例。
首先,创建长度为n的所有二进制序列,即0到2之间的所有数字 n -1:
<00> 00,001,001,0010,0011,0100,0101,0110,0111,1000,1001,1010, 1011,1100,1101,1110,1111
然后,删除不是最低旋转的序列,例如0110
可以旋转到更小的0011
:
<00> 0000,0001,0011,0101,0111,1111
(您会注意到除了0000
之外的所有偶数除去0111
以及除1111
之外的所有大于0101
的数字,这有助于简化代码。)< / i>
然后将序列减少到它们的“非周期性前缀”,即如果它们是较短序列的重复,则使用较短的序列;例如01
重复1111
,1
重复function DeBruijnFKM(n) {
var seq = "0"; // start with 0 precalculated
for (var i = 1; i < n; i++) { // i = number of significant bits
var zeros = "", max = Math.pow(2, i);
for (var j = n; j > i; j--) zeros += "0"; // n-i leading zeros
for (var k = i > 1 ? max / 2 + 1 : 1; k < max; k += 2) { // odd numbers only
var bin = k.toString(2); // bin = significant bits
if (isSmallestRotation(zeros, bin)) {
seq += aperiodicPrefix(zeros, bin);
}
}
}
return seq + Math.pow(2, n - 1).toString(2); // append 2^N-1 and trailing zeros
function isSmallestRotation(zeros, bin) {
var len = 0, pos = 1; // len = number of consecutive zeros in significant bits
for (var i = 1; i < bin.length; i++) {
if (bin.charAt(i) == "1") {
if (len > zeros.length) return false; // more zeros than leading zeros
if (len == zeros.length
&& zeros + bin > bin.substr(pos) + zeros + bin.substr(0, pos)) {
return false; // smaller rotation found
}
len = 0;
pos = i + 1;
}
else ++len;
}
return true;
}
function aperiodicPrefix(zeros, bin) {
if (zeros.length >= bin.length) return zeros + bin; // too many leading zeros
bin = zeros + bin;
for (var i = 2; i <= bin.length / 2; i++) { // skip 1; not used for 0 and 2^N-1
if (bin.length % i) continue;
var pre = bin.substr(0, i); // pre = prefix of length i
for (var j = i; j < bin.length; j += i) {
if (pre != bin.substr(j, i)) break; // non-equal part found
}
if (j == bin.length) return pre; // all parts are equal
}
return bin; // no repetition found
}
}
document.write(DeBruijnFKM(10));
:
0,0001,0011,01,0111,1
加入序列,你就有了De Bruijn序列:
0000100110101111
对于非循环序列,添加n-1个零:
0000100110101111000
(更多信息:F. Ruskey, J. Sawada, A. Williams: "De Bruijn Sequences for Fixed-Weight Binary Strings"和B. Stevens,A。Williams:“最酷的二进制字符序列”,来自:“Fun With Algorithms”,2012,pp.327-328)< / I>
我很想知道FKM与我的其他算法相比如何表现,所以我写了这个相当笨拙的JavaScript实现。它确实要快得多,并且在一秒钟内生成N = 20的1,048,595个数字序列。用严肃的语言,这应该非常快。
Field.this
答案 1 :(得分:0)
Using the free Minizinc constraint solver, you can write the search for a given sequence length as follows:
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];
For n=3
, the longest sequence is
1110100011
k=11
yields UNSATISFIABLE
It took 71ms to find the sequence on k=10 bits for sub-sequence length n=3. For sub-sequence length n=9, the total sequence of 520 bits was found in 6.1s.
答案 2 :(得分:0)
n
- 位linear feedback shift register,如果它可以在最长时间运行,则必须满足大多数要求。这是因为它的操作状态是测试窗口的大小。如果有一个模式不止一次发生,那么它的状态将恢复到之前的状态,并且它的周期将比预期的短。
不幸的是,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);
}
如果您需要一个长度超过64位的测试窗口,那么只需使用64位(或者如果您必须将算术扩展到128位)。除了64位之外,在发现位串不是最大长度之前,其他一些资源将耗尽。
为了完整性,使用奇偶校验功能:
int parity(uint64_t m) {
int p = 0;
while (m != 0) {
m &= m - 1;
p ^= 1;
}
return p;
}
n = 3,4和5的输出:
3: 0001011100
4: 0000100110101111000
5: 000001001011001111100011011101010000
答案 3 :(得分:0)
在找到FKM算法之前,我用一个简单的递归算法来尝试,它尝试0和1的每个组合并返回(按字典顺序)第一个结果。我发现这种方法很快耗尽内存(至少在浏览器的JavaScript中),所以我尝试根据这些观察结果提出改进的非递归版本:
通过运行从0到2 N -1的N长度二进制字符串,并检查它们是否已经存在于序列中,如果不存在,则检查它们是否部分重叠在序列结束时,您可以使用N长度块而不是每比特来构建按字典顺序排列的最小二进制De Bruijn序列。
你只需要经过最长为2 N-1 -1的N长度二进制字符串,然后在不重叠的情况下追加2 N-1 。以&#39; 1&#39;开头的N长度字符串无需检查。
您可以跳过大于2的偶数;它们是已经在序列中的较小数字的位移版本。需要数字2以避免1和3错误重叠;在代码方面,您可以通过启动已经存在0,1和2的序列来解决此问题(例如,对于N = 5,例如0000010
),然后迭代从3开始的每个奇数。
N = 5的例子:
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
如您所见,序列是使用字符串00000
到01111
和附加的10000
构建的,字符串10001
到11111
需要不被检查。可以跳过所有大于2的偶数(数字9和13也可以)。
此代码示例显示了JavaScript中的一个简单实现。它快到N = 14左右,如果你有几分钟,它会给你所有1,048,595个字符,N = 20。
function binaryDeBruijn(n) {
var zeros = "", max = Math.pow(2, n - 1); // check only up to 2^(N-1)
for (var i = 1; i < n; i++) zeros += "0";
var seq = zeros + (n > 2 ? "010" : "0"); // start with 0-2 precalculated
for (var i = 3; i < max; i += 2) { // odd numbers from 3
var part = (zeros + i.toString(2)).substr(-n, n); // binary with leading zeros
if (seq.indexOf(part) == -1) { // part not already in sequence
for (var j = n - 1; j > 0; j--) { // try partial match at end
if (seq.substr(-j, j) == part.substr(0, j)) break; // partial match found
}
seq += part.substr(j, n); // overlap with end or append
}
}
return seq + "1" + zeros; // append 2^(N-1)
}
document.write(binaryDeBruijn(10));
&#13;
除偶数之外还有其他数字可以跳过(例如示例中的数字9和13);如果你能预测这些数字,这当然会使算法更有效率,但我不确定那里有一个明显的模式。