最长的二进制序列,没有相等的n长度子序列

时间:2016-02-12 19:17:31

标签: algorithm binary-data

我们正在寻找具有以下标准的算法。

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

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

例如,如果n = 3

由于重复的10111010子序列,

101无效。由于多次出现01010010也无效。 01101001有效,但显然不是最长的序列。

4 个答案:

答案 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重复11111重复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

如您所见,序列是使用字符串0000001111和附加的10000构建的,字符串1000111111需要不被检查。可以跳过所有大于2的偶数(数字9和13也可以)。

此代码示例显示了JavaScript中的一个简单实现。它快到N = 14左右,如果你有几分钟,它会给你所有1,048,595个字符,N = 20。

&#13;
&#13;
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;
&#13;
&#13;

除偶数之外还有其他数字可以跳过(例如示例中的数字9和13);如果你能预测这些数字,这当然会使算法更有效率,但我不确定那里有一个明显的模式。