Как вычислить последовательности Bruijn для алфавитов с не-силовым размером?

Я пытаюсь вычислить de Bruijn последовательности для алфавитов, которые имеют несколько символов, которые не являются степенью двух.

Для алфавитов с символами 2 ^ k вычисление последовательностей Bruijn легко: существует несколько простых правил, таких как "Prefernes" и "Prefer Opposites" , которые работают для генерации B (2, n). B (2 ^ k, n) точно совпадает с B (2, kn), если вы читаете 1s и 0s как двоичные коды для фактических символов в вашем алфавите. Например, вы можете интерпретировать B (2,8n) как более n-длинные последовательности байтов.

Предпочитают достаточно просто: напишите n нулей. Затем всегда записывайте одно, если это не приведет к повторению строки длиной n; иначе напишите нуль.

В настоящее время я не вижу, как обобщать такие правила на алфавиты с нестандартными размерами.

Существует общий метод вычисления последовательностей де Брейна через графики: пусть каждая последовательность n-длины, сгенерированная вашим алфавитом, будет node; поместите ребро из A в B, если самые правые n-1 символы A совпадают с крайними левыми n-1 символами B. Пометьте каждое ребро последним символом строки в вершине головы. Любой Эйлеровский путь через этот график будет генерировать последовательность де Брейна, а особая конструкция, которую мы использовали, гарантирует, что будет хотя бы один такой путь. Мы можем использовать Fleury Algorithm для (недетерминистически) построения эйлерова пути:

  • Выберите вершину.
  • Оставьте эту вершину через некоторое ребро и удалите это ребро, только выбрав ребра, удаление которых приведет к отключению вершины от графика, если альтернативы нет.
  • Добавьте в свою строку метку края, который вы только что удалили.
  • Перейти к 2, пока все ребра не исчезнут.

Результирующая строка будет последовательностью de Bruijn.

Этот алгоритм несколько сложнее реализовать, чем Prefer Ones. Простота Предпочтений заключается в том, что нужно только проконсультироваться с уже полученным результатом, чтобы определить, что делать. Есть ли простой способ обобщить Prefernes (или, возможно, предпочтительные противоположности) на алфавиты размеров без питания?

Ответы

Ответ 1

Это моя реализация на С++ алгоритма на рисунке 1 из документ Sawada и Ruskey:

void debruijn(unsigned int t,
              unsigned int p,
              const unsigned int k,
              const unsigned int n,
              unsigned int* a,
              boost::function<void (unsigned int*,unsigned int*)> callback)
{
  if (t > n) {
    // we want only necklaces, not pre-necklaces or Lyndon words
    if (n % p == 0) {
      callback(a+1, a+p+1);
    }
  }
  else {
    a[t] = a[t-p];

    debruijn(t+1, p, k, n, a, callback);

    for (unsigned int j = a[t-p]+1; j < k; ++j) {
      a[t] = j;
      debruijn(t+1, t, k, n, a, callback);
    }
  }
}

struct seq_printer {
  const std::vector<char>& _alpha;

  seq_printer(const std::vector<char>& alpha) : _alpha(alpha) {}

  void operator() (unsigned int* a, unsigned int* a_end) const {
    for (unsigned int* i = a; i < a_end; ++i) {
      std::cout << _alpha[*i];
    }
  }
};

...

std::vector<char> alpha;
alpha.push_back('a');
alpha.push_back('b');
alpha.push_back('c');

unsigned int* a = new unsigned int[N+1];
a[0] = 0;

debruijn(1, 1, alpha.size(), N, a, seq_printer(alpha));
if (N > 1) std::cout << alpha[0];
std::cout << std::endl;

delete[] a;

Полный текст статьи: Джо Савада и Фрэнк Руске, "Эффективный алгоритм создания ожерелий с фиксированной плотностью", SIAM Journal of Computing 29: 671-684, 1999.

Ответ 2

В соответствии с этой веб-страницей в комбинаторной группе отдела CS в UVic, результат Фридрихсена приводит к тому, что вы можете создать de Bruijn (фактически, лексикографически наименьший), объединяя "лексикографическую последовательность слова Линдона длин, делимых на n". Там даже исходный код для создания последовательности, которую вы можете запросить.

Ответ 3

Вас интересует только обобщение предпочтений или вам нужен не такой сложный алгоритм? Если последнее верно, то может быть полезно помочь рекурсивная реализация Frank Ruskey.

Год назад Я перевел этот файл в Ruby.

# De Bruijn sequence
# Original implementation by Frank Ruskey (1994)
# translated to C by Joe Sawada
# and further translated to Ruby by Jonas Elfström (2009)

@n=4
@k=10
@a=[0]
@sequence=[]

def debruijn(t, p, alike)
  if t>@n
    if @n%p==0
      1.upto(p) {|j| @sequence<<@a[j]}
    end
  else
    @a[t][email protected][t-p]
    if @a[t]>0
      debruijn(t+1,p,alike+1)
    else
      debruijn(t+1,p,alike)
    end
    (@a[t-p]+1).upto(@k-1) {|j|
      @a[t]=j
      debruijn(t+1,t,alike+1)
    }
  end
end

debruijn(1,1,0)
print @sequence.join

Uckelman заметил, что переменная alike ничего не делает. Следующее производит ту же последовательность.

@n=4
@k=10
@a=[0]
@sequence=[]

def debruijn(t, p)
  if t>@n
    if @n%p==0
      1.upto(p) {|j| @sequence<<@a[j]}
    end
  else
    @a[t][email protected][t-p]
    debruijn(t+1,p)
    (@a[t-p]+1).upto(@k-1) {|j|
      @a[t]=j
      debruijn(t+1,t)
    }
  end
end

debruijn(1,1)
print @sequence.join

Ответ 4

Алгоритм Дюваля делает то же самое итеративно (в Python на этот раз):

def debruijn(k, n):
    v = [0 for _ in range(n)]
    l = 1
    r = []
    while True:
        if n % l == 0:
            r.extend(v[0:l])
        for i in range(l, n):
            v[i] = v[i-l]
        l = n
        while l > 0 and v[l-1] >= k-1:
            l-=1
        if l == 0:
            break
        v[l-1] += 1
    return r

print(debruijn(3,5))

Ответ 5

или вы можете использовать:

def de_bruijn(k, n):
    a = [0] * k * n
    sequence = []
    def db(t, p):
        if t > n:
            if n % p == 0:
                for j in range(1, p + 1):
                    sequence.append(a[j])
        else:
            a[t] = a[t - p]
            db(t + 1, p)
            for j in range(a[t - p] + 1, k):
                a[t] = j
                db(t + 1, t)
    db(1, 1)
    return sequence

print de_bruijn(2,9)