Läuft jeder Datentyp nur auf Knoten mit Zeigern hinaus?

21

Ein Array oder Vektor ist nur eine Folge von Werten. Sie können sicher mit einer verknüpften Liste implementiert werden. Dies ist nur ein Bündel von Knoten mit Zeigern auf den nächsten Knoten.

Stapel und Warteschlangen sind zwei abstrakte Datentypen, die in Intro CS-Kursen gelehrt werden. Irgendwo in der Klasse müssen die Schüler häufig Stapel und Warteschlangen implementieren, indem sie eine verknüpfte Liste als zugrunde liegende Datenstruktur verwenden. Dies bedeutet, dass wir wieder zur gleichen Idee der "Sammlung von Knoten" zurückkehren.

Prioritätswarteschlangen können mit einem Heap erstellt werden. Ein Haufen kann als Baum mit dem minimalen Wert an der Wurzel betrachtet werden. Bäume aller Art, einschließlich BSTs, AVLs und Heaps, können als Sammlung von Knoten betrachtet werden, die durch Kanten verbunden sind. Diese Knoten sind dort miteinander verbunden, wo ein Knoten auf einen anderen zeigt.

Es scheint, als könne sich jedes Datenkonzept immer auf Knoten mit Zeigern auf einen anderen geeigneten Knoten beschränken. Ist das richtig? Wenn es so einfach ist, warum erklären Lehrbücher nicht, dass Daten nur ein Bündel von Knoten mit Zeigern sind? Wie gelangen wir von Knoten zu Binärcode?

derekchen14
quelle
5
Die grundlegende Datenstruktur, auf die Sie anspielen, wird als "Cons-Zelle" bezeichnet. Sie können jede beliebige Datenstruktur daraus erstellen. Wenn Sie wissen möchten, warum ein bestimmter Lehrbuchautor die Nachteile von Zellen nicht erklärt hat, fragen Sie diesen Autor, warum er diese Entscheidung getroffen hat. Von einer Beschreibung einer Knotenanordnung zu einem Binärcode zu gelangen, wird "Kompilierung" genannt und ist die Aufgabe eines "Compilers".
Eric Lippert
18
Sie können auch argumentieren, dass alle Datenstrukturen auf ein Array reduziert sind. Schließlich landen sie alle im Speicher, der nur ein sehr großes Array ist.
BlueRaja - Danny Pflughoeft
10
Sie können ein Array nicht mithilfe einer verknüpften Liste implementieren, wenn Sie die Indizierung von O (1) beibehalten möchten O(1).
Svick
5
Tut mir leid, aber wenn Sie von "Knoten und Zeigern" sprechen, haben Sie bereits Quiche gegessen. " Wie alle echten Programmierer wissen, ist die einzige nützliche Datenstruktur das Array. Strings, Listen, Strukturen, Mengen - dies sind alles Sonderfälle von Arrays und können genauso einfach behandelt werden, ohne Ihre Programmiersprache durcheinander zu bringen von Komplikationen. "Ref:" Echte Programmierer verwenden Pascal nicht ", von web.mit.edu/humor/Computers/real.programmers
alephzero
3
... aber im Ernst, das Wichtigste an Datenstrukturen ist, was Sie damit machen können , und nicht, wie sie implementiert werden. Im 21. Jahrhundert ist es nur eine Programmierübung, sie selbst umzusetzen - und für faule Pädagogen überwiegt die Tatsache, dass solche Übungen leicht zu bewerten sind, die Tatsache, dass sie bestenfalls sinnlos und im schlimmsten Fall positiv schädlich sind, wenn sie die Schüler dazu ermutigen, dies zu denken. " "Räder neu erfinden" ist eine nützliche Aktivität in der realen Programmierung.
Alephzero

Antworten:

14

Nun, das ist im Grunde genommen, worauf alle Datenstrukturen hinauslaufen. Daten mit Verbindungen. Die Knoten sind alle künstlich - sie existieren tatsächlich nicht physisch. Hier kommt der Binärteil ins Spiel. Sie sollten einige Datenstrukturen in C ++ erstellen und überprüfen, wo Ihre Objekte im Speicher landen. Es kann sehr interessant sein, zu erfahren, wie die Daten im Speicher abgelegt sind.

Der Hauptgrund für so viele verschiedene Strukturen ist, dass sie sich alle auf die eine oder andere Sache spezialisieren. Beispielsweise ist es in der Regel schneller, einen Vektor anstelle einer verknüpften Liste zu durchlaufen, da die Seiten aus dem Speicher abgerufen werden. Eine verknüpfte Liste ist besser zum Speichern größerer Typen geeignet, da Vektoren zusätzlichen Platz für nicht verwendete Slots reservieren müssen (dies ist beim Entwurf eines Vektors erforderlich).

Als Randnotiz ist eine interessante Datenstruktur, die Sie sich ansehen möchten, eine Hash-Tabelle. Es folgt nicht ganz dem von Ihnen beschriebenen Knoten- und Zeigersystem.

TL; DR: Container sind im Grunde alle Knoten und Zeiger, haben aber sehr spezifische Verwendungen und sind für etwas besser und für andere schlechter.

user3853544
quelle
1
Ich gehe davon aus, dass die meisten Daten tatsächlich als ein Bündel von Knoten mit Zeigern dargestellt werden können. Dies liegt jedoch nicht daran, dass (a) auf der physischen Ebene das nicht der Fall ist und (b) auf der konzeptionellen Ebene das Denken der Werte als verknüpfte Liste nicht so nützlich ist, um über zugrunde liegende Daten nachzudenken. Es sind sowieso alles nur Abstraktionen, um unser Denken zu vereinfachen. Sie können also auch die beste Abstraktion für eine Situation auswählen, selbst wenn eine andere funktionieren könnte.
derekchen14
13

Es scheint, als könne sich jedes Datenkonzept immer auf Knoten mit Zeigern auf einen anderen geeigneten Knoten beschränken.

Oh, mein Lieber nein. Du tust mir weh.

Wie ich an anderer Stelle versucht habe zu erklären (" Was ist der Unterschied zwischen einem binären Suchbaum und einem binären Heap? "), Gibt es auch für eine feste Datenstruktur mehrere Ebenen, um dies zu verstehen.

Wie die Prioritätswarteschlange, die Sie erwähnen, ist sie ein abstrakter Datentyp, wenn Sie sie nur verwenden möchten. Sie verwenden es, um zu wissen, welche Art von Objekten darin gespeichert sind und welche Fragen Sie ihm stellen können. Das sind mehr Datenstrukturen als eine Tüte mit Gegenständen. Auf der nächsten Ebene der berühmten Implementierung, die binäre Haufen, kann verstanden als binärer Baum, aber die letzte Ebene ist aus Effizienzgründen als Array implementiert. Keine Knoten und Zeiger dort.

Und auch für Diagramme, die mit Sicherheit wie Knoten und Zeiger (Kanten) aussehen, stehen zwei grundlegende Darstellungen zur Verfügung: das Adjazenzarray und die Adjazenzlisten. Nicht alle Hinweise stelle ich mir vor.

Wenn Sie wirklich versuchen, Datenstrukturen zu verstehen, müssen Sie ihre guten Punkte und Schwächen untersuchen. Manchmal verwendet eine Darstellung ein Array aus Gründen der Effizienz (entweder räumlich oder zeitlich), manchmal gibt es Hinweise (aus Gründen der Flexibilität). Dies gilt auch, wenn Sie gute "vorgefertigte" Implementierungen wie die C ++ - STL haben, da Sie auch dort manchmal die zugrunde liegenden Darstellungen auswählen können. Da gibt es immer einen Kompromiss.

Hendrik Jan
quelle
10

Machen wir eine Analogie zur Mathematik. Betrachten Sie den folgenden Satz: " ist eine stetige Funktion". Funktionen sind wirklich definiert als Beziehungen, die definiert sind als Mengen. Die Menge der reellen Zahlen ist das eindeutige vollständige, vollständig geordnete Feld: Alle diese Konzepte sind einfacher definiert. Um von Kontinuität zu sprechen, braucht man das Konzept der Nachbarschaft, das in Bezug auf eine Topologie definiert ist ... und so weiter bis zu den Axiomen von ZFC.f:RR

Niemand erwartet von Ihnen, dass Sie all das sagen, um eine kontinuierliche Funktion zu definieren, sonst wäre niemand in der Lage, irgendwelche Arbeiten zu erledigen. Wir "vertrauen" einfach darauf, dass jemand die langweilige Arbeit für uns gemacht hat.

Jede Datenstruktur, die Sie sich vorstellen können, beschränkt sich auf die grundlegenden Objekte, mit denen Ihr zugrunde liegendes Rechenmodell umgeht, ganze Zahlen in einem Register, wenn Sie eine Maschine mit wahlfreiem Zugriff verwenden, oder Symbole auf einem Band, wenn Sie eine Turing-Maschine verwenden.

Wir verwenden Abstraktionen, weil sie unseren Geist von Trivialitäten befreien und es uns ermöglichen, über komplexere Probleme zu sprechen. Es ist durchaus vernünftig, nur zu "vertrauen", dass diese Strukturen funktionieren: Bis ins kleinste Detail zu gehen, ist - es sei denn, Sie haben einen bestimmten Grund dafür - eine vergebliche Übung, die Ihrem Argument nichts hinzufügt.

schnelle Sorte
quelle
10

Hier ein Gegenbeispiel: In der λ-Rechnung läuft jeder Datentyp auf Funktionen hinaus. λ-Kalkül hat keine Knoten oder Zeiger, das einzige, was es hat, sind Funktionen, daher muss alles mit Funktionen implementiert werden.

Dies ist ein Beispiel für die Codierung von Booleschen Werten als Funktionen in ECMAScript:

const T   = (thn, _  ) => thn,
      F   = (_  , els) => els,
      or  = (a  , b  ) => a(a, b),
      and = (a  , b  ) => a(b, a),
      not = a          => a(F, T),
      xor = (a  , b  ) => a(not(b), b),
      iff = (cnd, thn, els) => cnd(thn, els)();

Und das ist eine Nachteile-Liste:

const cons = (hd, tl) => which => which(hd, tl),
      first  = list => list(T),
      rest   = list => list(F);

Natürliche Zahlen können als Iteratorfunktionen implementiert werden.

Eine Menge ist dasselbe wie ihre charakteristische Funktion (dh die containsMethode).

Beachten Sie, dass bei der obigen Kodierung von Booleschen Werten in der Kirche tatsächlich Bedingungen in OO-Sprachen wie Smalltalk implementiert werden, die keine Booleschen Werte, Bedingungen oder Schleifen als Sprachkonstrukte haben und diese lediglich als Bibliotheksfunktion implementieren. Ein Beispiel in Scala:

sealed abstract trait Boolean {
  def apply[T, U <: T, V <: T](thn: => U)(els: => V): T
  def(other: => Boolean): Boolean
  def(other: => Boolean): Boolean
  val ¬ : Boolean

  final val unary_! = ¬
  final def &(other: => Boolean) =(other)
  final def |(other: => Boolean) =(other)
}

case object True extends Boolean {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): U = thn
  override def(other: => Boolean) = other
  override def(other: => Boolean): this.type = this
  override final val ¬ = False
}

case object False extends Boolean {
  override def apply[T, U <: T, V <: T](thn: => U)(els: => V): V = els
  override def(other: => Boolean): this.type = this
  override def(other: => Boolean) = other
  override final val ¬ = True
}

object BooleanExtension {
  import scala.language.implicitConversions
  implicit def boolean2Boolean(b: => scala.Boolean) = if (b) True else False
}

import BooleanExtension._

(2 < 3) { println("2 is less than 3") } { println("2 is greater than 3") }
// 2 is less than 3
Jörg W. Mittag
quelle
2
@Hamsteriffic: Versuchen Sie Folgendes: Auf diese Weise werden Bedingungen in OO-Sprachen wie Smalltalk implementiert. Smalltalk hat keine Booleschen Werte, Bedingungen oder Schleifen als Sprachkonstrukt. Alle diese sind rein als Bibliotheken implementiert. Noch nichts dagegen geblasen? William Cook weist auf etwas hin, das schon vor langer Zeit hätte offensichtlich sein müssen, aber nicht wirklich bemerkt wurde: Da es bei OO um Verhaltensabstraktion geht und Verhaltensabstraktion die einzige Art von Abstraktion ist, die in λ-calculus existiert, folgt, dass alle Programme in geschrieben sind λ-Kalkül sind zwangsläufig OO. Ergo, λ-Kalkül ist der älteste und…
Jörg W Mittag
… Reinste OO-Sprache!
Jörg W Mittag
1
Ein schlechter Tag mit Smalltalk schlägt einen guten Tag mit C ++ :-)
Bob Jarvis - Reinstate Monica
@ JörgWMittag Ich glaube nicht, dass Ihre Schlussfolgerung aus Ihrer Annahme folgt, ich glaube nicht, dass Ihre Annahme überhaupt wahr ist, und ich glaube definitiv nicht, dass Ihre Schlussfolgerung wahr ist.
Miles Rout
4

Viele (die meisten?) Datenstrukturen bestehen aus Knoten und Zeigern. Arrays sind ein weiteres kritisches Element einiger Datenstrukturen.

Letztendlich ist jede Datenstruktur nur eine Ansammlung von Wörtern im Speicher oder nur eine Ansammlung von Bits. Es ist wichtig, wie sie strukturiert sind und wie wir sie interpretieren und verwenden.

DW
quelle
2
Letztendlich sind Bits ein Bündel elektrischer Signale auf einem Draht oder Lichtsignale in einem Glasfaserkabel oder spezifisch magnetisierte Partikel auf einer Platte oder Radiowellen einer bestimmten Wellenlänge oder oder oder oder. Die Frage ist also, wie tief möchten Sie gehen? :)
Wildcard
2

Die Implementierung von Datenstrukturen läuft immer auf Knoten und Zeiger hinaus, ja.

Aber warum dort aufhören? Die Implementierung von Knoten und Zeigern beschränkt sich auf Bits.

Die Implementierung von Bits beruht auf elektrischen Signalen, Magnetspeichern, möglicherweise Glasfaserkabeln usw. (kurz gesagt: Physik).

Dies ist die reductio ad absurdum der Aussage "Alle Datenstrukturen laufen auf Knoten und Zeiger hinaus." Es ist wahr - aber es bezieht sich nur auf die Implementierung.


Chris Date kann sehr gut zwischen Implementierung und Modell unterscheiden , obwohl sein Aufsatz sich insbesondere an Datenbanken richtet.

Wir können noch ein bisschen weiter gehen, wenn wir feststellen, dass es keine einzige Trennlinie zwischen Modell und Implementierung gibt. Dies ist ähnlich (wenn nicht identisch) mit dem Konzept der "Abstraktionsschichten".

Auf einer bestimmten Abstraktionsebene sind die Ebenen "unter" Ihnen (die Ebenen, auf denen Sie aufbauen) lediglich "Implementierungsdetails" für die Abstraktion oder das Modell, auf die Sie sich beziehen.

Die unteren Abstraktionsschichten selbst weisen jedoch Implementierungsdetails auf.

Wenn Sie ein Handbuch für eine Software lesen, lernen Sie die Abstraktionsschicht kennen, die von dieser Software "dargestellt" wird, auf der Sie Ihre eigenen Abstraktionen erstellen können (oder einfach Aktionen wie das Senden von Nachrichten ausführen können).

Wenn Sie die Implementierungsdetails der Software kennen, erfahren Sie, wie die Schöpfer die von ihnen erstellten Abstraktionen untermauerten. Die "Implementierungsdetails" können unter anderem Datenstrukturen und Algorithmen umfassen.

Sie würden die Spannungsmessung jedoch nicht als Teil der "Implementierungsdetails" für ein bestimmtes Softwareteil betrachten, obwohl dies dahingehend erklärt wird, wie "Bits" und "Bytes" und "Speicher" tatsächlich auf dem physischen Computer funktionieren.

Zusammenfassend sind Datenstrukturen eine Abstraktionsschicht zum Überlegen und Implementieren von Algorithmen und Software. Die Tatsache, dass diese Abstraktionsschicht auf Implementierungsdetails niedrigerer Ebene wie Knoten und Zeiger aufbaut, ist wahr, aber innerhalb der Abstraktionsschicht irrelevant .


Ein großer Teil des Verständnisses eines Systems ist das Erfassen, wie die Abstraktionsschichten zusammenpassen. Daher ist es wichtig zu verstehen, wie Datenstrukturen implementiert werden. Aber die Tatsache , dass sie sind , umgesetzt werden , bedeutet nicht , dass die Abstraktion von Datenstrukturen nicht vorhanden ist .

Platzhalter
quelle
2

Ein Array oder Vektor ist nur eine Folge von Werten. Sie können sicher mit einer verknüpften Liste implementiert werden. Dies ist nur ein Bündel von Knoten mit Zeigern auf den nächsten Knoten.

Ein Array oder ein Vektor kann mit einer verknüpften Liste implementiert werden, sollte dies aber so gut wie nie sein.

nnΘ(n)Θ(logn)Θ(1)(dh ein sequentieller Block des Direktzugriffsspeichers). Außerdem ist der Zugriff auf das eigentliche Array auf der CPU viel einfacher zu implementieren und schneller auszuführen, und das Speichern nimmt weniger Speicher in Anspruch, da auf Zeigern zwischen separaten Knoten kein Platz verschwendet werden muss.

Θ(n)Θ(1)Θ(1)im Durchschnitt auf Kosten von höchstens einem konstanten Faktor zusätzlichen Speichers, indem die tatsächlich zugewiesene Größe des Arrays auf zB die nächste Potenz von 2 gerundet wird Elemente in der Mitte Ihrer Liste: Ein physisches Array ist möglicherweise nicht die beste Implementierung für Ihre Datenstruktur. Ziemlich oft können Sie Einfügungen und Entfernungen durch günstige Tauschgeschäfte ersetzen.

Wenn Sie Ihren Anwendungsbereich ein wenig erweitern, um physisch zusammenhängende Arrays in Ihre Toolbox aufzunehmen, können tatsächlich fast alle praktischen Datenstrukturen mit diesen zusammen mit Knoten und Zeigern implementiert werden.

Θ(1)Umkehrbetrieb). In der Praxis sind diese Funktionen jedoch selten nützlich genug, um ihre Nachteile zu überwinden, darunter zusätzliche Implementierungskomplexität und Inkompatibilität mit Standard- Garbage-Collection- Schemata.

Ilmari Karonen
quelle
1

Wenn es so einfach ist, warum erklären Lehrbücher nicht, dass Daten nur ein Bündel von Knoten mit Zeigern sind?

Denn das ist nicht was "Daten" bedeutet. Sie verbinden abstrakte Ideen mit Umsetzungen. "Daten" ist eine sehr abstrakte Idee: Es ist nur ein anderer Name für "Informationen". Ein Bündel verknüpfter Knoten mit Zeigern (auch als "verknüpfte Datenstruktur" bezeichnet) ist eine viel konkretere Idee: Es handelt sich um eine bestimmte Art der Darstellung und Organisation von Informationen.

Einige Datenabstraktionen eignen sich sehr gut für "verknüpfte" Implementierungen. Es gibt nicht viele gute Möglichkeiten, die Verzweigung eines vollständig allgemeinen Baums ohne die Verwendung expliziter Knoten und Zeiger (oder einer gewissen Isomorphie von Knoten und Zeigern) zu implementieren. Andererseits gibt es andere Abstraktionen, die Sie niemals mit Knoten und Zeigern implementieren würden. Fließkommazahlen kommen in den Sinn.

Stapel und Warteschlangen liegen irgendwo dazwischen. Es gibt Zeiten, in denen eine verknüpfte Implementierung eines Stacks sehr sinnvoll ist. In anderen Fällen ist es viel sinnvoller, ein Array und einen einzelnen "Stapelzeiger" zu verwenden.

Solomon Slow
quelle