lista más larga de palabras con letras iniciales y finales coincidentes

11

Mi amigo me dio un problema que dice que es fácil, pero no puedo encontrar un buen algoritmo para hacerlo.

Te dan una entrada de 100 palabras aleatorias en inglés. Tienes que encontrar la cadena de palabras más larga donde la última letra de una palabra coincide con la primera letra de la siguiente palabra. Solo puedes usar cada palabra una vez.

Por ejemplo, si le dieran las palabras "gato", "perro", "eso", la cadena más larga que podría hacer sería "gato -> eso". Si le dieran las palabras "mouse", "alce", "unicornio", la cadena más larga que podría hacer sería una sola palabra (ya que ninguna de esas palabras se vincula). Si le dieran las palabras "pájaro", "plato", "harb", la cadena más larga que podría hacer sería "harb -> bird -> dish" (o "dish -> harb -> bird" o "bird - > plato -> harb ").

Se me ocurrió la idea de modelar esto como un gráfico cíclico dirigido. Cada nodo sería solo una palabra, con vértices que van a cada palabra / nodo que comenzó con la letra con la que terminó esta palabra.

+-------+         \ +------+
|  cat  |-----------| that |
+-------+         / +------+
    |                  |
   \|/                 |
+-------+ /            |
|  the  |--------------+
+-------+ \

Este problema parece ser una búsqueda de ruta más larga , que es NP-Hard.

Hay una mejor manera de hacerlo? ¿O incluso algún tipo de algoritmo de aproximación que podría usarse? ¿O alguna forma de explotar las cualidades del inglés para reducir el espacio de búsqueda?

Abe Tool
fuente
44
¡Con 100 palabras, obtienes (al menos) 100! = 9.332622e + 157 combinaciones. Buena suerte con eso, creo que tu amigo está tirando de tu pierna diciendo que esto es fácil.
Martin Wickman
1
Pero, el número de combinaciones posibles es mucho menor que eso, porque en promedio una sola palabra solo está vinculada a unas 6 u 7 palabras más.
Abe Tool
2
Tiene razón en que esta es exactamente una búsqueda de ruta más larga. Creo que tu amigo está equivocado. Sin embargo, una búsqueda exhaustiva no es difícil de codificar, y puede que no se ejecute tanto tiempo.
Kevin Cline
44
Solo por diversión, codifiqué una búsqueda exhaustiva de fuerza bruta (como señaló @kevincline) en Ruby ( gist.github.com/anonymous/6225361 ). Con 100 palabras, solo tardó ~ 96 segundos ( gist.github.com/anonymous/6225364 ). Y este era un script muy ineficiente, no optimizado, de lenguaje interpretado, rápido y sucio. Entonces, con solo 100 palabras, incluso una versión lenta de la fuerza bruta se ejecuta en un tiempo razonable. Mi código en realidad no crea un gráfico acíclico y luego lo busca, solo construye recursivamente todos los caminos posibles a partir de cada palabra y realiza un seguimiento de los más largos.
Ben Lee
3
El problema dice que hay 100 palabras. Creo que esto significa que puede aplicar una solución de programación dinámica, que se menciona en el artículo al que se refiere.
Julien Guertault

Respuestas:

5

Creo que esto está relacionado con el problema de la ruta más larga (LP) que mencionaste, pero es un poco diferente. La principal diferencia es que el problema de LP tiene un mayor grado de conectividad que el problema sugerido. Al restringir sus conexiones a la última y primera letra, elimina una gran cantidad de combinaciones potenciales.

Así es como recomendaría abordar este:

  1. Para cada palabra en la lista, cuente las posibles conexiones de entrada y de salida.
  2. Deseche cualquier palabra que tenga 0 entradas y 0 salidas.
  3. Identifique un conjunto inicial de "palabras iniciales" con el menor número de entradas y salidas, y las salidas deben ser mayores que 0.
  4. Cada palabra de inicio recibe su propia copia de trabajo del recuento de conexiones de entradas / salidas. Esto forma la cabeza de la cadena.
  5. Para cada cadena, identifique una lista de "siguientes palabras" basada en:
    • última carta de inicio o palabra anterior
    • menor número de conexiones de entradas y salidas (nuevamente, las salidas deben ser mayores que 0)
  6. Para cada uno next word, repita el paso 5 hasta que la cadena termine.

Manten eso en mente:

  • Deberá realizar un seguimiento de la longitud de las cadenas y tener algún mecanismo global para identificar la cadena más larga.

  • También deberá eliminar cada palabra de la copia de trabajo de los recuentos de conexiones para evitar un bucle recursivo.

  • En algún momento, su cadena terminará y tendrá que seleccionar una palabra con un recuento de conexiones 0 fuera.

  • Puede que tenga que volver a calcular entradas / salidas a medida que las palabras se eliminan de las listas de trabajo. A primera vista, no creo que esto sea necesario ya que los conjuntos generales serán relativamente pequeños. Si escalaste a 1000 palabras, tener conteos estáticos puede ralentizar la convergencia del algoritmo.

En cierto modo, vi esto como un problema de embalaje. Para mí, las conexiones dentro y fuera identifican la forma que se va a empacar. Cuanto más bajas son las conexiones, más extraña es la forma. Cuanto más extraña es la forma, más pronto quiero empacarla ya que percibo que hay menos probabilidades de poder empacar una forma extraña cuanto más tarde me meto en la cadena.

Como ejemplo:

{dog, gopher, alpha, cube, elegant, this, that, bart}

dog     0, 1
gopher  1, 0
alpha   0, 0
cube    0, 1
elegant 1, 2
this    3, 0
that    2, 1
bart    0, 2

//alpha is dropped with 0 in and 0 out.
//two candidates found: dog, cube

//chain 1
dog => gopher
//chain 2
cube => elegant => that => this

//Note 1: the following chain won't occur due to selection rules
//that takes priority over this because of output count
cube => elegant => this

//Note 2: this chain won't occur either due to selection rules
bart => that => this

fuente
2
¿Existe alguna garantía de que este algoritmo siempre encuentre el camino más largo? Fuera de mi cabeza, no puedo pensar en un contraejemplo, pero parece que podría caer en una solución de tipo "máximo local".
Ben Lee
@BenLee: soy ingeniero de software; Nunca garantizo mi código. :-) En serio, no sé la respuesta a tu pregunta. Mi teoría de conjuntos y mis habilidades de prueba matemática son débiles, por decirlo suavemente, por lo que no tengo más que una evaluación empírica para validar mi algoritmo. No estoy seguro de que este problema sea realmente NP-hard, pero tampoco puedo validar esa afirmación. Si no es NP-hard, entonces debería haber un medio para validar el algoritmo.
2
¿Qué pasa con una lista de palabras como esta: "perro, gopher, bollo, monja, mediodía, protuberancia". El algoritmo elegiría incorrectamente la lista más larga como "perro -> gopher", cuando en realidad es cualquier combinación de "bollo, monja, mediodía, nub".
Abe Tool
1
@AbeTool - buen ejemplo allí. Entonces agregaría otra iteración (o dos) para permitir las combinaciones "entrada más baja> = 1" y "salida más baja> = 1".
2
No creo que eso resuelva el problema en todos los casos. Creo que esto cae en una solución de tipo "máximo local".
Abe Tool
3

Si realiza una matriz 26X26 para representar el gráfico dirigido de vértice como cada alfabeto y las palabras como borde. Por ejemplo, la palabra: APPLE conecta el vértice A y E con el borde dirigido de A a E. Ahora el problema se reduce a encontrar el rastro euleriano más grande (ruta que incluye el número máximo de bordes, visitando cada borde una vez con la posible repetición de vértices) en el gráfico. Uno de los algoritmos O (E) sería comenzar aleatoriamente desde un par de vértices. Encuentra un camino entre ellos. Que seguir relajando el camino hasta que sea posible.

update @ GlenH7 Resolví una pregunta similar en www.hackerearth / jda recientemente, hubo marcas relativas con respecto a la mejor solución y obtuve las calificaciones más altas con el siguiente enfoque:

Lista de palabras dada. Encuentra la cadena más larga que puedan formar. Una cadena es válida si cada palabra comienza con una letra * que termina al final de la última palabra.

Aproximación =

1) haz la gráfica de los alfabetos como vértices y las palabras como bordes. En lugar de usar varios bordes, use uno con un peso igual al número de bordes.

2) encuentre el componente fuertemente conectado del gráfico con aristas máximas. Deseche temporalmente otros bordes.

3) Para cada vértice, haga que su grado de entrada sea igual a su grado de salida.

4) Ahora existe su circuito euleriano en gráfico. Encuéntralo.

5) Ahora en el gráfico restante (gráfico original de wrt, encuentre el camino más largo con el primer vértice en el componente fuertemente conectado elegido. Creo que esto es NP difícil.

6) Incluya la ruta anterior en el circuito Eleriano convirtiendo el circuito euleriano en ruta.

Por qué: acepto que esta pregunta es probablemente NP difícil (supongo, no matemáticamente hablando). Pero el enfoque anterior funciona mejor cuando hay una larga lista (más de 1000) de palabras distribuidas uniformemente (es decir, no está destinado a ser wc para el enfoque anterior). Supongamos que después de convertir la lista dada al gráfico mencionado anteriormente, afortunadamente resulta ser un gráfico euleriano (consulte http://en.wikipedia.org/wiki/Eulerian_path para conocer las condiciones), entonces sin ninguna duda podemos decir esa respuesta la pregunta anterior es P y en realidad es la ruta euleriana en el gráfico (consulte http://www.graph-magics.com/articles/euler.php para obtener una aproximación muy simple y verifique que su gráfico tenga solo http://www.geeksforgeeks.org/strongly-connected-components/y si no se limpia temporalmente otro scc pequeño porque existe una ruta euleriana para scc único). Por lo tanto, para los casos no afortunados (que son casi todos los casos) trato de convertirlos en casos afortunados (es decir, se cumplen las condiciones del rastro euleriano). ¿Como hacer esto? Intenté hacer una búsqueda de profundidad creciente para bordes irrelevantes (el conjunto de bordes en una ruta que mira desde el vértice con un grado mayor que indegrado y que termina en el vértice con un grado mayor que excedido). La búsqueda de profundidad creciente significa que primero busqué todo el conjunto de un borde en la ruta que dos bordes en la ruta y así sucesivamente. A primera vista puede parecer que la búsqueda de profundidad i-ésima tomaría O (nodos ^ i), por lo tanto, la complejidad del tiempo total de O (nodos + nodos ^ 2 + nodos ^ 3 + ....) hasta que sea un caso afortunado. Pero el análisis amortizado lo deleitará es O (bordes). Una vez que se reduce el caso de la suerte, busca el circuito euleriano

Hasta aquí todo era tiempo polinómico. Esto daría casi la mejor solución. Pero para aumentar aún más su solución (la solución perfecta es NP difícil) intente un enfoque codicioso en el gráfico restante para encontrar un camino largo mirando con uno de los vértices en el scc elegido. Ahora agregue esto al rastro euleriano encontrado anteriormente para aumentarlo aún más.

vishfrnds
fuente
@ GlenH7 Resolví una pregunta similar en www.hackerearth / jda recientemente, hubo marcas relativas con respecto a la mejor solución y obtuve las mejores calificaciones con el siguiente enfoque
vishfrnds
0

Idea:

Primero, cree dos mapas (hashes), digamos, S y E, desde letras del alfabeto hasta palabras; el primero, S, asigna letras iniciales a palabras, el segundo, E, hace lo mismo con letras finales.

Por ejemplo, si el diccionario está hecho de:

pájaro, plato, perro, harb

tenemos:

S:

a -> [ ]
b -> [ bird ]
c -> [ ]
d -> [ dish, dog ]
...
h -> [ harb ]
...

y,

E:

a -> [ ]
b -> [ harb ]
c -> [ ]
d -> [ bird ]
...
g -> [ dog ]
h -> [ dish ]
...

Luego, usando S y E para búsquedas rápidas, cree un bosque (conjunto de árboles), del mismo tamaño que el diccionario, con raíces en cada palabra, y que no permita que una palabra aparezca más de una vez en un árbol: guarde en caché profundidades de los árboles a medida que los construyes:

bird (depth: 2)
   dish
      harb
   dog

dish (depth: 3)
   harb
      bird
         dog

dog (depth: 0)

harb (depth: 2)
   bird
      dish
      dog

Finalmente, repita sobre el bosque y encuentre los árboles de mayor profundidad.

La (s) solución (es) estará en el eje descendente de esos árboles.

P.ej,

dish / harb / bird / dog

encima.

YSharp
fuente