Ogni tipo di dati si riduce a nodi con puntatori?


21

Un array o un vettore è solo una sequenza di valori. Possono sicuramente essere implementati con un elenco collegato. Questo è solo un mucchio di nodi con puntatori al nodo successivo.

Stack e code sono due tipi di dati astratti comunemente insegnati nei corsi Intro CS. Da qualche parte nella classe, gli studenti devono spesso implementare stack e code utilizzando un elenco collegato come struttura di dati sottostante, il che significa che siamo tornati alla stessa idea "raccolta di nodi".

Le code prioritarie possono essere create utilizzando un heap. Un heap può essere pensato come un albero con il valore minimo alla radice. Alberi di ogni tipo, inclusi BST, AVL, heap, possono essere pensati come una raccolta di nodi collegati da bordi. Questi nodi sono collegati insieme dove un nodo punta a un altro.

Sembra che ogni concetto di dati possa sempre ridursi a soli nodi con puntatori ad un altro nodo appropriato. È giusto? Se è così semplice, perché i libri di testo non spiegano che i dati sono solo un mucchio di nodi con puntatori? Come passiamo dai nodi al codice binario?


5
La struttura dei dati fondamentali a cui alludi è chiamata "cella contro"; puoi costruire qualsiasi struttura di dati che ti piace da loro. Se vuoi sapere perché un determinato autore di libri di testo non ha scelto di spiegare le celle contro, chiedi a quell'autore perché hanno fatto quella scelta. Passare da una descrizione di una disposizione di nodi a un codice binario si chiama "compilation" ed è il compito di un "compilatore".
Eric Lippert,

18
Si potrebbe anche sostenere che tutte le strutture di dati si riducono a un array. Dopotutto, finiscono tutti nella memoria, che è solo un array molto grande.
BlueRaja - Danny Pflughoeft

10
Non è possibile implementare un array utilizzando un elenco collegato se si desidera mantenere l'indicizzazione . O(1)
svick,

5
Ci dispiace, ma parlare di "nodi e puntatori" significa che hai già ceduto al quiche-eating. " Come sanno tutti i programmatori reali, l'unica struttura di dati utile è l'array. Stringhe, elenchi, strutture, set: questi sono tutti casi speciali di array e possono essere trattati in questo modo altrettanto facilmente senza incasinare il linguaggio di programmazione con tutti i tipi di complicazioni. "Rif:" I veri programmatori non usano Pascal ", da web.mit.edu/humor/Computers/real.programmers
alephzero

3
... ma più seriamente, la cosa importante delle strutture di dati è cosa puoi farci , non come sono implementate. Nel 21 ° secolo implementarli da soli è solo un esercizio di programmazione - e per gli educatori pigri, il fatto che tali esercizi siano facili da valutare supera il fatto che nella migliore delle ipotesi sono inutili e nella peggiore delle ipotesi dannosi se incoraggiano gli studenti a pensare che " reinventare le ruote "è un'attività utile nella programmazione del mondo reale.
alephzero,

Risposte:


14

Bene, questo è sostanzialmente ciò a cui si riducono tutte le strutture di dati. Dati con connessioni. I nodi sono tutti artificiali - in realtà non esistono fisicamente. Qui entra in gioco la parte binaria. Dovresti creare alcune strutture di dati in C ++ e verificare dove i tuoi oggetti atterrano nella memoria. Può essere molto interessante conoscere come sono disposti i dati in memoria.

Il motivo principale di così tante strutture diverse è che sono tutti specializzati in una cosa o nell'altra. Ad esempio, è in genere più veloce per scorrere attraverso un vettore anziché un elenco collegato, a causa del modo in cui le pagine vengono estratte dalla memoria. Un elenco collegato è meglio per archiviare tipi di dimensioni maggiori perché i vettori devono allocare spazio aggiuntivo per gli slot non utilizzati (questo è necessario nella progettazione di un vettore).

Come nota a margine, un'interessante struttura di dati che potresti voler guardare è una tabella hash. Non segue del tutto il sistema Nodes and Pointers che stai descrivendo.

TL; DR: i contenitori sono sostanzialmente tutti nodi e puntatori ma hanno usi molto specifici e sono migliori per qualcosa e peggio per gli altri.


1
La mia opinione è che la maggior parte dei dati può essere rappresentata come un gruppo di nodi con puntatori. Tuttavia, non sono perché (a) a livello fisico, non è quello che succede e (b) a livello concettuale, pensare ai valori come un elenco collegato non è utile per ragionare sui dati sottostanti. Sono comunque solo astrazioni a semplificare il nostro modo di pensare, quindi potrebbe anche scegliere l'astrazione migliore per una situazione, anche se un'altra potrebbe funzionare.
derekchen14,

13

Sembra che ogni concetto di dati possa sempre ridursi a soli nodi con puntatori ad un altro nodo appropriato.

Oh, caro no. Mi stai facendo male.

Come ho cercato di spiegare altrove (" Qual è la differenza tra un albero di ricerca binario e un heap binario? ") Anche per una struttura dati fissa ci sono diversi livelli per capirlo.

Come la coda di priorità che menzioni, se vuoi solo usarla, si tratta di un tipo di dati astratto. Lo usi sapendo che tipo di oggetti memorizza e quali domande puoi porgli. Sono più strutture di dati come un sacco di oggetti. Al livello successivo la sua famosa implementazione, l'heap binario, può essere intesa come un albero binario, ma l'ultimo livello è per motivi di efficienza implementato come un array. Non ci sono nodi e puntatori lì.

E anche per i grafici, ad esempio, che sembrano certamente nodi e puntatori (bordi), hai due rappresentazioni di base, un array di adiacenza e un elenco di adiacenza. Non tutti i puntatori che immagino.

Quando si cerca davvero di comprendere le strutture dei dati, è necessario studiarne i punti positivi e i punti deboli. A volte una rappresentazione usa un array per efficienza (spazio o tempo), a volte ci sono puntatori (per flessibilità). Questo vale anche quando si hanno buone implementazioni "in scatola" come il C ++ STL, perché anche lì è possibile scegliere a volte le rappresentazioni sottostanti. C'è sempre un compromesso lì.


10

f:RR

Nessuno si aspetta che tu dica tutto ciò solo per definire una funzione continua, altrimenti nessuno sarebbe in grado di svolgere alcun lavoro. Ci limitiamo a "fidarci" che qualcuno abbia reso noioso il lavoro per noi.

Ogni struttura di dati a cui puoi pensare si riduce agli oggetti di base che il tuo modello computazionale sottostante tratta, numeri interi in alcuni registri se usi una macchina ad accesso casuale o simboli su alcuni nastri se usi una macchina Turing.

Usiamo le astrazioni perché liberano la nostra mente da questioni banali, permettendoci di parlare di problemi più complessi. È assolutamente ragionevole "fidarsi" del fatto che quelle strutture funzionino: scendere a spirale in ogni singolo dettaglio è - a meno che tu non abbia una ragione specifica per farlo - un esercizio inutile che non aggiunge nulla alla tua discussione.


10

Ecco un contro-esempio: nel calcolo λ, ogni tipo di dato si riduce a funzioni. λ-calculus non ha nodi o puntatori, l'unica cosa che ha sono le funzioni, quindi tutto deve essere implementato usando le funzioni.

Questo è un esempio di codifica dei booleani come funzioni, scritto 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)();

E questa è una lista di contro:

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

I numeri naturali possono essere implementati come funzioni iteratore.

Un set è la stessa cosa della sua funzione caratteristica (cioè il containsmetodo).

Nota che la precedente codifica della Chiesa dei booleani è in realtà il modo in cui i condizionali sono implementati in linguaggi OO come Smalltalk, che non hanno booleani, condizionali o cicli come costrutti di linguaggio e li implementano puramente come una funzione di libreria. Un esempio 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

2
@Hamsteriffic: prova questo: è così che i condizionali sono implementati in lingue OO come Smalltalk. Smalltalk non ha booleani, condizionali o loop come costrutto linguistico. Tutti questi sono puramente implementati come librerie. La mente non è ancora saltata? William Cook sottolinea qualcosa che avrebbe dovuto essere ovvio molto tempo fa ma non è stato davvero notato: poiché OO riguarda l'astrazione comportamentale e l'astrazione comportamentale è l'unico tipo di astrazione che esiste nel calcolo λ, ne consegue che tutti i programmi scritti in I calcoli λ sono per necessità OO. Ergo, λ-calculus è il più antico e ...
Jörg W Mittag

... il linguaggio OO più puro!
Jörg W Mittag

1
Una brutta giornata con Smalltalk batte una buona giornata con C ++ :-)
Bob Jarvis - Ripristina Monica

@ JörgWMittag Non penso che la tua conclusione derivi dal tuo presupposto, non penso che il tuo presupposto sia nemmeno vero, e sicuramente non penso che la tua conclusione sia vera.
Miles Rout

4

Molte (la maggior parte?) Strutture di dati sono costruite con nodi e puntatori. Le matrici sono un altro elemento critico di alcune strutture di dati.

In definitiva, ogni struttura di dati è solo un mucchio di parole in memoria, o solo un mucchio di bit. È come sono strutturati e come li interpretiamo e li usiamo che conta.


2
In definitiva, i bit sono un gruppo di segnali elettrici su un filo, o segnali luminosi in un cavo a fibre ottiche, o particelle specificamente magnetizzate su un piatto, o onde radio di particolare lunghezza d'onda, o, o, o. Quindi la domanda è: quanto in profondità vuoi andare? :)
Wildcard il

2

L'implementazione delle strutture dati si riduce sempre a nodi e puntatori, sì.

Ma perché fermarsi qui? L'implementazione di nodi e puntatori si riduce a bit.

L'implementazione di bit si riduce a segnali elettrici, memoria magnetica, forse cavi a fibre ottiche, ecc. (In una parola, fisica.)

Questa è la reductio ad absurdum dell'istruzione "Tutte le strutture di dati si riducono a nodi e puntatori". È vero, ma riguarda solo l' implementazione.


Chris Date distingue molto abilmente tra implementazione e modello , anche se il suo saggio si rivolge in particolare ai database.

Possiamo andare un po 'oltre se ci rendiamo conto che non esiste un'unica linea di demarcazione tra modello e implementazione. Questo è simile (se non identico) al concetto di "strati di astrazione".

A un dato livello di astrazione, i livelli "sotto" di te (i livelli su cui stai costruendo) sono semplici "dettagli di implementazione" per l'astrazione o il modello a cui ti stai rivolgendo.

Tuttavia, i livelli inferiori di astrazione stessi hanno dettagli di implementazione.

Se leggi un manuale per un software, stai imparando il livello di astrazione "presentato" da quel software, sul quale puoi costruire le tue astrazioni (o semplicemente eseguire azioni come l'invio di messaggi).

Se apprendi i dettagli di implementazione del software, imparerai come i creatori hanno sostenuto le astrazioni che hanno costruito. I "dettagli di implementazione" possono includere strutture di dati e algoritmi, tra le altre cose.

Tuttavia, non considereresti la misurazione della tensione come parte dei "dettagli di implementazione" per qualsiasi particolare software, anche se questo è alla base di come "bit" e "byte" e "memoria" funzionano effettivamente sul computer fisico.

In sintesi, le strutture di dati sono un livello di astrazione per ragionare e implementare algoritmi e software. Il fatto che questo livello di astrazione sia basato su dettagli di implementazione di livello inferiore come nodi e puntatori è vero ma irrilevante all'interno del livello di astrazione.


Una grande parte della comprensione reale di un sistema è capire come gli strati di astrazione si incastrano. Quindi è importante capire come vengono implementate le strutture dati . Ma il fatto che siano implementati, non significa che l'astrazione delle strutture di dati non esista.


2

Un array o un vettore è solo una sequenza di valori. Possono sicuramente essere implementati con un elenco collegato. Questo è solo un mucchio di nodi con puntatori al nodo successivo.

Un array o un vettore può essere implementato con un elenco collegato, ma quasi mai dovrebbe esserlo.

nnΘ(n)Θ(logn)Θ(1)(ovvero un blocco sequenziale di memoria ad accesso casuale). Inoltre, sulla CPU, l'accesso all'array reale è molto più semplice da implementare e più veloce da eseguire, e la sua memorizzazione richiede meno memoria a causa della necessità di non sprecare spazio sui puntatori tra nodi separati.

Θ(n)Θ(1)Θ(1)in media, al costo al massimo di un fattore costante di memoria aggiuntiva, semplicemente arrotondando la dimensione effettiva allocata dell'array fino ad esempio alla potenza più vicina di 2. Ma se è necessario eseguire molti inserimenti e / o rimozioni di elementi al centro dell'elenco, un array fisico potrebbe non essere la migliore implementazione per la struttura dei dati. Abbastanza spesso, tuttavia, è possibile sostituire inserimenti e rimozioni con swap, che sono economici.

Se allarghi un po 'il tuo ambito di applicazione, per includere array contigui nella tua casella degli strumenti, quasi tutte le strutture dati pratiche possono essere effettivamente implementate con quelle insieme a nodi e puntatori.

Θ(1)operazione di inversione). In pratica, tuttavia, queste funzionalità sono raramente abbastanza utili da superare i suoi inconvenienti, che includono una maggiore complessità di implementazione e incompatibilità con gli schemi standard di garbage collection .


1

Se è così semplice, perché i libri di testo non spiegano che i dati sono solo un mucchio di nodi con puntatori?

Perché non è questo che significa "dati". Stai fondendo idee astratte con implementazioni. "Dati" è un'idea altamente astratta: è solo un altro nome per "informazione". Un gruppo di nodi collegati con puntatori (noto anche come "struttura di dati collegati") è un'idea molto più concreta: è un modo specifico di rappresentare e organizzare le informazioni.

Alcune astrazioni di dati si prestano molto bene alle implementazioni "collegate". Non ci sono molti buoni modi per implementare la natura ramificata di un albero completamente generale senza usare nodi e puntatori espliciti (o, qualche isomorfismo di nodi e puntatori). Ma poi, ci sono altre astrazioni che non implementereste mai usando nodi e puntatori. Vengono in mente numeri in virgola mobile.

Pile e code cadono da qualche parte nel mezzo. Ci sono momenti in cui ha molto senso eseguire un'implementazione collegata di uno stack. Ci sono altre volte in cui ha molto più senso usare un array e un singolo "puntatore dello stack".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.