3

Ich möchte einen Algorithmus finden, die checks a linked-list with n elements for consistency. Die verkettete Liste verwendet einen dummy head (auch bekannt als Sentinel-Knoten). Der Algorithmus muss run in O(n) time und darf use O(1) extra space abgesehen von dem Platz, der benötigt wird, um durch die Liste zu durchlaufen. Die size der Liste is unknown. Außerdem ist es forbidden to modify the list.Algorithmus zum Überprüfen einer verknüpften Liste für Schleifen

Eine Liste gilt als inkonsistent, wenn ein Listenelement auf ein vorheriges Listenelement zeigt. Zuerst dachte ich darüber nach, das erste Element zu speichern und dann die Liste zu durchlaufen, während ich das aktuelle Element mit dem ersten vergleiche.

+0

Kennen Sie 'n' im Voraus? – joop

+0

nein, weil die Größe der Liste unbekannt ist. Also sollte der Algorithmus in der Lage sein, Konsistenz für jede Größe (oder n Elemente) zu überprüfen – Snelfie

+1

Nun, in diesem Fall ist Floyds Algorithmus der beste, den Sie bekommen können (ohne zusätzlichen Speicher zu verwenden) – joop

Antwort

1

Hier ist meine Lösung für die zweite Frage.

IsConsistent(LinkedList<Item> list) :N 
    slow = List.Sentinel.next :Element 
    fast = slow.next :Element 
    isConsistent = true :boolean 
    while(fast != list.Sentinel && fast.next != list.Sentinel && isConsistent) do 
     if(slow == fast) 
      isConsistent = false 
     else 
      slow:= slow.next 
      fast:= fast.next.next 
    od 
    if(isConsistent) 
     return 0 
    else 
     position = 0 : N 
     slow:= list.Sentinel 
     while(slow != fast) do 
      slow:= slow.next 
      fast:= fast.next 
      position:= position + 1 
     od 
     return position 
+1

Sie sollten nur die zweite while-Schleife ausführen, die die Position des Zyklus findet, wenn die erste while-Schleife einen Zyklus gefunden hat, andernfalls wird sie nie enden, weil ohne 'langsame' niemals 'schnell' gleich sein kann. – GrahamS

+0

@GrahamS Ich denke, jetzt ist es richtig – Snelfie

+1

Das sieht besser aus. Sie brauchen die "else" -Klausel nicht wirklich, da sie früh zurückkehrt, wenn isConsistent, aber es ist okay, sie zu belassen. Ich würde die Funktion wahrscheinlich in etwas wie 'FindCyclePosition' umbenennen und den Rückgabetyp in N ändern. – GrahamS

0

Grundsätzlich bedeutet das Zeigen eines vorherigen Elements, dass eine Schleife in der Liste enthalten ist. In diesem Fall scheint eine Schleifenprüfung angebracht zu sein.

+0

Es ist offensichtlich, dass ich für eine Schleife überprüfen muss. Ohne Schleife gibt es keine Inkonsistenz. Die Sache ist, dass es immer eine Schleife in einer verketteten Liste mit Dummy-Kopf gibt. Wie finde ich heraus, ob es nur die vom Dummy-Kopf erzeugte Schleife gibt oder ob es eine andere Schleife gibt, die Inkonsistenz erzeugt? – Snelfie

+0

Der Dummy-Kopf zeigt auf das erste Element der verketteten Liste und das letzte Element der Liste zeigt zurück auf den Dummy-Kopf (Sentinel-Knoten) – Snelfie

1

Sie benötigen etwas Arbeitsspeicher dafür, wenn Sie für jedes Element in der verknüpften Liste keine Visited-Eigenschaft haben. Wenn Sie eine Visited-Eigenschaft haben, müssen Sie sie zuerst löschen, bevor Sie den Algorithmus ausführen. Dies wird wahrscheinlich nicht Ihren Big-O-Anforderungen entsprechen.

Es ist nicht klar, was Sie mit "Punkte bei vorherigen Listenelement" meinen. Ist gleich durch Referenz (Objekt) oder gleichen Wert/Menge von Eigenschaftswerten (struct)? Ich nehme Bezug auf. Der folgende Code kann leicht geändert werden, um auch Strukturen zu behandeln.

static void Main(string[] args) 
{ 
    var list = BuildALinkedListFromSomeData(); 
    var isConsitent = IsConsistent(list); 
} 

static bool IsConsistent(LinkedList<Item> list) 
{ 
    var visited = new List<LinkedListNode<Item>>() 
    var runner = list.First; 
    while(runner != null) 
    { 
     if (visited.Contains(runner)) 
      return false; 
     visited.Add(runner); 
     runner = runner.Next; 
    } 
    return true; 
} 

AO (n) Lösung, die einen vorhandenen numerischen VisitCounter verwendet, die bereits Speicherplatz verwendet (keine zusätzliche Speicherung erforderlich):

static bool IsConsistent(LinkedList<Item> list) 
{ 
    var runner = list.First; 
    if (runner == null) 
     return false; // Assume consistent if empty 

    var consistent = true; 
    var runId = runner.Value.VisitCount; 
    while (runner != null) 
    { 
     // Does the traversed item match the current run id? 
     if(runner.Value.VisitCount > runId) 
     { 
      // No, Flag list as inconsistent. It must have been visited previously during this run 
      consistent = false; 
      // Reset the visit count (so that list is ok for next run) 
      runner.Value.VisitCount = runId; 
     } 
     // Increase visit count 
     runner.Value.VisitCount++; 
     // Visit next item in list 
     runner = runner.Next; 
    } 
    return consistent; 
} 

Dies macht Änderungen an den Inhalt eines Elements in der Liste, aber nicht die Liste selbst. Wenn Sie den Inhalt eines Elements in der Liste nicht ändern dürfen, ist dies natürlich auch keine Lösung. Nun, ich denke, das ist keine mögliche Lösung. Wenn die Liste inkonsistent ist, ist sie kreisförmig und der letzte Algorithmus wird nie enden :)

Sie müssen dann die Liste rückwärts von jedem besuchten Element in Ihrer Liste durchlaufen und dies wird Ihre O (n + 1) Anforderung brechen.

Fazit: Nicht so Mission Impossible, wenn Count verfügbar ist. Siehe GrahamS 'Antwort

+0

Es gibt eine besuchte Eigenschaft, aber es darf nicht verwendet werden, weil dies implizieren würde eine Änderung der Liste .... dachte auch über diese Methode: -/ – Snelfie

+1

Wenn Sie ein Array von besuchten Objekten außerhalb Ihrer Liste erstellen können, sollte dieser Code funktionieren. Unterhalb oder bei O (n) – Frode

+0

mit "Punkten im vorherigen Listenelement" meine ich folgendes: Es ist bekannt, dass jedes Element einer verknüpften Liste auf das nächste Element der Liste zeigt. Stellen Sie sich vor, es gibt ein Element in der Liste mit dem Index "a" und wenn dieses Element auf ein anderes Element mit einem niedrigeren Index "b" (a Snelfie

3

Enthält die Liste eine Size-Eigenschaft, die Ihnen sagt, wie viele Elemente es enthält (n), ohne es zu durchlaufen?

Wenn dies der Fall ist, dann ist eine einfache Lösung, die alle Ihre großen O-Anforderungen erfüllt, der Versuch, die Liste zu durchlaufen und dabei die Elemente zu zählen. Wenn die Anzahl die erwartete Anzahl von Elementen in der Liste übersteigt, hat sie eine Schleife.

Pseudo-Code würde wie folgt aussehen:

bool isConsistent (List list) 
{ 
    bool consistent = true; 
    Node node = list.Sentinel.Next; 
    int count = 0; 

    while (node != list.Sentinel && consistent) 
    { 
     count++; 

     if (count > list.Size) 
      consistent = false; 

     node = node.Next; 
    } 

    return consistent; 
} 

Die in O (n) abgeschlossen ist und verwendet O (1) Lagerung.

+0

Ich habe gerade über die gleiche Idee nachgedacht. Wenn der Count verfügbar ist, ist dies die richtige Antwort. Sie können die Zirkularität der Liste ausnutzen und brechen, wenn Sie den Zählwert überschreiten. – Frode

+0

Okay @joop, wenn keine Größe/Anzahl verfügbar ist, dann denke ich, dass Sie Recht haben, dass Floyds Tortoise and Hare Algorithmus wahrscheinlich die richtige/erwartete Lösung ist Das OP scheint das nicht zu glauben. – GrahamS

0

Zuerst dachte ich nicht darüber nach, listen.Sentinel zu verwenden. Jetzt habe ich eine neue Idee.

IsConsistent(LinkedList<Item> list) :boolean 
    slow = List.Sentinel.next :Element 
    fast = slow.next :Element 
    while(slow != list.Sentinel && fast != list.Sentinel) do 
     if(slow == fast) return false 
     else 
       slow:= slow.next 
       fast:= fast.next.next 
    od 
    return true 
+0

Das ist mehr oder weniger der Anfang des Tortoise and Hare-Algorithmus, aber das OP scheint das in der Frage abzulehnen. https://en.wikipedia.org/wiki/Cycle_detection#Tortoise_and_hare – GrahamS

+2

Es ist auch trivial. Der (hässliche!) Headnode/Sentinel ist nur ein kleines Detail. @GrahamS: Das ** ist ** das OP ... – joop

+0

Warum überprüfen Sie 'langsam' für null? Wann würde das passieren?Die Verwendung eines Sentinel-Knotens sollte Nullen vermeiden, daher sollte "langsam" nur bei "Sentinel.next" beginnen. Und warum andere Bedingungen in der Schleife überprüfen, können Sie einfach 'while (langsam! = List.Sentinel && fast! = List.Sentinel)' – GrahamS

2

Floyd's "Tortoise and Hare" algorithm tut, was man braucht und benötigt nur eine kleine Änderung mit dem Dummy-Kopf/Schwanz (Sentinel) Knoten zu arbeiten.

Hier ist, wie ich die Pseudo-Code schreiben würde:

bool IsConsistent (List list) 
{ 
    Node tortoise = list.Sentinel.Next; 
    Node hare = tortoise.Next; 

    while (tortoise != list.Sentinel && hare != list.Sentinel) 
    { 
     if (tortoise == hare) 
      return false; 

     tortoise = tortoise.Next; 
     hare = hare.Next.Next; 
    } 

    return true; 
} 
+0

Ich dachte darüber nach herauszufinden, an welcher Stelle in der Liste die Inkonsistenz auftritt. Zum Beispiel: Wenn es keine Inkonsistenz gibt, geben wir -1 zurück, sonst geben wir den spezifischen Index zurück. Ich weiß, dass ein solcher Algorithmus mehr Zeit braucht, vielleicht O (n^2) oder o (n^2). Um dies zu vereinfachen, nehmen wir an, dass die Größe der Liste bekannt ist. – Snelfie

+1

Schau auf die Wiki-Seite, die ich verlinkt habe. Der Rest des Tortoise and Hare-Algorithmus beschreibt, wie man den Index der Schleife (μ) und die Länge der Schleife (λ) findet. – GrahamS

+0

Ich habe eine zweite Antwort für diese Frage hinzugefügt. Könnten Sie sich das bitte anschauen? – Snelfie

Verwandte Themen