vettore vs. elenco in STL


238

L'ho notato in Effective STL

vettore è il tipo di sequenza che dovrebbe essere utilizzata per impostazione predefinita.

Cosa vuol dire? Sembra che ignorare l'efficienza vectorpossa fare qualsiasi cosa.

Qualcuno potrebbe offrirmi uno scenario in cui vectornon è un'opzione fattibile ma listdeve essere utilizzata?


6
Sebbene non sia quello che ti è stato chiesto, vale la pena sottolineare che il default di vector significa anche che puoi facilmente interagire con codici più vecchi, librerie C o librerie non template, poiché vector è un wrapper sottile attorno alla matrice dinamica "tradizionale" di un puntatore e dimensioni.

18
Bjarne Strostrup ha effettivamente effettuato un test in cui ha generato numeri casuali e li ha aggiunti rispettivamente a un elenco e un vettore. Gli inserimenti sono stati fatti in modo tale che la lista / vettore fosse ordinata in ogni momento. Anche se in genere si tratta di "dominio elenco", il vettore ha sovraperformato l'elenco di un GRANDE margine. Il motivo è che l'accesso alla memoria è lento e la memorizzazione nella cache funziona meglio per i dati sequenziali. E 'tutto a disposizione nel suo keynote da "GoingNative 2012"
eludendo


1
Se vuoi vedere il keynote di Bjarne Stroustrup che @evading ha menzionato, l'ho trovato qui: youtu.be/OB-bdWKwXsU?t=2672
brain56

Risposte:


98

Situazioni in cui si desidera inserire più elementi in qualsiasi punto tranne la fine di una sequenza ripetutamente.

Scopri le garanzie di complessità per ogni diverso tipo di contenitore:

Quali sono le garanzie di complessità dei contenitori standard?


2
L'inserimento di elementi alla fine conta anche perché può portare all'allocazione della memoria e ai costi di copia degli elementi. E inoltre, inserire elenet all'inizio di un vettore è listpush_front
quasi

9
No, l'inserimento di elementi alla fine di un vettore è un tempo costante ammortizzato. Le allocazioni di memoria avvengono solo occasionalmente ed è possibile preallocare il vettore per impedirlo. Naturalmente, se DEVI aver garantito costanti inserimenti temporali costanti, suppongo che questo sia ancora un problema.
Brian,

16
@Notinlist - Il seguente "quasi impossibile" per te? v.insert (v.begin (), i)
Manuel,

5
@Notinlist - Sono d'accordo con te, è solo che non volevo che l'OP pensasse che l'interfaccia non fosse lì nel caso in cui si volesse sparare a suo vantaggio (prestazioni).
Manuel,

16
Bjarne Strostrup ha effettivamente effettuato un test in cui ha generato numeri casuali e li ha aggiunti rispettivamente a un elenco e un vettore. Gli inserimenti sono stati fatti in modo tale che la lista / vettore fosse ordinata in ogni momento. Anche se in genere si tratta di "dominio elenco", il vettore ha sovraperformato l'elenco di un GRANDE margine. Il motivo è che l'accesso alla memoria è lento e la memorizzazione nella cache funziona meglio per i dati sequenziali. E 'tutto a disposizione nel suo keynote da "GoingNative 2012"
eludendo

409

vettore:

  • Memoria contigua.
  • Pre-alloca lo spazio per gli elementi futuri, quindi spazio extra richiesto oltre a quello necessario per gli elementi stessi.
  • Ogni elemento richiede solo lo spazio per il tipo di elemento stesso (senza puntatori extra).
  • È possibile riassegnare la memoria per l'intero vettore ogni volta che si aggiunge un elemento.
  • Gli inserimenti alla fine sono costanti, tempo ammortizzato, ma gli inserimenti altrove sono costosi O (n).
  • Le cancellazioni alla fine del vettore sono tempo costante, ma per il resto è O (n).
  • Puoi accedere in modo casuale ai suoi elementi.
  • Gli iteratori vengono invalidati se si aggiungono o rimuovono elementi da o verso il vettore.
  • Puoi facilmente accedere all'array sottostante se hai bisogno di un array degli elementi.

elenco:

  • Memoria non contigua.
  • Nessuna memoria pre-allocata. L'overhead di memoria per l'elenco stesso è costante.
  • Ogni elemento richiede spazio aggiuntivo per il nodo che contiene l'elemento, inclusi i puntatori agli elementi successivo e precedente nell'elenco.
  • Non è necessario riassegnare la memoria per l'intero elenco solo perché si aggiunge un elemento.
  • Inserimenti e cancellazioni sono economici, indipendentemente da dove si trovano nell'elenco.
  • È economico combinare liste e giunzioni.
  • Non è possibile accedere in modo casuale agli elementi, quindi ottenere un determinato elemento nell'elenco può essere costoso.
  • Gli iteratori rimangono validi anche quando aggiungi o rimuovi elementi dall'elenco.
  • Se hai bisogno di una matrice di elementi, dovrai crearne una nuova e aggiungerle tutte, poiché non esiste una matrice sottostante.

In generale, usa vector quando non ti interessa il tipo di contenitore sequenziale che stai usando, ma se stai facendo molti inserimenti o cancellazioni da e verso qualsiasi parte del contenitore diverso dalla fine, vorrai per usare la lista. O se hai bisogno di un accesso casuale, allora vorrai il vettore, non l'elenco. Oltre a ciò, ci sono naturalmente casi in cui avrai bisogno dell'uno o dell'altro in base alla tua applicazione, ma in generale, queste sono buone linee guida.


2
Inoltre, l'allocazione dal negozio gratuito non è gratuita. :) L'aggiunta di nuovi elementi a un vettore esegue allocazioni di negozio gratuite O (log n), ma è possibile chiamare reserve()per ridurlo a O (1). L'aggiunta di nuovi elementi a un elenco (ovvero non la loro unione) esegue allocazioni di negozi O (n) gratuite.
bk1e,

7
Un'altra considerazione è che listlibera memoria quando cancelli elementi, ma vectornon lo fa. A vectornon riduce la sua capacità quando si riducono le sue dimensioni, a meno che non si usi il swap()trucco.
bk1e,

@ bk1e: Voglio davvero conoscere il tuo trucco in reserve () e swap () :)
Dzung Nguyen

2
@nXqd: se è necessario aggiungere N elementi a un vettore, chiamare v.reserve (v.size () + N) in modo che esegua una sola allocazione di negozio gratuita. Lo swap () trucco è qui: stackoverflow.com/questions/253157/how-to-downsize-stdvector
bk1e

1
@simplename No. Quello che dice è corretto. il vettore alloca spazio extra oltre lo spazio per gli elementi attualmente nel vettore; tale capacità aggiuntiva viene quindi utilizzata per far crescere il vettore in modo che la sua crescita sia ammortizzata O (1).
Jonathan M Davis,

35

Se non è necessario inserire elementi spesso, un vettore sarà più efficiente. Ha una localizzazione della cache della CPU molto migliore rispetto a un elenco. In altre parole, l'accesso a un elemento rende molto probabile che l'elemento successivo sia presente nella cache e possa essere recuperato senza dover leggere RAM lenta.


32

Alla maggior parte delle risposte qui manca un dettaglio importante: a cosa serve?

Cosa vuoi conservare nel contenitore?

Se si tratta di una raccolta di ints, allora std::listperderà in ogni scenario, indipendentemente dal fatto che tu possa riallocare, rimuovi solo dalla parte anteriore, ecc. Gli elenchi sono più lenti da attraversare, ogni inserimento ti costa un'interazione con l'allocatore. Sarebbe estremamente difficile preparare un esempio, in cui list<int>batte vector<int>. E anche allora, deque<int>potrebbe essere migliore o vicino, non giustificando l'uso degli elenchi, che avranno un sovraccarico di memoria maggiore.

Tuttavia, se hai a che fare con grosse e brutte porzioni di dati - e pochi di loro - non vuoi sovrallocare durante l'inserimento e la copia a causa della riallocazione sarebbe un disastro - allora potresti, forse, stare meglio con un list<UglyBlob>di vector<UglyBlob>.

Tuttavia, se passi a vector<UglyBlob*>o anche vector<shared_ptr<UglyBlob> >, di nuovo - l'elenco resterà indietro.

Quindi, il modello di accesso, il conteggio degli elementi target ecc. Influenza ancora il confronto, ma a mio avviso - la dimensione degli elementi - il costo della copia ecc.


1
Un'altra riflessione, che ho avuto leggendo "Effective STL" di Meyers: una proprietà peculiare di list<T>è la possibilità di splicein O (1) . Se hai bisogno di giunzioni a tempo costante, l'elenco potrebbe anche essere la struttura di scelta;)
Tomasz Gandor,

+1 - Neppure non deve essere un UglyBlob- anche un oggetti con pochi componenti stringa possono facilmente essere proibitivo per copiare, quindi le riassegnazioni saranno costo. Inoltre: non trascurare lo spazio ambientale che vectorpuò causare la crescita esponenziale di oggetti di dimensioni pari a poche decine di byte (se non è possibile reservein anticipo).
Martin Ba,

Per quanto riguarda vector<smart_ptr<Large>>vs. list<Large>- Direi che, se hai bisogno di un accesso casuale agli elementi, vectorha senso. Se non hai bisogno di un accesso casuale, listsembra più semplice e dovrebbe funzionare allo stesso modo.
Martin Ba,

19

Una capacità speciale di std :: list è lo splicing (che collega o sposta parte o un intero elenco in un altro elenco).

O forse se i tuoi contenuti sono molto costosi da copiare. In tal caso, ad esempio, potrebbe essere più economico ordinare la raccolta con un elenco.

Si noti inoltre che se la raccolta è piccola (e il contenuto non è particolarmente costoso da copiare), un vettore potrebbe comunque superare le prestazioni di un elenco, anche se si inserisce e si cancella ovunque. Un elenco assegna ogni nodo individualmente e ciò potrebbe essere molto più costoso rispetto allo spostamento di alcuni oggetti semplici.

Non credo ci siano regole molto rigide. Dipende da ciò che si desidera fare principalmente con il contenitore, nonché dalla dimensione prevista del contenitore e dal tipo contenuto. Un vettore generalmente supera un elenco, perché alloca i suoi contenuti come un singolo blocco contiguo (è fondamentalmente un array allocato dinamicamente e nella maggior parte dei casi un array è il modo più efficiente per contenere un mucchio di cose).


1
+1. La giunzione è trascurata, ma sfortunatamente non è un tempo costante come desiderato. : (((Non può essere se list :: size è a tempo costante.)

Sono abbastanza sicuro che la lista :: size è (può essere) lineare per questo motivo.
UncleBens,


@Potatoswatter: che lo standard sia vago e che di conseguenza non si possa fare affidamento su implementazioni "conformi" lo rende ancora più un problema. Devi letteralmente evitare lo stdlib per ottenere una garanzia portatile e affidabile.

@Roger: sì, sfortunatamente. Il mio attuale progetto si basa fortemente sulle operazioni di giunzione e quella struttura è quasi diritta C. Ancora più sfortunatamente, nella sequenza N3000 splicetra elenchi diversi è specificata come complessità lineare ed sizeè specificamente costante. Quindi, per accogliere i principianti che ripetono sizeo altro, un'intera classe di algoritmi è fuori portata per la STL o per qualsiasi periodo di contenitori "conforme".
Potatoswatter,

13

Beh, gli studenti della mia classe sembrano incapaci di spiegarmi quando è più efficace usare i vettori, ma sembrano abbastanza felici quando mi consigliano di usare le liste.

Ecco come lo capisco

Elenchi : ogni articolo contiene un indirizzo per l'elemento successivo o precedente, quindi con questa funzione è possibile randomizzare gli articoli, anche se non sono ordinati, l'ordine non cambierà: è efficace se la memoria è frammentata. Ma ha anche un altro grande vantaggio: puoi facilmente inserire / rimuovere elementi, perché l'unica cosa che devi fare è cambiare alcuni puntatori. Svantaggio: per leggere un singolo elemento casuale, devi saltare da un elemento all'altro fino a trovare l'indirizzo corretto.

Vettori : quando si usano i vettori, la memoria è molto più organizzata come normali matrici: ogni n-esimo elemento viene archiviato subito dopo (n-1) e prima (n + 1). Perché è meglio dell'elenco? Perché consente un accesso casuale rapido. Ecco come: se conosci la dimensione di un elemento in un vettore e se sono contigui in memoria, puoi facilmente prevedere dove si trova l'ennesimo elemento; non devi sfogliare tutti gli elementi di un elenco per leggere quello che desideri, con il vettore, lo leggi direttamente, con un elenco che non puoi. D'altra parte, modificare l'array vettoriale o cambiare un valore è molto più lento.

Gli elenchi sono più appropriati per tenere traccia degli oggetti che possono essere aggiunti / rimossi in memoria. I vettori sono più appropriati quando si desidera accedere a un elemento da una grande quantità di singoli elementi.

Non so come siano ottimizzati gli elenchi, ma devi sapere che se vuoi un accesso in lettura veloce, dovresti usare i vettori, perché quanto sono buoni gli elenchi STL, non sarà così veloce in accesso in lettura rispetto al vettore.


"modificare l'array vettoriale o cambiare un valore è molto più lento" - come letto, questo sembra contraddire ciò che hai detto poco prima che il vettore fosse predisposto per una buona prestazione a causa della loro natura di basso livello e contigua. intendevi dire che riallocare la vectorcausa cambiando le sue dimensioni può essere lento? poi concordato, ma nei casi in cui reserve()può essere utilizzato, ciò evita tali problemi.
underscore_d

10

Fondamentalmente un vettore è un array con gestione automatica della memoria. I dati sono contigui in memoria. Cercare di inserire dati nel mezzo è un'operazione costosa.

In un elenco i dati sono memorizzati in posizioni di memoria non correlate. L'inserimento nel mezzo non comporta la copia di alcuni dei dati per fare spazio a quello nuovo.

Per rispondere in modo più specifico alla tua domanda, citerò questa pagina

i vettori sono generalmente i più efficienti nel tempo per accedere agli elementi e per aggiungere o rimuovere elementi dalla fine della sequenza. Per le operazioni che comportano l'inserimento o la rimozione di elementi in posizioni diverse dalla fine, eseguono risultati peggiori rispetto a deques ed elenchi e presentano iteratori e riferimenti meno coerenti rispetto agli elenchi.


9

Ogni volta che non è possibile invalidare gli iteratori.


2
Ma non saltare mai a quella conclusione sugli iteratori senza chiedere se fossero sufficienti riferimenti persistenti a un deque.
Potatoswatter,

8

Quando si hanno molti inserimenti o eliminazioni nel mezzo della sequenza. ad es. un gestore di memoria.


quindi la differenza tra loro è solo l'efficienza, non il problema funzionale.
skydoor

Ovviamente entrambi modellano una sequenza di elementi. C'è una piccola differenza nell'uso, come menzionato da @dirkgently ma devi guardare alla complessità delle tue operazioni "spesso fatte" per decidere quale sequenza scegliere (risposta @Martin).
AraK,

@skydoor - Ci sono alcune differenze funzionali. Ad esempio, solo il vettore supporta l'accesso casuale (ovvero può essere indicizzato).
Manuel,

2
@skydoor: l'efficienza si traduce in performance. Scarse prestazioni possono rovinare la funzionalità. Le prestazioni sono il vantaggio di C ++, dopo tutto.
Potatoswatter,

4

Preservare la validità degli iteratori è uno dei motivi per utilizzare un elenco. Un altro è quando non si desidera riallocare un vettore quando si spinge un oggetto. Questo può essere gestito da un uso intelligente di reserve (), ma in alcuni casi potrebbe essere più semplice o più fattibile utilizzare solo un elenco.


4

Quando si desidera spostare oggetti tra contenitori, è possibile utilizzare list::splice.

Ad esempio, un algoritmo di partizionamento grafico può avere un numero costante di oggetti suddivisi in modo ricorsivo tra un numero crescente di contenitori. Gli oggetti devono essere inizializzati una volta e rimangono sempre nelle stesse posizioni in memoria. È molto più veloce riordinarli ricollegandoli piuttosto che riallocandoli.

Modifica: mentre le biblioteche si preparano a implementare C ++ 0x, il caso generale di unione di una sottosequenza in un elenco sta diventando complessità lineare con la lunghezza della sequenza. Questo perché splice(ora) è necessario scorrere la sequenza per contare il numero di elementi in essa contenuti. (Perché l'elenco deve registrare le sue dimensioni.) Il semplice conteggio e ricollegamento dell'elenco è ancora più veloce di qualsiasi alternativa, e la giunzione di un intero elenco o di un singolo elemento sono casi speciali con complessità costante. Ma, se hai lunghe sequenze da ricucire, potresti dover cercare un contenitore migliore, vecchio stile e non conforme.


2

L'unica regola rigida in cui è listnecessario utilizzare è dove è necessario distribuire i puntatori agli elementi del contenitore.

A differenza di vector, sai che la memoria degli elementi non verrà riallocata. Se così fosse, potresti avere dei puntatori alla memoria inutilizzata, che nella migliore delle ipotesi è un grande no-no e nella peggiore delle ipotesi a SEGFAULT.

(Tecnicamente una vectordelle *_ptrsarebbe anche lavoro, ma in questo caso si sta emulando listcosì questo è solo semantica.)

Altre regole soft hanno a che fare con i possibili problemi di prestazioni dell'inserimento di elementi nel mezzo di un contenitore, al che listsarebbe preferibile.


2

Semplifica-
Alla fine della giornata quando sei confuso scegliendo i contenitori in C ++ usa questa immagine del diagramma di flusso (dì grazie a me): - inserisci qui la descrizione dell'immagine

Il
vettore 1. Il vettore si basa sulla memoria contagiosa
2. Il vettore è la strada da percorrere per un piccolo set di dati
3. Il vettore esegue più velocemente mentre si attraversa il set di dati
4. La cancellazione dell'inserimento del vettore è lenta su un set di dati enorme ma veloce per molto piccoli

Elenco-
1. L'elenco si basa sulla memoria heap
2. L'elenco è la strada da percorrere per un set di dati molto grande
3. L'elenco è relativamente lento nel percorrere set di dati piccoli ma veloce in un set di dati enorme
4. L'eliminazione dell'inserimento dell'elenco è veloce su enorme set di dati ma lento su quelli più piccoli


1

Gli elenchi sono solo un wrapper per un doppio elenco di collegamenti in stl, offrendo quindi funzionalità che ci si potrebbe aspettare da d-linklist, ovvero l'inserimento e l'eliminazione di O (1). Mentre i vettori sono sequenze di dati contagiose che funzionano come un array dinamico.PS- più facili da attraversare.


0

Nel caso di un vettore e di un elenco , le principali differenze che mi emergono sono le seguenti:

vettore

  • Un vettore memorizza i suoi elementi nella memoria contigua. Pertanto, l'accesso casuale è possibile all'interno di un vettore, il che significa che l'accesso a un elemento di un vettore è molto veloce perché possiamo semplicemente moltiplicare l'indirizzo di base con l'indice dell'articolo per accedere a quell'elemento. In effetti, per questo scopo sono necessari solo O (1) o tempo costante.

  • Poiché un vettore praticamente avvolge un array, ogni volta che si inserisce un elemento nel vettore (array dinamico), deve ridimensionare se stesso trovando un nuovo blocco contiguo di memoria per accogliere i nuovi elementi, che è costoso in termini di tempo.

  • Non consuma memoria aggiuntiva per memorizzare puntatori ad altri elementi al suo interno.

elenco

  • Un elenco memorizza i suoi elementi in una memoria non contigua. Pertanto, l'accesso casuale non è possibile all'interno di un elenco, il che significa che per accedere ai suoi elementi dobbiamo usare i puntatori e attraversare l'elenco che è più lento rispetto al vettore. Questo richiede O (n) o tempo lineare che è più lento di O (1).

  • Poiché un elenco utilizza una memoria non contigua, il tempo impiegato per inserire un elemento all'interno di un elenco è molto più efficiente rispetto al caso della sua controparte vettoriale poiché si evita la riallocazione della memoria.

  • Consuma memoria aggiuntiva per memorizzare i puntatori all'elemento prima e dopo un elemento particolare.

Quindi, tenendo conto di queste differenze, di solito consideriamo la memoria , il frequente accesso casuale e l' inserzione per decidere il vincitore della lista vettoriale vs in un determinato scenario.


0

La lista è doppiamente collegata, quindi è facile inserire ed eliminare un elemento. Dobbiamo solo cambiare i pochi puntatori, mentre nel vettore se vogliamo inserire un elemento nel mezzo, ogni elemento dopo deve spostarsi di un indice. Inoltre, se la dimensione del vettore è piena, deve prima aumentarne la dimensione. Quindi è un'operazione costosa. Pertanto, laddove è necessario eseguire le operazioni di inserimento ed eliminazione da eseguire più spesso in tale elenco di casi, è necessario utilizzare un elenco.

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.