Vielen Dank für Ihre Antworten! Ich bin mir bewusst, dass diese Frage vor fast zwei Jahren gestellt wurde und eine Antwort schon lange akzeptiert wurde, aber ich war ein wenig verwirrt von den Antworten. Obwohl der Fragesteller sich wahrscheinlich nicht um neue Antworten kümmert, würde ich gerne meine Version hinzufügen, falls andere Leser ebenfalls verwirrt wären und meinen eigenen Ansatz dokumentieren würden. Einige davon mögen passender als Kommentare gewesen sein, aber ich habe noch nicht den Ruf, sie zu kommentieren.
Erstens, egal wie oft ich es ansehe - auf dem Whiteboard oder im Debugger - ich kann nicht vermeiden, mit einer Schleife im ersten Knoten zu enden, d.auf sich selbst zeigen, wenn ich keine Bedingung verwende, um zwischen Fällen von benachbarten Knoten zu unterscheiden, auch nicht mit den abstrakten Schritten oder dem konkreten Code aus der aktuell akzeptierten Antwort von Smashery. Ich habe mich ein bisschen im Internet umgeschaut, um Code im Stil der aktuell akzeptierten Antwort zu finden, um eine solche Bedingung zu vermeiden, aber für mich ist es überraschend schwierig zu vermeiden und meine Recherchen haben keine solche Lösung ergeben (außer ich liege falsch über die vorgeschlagene möglicherweise falsch). Wenn es einen cleveren Ausdruck gäbe, der die Adresse des ersten Knotens ergibt, wenn sie benachbart sind, und die Adresse des nächsten Knotens, wenn nicht, dann wäre diese Bedingung nicht erforderlich, weil das Herausfinden des neuen Knotens (was offensichtlich ist) notwendig ist das bedingt.
Zweitens habe ich die gleiche Frage über die aufeinanderfolgenden Zuweisungen an die gleichen Variablen in der angenommenen Antwort als andere Kommentatoren. Ich hoffe, dass ich hier nicht extrem dicht bin, aber der gleichen Variablen nacheinander verschiedene Werte zuzuordnen, außer vielleicht im Falle von Nebenwirkungen, scheint mir die Variable niemals einen anderen Wert als die letzte Zuweisung zu haben, egal welche Konfiguration der Knoten ich betrachte und damit die vorherigen Zuweisungen scheinbar überflüssig mache. Wenn ich falsch liege und dieser Ansatz dieses Problem tatsächlich lösen würde, wäre ich in der Lage gewesen, die letzte Bedingung in dem Code unten zu eliminieren, den ich versuchte loszuwerden, als ich zum ersten Mal im Internet nach einer Lösung suchte Tauschen von Knoten ohne spezielle benachbarte Knoten. Ich bin mir nicht ganz sicher, aber es hörte sich an, als ob Smashery diese wiederholten Aufgaben absichtlich aus der logischen Strenge ließ und die Prozedur besser illustrierte - ich habe das vielleicht missverstanden.
Drittens, auf dieser und anderen Seiten habe ich oft die Aussage aus anderen Antworten wiederholt, dass es besser ist, den Inhalt der Knoten zu tauschen, als die Zeiger. Im Fall von einfachen ganzen Zahlen, wie in den Beispielen bisher, ergibt dies natürlich einen kürzeren, einfacheren Code. Wenn wir jedoch verknüpfte Listen mit Knoten diskutieren, die ganze Zahlen enthalten, ist dies normalerweise ein Ersatz für die Hintergrunddatenstruktur eines komplexeren und allgemeineren Containers. Daher glaube ich nicht, dass das Austauschen der Inhalte der Knoten wirklich so einfach ist, zumindest wenn die Implementierung der Datenstruktur keine Annahmen über die Kopiesemantik der Container-Elemente machen kann. Außerdem kann ich durch die Möglichkeit, den Inhalt von Knoten auszutauschen, erkennen, dass die verknüpfte Liste Eigentümer des Inhalts dieser Knoten ist, da Code außerhalb der Methode der verknüpften Liste sonst Verweise auf die Objekte in diesen Knoten enthält, deren Werte plötzlich unter ihnen wechseln.
Ich gebe jedoch zu, dass dies von der Semantik des Containers abhängen könnte. Für ein Array wird erwartet, dass eine Swap-Methode den Wert unterhalb von Referenzen auf einen bestimmten Index dieses Arrays ändert. Das würde bedeuten, dass die Referenzen sich nicht auf ein bestimmtes Objekt beziehen, sondern auf eine Position in einem Container, die man indexieren kann. Wenn wir eine verkettete Liste als ein Mittel ansehen, um nur eine Menge von Objekten anzuordnen, die außerhalb der verknüpften Liste verwendet werden, würde ein Benutzer wahrscheinlich erwarten, dass eine Austauschoperation nur die Position und nicht den Inhalt tauscht.
Stellen Sie sich zum Beispiel vor, dass die verkettete Liste Objekte vom Typ "Auto" darstellt. Jedes Auto hat einen Besitzer und dieser Besitzer referenziert sein Auto durch einen Zeiger darauf. Angenommen, die verkettete Liste stellt die Reihenfolge dar, in der ein Satz von Fahrzeugen zur Inspektion bei einem Autohändler gewartet werden soll. Wenn wir den Inhalt von zwei Knoten tauschten, um die Zeitpläne für zwei Autos zu tauschen, und indem wir ihren Inhalt austauschten, würde die Wartung tatsächlich in der neuen, richtigen Reihenfolge geschehen - aber die Leute würden auch plötzlich Eigentümer sein verschiedene Autos! (Ich hätte aber nichts dagegen, mit einem Tesla zu tauschen, weil ich nur einen Corolla fahre.)
Wenn die verkettete Liste, wie im Beispiel des Arrays, basierend auf Indizierung Semantik dann die Position im Knoten könnte einfach die Reihenfolge darstellen, in der die Autos auf ein Schiff zum Transport geladen werden. Zu diesem Zeitpunkt haben die Autos keine Besitzer und wir kümmern uns wirklich nur darum, in welchem Slot sie sich befinden. Dann schadet es wirklich nicht, Autos auszutauschen, d. H. Den Inhalt der Objekte, auf die die Knoten verweisen.
Schließlich zum Code. Wie ich oben sagte, konnte ich das Spezialgehäuse für benachbarte Knoten nicht vermeiden.
Zuerst wird die Definition eines Hilfs Methode:
int find_node(int v, node* root, node** n, node** pn) {
int i = 0;
for (*n = root; *n != NULL && (*n)->v != v; ++i) {
*pn = *n;
*n = (*n)->next;
}
return i;
}
Diese Methode findet einen Knoten durch seinen Wert. Die zurückgegebene Ganzzahl ist die auf Null basierende Position des Node in der verknüpften Liste (nennen Sie es ihren Index, wenn Sie möchten). Ich fand, dass die Adjazenz durch Position statt durch Zeigervergleiche besser lesbar ist. Der Anfang der Liste ist root. Die Methode setzt n auf den Knoten, der den übergebenen Wert enthält. In pn speichert die Methode den Vorgänger von n.
Im Folgenden ist der eigentliche swap:
void swap_nodes(node **root, int v1, int v2) {
if (v1 == v2) return;
node *n1, *n2, *pn1, *pn2;
int p1 = find_node(v1, *root, &n1, &pn1);
int p2 = find_node(v2, *root, &n2, &pn2);
if (p1 > p2) {
std::swap(n1, n2);
std::swap(pn1, pn2);
std::swap(p1, p2);
}
if (p1 == 0) *root = n2;
else pn1->next = n2;
node* nn1 = n1->next;
n1->next = n2->next;
if (p2 - p1 > 1) {
n2->next = nn1;
pn2->next = n1;
} else {
n2->next = n1;
}
}
Es tut mir leid, dass ich die Unterschrift des Verfahrens der OP ein wenig geändert haben. Ich fand es bequemer, die Werte der Knoten zu tauschen, im Gegensatz zu Knotenzeigern. Wenn Sie nur Knotenzeiger an die jeweiligen Knoten übergeben, müssen Sie eine weitere Traversierung durchführen, um die Vorgänger mit dieser Lösung zu finden, was mir etwas peinlich war. Wenn wir Knoten nicht anhand dieser Werte unterscheiden können, z. Werte sind nicht eindeutig, wir benötigen jedoch Zeiger auf die Knoten.
Wie bei der obigen Erläuterung zu find_node finden wir zuerst die Positionen, Knoten und Vorgänger für die Knotenwerte, die über v1 und v2 an swap_nodes übergeben werden. Die Werte für den ersten und zweiten Knoten werden alle vertauscht, wenn der zweite zu tauschende Knoten vor dem ersten angezeigt wird. Es ist nicht viel Code zu tun, reduziert spezielle Gehäuse und macht es ein wenig einfacher zu visualisieren.
Jetzt sind nur noch zwei weitere Bedingungen übrig, von denen keine zu trivial zu vermeiden war. Wenn sich der erste Knoten an der Spitze der verknüpften Liste befindet, d. H. An der Position Null, muss die Wurzel auf den zweiten Knoten zeigen. Andernfalls zeigt der Vorgänger des ersten Knotens auf den zweiten Knoten.
Der vorherige Wert des Nachfolgers des ersten Knotens muss gespeichert werden, falls die Knoten nicht benachbart sind. Dann wird der Nachfolger des ersten Knotens auf den aktuellen Nachfolger des zweiten Knotens gesetzt. Dies ist die einzige Änderung, die für alle Fälle gilt: Der neue Nachfolger des ersten Knotens, der der alte Nachfolger des zweiten Knotens ist, ist die einzige Gewissheit und hilfreich, um mit den Zeigeroperationen und ihrer Reihenfolge bei der Implementierung dieses Austauschs zu beginnen .
Wenn die Positionen der Knoten sich um mehr als eins unterscheiden, sind sie nicht benachbart. Dann wird der neue Nachfolger des zweiten Knotens der alte Nachfolger des ersten Knotens - oben gespeichert - und der Vorgänger des zweiten Knotens zeigt nun auf den ersten Knoten. Wenn sie benachbart sind, gibt es keine Knoten zwischen den Knoten, die ausgetauscht werden müssen, die aktualisiert werden müssen, so dass nur noch der zweite Knoten mit dem ersten verbunden werden muss.
Hey Reti, ich habe auf edit1 in meiner Antwort geantwortet. Wenn Sie einen segfault erhalten, überprüfen Sie, ob Sie die tmp-Variable richtig zuordnen und löschen. – Smashery