web-dev-qa-db-fra.com

Recherche d'une boucle dans une liste chaînée

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é. 

54
Jainendra

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:

  • Avancez hare et définissez size sur 1.
  • Ensuite, tant que 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.
  • À ce stade, si 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 returnhare` commence et que vous passez le reste de la procédure ci-dessous.
  • Sinon, définissez 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.
  • Ensuite, tant que 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 harerenvoie 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:

  • une question sur notre site soeur;
  • la détection de cycle Wikipedia page; ou
  • "La tortue et l'algorithme de lièvre" de Peter Gammie, 17 avril 2016. 

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
121
paxdiablo

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.

34
Dongliang Yu

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. 

6

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;
        }
2
Ayush Chaurasia

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;

}
1
jitsceait

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 Floydenter image description here

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())
1
grepit

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).

0
Be Wake Pandey

Une autre solution

Détecter une boucle:

  1. créer une liste 
  2. parcourez la liste liée et continuez d’ajouter le nœud à la liste.
  3. Si le nœud est déjà présent dans la liste, nous avons une boucle. 

Retrait de la boucle:

  1. À l'étape 2 ci-dessus, tout en parcourant la liste chaînée, nous gardons également trace du nœud précédent. 
  2. 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')
    
0
Santosh Pillai

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

0
Mark Z. Kumler

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();
} 
0
Inamur Rahman
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).

0
Apurva Sharma
                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;
                }
0