web-dev-qa-db-fra.com

Comment déterminer si une liste chaînée a un cycle utilisant seulement deux emplacements de mémoire

Est-ce que quelqu'un connaît un algorithme permettant de déterminer si une liste liée effectue une boucle sur elle-même en utilisant seulement deux variables pour parcourir la liste? Supposons que vous ayez une liste d'objets liés, peu importe le type d'objet. J'ai un pointeur sur l'en-tête de la liste liée dans une variable et je ne dispose que d'une autre variable pour parcourir la liste. 

Mon plan est donc de comparer les valeurs de pointeur pour voir si tous les pointeurs sont identiques. La liste est de taille finie mais peut être énorme. Je peux définir les deux variables en tête, puis parcourir la liste avec l'autre variable, en vérifiant toujours si elle est égale à l'autre variable, mais si je réussis une boucle, je n'en sortirai jamais. Je pense que cela a à voir avec différents taux de parcours de la liste et de comparaison des valeurs de pointeur. Des pensées?

44
jeffD

Je suggérerais d'utiliser Floyd's Cycle-Finding Algorithmaka le Tortoise and the Hare Algorithm. Il a O(n) complexité et je pense que cela répond à vos besoins.

Exemple de code:

function boolean hasLoop(Node startNode){
  Node slowNode = Node fastNode1 = Node fastNode2 = startNode;
  while (slowNode && fastNode1 = fastNode2.next() && fastNode2 = fastNode1.next()){
    if (slowNode == fastNode1 || slowNode == fastNode2) return true;
    slowNode = slowNode.next();
  }
  return false;
}

Plus d'informations sur Wikipedia: Algorithme de recherche de cycle de Floyd .

46
Baishampayan Ghose

Vous pouvez utiliser l'algorithme Turtle and Rabbit

Wikipedia a aussi une explication, et ils l'appellent " l'algorithme de recherche de cycle de Floyd " ou "Tortoise and hare"

17
martinus

Absolument. Une solution peut en effet parcourir la liste avec les deux pointeurs, l'un voyageant deux fois plus vite que l'autre.

Commencez par le pointeur 'lent' et le pointeur 'rapide' pointant vers n'importe quel emplacement de la liste. Exécutez la boucle de traversée. Si le pointeur «rapide» coïncide à un moment quelconque avec le pointeur lent, vous avez une liste chaînée circulaire.

int *head = list.GetHead();
if (head != null) {
    int *fastPtr = head;
    int *slowPtr = head;

    bool isCircular = true;

    do 
    {
        if (fastPtr->Next == null || fastPtr->Next->Next == null) //List end found
        {
            isCircular = false;
            break;
        }

        fastPtr = fastPtr->Next->Next;
        slowPtr = slowPtr->Next;
    } while (fastPtr != slowPtr);

    //Do whatever you want with the 'isCircular' flag here
}
9
Frederick The Fool

J'ai essayé de résoudre ce problème moi-même et j'ai trouvé une solution différente (moins efficace mais toujours optimale).

L’idée est basée sur l’inversion d’une liste à liens simples en temps linéaire. Cela peut être fait en effectuant deux échanges à chaque étape de la répétition de la liste. Si q est l'élément précédent (initialement nul) et que p est le courant, swap (q, p-> suivant) swap (p, q) inversera le lien et fera avancer les deux pointeurs en même temps. Les échanges peuvent être effectués à l'aide de XOR pour éviter de devoir utiliser un troisième emplacement de mémoire.

Si la liste a un cycle, alors à un moment de l'itération, vous arriverez à un nœud dont le pointeur a déjà été modifié. Vous ne pouvez pas savoir quel nœud il s'agit, mais en poursuivant l'itération, en échangeant certains éléments deux fois, vous arrivez à nouveau en tête de la liste.

En inversant la liste deux fois, la liste reste inchangée dans le résultat et vous pouvez savoir si elle avait un cycle basé sur si vous êtes arrivé en tête de liste ou non.

3
user101596
int isListCircular(ListNode* head){
    if(head==NULL)
        return 0;
    ListNode *fast=head, *slow=head;
    while(fast && fast->next){
        if(fast->next->next==slow)
            return 1;
        fast=fast->next->next;
        slow=slow->next;
    }
    return 0;
}
2
rajya vardhan
boolean findCircular(Node *head)
{
    Node *slower, * faster;
    slower = head;
    faster = head->next;
    while(true) {
        if ( !faster || !faster->next)
            return false;
        else if (faster == slower || faster->next == slower)
            return true;
        else
            faster = faster->next->next;
    }
}
1
user5297378

Si nous examinons ce problème à une étape ultérieure, nous identifierons le cycle (c’est-à-dire que le cycle existe, mais où il se trouve exactement dans la liste). L’algorithme Tortoise and Hare peut être utilisé dans le même sens, mais nous devrons garder une trace de la tête de liste en tout temps. Une illustration de cet algorithme peut être trouvée ici .

0
ND_27