Как эффективно создать все двоичные последовательности с одинаковым количеством нулей и единиц?

10

Двоичная последовательность длины просто упорядоченная последовательность , так что каждый является либо или . Чтобы сгенерировать все такие двоичные последовательности, можно использовать очевидную структуру двоичного дерева следующим образом: корень «пустой», но каждый левый дочерний элемент соответствует добавлению к существующей строке, а каждый правый дочерний элемент - , Теперь каждая двоичная последовательность - это просто путь длиной начинающийся с корня и заканчивающийся на листе.nx1,,xnxj0101n+1

Вот мой вопрос:

Можем ли мы добиться большего успеха, если хотим создать только двоичные строки длиной которые имеют ровно нулей и единиц?2nnn

Под «мы можем сделать лучше» я имею в виду, что мы должны иметь меньшую сложность, чем глупый алгоритм, который сначала строит все дерево выше, а затем пытается найти те пути с равным количеством «левого» и «правого» ребер.

Видит Нанда
источник
Можете ли вы найти способ эффективно генерировать все строго возрастающие последовательности из чисел в диапазоне от 1 до ? n2n
Корнелиус Бранд
Я не могу комментировать сложность, но мой наивный алгоритм генерировал бы прогулки по краям квадратной сетки от одного угла до диагонального, используя своего рода схему возврата. Это означает, что 01 и 10 оказываются в той же позиции (в отличие от вашего дерева), но с возвратом мы знаем эту историю.
Хендрик Ян
С другой стороны, вот реализация Java -iterator . (nk)
Пол GD

Ответы:

6

Очевидно, есть двоичных строк длиной . Чтобы пройти через двоичный файл, алгоритм должен посетить каждый узел один раз, т.е. 4n2n

i=02n2i=22n+11=O(4n)
шаги.

Давайте рассмотрим рекурсивный алгоритм, который обходит дерево, которое вы описали, но подсчитывает количество единиц и нулей на своем пути, то есть он будет проходить только хорошую часть дерева.
Но сколько таких двоичных строк с 0 и 1 есть? Мы выбираем 1 для наших строк длиной и используем формулу Стирлинга на шаге 2: nnn2n

(2nn)=(2n)!(n!)2=4nπn(1+O(1/n))

РЕДАКТИРОВАТЬ
Благодаря комментариям Питера Шора мы также можем проанализировать количество шагов, необходимых для второго алгоритма, который считает 1 и 0. Я цитирую его комментарий снизу:

Мы хотим найти все двоичные последовательности с точно 0 и 1. Мы пересекаем двоичное дерево, где каждый узел является последовательностью не более 0 и 1. Нам не нужно посещать какие-либо узлы с более чем 0 или более 1. сколько узлов нам нужно посетить? Есть строки с 0 и 1. Суммируя это по всем даетnn2nnn(i+ji)iji,jni=0nj=0n(i+ji)=(2n+2n+1)1 , Теперь нам нужно посетить каждый из этих узлов с постоянной средней стоимостью за узел. Мы можем сделать это, сначала посетив каждого левого ребенка, а затем каждого правого ребенка.

Снова используя формулу Стирлинга, мы получаем как время работы нового алгоритма.

(2n+2n+1)1=4n+11n+1(1+O(1/n))1=O(4nn)
tranisstor
источник
Вы должны быть немного осторожнее. Предположительно после генерации каждой строки мы обрабатываем ее за время . Так что простая обработка всех сбалансированных строк занимает время . Если оптимизированный «глупый» алгоритм генерации действительно равен , то переключиться на более умный алгоритм нечего, кроме возможностей для ошибок. Ω(n)Ω(4nn)O(4n)
Ювал Фильмус
@Yuval Filmus: Что именно вы подразумеваете под «обработкой строк»? Если вы имеете в виду время, потраченное на вывод, которое, безусловно, равно , то вы должны учитывать этот фактор также во время работы "глупого" алгоритма, который тогда равен . Θ(n)O(4nn)
транзистор
2
Я хотел сказать, что если вы обеспокоены разницей между и , то как минимум вы должны указать правильное время выполнения; недостаточно для выявления разницы потенциалов между двумя алгоритмами. Кроме того, вы должны быть осторожны при анализе предложенного вами нового алгоритма, чтобы убедиться, что эти «незначительные» малые факторы не делают его медленнее, чем тривиальный алгоритм. 4n4n/nO~(4n)
Ювал Фильмус
2
Как построить только «хорошую часть» дерева, не добавляя при этом и «плохие части»? Вам нужно включить все узлы дерева, которые не имеют более левых или правых потомков на пути от корня до них. Это работает, но вам нужен дополнительный аргумент, чтобы показать, что это работает. В частности, вам нужно использовать формулу . nni=0nj=0n(i+ji)=(2n+2n+1)1
Питер Шор
2
Мы хотим найти все двоичные последовательности с точно 0 и 1. Мы пересекаем двоичное дерево, где каждый узел является последовательностью не более 0 и 1. Нам не нужно посещать какие-либо узлы с более чем 0 или более 1. сколько узлов нам нужно посетить? Есть строк с 0 и 1. Суммируя это по всем даетnn2nnn(i+ji)iji,jni=0nj=0n(i+ji)=(2n+2n+1)1 , Теперь нам нужно посетить каждый из этих узлов с постоянной средней стоимостью за узел. Мы можем сделать это, сначала посетив каждого левого ребенка, а затем каждого правого ребенка.
Питер Шор
2

Возможно, я хладнокровен, но в первоначальном вопросе был задан способ генерации всех «сбалансированных» двоичных последовательностей длины 2n, который был бы более эффективным, чем обход дерева всех двоичных последовательностей длины 2n и вывод только тех, которые были сбалансированы. Так зачем вообще использовать дерево?

Вот псевдокод для рекурсивного алгоритма, который генерирует все такие последовательности (ключевое слово «yield» отправляет последовательность на выход):

function all-balanced(n) {
  all-specified( "", n, n );
};

function all-specified( currentString, zeroes, ones ) {

  if (zeroes == 0) {
    for i = 0 to ones {
      currentString += "1";
    };
    yield currentString;
    return;
  };

  if (ones == 0) {
    for i = 0 to zeroes {
      currentString += "0";
    };
    yield currentString;
    return;
  };

  all-specified( currentString+"0", zeroes-1, ones );
  all-specified( currentString+"1", zeroes, ones-1 );
  return;
};

Если я что-то неправильно понимаю, пожалуйста, скажите мне, но мне кажется, что это наиболее эффективный ответ на поставленную проблему, который никогда не определял использование дерева.

afeldspar
источник