¿Cómo se producen eficientemente todas las secuencias binarias con igual número de 0 y 1?

10

Una secuencia binaria de longitud es solo una secuencia ordenada para que cada sea o . Para generar todas esas secuencias binarias, se puede usar la estructura de árbol binario obvia de la siguiente manera: la raíz está "vacía", pero cada hijo izquierdo corresponde a la adición de a la cadena existente y cada hijo derecho a un . Ahora, cada secuencia binaria es simplemente una ruta de longitud comienza en la raíz y termina en una hoja.x 1 , , x n x j 0 1 0 1 n + 1nx1,,xnxj0101n+1

Aquí está mi pregunta:

¿Podemos hacerlo mejor si solo queremos generar todas las cadenas binarias de longitud que tienen exactamente ceros unos?n n2nnn

Por "podemos hacerlo mejor", quiero decir que deberíamos tener una complejidad menor que el algoritmo tonto que primero construye todo el árbol de arriba y luego trata de encontrar esos caminos con el mismo número de bordes "izquierdo" y "derecho".

Vidit Nanda
fuente
¿Puedes encontrar una manera de generar eficientemente todas las secuencias estrictamente crecientes de números en el rango de 1 a ? 2 nn2n
Cornelius Brand
No puedo comentar sobre la complejidad, pero mi ingenuo algoritmo generaría los recorridos a lo largo de los bordes de una cuadrícula cuadrada de una esquina a una diagonal, utilizando una especie de esquema de retroceso. Eso significa que 01 y 10 terminan en la misma posición (a diferencia de su árbol) pero con retroceso conocemos esta historia.
Hendrik Jan
En una nota posiblemente diferente, aquí hay una implementación de Java de un -iterator . (nk)
Pål GD

Respuestas:

6

Obviamente hay cadenas binarias de longitud . Para atravesar el binario, un algoritmo debe visitar cada nodo una vez, es decir, debe hacer pasos.4n2n

i=02n2i=22n+11=O(4n)

Consideremos un algoritmo recursivo que atraviesa el árbol que describió, pero cuenta el número de unos y ceros en su camino, es decir, solo atravesará la parte buena del árbol.
Pero, ¿cuántas cadenas binarias con 0 y 1 hay? Elegimos 1 para nuestras cadenas de longitud y utilizamos la fórmula de Stirling en el paso 2: nnn2n

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

EDITAR
Gracias a los comentarios de Peter Shor, también podemos analizar la cantidad de pasos necesarios para el segundo algoritmo, que cuenta los 1 y los 0. Cito su comentario desde abajo:

Queremos encontrar todas las secuencias binarias con exactamente 0 y 1. Atravesamos el árbol binario donde cada nodo es una secuencia de como máximo 0's y 1's. No necesitamos visitar ningún nodo con más de 0 o más de 1. ¿Cuántos nodos necesitamos visitar? Hay cadenas con 0's y 1's. Resumiendo esto sobre todo da . Ahora, necesitamos visitar cada uno de estos nodos a un costo promedio constante por nodo. Podemos hacer esto visitando primero a cada niño izquierdo y luego a cada niño derecho.nn2nnn(i+ji)iji,jni=0nj=0n(i+ji)=(2n+2n+1)1

Usando la fórmula de Stirling nuevamente obtenemos como el tiempo de ejecución del nuevo algoritmo.

(2n+2n+1)1=4n+11n+1(1+O(1/n))1=O(4nn)
tranisstor
fuente
Tienes que ser un poco más cuidadoso. Presumiblemente después de generar cada cadena, la procesamos en tiempo . Por lo tanto, el procesamiento de todas las cadenas equilibradas lleva tiempo . Si el algoritmo de generación "tonto" optimizado es de hecho , entonces no hay mucho que ganar cambiando a un algoritmo más inteligente, aparte de las oportunidades de errores. Ω(n)Ω(4nn)O(4n)
Yuval Filmus
@Yuval Filmus: ¿Qué quieres decir exactamente con "procesamiento de cadenas"? Si te refieres al tiempo dedicado a la salida, que ciertamente es , entonces debes considerar ese factor también en el tiempo de ejecución del algoritmo "tonto", que entonces es . Θ(n)O(4nn)
tranisstor
2
Mi punto era que si le preocupa la diferencia entre y , como mínimo debe indicar los tiempos de ejecución correctos; no es suficiente para revelar ninguna diferencia potencial entre los dos algoritmos. Además, debe tener cuidado al analizar su nuevo algoritmo propuesto, para ver que estos pequeños factores "insignificantes" no lo hacen más lento que el algoritmo trivial. 4n4n/nO~(4n)
Yuval Filmus
2
¿Cómo se construye la "parte buena" del árbol sin tener que incluir también las "partes malas"? Debe incluir todos los nodos del árbol que no tengan más de hijos izquierdos o hijos derechos en la ruta desde la raíz hasta ellos. Esto funciona, pero necesita un argumento adicional para demostrar que funciona. Específicamente, debe usar la fórmula . nni=0nj=0n(i+ji)=(2n+2n+1)1
Peter Shor
2
Queremos encontrar todas las secuencias binarias con exactamente 0 y 1. Atravesamos el árbol binario donde cada nodo es una secuencia de como máximo 0's y 1's. No necesitamos visitar ningún nodo con más de 0 o más de 1. ¿Cuántos nodos necesitamos visitar? Hay cadenas con 0's y 1's. Resumiendo esto sobre todo da . Ahora, necesitamos visitar cada uno de estos nodos a un costo promedio constante por nodo. Podemos hacer esto visitando primero a cada niño izquierdo y luego a cada niño derecho.nn2nnn(i+ji)iji,jni=0nj=0n(i+ji)=(2n+2n+1)1
Peter Shor
2

Tal vez estoy siendo pesado, pero la pregunta original pedía una forma de generar todas las secuencias binarias "equilibradas" de longitud 2n que fuera más eficiente que atravesar un árbol de todas las secuencias binarias de longitud 2n y generar solo aquellas que estaban equilibradas. Entonces, ¿por qué usar un árbol?

Aquí hay un pseudocódigo para un algoritmo recursivo que genera todas esas secuencias (la palabra clave "rendimiento" envía una secuencia a la salida):

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;
};

Si no estoy entendiendo algo, por favor dígame, pero me parece que esta es la respuesta más eficiente al problema realmente planteado, que nunca especificó el uso de un árbol.

afeldspar
fuente