¿Es un árbol linealizado? (Ancho-primera edición)

11

Antecedentes

Un árbol sin etiquetar puede verse así:

   o
 / | \
o  o  o
|    / \
o   o   o

Para linealizar este árbol, primero etiquetamos cada nodo ocon su número de nodos secundarios:

   3
 / | \
1  0  2
|    / \
0   0   0

y luego escribe los números en una lista de una manera impresionante, es decir, línea por línea y de izquierda a derecha:

[3, 1, 0, 2, 0, 0, 0]

Esta es una representación única e inequívoca del árbol de arriba, lo que significa que no hay dos árboles puros diferentes tendrán las mismas linealizaciones y que podemos reconstruir el árbol original de la lista.

Aunque cada árbol corresponde a una determinada lista de enteros, no cada lista de enteros representa un árbol linealizado válido: por ejemplo [2, 0, 0, 0], no representa un árbol válido, si intentamos deslinealizarlo terminamos con este árbol

[2,0,0,0] -> 2 [0,0,0] -> 2 [0,0] -> 2 [0]
            / \          / \        / \
                        0          0   0

pero todavía tengo una 0izquierda en la lista y ningún lugar para ponerla. Del mismo modo, [2, 0]tampoco es una linealización de árbol válida, ya que el árbol des-linealizado tiene un punto hijo vacío:

  2
 / \
0

Tarea

Dada una lista entera, decida si es una linealización válida de un árbol utilizando la menor cantidad de bytes posible. Puede escribir un programa completo o una función.

Entrada: una lista no vacía de enteros no negativos.

Salida: un valor verdadero si la lista es una linealización de un árbol, un valor falso de lo contrario.

Casos de prueba

Verdad
[0]
[2, 0, 0]
[1, 1, 1, 1, 1, 0]
[3, 1, 0, 2, 0, 0, 0]
[2, 0, 2, 2, 0, 0, 2, 0, 0]
[3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0]
[1, 5, 3, 0, 2, 1, 4, 0, 1, 0, 0, 2, 1, 0, 0, 1, 1, 0, 0, 0, 0, 2, 1, 0, 0, 1, 0]
Falsa
[0, 1]
[2, 0]
[2, 0, 0, 0]
[1, 0, 1]
[3, 2, 1, 0]
[2, 0, 0, 2, 0, 0]
[4, 1, 0, 3, 0, 0, 0, 0]
[4, 2, 0, 3, 1, 0, 0, 0, 0, 0]
Laikoni
fuente

Respuestas:

4

Haskell, 44 bytes

f[n:k]=iterate f[k]!!n
f _=[]
g x=f[x]==[[]]

Define una función gque toma una lista y devuelve un valor booleano. Véalo pasar todos los casos de prueba .

Explicación

Esto se basa en el hecho de que las linealizaciones primero en profundidad y primero en anchura producen las mismas matrices. Ver las respuestas de Martin para más detalles; Básicamente, ambos dan la misma condición aritmética en la matriz.

La función frecibe la lista de entrada envuelta en una lista singleton. Aparece un número nde la lista, luego se llama a sí mismo nveces en la lista restante para procesar los elementos secundarios del nodo emergente (primero la profundidad). Aparecer los resultados de la lista vacía [], que uso como estado de error. La función gcomprueba que el resultado final es [[]]el estado único no erróneo sin nodos no procesados. Si Haskell se tipeó débilmente, podría usar 0o algo así como el estado de error, y no tendría que incluir la entrada en otra lista.

Zgarb
fuente
3

Mathematica, 38 bytes

Last@#<0<=Min@Most@#&@Accumulate[#-1]&

La idea básica es que hacemos un seguimiento de varios nodos para llenar. Cada elemento de la lista usa un nodo y agrega tantos como tiene hijos. Entonces cada elemento icambia la cuenta total por i-1. Este recuento está desactivado en uno, porque debería comenzar desde 1(la raíz), no 0.

Para que el árbol sea válido, a) nunca podemos ir abajo a lo 0largo de la lista, porque no tendríamos ningún lugar para colocar el nodo actual yb) necesitamos terminar -1al final, de lo contrario nos quedan nodos no utilizados.

Obtenemos este total acumulado de nodos restantes con Accumulate[#-1](que calcula las sumas de prefijos de la lista de entrada menos uno). Y luego verificamos que el último elemento y solo el último elemento esté -1con:

Last@#<0<=Min@Most@#

Tenga en cuenta que comprobar que el último elemento es negativo es suficiente, ya que nunca podemos disminuir más de lo que 1, por lo tanto, si los últimos valores -2fueran o menores, sería imposible que el mínimo los otros no fueran negativos.

Martin Ender
fuente
2

Retina , 29 bytes

\d+
$*
^(?<-1>(1)*,)*$(?(1)!)

Pruébalo en línea! (La primera línea habilita un conjunto de pruebas separado por salto de línea).

Explicación

La idea básica es la misma que la de mi respuesta de Mathematica : hacemos un seguimiento del total acumulado de nodos restantes, nos aseguramos de que nunca baje de cero sino que termine en cero. Sin embargo, la forma en que esto se implementa con regex es muy diferente.

\d+
$*

Esto simplemente convierte la entrada a unario, convirtiendo cada número entero nen n1s.

^(?<-1>(1)*,)*$(?(1)!)

Aquí es donde sucede la verdadera magia. Es una expresión regular bastante corta que solo coincide con árboles válidos, pero su mecánica es bastante sutil.

Estoy usando grupos de equilibrio para realizar un seguimiento de la cantidad de nodos, que son una forma de trabajar con pilas dentro de la expresión regular.

En primer lugar, por supuesto, una pila de este tipo nunca puede tener una profundidad negativa, por lo que realmente no podemos terminar con una representación de -1al final, como lo hacemos en la solución de Mathematica. Sin embargo, podemos notar que el elemento final de la entrada tiene que ser cero en una pila válida (de lo contrario no podríamos terminar -1). Resulta que lo que realmente salva bytes para comprobar tanto que terminamos en cero y con cero nodos restantes.

Así que aquí hay un desglose de la expresión regular:

^        e# Anchor the match to the beginning of the string.
(?<-1>   e# Each repetition of this group will match one number. 
         e# We can ignore the <-1> for now.
  (1)*   e#   Match each unary digit of the current number, pushing
         e#   a capture onto stack 1. This increments our total of
         e#   remaining nodes by 1 for each child.
  ,      e#   Match a comma. Note that this requires that there is at
         e#   least one more number in the list.
)*       e# At the end of the repetition the <-1> pops one capture from
         e# the stack. This is the node that the current number itself
         e# takes up.
$        e# Match the end of the string. This requires the input to end
         e# in a zero, because the last thing we matched was a comma.
(?(1)!)  e# Make sure that stack 1 is empty, so that we don't have any
         e# unused nodes.
Martin Ender
fuente
1

CJam (20 bytes)

{X0@{+\(_0>{\}*}/|!}

Conjunto de pruebas en línea . Este es un bloque anónimo que toma una matriz en la pila y deja 0 o 1 en la pila.

Disección

En pseudocódigo esto es:

p = 1
q = 0
foreach (i in input):
  q += i
  if (--p <= 0):      # in practice, if (--p == 0):
      p, q = q, p
return (p | q) == 0   # i.e. p == 0 && q == 0

qacumula la suma de las etiquetas de los nodos en el nivel actual del árbol; pcuenta regresiva los nodos restantes en el nivel actual.

Peter Taylor
fuente
{X0@{+\(_{\}&}/|!}¿Yo creo que?
Martin Ender
También parece que debería poder guardar un byte utilizando un programa completo para evitarlo @.
Martin Ender
1

Laberinto , 17 bytes

(
+?
;-)
,_"
@@,!

Pruébalo en línea!

La salida de -1verdad es y la salida de falsa está vacía. Definir la verdad y la falsedad en Labyrinth es un poco complicado, porque las ramas de Labyrinth son principalmente ternarias. Sin embargo, la única forma de construir un condicional con dos ramas confiables, solo puede hacer esto:

>"F
 T

En este caso, consideraría avanzar hacia adelante falsamente (porque la dirección del movimiento no se ve afectada) y convertir la verdad. Estos corresponden a cero y no cero respectivamente. La razón por la que estoy usando una salida vacía para representar cero es que, si tuviera que canalizar la salida de nuevo a otro programa Labyrinth, el operador de entrada ?realmente empujaría un cero si la entrada está vacía, por lo que considero que la cadena vacía es válida representación de cero.

Explicación

El algoritmo sigue siendo el mismo que en mis respuestas de Mathematica y Retina, pero debido al flujo de control de Labyrinth, esta vez funciona un poco diferente:

  • No trabajamos con el contador total off-by-one aquí. En cambio, a) trabajamos con un contador negativo yb) lo inicializamos -11inicialmente, de modo que queremos que el contador sea negativo en toda la lista y llegue a cero en la última entrada. Esto realmente simplifica el flujo de control aquí.
  • En lugar de construir la lista completa y verificar si contenía el valor incorrecto, hay tres posibles condiciones de terminación:

    1. Llegamos a EOF antes de alcanzar un conteo total de cero. En ese caso, quedan nodos no utilizados y no imprimimos nada.
    2. Llegamos a cero y estamos en EOF. En este caso, tenemos un árbol válido.
    3. Llegamos a cero y aún no estamos en EOF. En este caso, nos quedamos sin nodos antes de cubrir todos los elementos, y no imprimimos nada.

En cuanto al código real, comenzamos en la esquina superior izquierda. El (convierte el cero implícita en la parte superior de la pila en una -1, que será el total acumulado. Luego ingresamos al ciclo principal muy apretado del programa +?-)"_,;+:

+   Add the top two values. This does nothing on the first iteration,
    but gets rid of a helper-zero on subsequent iterations.
?   Read and push integer.
-   Subtract it from running total.
)   Increment.
"   No-op. There is a branch at this point. If the running total is zero,
    we move straight ahead onto the , (see below). Otherwise, the loop continues.
_   Push a zero. This is necessary to prevent the IP from turning south.
,   Read a character. This will either be the next separator (some positive
    number) or EOF (-1). If it's EOF, the IP turns south and the program
    terminates. Otherwise, the loop continues.
;   Discard the separator.

Eso deja solo los casos en los que hemos reducido el total acumulado a cero en algún momento. La IP se mueve hacia la esquina inferior derecha ,y lee otro carácter para verificar si hemos llegado a EOF. De lo contrario, el valor será positivo y la IP gira hacia el oeste hacia @y el programa termina. Si llegamos a EOF, la IP gira hacia el este e imprime -1con !. La IP se abrirá camino hacia la esquina inferior izquierda a @través de un camino un poco extraño para finalizar el programa.

Martin Ender
fuente
0

Python, 82 bytes

lambda l:len(l)==sum(l)+1 and not any(list(l[x]>=len(l)-x for x in range(len(l))))

Necesita más casos de prueba.

Sparr
fuente
No debería tener que emitir listsi este es Python 2 al menos, y al reorganizar e invertir la segunda condición, puede obtenerlo a 70 bytes:lambda l:all(l[x]<len(l)-x for x in range(len(l)))and len(l)==sum(l)+1
Kade
^ En relación con esto, puede cambiar el cuerpo de allser x<len(l)-y for y,x in enumerate(l)para guardar otros 2 bytes para llegar a 68.
Kade
No estoy jugando más al golf en este momento porque no creo que sea una solución precisa. Gracias por los consejos.
Sparr
0

Pyth, 13 bytes

qxsM._tMQ_1tl

Comenzamos calculando el relleno actual del árbol en todos los puntos de la representación de entrada. Esa parte de la idea fue tomada en gran parte de Martin Ender, así que gracias a él.sM._tMQ

Una vez que tenemos esta lista, verificamos si el primer índice que contiene -1( x..._1) es la longitud de la entrada menos uno ( q...tl(Q)).

¿No crees que funciona? ¡Inténtalo tú mismo!

Steven H.
fuente