Aufdringliche Listen

Lesezeit: 10 Minuten

Ich konnte online nicht allzu viele Informationen über sie finden. Was sind sie und wann werden sie typischerweise verwendet?

Vielen Dank.

Eine aufdringliche Liste ist eine Liste, bei der der Zeiger auf den nächsten Listenknoten in derselben Struktur wie die Knotendaten gespeichert ist. Dies ist normalerweise eine schlechte Sache, da es die Daten an die spezifische Listenimplementierung bindet. Die meisten Klassenbibliotheken (z. B. die C++-Standardbibliothek) verwenden nicht aufdringliche Listen, bei denen die Daten nichts über die Implementierung der Liste (oder eines anderen Containers) wissen.

  • Eine nicht-intrusive Liste bedeutet, dass sich der “nächste” Zeiger auf einer anderen Cache-Zeile befindet als die Daten selbst; Daher ist es für verschiedene Vorgänge bis zu 2-mal langsamer als eine aufdringliche Liste. Es bedeutet auch, “Link-Blöcke” zuzuweisen und zu verwalten, was den Speicherverbrauch und den Alloc/Free-Overhead erhöht. Abhängig von der Perspektive sind aufdringliche Listen gut (für die Leistung) oder schlecht (für die Bequemlichkeit in Fällen, in denen es Ihnen egal ist, dass Ihr Endprodukt von schlechter Qualität ist).

    – Brenda

    7. Juli 2016 um 6:19 Uhr

  • @Brendan: Das ist hauptsächlich ein C/Java-Problem. C++ hat dieses Problem nicht; std::list<T> kann umgesetzt werden als struct _Node { _Node* prev,next; T object }. Vorlagen in Kombination mit starker Wertsemantik machen diese unaufdringlichen Listen effizient. Wie Sie sehen können, gibt es keine separaten Cache-Zeilen oder Verbindungsblöcke.

    – MSalter

    12. September 2016 um 15:34 Uhr

  • @MSalters wie macht eine Vorlage etwas effizienter? (Es ist vielleicht wartbarer, aber es würde nicht schneller ausgeführt als etwas, das in C implementiert ist?

    – nö

    28. Oktober 2016 um 14:40 Uhr

  • @nhed: Das Problem ist, dass Sie in C zwischen einer generischen und einer schnellen Lösung wählen müssen. Entweder ist die Lösung generisch (mit void*) und Sie verlieren die Referenzlokalität, oder Sie schreiben Ihre Listenknotentypen für jeden Elementtyp neu. Ein gutes Beispiel ist qsort vs std::sort, wobei C++ C bei Ganzzahlen typischerweise um einen Faktor von 600 % schlägt. Ja, ein intsort() in C könnte so schnell sein wie C++, konnte aber Floats nicht sortieren.

    – MSalter

    28. Oktober 2016 um 19:32 Uhr

  • @nhed std::sort Vorteil liegt an Inlining. qsort empfängt einen typgelöschten Zeiger auf eine Funktion, die auf void* wirkt, während std::sort (aufgrund von Vorlagen) die Typen seiner Argumente und, was noch wichtiger ist, die beim Sortieren verwendete Funktion kennt (wenn sie sich in derselben TU befindet), also wenn ja klein genug (und meistens ist es das auch), kann es einfach inline eingebunden werden und Anruf-Overhead loswerden.

    –Dan M.

    20. August 2019 um 16:47 Uhr

Benutzeravatar von nhed
nh

Ich mag eigentlich das aufdringliche Modell.

  1. Es ist besser für den Speicher (nicht viele kleine Zuweisungen für Dinge, die auf andere Dinge zeigen)
  2. Es ermöglicht Ihnen, ein Objekt gleichzeitig in mehreren Containern zu haben.
  3. Es ermöglicht Ihnen, ein Element mit einem Suchmodus (Hash) zu finden, aber dann das nächste Element in lexografischer Reihenfolge zu finden
    • Das ist nicht das gleiche wie Nr. 2, aber es kann mit Boosts erreicht werden multi_index_containeraber beachte das multi_index_container weist bestimmte Mängel auf, die bei aufdringlichen Containern keine Probleme darstellen.

Aufdringlich ist gut

…man muss nur wissen, was man tut (was für jeden Container gilt).

  • @Andrew offensichtlich hilfreiche Antwort auf viele (im Vergleich zur Top-Antwort), ohne eine Einfügung einer Handbuchseite zu sein. Es geht darauf ein, warum/wann Sie es verwenden würden (2. Teil der Frage). Genieße den Rest deines Tages

    – nö

    22. Februar 2018 um 19:11 Uhr


  • Die Antwort ist praktisch ein Non-Sequitur, Downvoting.

    – Der Dembinski

    24. Dezember 2018 um 6:24 Uhr

  • Punkt 2 scheint hier genau falsch zu sein: Daten in einer aufdringlichen Liste können nur in einem Container sein, weil das Datenobjekt nur einen Next-Zeiger hat, der als “Container” dient.

    – Ned Batchelder

    1. Mai 2021 um 16:36 Uhr

  • @ned-batchelder Sie können in einem aufdringlichen Datenknoten so viele nächste Zeiger haben, wie Sie möchten. Zusätzlich zu den Datenmitgliedern Ihres Knotens können Sie einen oder mehrere Listenknoten mit jeweils eigenen Weiter-/Zurück-Links haben.

    – nö

    3. Mai 2021 um 11:55 Uhr

  • Sicher, aber diese Liste sollte “Vorteile des intrusiven Modells gegenüber dem nicht-intrusiven” enthalten. Aufdringlich lässt ein Objekt Teil mehrerer Sammlungen sein, aber nur einer vorgeplanten Anzahl spezifischer Sammlungen. Non-intrusive ist viel besser darin, zuzulassen, dass ein Objekt gleichzeitig in mehreren Containern vorhanden ist.

    – Ned Batchelder

    3. Mai 2021 um 18:14 Uhr

Ashs Benutzeravatar
Asche

Es ist überraschend, wie viele Leute das völlig falsch verstehen (wie die Antwort von Ziezi). Die Leute scheinen Dinge zu verkomplizieren, wenn es wirklich ziemlich einfach ist.

In einer aufdringlichen verknüpften Liste gibt es keine explizite ‘Node’-Struktur/Klasse. Stattdessen enthält die ‘Data’-Struktur/Klasse selbst einen Next- und Prev-Zeiger/Verweis auf andere Daten in der verknüpften Liste.

Zum Beispiel (aufdringlicher verknüpfter Listenknoten):

struct Data { 
   Data *next; 
   Data *prev; 
   int fieldA; 
   char * fieldB; 
   float fieldC; 
}  

Beachten Sie, wie die next- und prev-Zeiger neben und stehen eindringen auf den privaten Datenfeldern der Entität wie fieldA. Dies ‘verstößt’ gegen die Trennung von Belangen, die durch standardmäßige verknüpfte Listen (siehe unten) erzwungen wird, hat aber Vorteile, da es den Umfang des Durchlaufens von Listen zum Auffinden bestimmter Knoten sowie geringere Speicherzuweisungen erheblich reduziert.

In einer aufdringlichen verketteten Liste ist die ‘verkettete Liste’ selbst oft virtuell, es besteht normalerweise überhaupt keine Notwendigkeit, eine Struktur/Klasse einer verketteten Liste zu erstellen.

Stattdessen können Sie einfach einen Kopfzeiger auf das erste Datenelement in einem Eigentümer/Manager speichern. Dieser Manager enthält auch Funktionen zum Hinzufügen/Entfernen, um Zeiger nach Bedarf zu aktualisieren. Weitere Informationen finden Sie unter https://gameprogrammingpatterns.com/spatial-partition.html

Ein einzelnes Paar von Next/Prev-Zeigern bedeutet, dass jedes Objekt nur zu einer Liste gehören kann. Sie können jedoch natürlich nach Bedarf mehrere Paare von Next/Prev-Zeigern hinzufügen (oder ein Array von Next/Prev-Zeigern definieren), um Objekte in mehreren Listen zu unterstützen.

In einer nicht-intrusiven (dh standardmäßigen) verknüpften Liste sind die Next/Prev-Zeiger Teil einer dedizierten ‘Knoten’-Entität und die eigentliche Daten-Entität einfach ein Feld in diesem Knoten.

Zum Beispiel (nicht aufdringlicher verknüpfter Listenknoten und Daten):

struct Data { 
   int fieldA; 
   char * fieldB; 
   float fieldC; 
}  

struct Node { 
   Node *next; 
   Node *prev; 
   Data *data; 
}  

Beachten Sie, wie die next/prev-Zeiger nicht stören auf die eigentliche Datenentität und die Trennung von Bedenken wird beibehalten.

Aktualisieren:

Möglicherweise sehen Sie andere Websites wie z https://www.data-structures-in-practice.com/intrusive-linked-lists/ Verwenden Sie eine ‘List’-Struktur (eigentlich ein Knoten), die next/prev-Zeiger enthält und das einzige aufdringliche Feld in der ‘Data’-Struktur/Klasse ist.

Dies verbirgt zwar die next/prev-Zeiger vor den Daten, leidet jedoch unter der Notwendigkeit, eine Zeigerarithmetik durchzuführen, um einfach auf die tatsächlichen Daten zuzugreifen, die der Liste (Knoten) zugeordnet sind.

Dieser Ansatz fügt meiner Option unnötige Komplexität hinzu (im Gegensatz zum direkten Einbetten von Next/Prev-Feldern), nur um das zweifelhafte Ziel zu erreichen, die Next/Prev-Zeiger zu verbergen. Wenn Sie aufdringliche Listen benötigen, halten Sie sie so einfach wie möglich. (Außerdem ist es in Sprachen mit verwaltetem Speicher sowieso schwierig oder unmöglich, Zeigerarithmetik durchzuführen.)

  • Dein Data Die Klasse hat die gleiche Anzahl von Speicherzuweisungen wie die von Ziezi. Seine T data Mitglied ist kein std::unique_ptr<T> data; es ist zusammenhängend mit zugeordnet prev und next so wie in deinem beispiel.

    – MSalter

    21. November 2019 um 9:21 Uhr

  • Sicher, ich erwähne nur die Ziezi-Antwort für das Missverständnis in Bezug auf die Aufdringlichkeit und vergleiche die Anzahl der Speicherzuweisungen nicht direkt. Ich bin jedoch etwas eingerostet in Bezug auf C++-Speicherzuweisungen, da ich heutzutage nur noch ein schlechter Managed-Memory-Entwickler bin :), danke für die Info.

    – Asche

    21. November 2019 um 11:54 Uhr

  • Sie können (und sollten vielleicht) eine explizite Listenknotenstruktur für aufdringliche Listen haben. Siehe die Implementierung im Linux-Kernel.

    – Pavel Simerda

    2. Januar 2020 um 17:15 Uhr

Aufdringliche Listen sind Listen, bei denen Objekte selbst Köpfe oder Zellen von Listen sind. Sie sind gute oder schlechte Dinge, je nach Kontext.

Innerhalb eines definierten Moduls (nicht trennbare Gruppe von Klassen, die zusammenarbeiten) kann dies das BESTE Mittel sein, um Beziehungen zwischen Klassen zu knüpfen. Sie ermöglichen die kostenlose direkte und vollständige Verwaltung gemeinsamer Beziehungen wie Eindeutigkeit (z. B. Äpfel erscheinen nicht zweimal in Apfelbäumen, und dies erfordert keinen Schlüssel dafür, und Äpfel gehören nicht zu zwei verschiedenen Bäumen), sie sind navigierbar in beide Richtungen (direkter Zugang zum Apfelbaum bei einem Apfel und zu Äpfeln bei einem Apfelbaum). Alle Grundoperationen sind O(1) (keine Suche in einem externen Container).

Aufdringliche Listen sind SEHR SCHLECHT zwischen zwei Modulen. Weil sie miteinander verbunden werden und die Modulbegründung die Verwaltung der Codeunabhängigkeit ist.

Benutzeravatar von Ziezi
Ziezi

Hier eine kurze Beschreibung, die auch für Listen gilt:

I. Aufdringliche Container.

Das zu speichernde Objekt enthält zusätzliche Informationen, um die Integration in den Container zu ermöglichen. Beispiel:

struct Node
{
    Node* next;   // additional
    Node* prev;   // information 
    T data;
} 

1. Vorteile:

  • speichert die Objekte selbst.
  • beinhaltet keine Speicherverwaltung.
  • Iteration ist schneller.
  • bessere Ausnahmegarantien.
  • Vorhersagbarkeit beim Einfügen und Löschen von Objekten. (Es ist keine zusätzliche (nicht vorhersagbare) Speicherverwaltung erforderlich.)
  • bessere Speicherlokalität.

2. Nachteile:

  • enthält zusätzliche Daten für die Container-Integration. (Jeder Lagertyp muss an die Behälteranforderungen angepasst (modifiziert) werden.)
  • Vorsicht vor möglichen Nebeneffekten beim Ändern des Inhalts des gespeicherten Objekts. (insbesondere bei assoziativen Containern.)
  • Lebenszeitverwaltung des eingefügten Objekts, unabhängig vom Container.
  • Objekt kann möglicherweise verworfen werden, bevor es aus dem Container gelöscht wird, was zur Invalidierung des Iterators führt.
  • Aufdringliche Container sind NICHT kopierbar und NICHT zuweisbar.

II. Nicht-intrusive Container (C++-Standardcontainer)

Objekt “kennt” und enthält keine Details über den Container, in dem gespeichert werden soll. Beispiel:

struct Node
{
    T data;
}

1. Vorteile:

  • enthält keine zusätzlichen Informationen zur Container-Integration.
  • vom Container verwaltete Objektlebensdauer. (weniger komplex.)

2. Nachteile:

  • Kopien der vom Benutzer übergebenen Werte speichern. (Inplace-Bauweise möglich.)
  • ein Objekt kann nur zu einem Container gehören. (oder der Container sollte Zeiger auf Objekte speichern.)
  • Mehraufwand für das Speichern von Kopien. (Buchführung über jede Zuordnung.)
  • Nicht kopierbare oder nicht bewegliche Objekte KÖNNEN NICHT in unaufdringlichen Behältern aufbewahrt werden.
  • kann abgeleitetes Objekt nicht speichern und dennoch seinen ursprünglichen Typ beibehalten. (Slicing – löst Polymorphismus.)

  • schöner Nekro. In Bezug auf Ihre “Nachteile” im nicht aufdringlichen Segment; Punkt 1 – nicht unbedingt, Bau vor Ort möglich, dh “einlagern” Punkt 4 – Rundum falsch

    – sp2danny

    15. Juni 2017 um 11:14 Uhr

  • @sp2danny Ich habe es aktualisiert, um Ihre Bemerkungen widerzuspiegeln, danke!

    – Ziezi

    15. Juni 2017 um 11:49 Uhr

  • Es gibt einen Fehler im cons-Abschnitt: Ein Objekt kann nur im aufdringlichen Fall in einen Container gehören, nicht im nicht-aufdringlichen Fall. Die next/prev-Zeiger sind nur für eine Liste relevant.

    – Antony Riakiotakis

    30. Juni 2017 um 13:36 Uhr

  • -1: Das ist sehr verwirrend. Das Node Die Struktur der aufdringlichen Container, wie Sie sie schreiben, ist identisch mit einem typischen verketteten Listenknoten, der auf Vorlagen basiert T. Sie hätten erklären sollen, dass der Objekttyp in einer aufdringlichen Liste den Listenknotentyp erbt und sich effektiv mit dem aufdringlichen Containertyp verbindet.

    – ceztko

    17. Januar 2018 um 22:46 Uhr


  • Nicht nur sehr verwirrend, sondern schlichtweg falsch. Das erste Codebeispiel ist nur eine klassische NICHT aufdringliche Knotenstruktur.

    – Asche

    21. November 2019 um 8:36 Uhr

  • schöner Nekro. In Bezug auf Ihre “Nachteile” im nicht aufdringlichen Segment; Punkt 1 – nicht unbedingt, Bau vor Ort möglich, dh “einlagern” Punkt 4 – Rundum falsch

    – sp2danny

    15. Juni 2017 um 11:14 Uhr

  • @sp2danny Ich habe es aktualisiert, um Ihre Bemerkungen widerzuspiegeln, danke!

    – Ziezi

    15. Juni 2017 um 11:49 Uhr

  • Es gibt einen Fehler im cons-Abschnitt: Ein Objekt kann nur im aufdringlichen Fall in einen Container gehören, nicht im nicht-aufdringlichen Fall. Die next/prev-Zeiger sind nur für eine Liste relevant.

    – Antony Riakiotakis

    30. Juni 2017 um 13:36 Uhr

  • -1: Das ist sehr verwirrend. Das Node Die Struktur der aufdringlichen Container, wie Sie sie schreiben, ist identisch mit einem typischen verketteten Listenknoten, der auf Vorlagen basiert T. Sie hätten erklären sollen, dass der Objekttyp in einer aufdringlichen Liste den Listenknotentyp erbt und sich effektiv mit dem aufdringlichen Containertyp verbindet.

    – ceztko

    17. Januar 2018 um 22:46 Uhr


  • Nicht nur sehr verwirrend, sondern schlichtweg falsch. Das erste Codebeispiel ist nur eine klassische NICHT aufdringliche Knotenstruktur.

    – Asche

    21. November 2019 um 8:36 Uhr

1416670cookie-checkAufdringliche Listen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy