Comment puis-je détecter si une liste chaînée a une boucle ou non ?? Si elle a une boucle, comment trouver le point d’origine de la boucle, c’est-à-dire le nœud à partir duquel la boucle a commencé.
Vous pouvez le détecter en exécutant simplement deux pointeurs dans la liste. Ce processus est connu sous le nom d'algorithme tortoise et lièvre, après la fable du même nom.
Tout d'abord, vérifiez si la liste est vide (head
est null
). Si c'est le cas, aucune boucle n'est possible alors arrêtez-vous maintenant.
Sinon, démarrez le premier pointeur tortoise
sur le premier nœud head
et le deuxième pointeur hare
sur le deuxième nœud head.next
.
Puis boucle en boucle jusqu'à ce que hare
soit null
(ce qui peut déjà être vrai dans une liste à un élément), en faisant progresser tortoise
de un et hare
de deux à chaque itération. Le lièvre est assuré d’atteindre la fin en premier (s'il y a est une fin) puisqu'il a commencé en avance et court plus vite.
S'il n'y a pas de fin (c'est-à-dire s'il y a une boucle), ils pointeront finalement vers le même nœud et vous pourrez vous arrêter, sachant que vous avez trouvé un nœud quelque part dans la boucle.
Considérons la boucle suivante qui commence à 3
:
head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
À partir de tortoise
à 1 et hare
à 2, ils prennent les valeurs suivantes:
(tortoise,hare) = (1,2) (2,4) (3,6) (4,8) (5,4) (6,6)
Parce qu'ils deviennent égaux en (6,6)
, et puisque hare
doit toujours être au-delà de tortoise
dans une liste non en boucle, cela signifie que vous avez découvert une boucle.
Le pseudo-code ressemblera à ceci:
def hasLoop (head):
return false if head = null # Empty list has no loop.
tortoise = head # tortoise initially first element.
hare = tortoise.next # Set hare to second element.
while hare != null: # Go until hare reaches end.
return false if hare.next null # Check enough left for hare move.
hare = hare.next.next # Move hare forward two.
tortoise = tortoise.next # Move tortoise forward one.
return true if hare = tortoise # Same means loop found.
endwhile
return false # Loop exit means no loop.
enddef
La complexité temporelle de cet algorithme est O(n)
car le nombre de nœuds visités (par tortue et lièvre) est proportionnel au nombre de nœuds.
Une fois que vous connaissez un nœud dans la boucle, il existe également une méthode garantie par O(n)
pour rechercher le start de la boucle.
Revenons à la position initiale après avoir trouvé un élément quelque part dans la boucle, mais vous ne savez pas exactement où se trouve le début de la boucle.
head -> 1 -> 2 -> 3 -> 4 -> 5
^ |
| V
8 <- 7 <- 6
\
x (where hare and tortoise met).
C'est le processus à suivre:
hare
et définissez size
sur 1
.hare
et tortoise
sont différents, continue d'avancer hare
, augmentant size
à chaque fois. Cela donne finalement la taille de la boucle, six dans ce cas.size
est 1
, cela signifie que you must *already* be at the start of the loop (in a loop of size one, there is only one possible node that can *be* in the loop so it *must* be the first in that loop). In this case, you simply return
hare` commence et que vous passez le reste de la procédure ci-dessous.hare
et tortoise
sur l'élément first de la liste et avancez hare
exactement size
fois (au 7
dans ce cas). Cela donne deux pointeurs différents par exactement la taille de la boucle.hare
et tortoise
sont différents, avancez-les ensemble (avec le lièvre marchant à une allure plus calme, à la même vitesse que la tortue - je suppose qu’il est fatigué depuis sa première course). Etant donné qu'ils resteront toujours exactement size
éléments séparés, tortoise
atteindra le début de la boucle à exactement en même temps que hare
renvoie au début de la boucle.Vous pouvez voir cela avec la procédure suivante:
size tortoise hare comment
---- -------- ---- -------
6 1 1 initial state
7 advance hare by six
2 8 1/7 different, so advance both together
3 3 2/8 different, so advance both together
3/3 same, so exit loop
Par conséquent, 3
est le point de départ de la boucle et, étant donné que ces deux opérations (la détection de boucle et la découverte de début de boucle) sont O(n)
et exécutées de manière séquentielle, le tout pris ensemble est également O(n)
.
Si vous souhaitez une preuve plus formelle que cela fonctionne, vous pouvez examiner les ressources suivantes:
Si vous êtes simplement après avoir pris en charge la méthode (non une preuve formelle), vous pouvez exécuter le programme Python 3 suivant, qui évalue sa capacité de traitement pour un grand nombre de tailles (combien d'éléments dans le cycle) et d'introduction (éléments avant la début de cycle).
Vous constaterez qu'il trouve toujours un point de rencontre entre les deux pointeurs:
def nextp(p, ld, sz):
if p == ld + sz:
return ld
return p + 1
for size in range(1,1001):
for lead in range(1001):
p1 = 0
p2 = 0
while True:
p1 = nextp(p1, lead, size)
p2 = nextp(nextp(p2, lead, size), lead, size)
if p1 == p2:
print("sz = %d, ld = %d, found = %d" % (size, lead, p1))
break
La réponse sélectionnée donne une solution O (n * n) pour trouver le nœud de départ du cycle. Voici une solution O(n):
Une fois que nous trouvons le lent A et le rapide B se rencontrent dans le cycle, faites en sorte que l'un d'eux reste immobile et l'autre continue à faire un pas à chaque fois, pour décider du périmètre du cycle, disons P.
Ensuite, nous mettons un noeud en tête et le laissons passer par étapes, et mettons un autre noeud en tête. Nous avançons ces deux nœuds d’un pas à chaque fois, lorsqu’ils se rencontrent pour la première fois, c’est le point de départ du cycle.
Vous pouvez également utiliser une carte de hachage pour déterminer si une liste de liens possède une boucle ou non. La fonction utilise une carte de hachage pour déterminer si la liste de liens comporte ou non une boucle.
static bool isListHaveALoopUsingHashMap(Link *headLink) {
map<Link*, int> tempMap;
Link * temp;
temp = headLink;
while (temp->next != NULL) {
if (tempMap.find(temp) == tempMap.end()) {
tempMap[temp] = 1;
} else {
return 0;
}
temp = temp->next;
}
return 1;
}
Deux méthodes de pointeur sont la meilleure approche car la complexité temporelle est O(n) La hachage est une addition requise O(n) complexité de l'espace.
J'ai lu cette réponse dans le livre Structure de données de Narasimha Karamanchi.
Nous pouvons utiliser algorithme de recherche de cycle de Floyd , également appelé algorithme tortoise et lièvre . En cela, deux pointeurs sont utilisés; un (disons slowPtr
) est avancé par un seul nœud et un autre (disons fastPtr
) est avancé par deux nœuds. Si une boucle est présente dans la liste chaînée unique, elles se rencontreront sûrement à un moment donné.
struct Node{
int data;
struct Node *next;
}
// program to find the begin of the loop
int detectLoopandFindBegin(struct Node *head){
struct Node *slowPtr = head, *fastPtr = head;
int loopExists = 0;
// this while loop will find if there exists a loop or not.
while(slowPtr && fastPtr && fastPtr->next){
slowPtr = slowPtr->next;
fastPtr = fastPtr->next->next;
if(slowPtr == fastPtr)
loopExists = 1;
break;
}
S'il existe une boucle, nous pointons l'un des pointeurs vers la tête et nous les avançons désormais d'un seul noeud. Le nœud auquel ils se rencontreront sera le nœud start de la boucle dans la liste liée unique.
if(loopExists){
slowPtr = head;
while(slowPtr != fastPtr){
fastPtr = fastPtr->next;
slowPtr = slowPtr->next;
}
return slowPtr;
}
return NULL;
}
Le code suivant trouvera s'il y a une boucle dans SLL et, le cas échéant, retournera alors le nœud de départ.
int find_loop(Node *head){
Node * slow = head;
Node * fast = head;
Node * ptr1;
Node * ptr2;
int k =1, loop_found =0, i;
if(!head) return -1;
while(slow && fast && fast->next){
slow = slow->next;
/*Moving fast pointer two steps at a time */
fast = fast->next->next;
if(slow == fast){
loop_found = 1;
break;
}
}
if(loop_found){
/* We have detected a loop */
/*Let's count the number of nodes in this loop node */
ptr1 = fast;
while(ptr1 && ptr1->next != slow){
ptr1 = ptr1->next;
k++;
}
/* Now move the other pointer by K nodes */
ptr2 = head;
ptr1 = head;
for(i=0; i<k; i++){
ptr2 = ptr2->next;
}
/* Now if we move ptr1 and ptr2 with same speed they will meet at start of loop */
while(ptr1 != ptr2){
ptr1 = ptr1->next;
ptr2 = ptr2->next;
}
return ptr1->data;
}
Pour la plupart, toutes les réponses précédentes sont correctes mais voici une version simplifiée de la logique avec visual & code (pour Python 3.7)
La logique est très simple comme l'ont expliqué d'autres. Je vais créer Tortoise/slow et Hare/fast. Si nous déplaçons deux pointeurs avec une vitesse différente, nous serons finalement plus rapides! vous pouvez aussi penser à cela comme à deux coureurs dans un terrain circulaire. Si le coureur rapide continue à tourner en cercle, il rencontrera/dépassera le coureur lent.
Nous allons donc déplacer le pointeur Tortoise/lent avec la vitesse 1 à chaque itération tout en continuant à incrémenter ou déplacer le pointeur Lièvre/rapide à la vitesse 2. Lorsque nous nous rencontrons, nous savons qu’il ya un cycle. Ceci est également connu sous le nom de algorithme de recherche de cycle de Floyd
Voici le code Python qui fait cela (notez que la méthode has_cycle est la partie principale):
#!/usr/bin/env python3
class Node:
def __init__(self, data = None):
self.data = data
self.next = None
def strnode (self):
print(self.data)
class LinkedList:
def __init__(self):
self.numnodes = 0
self.head = None
def insertLast(self, data):
newnode = Node(data)
newnode.next = None
if self.head == None:
self.head = newnode
return
lnode = self.head
while lnode.next != None :
lnode = lnode.next
lnode.next = newnode # new node is now the last node
self.numnodes += 1
def has_cycle(self):
slow, fast = self.head ,self.head
while fast != None:
if fast.next != None:
fast = fast.next.next
else:
return False
slow = slow.next
if slow == fast:
print("--slow",slow.data, "fast",fast.data)
return True
return False
linkedList = LinkedList()
linkedList.insertLast("1")
linkedList.insertLast("2")
linkedList.insertLast("3")
# Create a loop for testing
linkedList.head.next.next.next = linkedList.head;
#let's check and see !
print(linkedList.has_cycle())
En consultant la réponse choisie, j'ai essayé quelques exemples et constaté que:
Si (A1, B1), (A2, B2) ... (AN, BN) sont les traversées des pointeurs A et B
où A étapes 1 élément et B étapes 2 éléments, et, Ai et Bj sont les nœuds traversés par A et B, et AN = BN.
Ensuite, le noeud à partir duquel la boucle commence est Ak, où k = plancher (N/2).
Une autre solution
Détecter une boucle:
Retrait de la boucle:
Une fois que nous avons détecté la boucle à l’étape 3, définissez la valeur suivante du noeud précédent sur NULL
#code
def detect_remove_loop (tête)
cur_node = head
node_list = []
while cur_node.next is not None:
prev_node = cur_node
cur_node = cur_node.next
if cur_node not in node_list:
node_list.append(cur_node)
else:
print('Loop Detected')
prev_node.next = None
return
print('No Loop detected')
ok - je l'ai rencontré dans une interview hier - pas de matériel de référence disponible et j'ai trouvé une réponse très différente (pendant que je conduisais chez moi ...) puisque les listes chaînées sont NORMALEMENT (pas toujours je l'avoue) attribuées en utilisant la logique de malloc on sait alors que la granularité des allocations est connue. Sur la plupart des systèmes, il s'agit de 8 octets. Cela signifie que les 3 derniers bits sont toujours des zéros. Considérez - si nous plaçons la liste chaînée dans une classe pour contrôler l’accès et utilisons un masque de 0x0E dans l’adresse suivante, nous pouvons utiliser les 3 bits les plus bas pour stocker une rupture crumb Ainsi, nous pouvons écrire une méthode qui stockera notre dernier fil d’ariane - Dis 1 ou 2 - et les alterner. Notre méthode qui vérifie la présence d'une boucle peut alors parcourir chaque nœud (à l'aide de notre méthode suivante) et vérifier si l'adresse suivante contient le fil d'Ariane actuel. Si c'est le cas, nous avons une boucle. Sinon, nous masquerions les 3 bits inférieurs. et ajoutez notre fil d'Ariane actuel. L'algorithme de vérification du fil d'ariane doit être à thread unique, car vous ne pouvez pas en exécuter deux à la fois, mais il permet aux autres threads d'accéder à la liste de manière asynchrone, avec les mises en garde habituelles concernant l'ajout/la suppression de nœuds. Qu'est-ce que tu penses? Si d'autres pensent que c'est une solution valable, je peux rédiger l'exemple de classe ... Pensez-y qu'une approche nouvelle est bonne et je suis toujours prêt à me faire dire que je viens juste de passer à côté de l'essentiel ... Merci à tous Mark
Tout d'abord, créer un nœud
struct Node {
int data;
struct Node* next;
};
Initialiser le pointeur principal globalement
Struct Node* head = NULL;
Insérer des données dans la liste liée
void insert(int newdata){
Node* newNode = new Node();
newNode->data = newdata;
newNode->next = head;
head = newNode;
}
Créer une fonction detectLoop ()
void detectLoop(){
if (head == NULL || head->next == NULL){
cout<< "\nNo Lopp Found in Linked List";
}
else{
Node* slow = head;
Node* fast = head->next;
while((fast && fast->next) && fast != NULL){
if(fast == slow){
cout<<"Loop Found";
break;
}
fast = fast->next->next;
slow = slow->next;
}
if(fast->next == NULL){
cout<<"Not Found";
}
}
}
Appelle la fonction depuis main ()
int main()
{
insert(4);
insert(3);
insert(2);
insert(1);
//Created a Loop for Testing, Comment the next line to check the unloop linkedlist
head->next->next->next->next = head->next;
detectLoop();
//If you uncomment the display function and make a loop in linked list and then run the code you will find infinite loop
//display();
}
boolean hasLoop(Node *head)
{
Node *current = head;
Node *check = null;
int firstPtr = 0;
int secondPtr = 2;
do {
if (check == current) return true;
if (firstPtr >= secondPtr){
check = current;
firstPtr = 0;
secondPtr= 2*secondPtr;
}
firstPtr ++;
} while (current = current->next());
return false;
}
Une autre solution O(n).
bool FindLoop(struct node *head)
{
struct node *current1,*current2;
current1=head;
current2=head;
while(current1!=NULL && current2!= NULL && current2->next!= NULL)
{
current1=current1->next;
current2=current2->next->next;
if(current1==current2)
{
return true;
}
}
return false;
}