Supposons que vous ayez une structure de liste liée en Java. Il est composé de nœuds:
class Node {
Node next;
// some user data
}
et chaque nœud pointe vers le nœud suivant, à l'exception du dernier nœud, qui a la valeur null pour le suivant. Supposons qu'il existe une possibilité que la liste contienne une boucle - c'est-à-dire que le nœud final, au lieu d'avoir un null, a une référence à l'un des nœuds de la liste qui l'a précédé.
Quelle est la meilleure façon d'écrire
boolean hasLoop(Node first)
qui retournerait true
si le nœud donné est le premier d'une liste avec une boucle, et false
sinon? Comment pourriez-vous écrire pour que cela prenne un espace constant et un temps raisonnable?
Voici une image de ce à quoi ressemble une liste avec une boucle:
java
algorithm
data-structures
linked-list
jjujuma
la source
la source
finite amount of space and a reasonable amount of time?
:)Réponses:
Vous pouvez utiliser l'algorithme de recherche de cycle de Floyd , également connu sous le nom d' algorithme de tortue et de lièvre .
L'idée est d'avoir deux références à la liste et de les déplacer à des vitesses différentes . Avancez un par
1
nœud et l'autre par2
nœuds.next
) deviendranull
.Fonction Java implémentant l'algorithme:
la source
fast.next
avant denext
if(fast.next!=null)fast=fast.next.next;
Voici un raffinement de la solution Fast / Slow, qui gère correctement les listes de longueurs impaires et améliore la clarté.
la source
slow == fast.next
alorsslow
sera égalfast
à la toute prochaine itération; il n'enregistre qu'une seule itération au maximum au détriment d'un test supplémentaire pour chaque itération.slow
ne peut pas devenir nul auparavantfast
car il suit le même chemin de références (sauf si vous avez une modification simultanée de la liste, auquel cas tous les paris sont désactivés).Mieux que l'algorithme de Floyd
Richard Brent a décrit un algorithme alternatif de détection de cycle , qui est à peu près comme le lièvre et la tortue [cycle de Floyd] sauf que le nœud lent ici ne bouge pas, mais est plus tard "téléporté" à la position du nœud rapide à fixe intervalles.
La description est disponible ici: http://www.siafoo.net/algorithm/11 Brent affirme que son algorithme est de 24 à 36% plus rapide que l'algorithme de cycle de Floyd. O (n) complexité temporelle, O (1) complexité spatiale.
la source
slow.next != null
? Pour autant que je puisse voirslow
est toujours derrière ou égal àfast
.Une solution alternative à la tortue et au lapin, pas tout à fait aussi agréable, car je change temporairement la liste:
L'idée est de parcourir la liste et de l'inverser au fur et à mesure. Ensuite, lorsque vous atteignez pour la première fois un nœud qui a déjà été visité, son prochain pointeur pointera "vers l'arrière", provoquant l'itération à continuer vers
first
là où elle se termine.Code de test:
la source
Tortue et lièvre
Jetez un œil à l'algorithme rho de Pollard . Ce n'est pas tout à fait le même problème, mais peut-être que vous en comprendrez la logique et l'appliquerez aux listes chaînées.
(Si vous êtes paresseux, vous pouvez simplement vérifier la détection de cycle - vérifiez la partie sur la tortue et le lièvre.)
Cela ne nécessite que du temps linéaire et 2 pointeurs supplémentaires.
En Java:
(La plupart de la solution ne vérifie pas les deux
next
et lesnext.next
valeurs nulles. De plus, comme la tortue est toujours derrière, vous n'avez pas à la vérifier pour null - le lièvre l'a déjà fait.)la source
L'utilisateur unicornaddict a un bon algorithme ci-dessus, mais malheureusement il contient un bogue pour les listes non bouclées de longueur impaire> = 3. Le problème est que
fast
peut se "bloquer" juste avant la fin de la liste, leslow
rattraper, et une boucle est (à tort) détectée.Voici l'algorithme corrigé.
la source
Dans ce contexte, les matériaux textuels sont partout chargés. Je voulais juste publier une représentation schématique qui m'a vraiment aidé à saisir le concept.
Quand rapide et lent se rencontrent au point p,
Distance parcourue par rapide = a + b + c + b = a + 2b + c
Distance parcourue lentement = a + b
Puisque le jeûne est 2 fois plus rapide que le lent. Donc a + 2b + c = 2 (a + b) , alors nous obtenons a = c .
Ainsi, lorsqu'un autre pointeur lent s'exécute à nouveau de la tête à q , en même temps, le pointeur rapide s'exécute de p à q , de sorte qu'ils se rencontrent au point q ensemble.
la source
a
est plus grand que la longueur de boucle, fast fera plusieurs boucles et la formuledistance (fast) = a + b + b + c
changera ena + (b+c) * k + b
introduisant un paramètre supplémentairek
qui compte le nombre de lopps faites par fast.Algorithme
Complexité
la source
n
, corrigéequals
ethashCode
. Ce n'est pas la même chose. Et cela déréférencenull
sur le dernier élément. Et la question n'a rien dit sur le stockage des nœuds dans unLinkedList
.Ce qui suit n'est peut-être pas la meilleure méthode - c'est O (n ^ 2). Cependant, cela devrait servir à faire le travail (éventuellement).
la source
Pardonnez-moi mon ignorance (je suis encore relativement nouveau pour Java et la programmation), mais pourquoi cela ne fonctionnerait-il pas?
Je suppose que cela ne résout pas le problème d'espace constant ... mais il y arrive au moins dans un délai raisonnable, n'est-ce pas? Il ne prendra que l'espace de la liste chaînée plus l'espace d'un ensemble avec n éléments (où n est le nombre d'éléments dans la liste chaînée, ou le nombre d'éléments jusqu'à ce qu'il atteigne une boucle). Et pour le temps, l'analyse du pire des cas, je pense, suggérerait O (nlog (n)). Les recherches de SortedSet pour contains () sont log (n) (vérifiez le javadoc, mais je suis sûr que la structure sous-jacente de TreeSet est TreeMap, qui à son tour est un arbre rouge-noir), et dans le pire des cas (pas de boucles, ou boucle à la toute fin), il devra faire n recherches.
la source
Si nous sommes autorisés à intégrer la classe
Node
, je résoudrais le problème tel que je l'ai implémenté ci-dessous.hasLoop()
s'exécute en temps O (n) et ne prend que l'espace decounter
. Cela semble-t-il une solution appropriée? Ou existe-t-il un moyen de le faire sans intégrationNode
? (Évidemment, dans une implémentation réelle, il y aurait plus de méthodes, commeRemoveNode(Node n)
, etc.)la source
Vous pouvez même le faire en temps O (1) constant (bien que ce ne soit pas très rapide ou efficace): il y a une quantité limitée de nœuds que la mémoire de votre ordinateur peut contenir, disons N enregistrements. Si vous parcourez plus de N enregistrements, alors vous avez une boucle.
la source
la source
Utilisez la fonction ci-dessus pour détecter une boucle dans une liste liée en Java.
la source
La détection d'une boucle dans une liste chaînée peut être effectuée de l'une des manières les plus simples, ce qui entraîne une complexité O (N) à l'aide de hashmap ou O (NlogN) à l'aide d'une approche basée sur le tri.
Lorsque vous parcourez la liste à partir de la tête, créez une liste d'adresses triée. Lorsque vous insérez une nouvelle adresse, vérifiez si l'adresse est déjà là dans la liste triée, ce qui prend de la complexité O (logN).
la source
Je ne vois aucun moyen de faire en sorte que cela prenne un temps ou un espace fixe, les deux augmenteront avec la taille de la liste.
Je voudrais utiliser un IdentityHashMap (étant donné qu'il n'y a pas encore d'IdentityHashSet) et stocker chaque nœud dans la carte. Avant qu'un nœud ne soit stocké, vous devez appeler containsKey dessus. Si le nœud existe déjà, vous avez un cycle.
ItentityHashMap utilise == au lieu de .equals afin que vous vérifiiez où l'objet est en mémoire plutôt que s'il a le même contenu.
la source
Je pourrais être terriblement en retard et nouveau pour gérer ce fil. Mais reste..
Pourquoi ne peut-on pas stocker l'adresse du nœud et le nœud "suivant" pointé dans une table
Si nous pouvions tabuler de cette façon
Il y a donc un cycle formé.
la source
Voici mon code exécutable.
Ce que j'ai fait est de révérer la liste des liens en utilisant trois nœuds temporaires (complexité de l'espace
O(1)
) qui gardent une trace des liens.Le fait intéressant de le faire est d'aider à détecter le cycle dans la liste chaînée, car à mesure que vous avancez, vous ne vous attendez pas à revenir au point de départ (nœud racine) et l'un des nœuds temporaires devrait aller à null, sauf si vous avoir un cycle qui signifie qu'il pointe vers le nœud racine.
La complexité temporelle de cet algorithme est
O(n)
la complexité spatialeO(1)
.Voici le nœud de classe pour la liste chaînée:
Voici le code principal avec un cas de test simple de trois nœuds que le dernier nœud pointe vers le deuxième nœud:
Voici un cas de test simple de trois nœuds que le dernier nœud pointe vers le deuxième nœud:
la source
Ce code est optimisé et produira un résultat plus rapidement qu'avec celui choisi comme meilleure réponse.Ce code évite d'entrer dans un très long processus de poursuite du pointeur de nœud avant et arrière qui se produira dans le cas suivant si nous suivons le `` meilleur answer 'method.Look à travers la marche à sec de ce qui suit et vous réaliserez ce que j'essaie de dire.Ensuite, regardez le problème à travers la méthode ci-dessous et mesurez le non. des mesures prises pour trouver la réponse.
1-> 2-> 9-> 3 ^ -------- ^
Voici le code:
la source
boolean hasLoop(Node first)
qui retournerait vrai si le nœud donné est le premier d'une liste avec une boucle, et faux sinon?Voici ma solution en java
la source
Vous pouvez également utiliser l'algorithme de tortue de Floyd comme suggéré dans les réponses ci-dessus.
Cet algorithme peut vérifier si une liste liée individuellement a un cycle fermé. Ceci peut être réalisé en itérant une liste avec deux pointeurs qui se déplaceront à une vitesse différente. De cette façon, s'il y a un cycle, les deux pointeurs se rencontreront à un moment donné dans le futur.
N'hésitez pas à consulter mon blog sur la structure de données des listes liées, où j'ai également inclus un extrait de code avec une implémentation de l'algorithme susmentionné en langage java.
Cordialement,
Andreas (@xnorcode)
la source
Voici la solution pour détecter le cycle.
la source
// fonction de boucle de recherche de liste liée
la source
Cette approche a une surcharge d'espace, mais une implémentation plus simple:
La boucle peut être identifiée en stockant les nœuds dans une carte. Et avant de mettre le nœud; vérifiez si le nœud existe déjà. Si le nœud existe déjà dans la carte, cela signifie que la liste liée a une boucle.
la source
la source