la plus longue liste de mots avec les lettres de début et de fin correspondantes

11

Mon ami m'a donné un problème qu'il dit être facile, mais je ne peux pas trouver un bon algorithme à utiliser pour le faire.

Vous obtenez une entrée de 100 mots anglais aléatoires. Vous devez trouver la plus longue chaîne de mots où la dernière lettre d'un mot correspond à la première lettre du mot suivant. Vous ne pouvez utiliser chaque mot qu'une seule fois.

Par exemple, si l'on vous donnait les mots "chat", "chien", "ça", la plus longue chaîne que vous pourriez faire serait "chat -> ça". Si on vous donnait les mots "souris", "orignal", "licorne", la plus longue chaîne que vous pourriez faire serait juste un mot (car aucun de ces mots ne lie). Si on vous donnait les mots "oiseau", "plat", "harb", la plus longue chaîne que vous pourriez faire serait "harb -> oiseau -> plat" (ou "plat -> harb -> oiseau" ou "oiseau - > plat -> harb ").

J'ai eu l'idée de modéliser cela comme un graphique cyclique dirigé. Chaque nœud serait juste un mot, avec des sommets allant à chaque mot / nœud commençant par la lettre de ce mot se terminant par.

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

Ce problème semble être une recherche de chemin la plus longue , qui est NP-Hard.

Y a-t-il une meilleure façon de le faire? Ou même une sorte d'algorithme d'approximation qui pourrait être utilisé? Ou un moyen d'exploiter les qualités de l'anglais pour réduire l'espace de recherche?

Outil Abe
la source
4
Avec 100 mots, vous obtenez (au moins) 100! = 9,332622e + 157 combinaisons. Bonne chance avec ça, je pense que votre ami vous tire la jambe en disant que c'est facile.
Martin Wickman
1
Mais, le nombre de combinaisons possibles est bien inférieur à cela, car en moyenne un seul mot n'est lié qu'à environ 6 ou 7 autres mots.
Abe Tool
2
Vous avez raison, il s'agit exactement d'une recherche de chemin la plus longue. Je pense que votre ami a tort. Cependant, une recherche exhaustive n'est pas difficile à coder et peut ne pas s'exécuter aussi longtemps.
kevin cline
4
Juste pour le plaisir, j'ai codé une recherche exhaustive par force brute (comme l'a souligné @kevincline) dans Ruby ( gist.github.com/anonymous/6225361 ). Avec 100 mots, cela n'a pris que ~ 96 secondes ( gist.github.com/anonymous/6225364 ). Et c'était un script très inefficace, non optimisé, en langage interprété, rapide et sale. Ainsi, avec seulement 100 mots, même une version lente de la force brute s'exécute dans un laps de temps raisonnable. Mon code ne crée pas réellement un graphique acyclique et ne le recherche pas, il construit simplement récursivement tous les chemins possibles à partir de chaque mot et garde la trace des plus longs.
Ben Lee
3
Le problème indique qu'il y a 100 mots. Je pense que cela signifie que vous pouvez appliquer une solution de programmation dynamique, qui est mentionnée dans l'article auquel vous faites référence.
Julien Guertault

Réponses:

5

Je pense que cela est lié au problème de chemin le plus long (LP) que vous avez mentionné, mais c'est un peu différent. La principale différence est que le problème LP a un degré de connectivité supérieur à celui de votre problème suggéré. En limitant vos connexions aux dernières et premières lettres, vous supprimez un grand nombre de combinaisons potentielles.

Voici comment je recommanderais d'aborder celui-ci:

  1. Pour chaque mot de la liste, comptez les connexions possibles entrantes et sortantes.
  2. Jetez tous les mots qui ont 0 entrées et 0 sorties.
  3. Identifiez un ensemble initial de "mots de départ" avec le plus petit nombre d'entrées et de sorties, et les sorties doivent être supérieures à 0.
  4. Chaque mot de départ reçoit sa propre copie de travail du nombre de connexions entrées / sorties. Cela forme la tête de la chaîne.
  5. Pour chaque chaîne, identifiez une liste de "mots suivants" basée sur:
    • dernière lettre du démarreur ou mot précédent
    • le plus petit nombre d'entrées et de sorties (encore une fois, les sorties doivent être supérieures à 0)
  6. Pour chacun next word, répétez l'étape 5 jusqu'à la fin de la chaîne.

Garde en tête que:

  • Vous devrez suivre la longueur des chaînes et disposer d'un mécanisme global pour identifier la chaîne la plus longue.

  • Vous devrez également supprimer chaque mot de la copie de travail du nombre de connexions afin d'éviter une boucle récursive.

  • À un moment donné, votre chaîne se terminera et vous devrez sélectionner un mot avec un décompte de connexion de 0.

  • Vous devrez peut-être recalculer les entrées / sorties lorsque les mots sont supprimés des listes de travail. À première vue, je ne pense pas que cela sera nécessaire car les ensembles globaux seront relativement petits. Si vous passez à 1 000 mots, le fait d'avoir un nombre statique peut ralentir la convergence de l'algorithme.

J'ai vu cela comme un problème d'emballage. Pour moi, les connexions à l'intérieur et à l'extérieur identifient la forme à emballer. Plus les connexions sont basses, plus la forme est étrange. Plus la forme est étrange, plus je veux l'emballer tôt car j'ai l'impression que les chances de pouvoir emballer une forme étrange diminuent plus tard dans la chaîne.

Par exemple:

{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

la source
2
Y a-t-il une garantie que cet algorithme trouvera toujours le chemin le plus long? Du haut de ma tête, je ne peux pas penser à un contre-exemple, mais cela semble que cela pourrait tomber pour une solution de type "maximum local".
Ben Lee
@BenLee - Je suis ingénieur logiciel; Je ne garantis jamais mon code. :-) Sérieusement cependant, je ne connais pas la réponse à votre question. Ma théorie des ensembles et mes compétences en preuve mathématique sont faibles, pour le moins, donc je n'ai pas d'autre moyen que l'évaluation empirique pour valider mon algorithme. Je ne suis pas sûr que ce problème soit vraiment NP-difficile, mais je ne peux pas non plus valider cette revendication. Si ce n'est pas NP-difficile, il devrait y avoir un moyen de valider l'algorithme.
2
Qu'en est-il d'une liste de mots comme celle-ci: "chien, gopher, chignon, nonne, midi, nub". L'algorithme sélectionnerait incorrectement la liste la plus longue comme "chien -> gopher", alors qu'il s'agit en fait d'une combinaison de "chignon, nonne, midi, nub".
Abe Tool
1
@AbeTool - bon exemple là-bas. J'ajouterais une autre itération (ou deux) pour permettre les combinaisons "entrée la plus basse> = 1" et "sortie la plus basse> = 1".
2
Je ne pense pas que cela résoudra le problème dans tous les cas. Je pense que cela tombe dans une solution de type "maximum local".
Abe Tool
3

Si vous faites une matrice 26X26 pour représenter le graphique dirigé du sommet comme chaque alphabet et les mots comme bord. Par exemple, le mot - APPLE connecte le sommet A et E avec l'arête dirigée de A à E. Maintenant, le problème se réduit à trouver la plus grande piste eulérienne (chemin qui comprend le nombre maximal d'arêtes, en visitant chaque arête une fois avec la répétition possible des sommets) dans le graphique. L'un des algorithmes O (E) consisterait à partir de façon aléatoire à partir d'une paire de sommets. Trouvez un chemin entre eux. Alors continuez à détendre le chemin jusqu'à ce qu'il soit possible.

update @ GlenH7 J'ai résolu une question similaire sur www.hackerearth / jda récemment, il y avait des notes relatives par rapport à la meilleure solution et j'ai obtenu les notes les plus élevées avec l'approche suivante.

Liste donnée de mots. Trouvez la plus longue chaîne qui puisse être formée par eux. Une chaîne est valide si chaque mot commence par une lettre * se terminant à la fin du dernier mot.

Approch =

1) faire le graphique des alphabets comme des sommets et des mots comme des bords. Au lieu d'utiliser plusieurs arêtes, utilisez-en une dont le poids est égal au nombre d'arêtes.

2) trouver la composante fortement connectée du graphe avec des arêtes maximales. Jeter temporairement les autres bords.

3) Pour chaque sommet, faites son indegree égal à son outdegree.

4) Maintenant leur circuit eulérien existe dans le graphe. Trouve le.

5) Maintenant, dans le graphique restant (wrt graphique d'origine trouver le chemin le plus long avec le premier sommet dans la composante fortement connectée choisie. Je pense que c'est NP difficile.

6) Inclure le sentier ci-dessus dans le circuit Elérien convertissant le circuit eulérien en sentier.

Pourquoi - J'accepte que cette question est très probablement NP difficile (devinez, pas mathématiquement parlant). Mais l'approche ci-dessus fonctionne mieux quand il y a une longue liste (1000+) de mots uniformément distribués (c'est-à-dire non destinés à être wc pour l'approche ci-dessus). Supposons qu'après avoir converti la liste donnée en graphique mentionné ci-dessus, cela se révèle heureusement être un graphique eulérien (voir http://en.wikipedia.org/wiki/Eulerian_path pour les conditions), alors sans aucun doute, nous pouvons dire cette réponse à la question ci-dessus est P et est en fait le chemin eulérien dans le graphique (voir http://www.graph-magics.com/articles/euler.php pour une approche très simple à faire et voyez ceci pour vérifier que votre graphique a simple http://www.geeksforgeeks.org/strongly-connected-components/et sinon nettoyer temporairement d'autres petits scc car le chemin eulérien existe pour un seul scc). Ainsi, pour les cas non chanceux (qui sont presque tous des cas), j'essaie de les convertir en cas chanceux (c'est-à-dire que la condition de piste eulérienne est remplie). Comment faire ça? J'ai essayé d'augmenter la recherche de profondeur pour les arêtes non pertinentes (l'ensemble des arêtes dans un chemin partant du sommet avec un degré supérieur à l'indegree et se terminant au sommet avec un degré supérieur à l'indegree). L'augmentation de la recherche de profondeur signifie que j'ai d'abord recherché tout cet ensemble d'un bord dans le chemin, puis deux bords dans le chemin et ainsi de suite. Il peut sembler à première vue que la recherche en profondeur nécessiterait O (nœuds ^ i) donc la complexité temporelle totale de O (nœuds + nœuds ^ 2 + nœuds ^ 3 + ....) jusqu'à ce que ce soit un cas chanceux. Mais l'analyse amortie révélera qu'il s'agit de O (bords). Une fois qu'il est réduit, le cas chanceux trouve le circuit eulérien.

Jusqu'ici, c'était tout le temps polynomial. Cela donnerait presque la meilleure solution. Mais pour augmenter encore votre solution (la solution parfaite est NP difficile) essayez une approche gourmande dans le graphique restant pour trouver une longue traînée fixant l'un des sommets dans le scc choisi. Ajoutez maintenant ceci à la piste eulérienne trouvée ci-dessus pour l'augmenter encore.

vishfrnds
la source
@ GlenH7 J'ai résolu une question similaire sur www.hackerearth / jda récemment, il y avait des notes relatives par rapport à la meilleure solution et j'ai obtenu les notes les plus élevées avec l'approche suivante
vishfrnds
0

Idée:

Tout d'abord, créez deux cartes (hachages), disons, S et E, des lettres de l'alphabet aux mots; le premier, S, associe les lettres de départ aux mots, le second, E, fait de même avec les lettres de fin.

Par exemple, si le dictionnaire est composé de:

oiseau, plat, chien, harb

nous avons:

S:

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

et,

E:

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

Ensuite, en utilisant S et E pour des recherches rapides, créez une forêt (ensemble d'arbres), de la même taille que le dictionnaire, avec des racines à chaque mot, et ne permettant pas à un mot d'apparaître plus d'une fois dans un arbre - cachez le les profondeurs des arbres lorsque vous les construisez:

bird (depth: 2)
   dish
      harb
   dog

dish (depth: 3)
   harb
      bird
         dog

dog (depth: 0)

harb (depth: 2)
   bird
      dish
      dog

Enfin, parcourez la forêt et trouvez le ou les arbres les plus profonds.

La ou les solutions seront sur l'axe descendant de ces arbres.

Par exemple,

dish / harb / bird / dog

au dessus de.

YSharp
la source