it-swarm.com.de

So können Sie feststellen, ob eine verknüpfte Liste einen Zyklus mit nur zwei Speicherplätzen hat

Kennt jemand einen Algorithmus, um herauszufinden, ob eine verknüpfte Liste sich nur mit zwei Variablen durchläuft, um die Liste zu durchlaufen. Angenommen, Sie haben eine verknüpfte Liste von Objekten, es spielt keine Rolle, welchen Objekttyp es hat. Ich habe einen Zeiger auf den Kopf der verknüpften Liste in einer Variablen, und mir wird nur eine andere Variable übergeben, mit der die Liste durchlaufen werden kann. 

Mein Plan ist also, Zeigerwerte zu vergleichen, um zu sehen, ob Zeiger gleich sind. Die Liste hat eine begrenzte Größe, kann aber riesig sein. Ich kann beide Variablen auf den Kopf setzen und dann die Liste mit der anderen Variablen durchqueren, wobei ich immer prüfe, ob sie mit der anderen Variablen identisch ist. Wenn ich jedoch einen Loop anschlage, werde ich nie herauskommen. Ich denke, es hat mit unterschiedlichen Raten zu tun, die Liste zu durchlaufen und Zeigerwerte zu vergleichen. Irgendwelche Gedanken?

44
jeffD

Ich würde vorschlagen, Floyd's Cycle-Finding Algorithmaka The Tortoise and the Hare Algorithm zu verwenden. Es hat O(n) Komplexität und ich denke, es passt zu Ihren Anforderungen.

Beispielcode:

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;
}

Weitere Informationen bei Wikipedia: Floyds Algorithmus zum Finden von Zyklen .

46

Sie können den Algorithmus Turtle and Rabbit verwenden. 

Wikipedia hat auch eine Erklärung, und sie nennen es " Floyds Zyklusfindungsalgorithmus " oder "Schildkröte und Hase"

17
martinus

Absolut. Eine Lösung kann in der Tat darin bestehen, die Liste mit beiden Zeigern zu durchlaufen, eine, die doppelt so schnell ist wie die andere.

Beginnen Sie mit dem 'langsamen' und dem 'schnellen' Zeiger, die auf eine beliebige Stelle in der Liste zeigen. Führen Sie die Durchlaufschleife aus. Wenn der 'schnelle' Zeiger zu einem Zeitpunkt mit dem langsamen Zeiger übereinstimmt, haben Sie eine zirkuläre verknüpfte Liste.

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

Ich habe versucht, das Problem selbst zu lösen und habe eine andere (weniger effiziente, aber dennoch optimale) Lösung gefunden.

Die Idee besteht darin, eine einzeln verknüpfte Liste in linearer Zeit umzukehren. Dies kann durch zwei Swaps bei jedem Schritt beim Durchlaufen der Liste durchgeführt werden. Wenn q das vorherige Element ist (anfangs null) und p der aktuelle ist, dann wird der Swap (q, p → next) swap (p, q) die Verknüpfung umkehren und die beiden Zeiger gleichzeitig vorrücken. Die Swaps können mit XOR ausgeführt werden, um zu verhindern, dass ein dritter Speicherplatz verwendet werden muss.

Wenn die Liste einen Zyklus hat, gelangen Sie zu einem Zeitpunkt während der Iteration zu einem Knoten, dessen Zeiger bereits geändert wurde. Sie können nicht wissen, um welchen Knoten es sich handelt, aber wenn Sie die Iteration fortsetzen und einige Elemente zweimal austauschen, gelangen Sie wieder an den Anfang der Liste.

Durch zweimaliges Umkehren der Liste bleibt die Liste im Ergebnis unverändert. Sie können anhand der Frage, ob Sie am ursprünglichen Kopf der Liste angekommen sind oder nicht, einen Zyklus erkennen.

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

Um dieses Problem auf einen nächsten Schritt zu bringen, wird der Zyklus identifiziert (dh nicht nur, dass der Zyklus existiert, sondern wo genau er in der Liste steht). Der Tortoise- und Hare-Algorithmus kann für dasselbe verwendet werden. Wir müssen jedoch immer den Kopf der Liste verfolgen. Eine Abbildung dieses Algorithmus finden Sie hier .

0
ND_27