Perché utilizziamo array invece di altre strutture di dati?


195

Mentre stavo programmando, non ho visto un'istanza in cui un array è migliore per la memorizzazione di informazioni rispetto a un'altra forma di esso. Avevo davvero immaginato che le "caratteristiche" aggiunte nei linguaggi di programmazione fossero migliorate su questo e da ciò le sostituirono. Vedo ora che non vengono sostituiti ma piuttosto dotati di nuova vita, per così dire.

Quindi, in pratica, qual è lo scopo dell'uso di array?

Questo non è tanto il motivo per cui utilizziamo le matrici dal punto di vista del computer, ma piuttosto perché dovremmo usare le matrici dal punto di vista della programmazione (una sottile differenza). Quello che fa il computer con l'array non era il punto della domanda.


2
Perché non considerare cosa fa il computer con l'array? Abbiamo un sistema di numerazione delle case perché abbiamo strade dritte . Lo stesso vale per gli array.
lcn,

Quali " altre strutture di dati " o " un altro modulo " intendi? E per quale scopo?
martedì

Risposte:


770

È ora di tornare indietro nel tempo per una lezione. Anche se oggi non pensiamo molto a queste cose nelle nostre fantasiose lingue gestite, sono costruite sulla stessa base, quindi vediamo come viene gestita la memoria in C.

Prima di immergermi, una rapida spiegazione del significato del termine " puntatore ". Un puntatore è semplicemente una variabile che "punta" verso una posizione nella memoria. Non contiene il valore effettivo in questa area di memoria, contiene l'indirizzo di memoria ad esso. Pensa a un blocco di memoria come a una cassetta postale. Il puntatore sarebbe l'indirizzo di quella casella di posta.

In C, un array è semplicemente un puntatore con un offset, l'offset specifica quanto lontano nella memoria guardare. Ciò fornisce il tempo di accesso O (1) .

  MyArray   [5]
     ^       ^
  Pointer  Offset

Tutte le altre strutture di dati si basano su questo o non usano la memoria adiacente per l'archiviazione, con conseguente scarso tempo di ricerca dell'accesso casuale (sebbene ci siano altri vantaggi nel non usare la memoria sequenziale).

Ad esempio, supponiamo di avere un array con 6 numeri (6,4,2,3,1,5), in memoria sarebbe simile a questo:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================

In un array, sappiamo che ogni elemento è uno accanto all'altro in memoria. L'array AC (chiamato MyArrayqui) è semplicemente un puntatore al primo elemento:

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^
MyArray

Se volessimo cercare MyArray[4], internamente si accederà in questo modo:

   0     1     2     3     4 
=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
                           ^
MyArray + 4 ---------------/
(Pointer + Offset)

Poiché possiamo accedere direttamente a qualsiasi elemento dell'array aggiungendo l'offset al puntatore, possiamo cercare qualsiasi elemento nello stesso lasso di tempo, indipendentemente dalle dimensioni dell'array. Ciò significa che ottenere MyArray[1000]richiederebbe lo stesso tempo di ottenere MyArray[5].

Una struttura di dati alternativa è un elenco collegato. Questo è un elenco lineare di puntatori, ognuno dei quali punta al nodo successivo

========    ========    ========    ========    ========
| Data |    | Data |    | Data |    | Data |    | Data |
|      | -> |      | -> |      | -> |      | -> |      | 
|  P1  |    |  P2  |    |  P3  |    |  P4  |    |  P5  |        
========    ========    ========    ========    ========

P(X) stands for Pointer to next node.

Nota che ho trasformato ogni "nodo" nel suo blocco. Questo perché non è garantito che siano (e molto probabilmente non lo saranno) adiacenti in memoria.

Se voglio accedere a P3, non posso accedervi direttamente, perché non so dove sia in memoria. Tutto quello che so è dove si trova la radice (P1), quindi devo iniziare da P1 e seguire ogni puntatore al nodo desiderato.

Questo è un tempo di ricerca O (N) (il costo di ricerca aumenta man mano che viene aggiunto ogni elemento). È molto più costoso arrivare a P1000 rispetto a P4.

Strutture di dati di livello superiore, come hashtable, stack e code, possono utilizzare internamente un array (o più array), mentre gli elenchi collegati e gli alberi binari di solito usano nodi e puntatori.

Potresti chiederti perché qualcuno dovrebbe usare una struttura di dati che richiede l'attraversamento lineare per cercare un valore invece di usare solo un array, ma hanno i loro usi.

Prendi di nuovo il nostro array. Questa volta, voglio trovare l'elemento dell'array che contiene il valore "5".

=====================================
|  6  |  4  |  2  |  3  |  1  |  5  |
=====================================
   ^     ^     ^     ^     ^   FOUND!

In questa situazione, non so quale offset aggiungere al puntatore per trovarlo, quindi devo iniziare da 0 e risalire fino a quando non lo trovo. Ciò significa che devo eseguire 6 controlli.

Per questo motivo, la ricerca di un valore in un array è considerata O (N). Il costo della ricerca aumenta all'aumentare dell'array.

Ricordi sopra dove ho detto che a volte l'utilizzo di una struttura dati non sequenziale può avere vantaggi? La ricerca di dati è uno di questi vantaggi e uno dei migliori esempi è l'albero binario.

Un albero binario è una struttura di dati simile a un elenco collegato, tuttavia invece di collegarsi a un singolo nodo, ogni nodo può collegarsi a due nodi figlio.

         ==========
         |  Root  |         
         ==========
        /          \ 
  =========       =========
  | Child |       | Child |
  =========       =========
                  /       \
            =========    =========
            | Child |    | Child |
            =========    =========

 Assume that each connector is really a Pointer

Quando i dati vengono inseriti in un albero binario, usano diverse regole per decidere dove posizionare il nuovo nodo. Il concetto di base è che se il nuovo valore è maggiore dei genitori, lo inserisce a sinistra, se è inferiore, lo inserisce a destra.

Ciò significa che i valori in un albero binario potrebbero apparire così:

         ==========
         |   100  |         
         ==========
        /          \ 
  =========       =========
  |  200  |       |   50  |
  =========       =========
                  /       \
            =========    =========
            |   75  |    |   25  |
            =========    =========

Quando si cerca un albero binario per il valore di 75, è necessario visitare solo 3 nodi (O (log N)) a causa di questa struttura:

  • 75 è inferiore a 100? Guarda il nodo destro
  • 75 è maggiore di 50? Guarda il nodo sinistro
  • C'è il 75!

Anche se nel nostro albero ci sono 5 nodi, non abbiamo avuto bisogno di guardare i due rimanenti, perché sapevamo che loro (e i loro figli) non potevano contenere il valore che stavamo cercando. Questo ci dà un tempo di ricerca che nel peggiore dei casi significa che dobbiamo visitare tutti i nodi, ma nel migliore dei casi dobbiamo visitare solo una piccola parte dei nodi.

È qui che le matrici vengono battute, forniscono un tempo di ricerca O (N) lineare, nonostante il tempo di accesso O (1).

Questa è una panoramica di livello incredibilmente alto sulle strutture di dati in memoria, saltando molti dettagli, ma si spera che illustri la forza e la debolezza di un array rispetto ad altre strutture di dati.


1
@Jonathan: hai aggiornato il diagramma in modo che punti al 5 ° elemento ma hai anche cambiato MyArray [4] in MyArray [5], quindi è ancora errato, cambia l'indice di nuovo a 4 e mantieni il diagramma così com'è e dovresti essere buono .
Robert Gamble,

54
Questo è ciò che mi
infastidisce

8
Bella risposta. Ma l'albero che descrivi è un albero di ricerca binario - un albero binario è solo un albero in cui ogni nodo ha al massimo due figli. Puoi avere un albero binario con gli elementi in qualsiasi ordine. L'albero di ricerca binario è organizzato come descritto.
Gnud,

1
Buona spiegazione, ma non posso fare a meno di nitpick ... se ti è permesso riordinare gli elementi in un albero di ricerca binario, perché non puoi riordinare gli elementi nell'array in modo che anche una ricerca binaria possa funzionare al suo interno? Potresti andare più in dettaglio riguardo a O (n) insert / delete per un albero, ma O (n) per un array.
commercializza il

2
La rappresentazione dell'albero binario non è una O (log n) perché il tempo di accesso aumenta logaritmicamente in relazione alle dimensioni del set di dati?
Evan Plaice,

73

Per O (1) accesso casuale, che non può essere battuto.


6
A che punto? Che cos'è O (1)? Che cos'è l'accesso casuale? Perché non può essere battuto? Un altro punto?
Jason,

3
O (1) significa tempo costante, ad esempio se si desidera ottenere l'elemento n-esim di un array, è sufficiente accedervi direttamente tramite l'indicizzatore (array [n-1]), ad esempio con un elenco collegato, si ha per trovare la testa e poi passare al nodo successivo in sequenza n-1 volte che è O (n), tempo lineare.
CMS,

8
La notazione Big-O descrive come la velocità di un algoritmo varia in base alla dimensione del suo input. Un algoritmo O (n) impiegherà il doppio del tempo per funzionare con il doppio degli oggetti e 8 volte il tempo per funzionare con 8 volte il numero degli oggetti. In altre parole, la velocità di un algoritmo O (n) varia con il [cont ...]
Gareth,

8
dimensione del suo input. O (1) implica che la dimensione dell'ingresso ('n') non tiene conto della velocità dell'algoritmo, è una velocità costante indipendentemente dalla dimensione dell'ingresso
Gareth,

9
Vedo la tua O (1) e ti sollevo O (0).
Chris Conway, il

23

Non tutti i programmi fanno la stessa cosa o funzionano sullo stesso hardware.

Questa è di solito la risposta al perché esistono varie funzionalità linguistiche. Le matrici sono un concetto fondamentale di informatica. La sostituzione di array con elenchi / matrici / vettori / qualunque struttura dati avanzata influirebbe gravemente sulle prestazioni e sarebbe assolutamente impraticabile in numerosi sistemi. Esistono numerosi casi in cui l'utilizzo di uno di questi oggetti "avanzati" di raccolta dati deve essere utilizzato a causa del programma in questione.

Nella programmazione aziendale (cosa che la maggior parte di noi fa), possiamo scegliere come target hardware relativamente potente. L'uso di un elenco in C # o Vector in Java è la scelta giusta da fare in queste situazioni perché queste strutture consentono allo sviluppatore di raggiungere gli obiettivi più rapidamente, il che a sua volta consente a questo tipo di software di essere più caratterizzato.

Quando si scrive un software incorporato o un sistema operativo, spesso un array può essere la scelta migliore. Mentre un array offre meno funzionalità, occupa meno RAM e il compilatore può ottimizzare il codice in modo più efficiente per le ricerche negli array.

Sono sicuro che tralascerò una serie di vantaggi per questi casi, ma spero che tu capisca il punto.


4
Ironia della sorte, in Java dovresti usare un ArrayList (o un LinkedList) invece di un Vector. Questo ha a che fare con un vettore sincronizzato che di solito è un sovraccarico non necessario.
Ashirley,

0

Un modo per esaminare i vantaggi degli array è vedere dove è richiesta la capacità di accesso O (1) degli array e quindi capitalizzata:

  1. Nelle tabelle di ricerca dell'applicazione (un array statico per l'accesso a determinate risposte categoriche)

  2. Memoization (risultati di funzioni complesse già calcolati, in modo da non calcolare nuovamente il valore della funzione, diciamo log x)

  3. Applicazioni di visione artificiale ad alta velocità che richiedono l'elaborazione di immagini ( https://en.wikipedia.org/wiki/Lookup_table#Lookup_tables_in_image_processing )

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.