Heap vs Binary Search Tree (BST)


169

Qual è la differenza tra un heap e BST?

Quando utilizzare un heap e quando utilizzare un BST?

Se vuoi ottenere gli elementi in modo ordinato, BST è meglio su heap?


13
Questa domanda sembra fuori tema perché riguarda l'informatica e dovrebbe essere posta su cs.stackexchange.com
Flow


3
Sento che si riferisce sia allo scambio di stack sia allo overflow dello stack. Quindi averlo qui va bene
Azizbro

Risposte:


191

Sommario

          Type      BST (*)   Heap
Insert    average   log(n)    1
Insert    worst     log(n)    log(n) or n (***)
Find any  worst     log(n)    n
Find max  worst     1 (**)    1
Create    worst     n log(n)  n
Delete    worst     log(n)    log(n)

Tutti i tempi medi su questa tabella sono gli stessi dei loro periodi peggiori, ad eccezione di Inserisci.

  • *: ovunque in questa risposta, BST == BST bilanciato, poiché uno sbilanciato fa schifo asintoticamente
  • **: usando una banale modifica spiegata in questa risposta
  • ***: log(n)per heap albero puntatore, nper heap di array dinamico

Vantaggi dell'heap binario rispetto a un BST

  • l'inserimento del tempo medio in un heap binario è O(1), poiché BST lo è O(log(n)). Questa è la caratteristica killer di heap.

    Ci sono anche altri cumuli che raggiungono l' O(1)ammortamento (più forte) come il mucchio di Fibonacci , e anche il caso peggiore, come la coda Brodal , anche se potrebbero non essere pratici a causa di prestazioni non asintotiche: i cumuli di Fibonacci o le code Brodal sono usati nella pratica ovunque?

  • gli heap binari possono essere implementati in modo efficiente su array dinamici o alberi basati su puntatori, BST solo alberi basati su puntatori. Quindi per l'heap possiamo scegliere l'implementazione dell'array più efficiente in termini di spazio, se possiamo permetterci latenze di ridimensionamento occasionali.

  • la creazione dell'heap binario è il O(n)caso peggiore , O(n log(n))per BST.

Vantaggio di BST rispetto all'heap binario

  • la ricerca di elementi arbitrari è O(log(n)). Questa è la caratteristica killer dei BST.

    Per l'heap, è O(n)in generale, ad eccezione dell'elemento più grande che è O(1).

Vantaggio "falso" dell'heap rispetto a BST

  • heap è O(1)trovare max, BST O(log(n)).

    Questo è un malinteso comune, perché è banale modificare un BST per tenere traccia dell'elemento più grande e aggiornarlo ogni volta che tale elemento può essere modificato: all'inserimento di uno più grande scambiare, alla rimozione trovare il secondo più grande. Possiamo usare l'albero di ricerca binario per simulare il funzionamento dell'heap? (menzionato da Yeo ).

    In realtà, questa è una limitazione degli heap rispetto ai BST: l' unica ricerca efficiente è quella per l'elemento più grande.

L'inserto heap binario medio è O(1)

fonti:

Argomento intuitivo:

  • i livelli dell'albero inferiore hanno esponenzialmente più elementi rispetto ai livelli superiori, quindi quasi sicuramente nuovi elementi andranno in fondo
  • l'inserimento dell'heap inizia dal basso , il BST deve iniziare dall'alto

In un heap binario, aumentare il valore in un determinato indice è anche O(1)per lo stesso motivo. Ma se si desidera farlo, è probabile che si desideri mantenere un indice aggiuntivo aggiornato sulle operazioni di heap Come implementare l' operazione O-logn chiave di riduzione per la coda di priorità basata su min-heap? ad es. per Dijkstra. Possibile senza costi aggiuntivi.

La libreria standard GCC C ++ inserisce benchmark su hardware reale

Ho confrontato C ++ std::set( Red-black tree BST ) e std::priority_queue( dynamic array heap ) per vedere se avevo ragione sui tempi di inserimento, e questo è quello che ho ottenuto:

inserisci qui la descrizione dell'immagine

  • codice di riferimento
  • trama della trama
  • dati della trama
  • testato su Ubuntu 19.04, GCC 8.3.0 in un laptop Lenovo ThinkPad P51 con CPU: CPU Intel Core i7-7820HQ (4 core / 8 thread, base 2,90 GHz, cache 8 MB), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB , 2400 Mbps), SSD: Samsung MZVLB512HAJQ-000L7 (512 GB, 3.000 MB / s)

Quindi chiaramente:

  • il tempo di inserimento dell'heap è sostanzialmente costante.

    Possiamo vedere chiaramente i punti di ridimensionamento della matrice dinamica. Dato che calcoliamo una media di ogni 10k inserti per poter vedere qualsiasi cosa sopra il rumore del sistema , tali picchi sono in effetti circa 10k volte più grandi di quanto mostrato!

    Il grafico ingrandito esclude essenzialmente solo i punti di ridimensionamento dell'array e mostra che quasi tutti gli inserti sono inferiori a 25 nanosecondi.

  • BST è logaritmico. Tutti gli inserti sono molto più lenti dell'inserto heap medio.

  • BST vs hashmap analisi dettagliata su: Qual è la struttura dei dati all'interno di std :: map in C ++?

La libreria standard GCC C ++ inserisce il benchmark su gem5

gem5 è un simulatore di sistema completo e quindi fornisce un orologio infinitamente accurato con m5 dumpstats. Quindi ho provato a usarlo per stimare i tempi per i singoli inserti.

inserisci qui la descrizione dell'immagine

Interpretazione:

  • l'heap è ancora costante, ma ora vediamo più in dettaglio che ci sono alcune righe e ogni riga superiore è più sparsa.

    Ciò deve corrispondere alle latenze di accesso alla memoria eseguite per inserti sempre più alti.

  • TODO Non riesco davvero a interpretare completamente il BST in quanto non sembra così logaritmico e un po 'più costante.

    Con questo maggiore dettaglio, tuttavia, possiamo vedere anche alcune linee distinte, ma non sono sicuro di cosa rappresentino: mi aspetto che la linea di fondo sia più sottile, dal momento che inseriamo la parte inferiore inferiore?

Benchmark con questa configurazione Buildroot su una CPU HPI aarch64 .

BST non può essere implementato in modo efficiente su un array

Le operazioni di heap devono solo fare il bubble up o down di un singolo ramo di un albero, quindi nella O(log(n))peggiore delle ipotesi, in O(1)media.

Mantenere un BST bilanciato richiede rotazioni dell'albero, che possono cambiare l'elemento superiore per un altro, e richiederebbero lo spostamento dell'intero array intorno ( O(n)).

Gli heap possono essere implementati in modo efficiente su un array

Gli indici padre e figlio possono essere calcolati dall'indice corrente come mostrato qui .

Non ci sono operazioni di bilanciamento come BST.

Elimina min è l'operazione più preoccupante in quanto deve essere dall'alto verso il basso. Ma si può sempre fare "percolando" un singolo ramo dell'heap come spiegato qui . Questo porta ad un caso peggiore O (log (n)), poiché l'heap è sempre ben bilanciato.

Se stai inserendo un singolo nodo per ognuno di quelli rimossi, perdi il vantaggio dell'inserto medio asintotico O (1) che heap fornisce in quanto l'eliminazione dominerebbe e potresti anche usare un BST. Dijkstra tuttavia aggiorna i nodi più volte per ogni rimozione, quindi stiamo bene.

Cumuli di array dinamici vs cumuli di alberi di puntatori

Gli heap possono essere implementati in modo efficiente su heap di puntatori: è possibile realizzare implementazioni heap binarie basate su puntatori efficienti?

L'implementazione di array dinamici è più efficiente in termini di spazio. Supponiamo che ogni elemento heap contenga solo un puntatore a un struct:

  • l'implementazione dell'albero deve memorizzare tre puntatori per ciascun elemento: padre, figlio sinistro e figlio destro. Quindi l'utilizzo della memoria è sempre 4n(3 puntatori ad albero + 1 structpuntatore).

    I BST degli alberi necessiterebbero inoltre di ulteriori informazioni di bilanciamento, ad esempio il rosso-nero.

  • l'implementazione di array dinamici può avere dimensioni 2nsubito dopo il raddoppio. Quindi in media lo sarà 1.5n.

D'altra parte, l'heap dell'albero ha un migliore inserimento nel caso peggiore, perché copiare l'array dinamico di supporto per raddoppiarne le dimensioni prende il O(n)caso peggiore, mentre l'heap dell'albero fa solo nuove piccole allocazioni per ciascun nodo.

Tuttavia, il raddoppio dell'array di supporto viene O(1)ammortizzato, quindi si riduce alla massima latenza. Menzionato qui .

Filosofia

  • I BST mantengono una proprietà globale tra un genitore e tutti i discendenti (a sinistra più piccolo, a destra più grande).

    Il nodo superiore di un BST è l'elemento intermedio, che richiede una conoscenza globale da mantenere (sapendo quanti elementi sempre più piccoli ci sono).

    Questa proprietà globale è più costosa da mantenere (log n insert), ma offre ricerche più potenti (log n search).

  • Heap mantiene una proprietà locale tra genitore e figli diretti (genitore> figli).

    Il nodo principale di un heap è il grande elemento, che richiede solo conoscenze locali per mantenere (conoscere il tuo genitore).

Confronto tra BST vs Heap vs Hashmap:

  • BST: può essere sia ragionevole:

    • set non ordinato (una struttura che determina se un elemento è stato precedentemente inserito o meno). Ma l'hashmap tende ad essere migliore grazie all'inserto ammortizzato O (1).
    • selezionatrice. Ma l'heap è generalmente migliore in questo, motivo per cui heapsort è molto più conosciuto dell'ordinamento degli alberi
  • heap: è solo una selezionatrice. Non può essere un set efficiente non ordinato, poiché è possibile verificare rapidamente solo l'elemento più piccolo / più grande.

  • mappa hash: può essere solo un set non ordinato, non una macchina di smistamento efficiente, poiché l'hash mescola qualsiasi ordine.

Elenco doppiamente collegato

Un elenco doppiamente collegato può essere visto come sottoinsieme dell'heap in cui il primo elemento ha la massima priorità, quindi confrontiamoli anche qui:

  • inserimento:
    • posizione:
      • elenco doppiamente collegato: l'elemento inserito deve essere il primo o l'ultimo, poiché abbiamo solo puntatori a quegli elementi.
      • heap binario: l'elemento inserito può finire in qualsiasi posizione. Meno restrittivo dell'elenco collegato.
    • tempo:
      • elenco doppiamente collegato: il O(1)caso peggiore dato che abbiamo puntatori agli elementi e l'aggiornamento è davvero semplice
      • heap binario: O(1)medio, quindi peggio dell'elenco collegato. Un compromesso per avere una posizione di inserimento più generale.
  • ricerca: O(n)per entrambi

Un caso d'uso per questo è quando la chiave dell'heap è il timestamp corrente: in tal caso, le nuove voci andranno sempre all'inizio dell'elenco. Quindi possiamo persino dimenticare del tutto il timestamp esatto e mantenere la posizione nell'elenco come priorità.

Questo può essere usato per implementare una cache LRU . Proprio come per le applicazioni heap come Dijkstra , vorrai mantenere una hashmap aggiuntiva dalla chiave al nodo corrispondente dell'elenco, per trovare quale nodo aggiornare rapidamente.

Confronto tra diversi BST bilanciati

Anche se i tempi di inserimento e di ricerca asintotici per tutte le strutture di dati che sono comunemente classificati come "BST bilanciati" che ho visto finora è lo stesso, BBST diversi hanno compromessi diversi. Non l'ho ancora studiato a fondo, ma sarebbe bene riassumere questi compromessi qui:

  • Albero rosso-nero . Sembra essere il BBST più comunemente usato dal 2019, ad esempio è quello utilizzato dall'implementazione GCC 8.3.0 C ++
  • Albero AVL . Sembra essere un po 'più bilanciato rispetto a BST, quindi potrebbe essere meglio trovare la latenza, al costo di reperti leggermente più costosi. Wiki riassume: "Gli alberi AVL sono spesso confrontati con alberi rosso-neri perché entrambi supportano lo stesso insieme di operazioni e impiegano [lo stesso] tempo per le operazioni di base. Per applicazioni ad alta intensità di ricerca, gli alberi AVL sono più veloci degli alberi rosso-nero perché sono più rigorosamente bilanciati. Analogamente agli alberi rosso-neri, gli alberi AVL sono bilanciati in altezza. Entrambi non sono, in generale, né bilanciati in peso né in mu per ogni mu <1/2; vale a dire, i nodi fratelli possono avere enormemente diverso numero di discendenti ".
  • WAVL . Il documento originale menziona i vantaggi di quella versione in termini di limiti alle operazioni di ribilanciamento e rotazione.

Guarda anche

Domanda simile su CS: /cs/27860/whats-the-difference-between-a-binary-search-tree-and-a-binary-heap


4
I + 1ed, ma la "carta" che giustifica l'inserimento dell'heap binario O (1) medio è ora un collegamento non funzionante e le "diapositive" indicano semplicemente la richiesta senza prove. Inoltre, penso che aiuterebbe a chiarire che "caso medio" qui significa che la media presuppone che i valori inseriti provengano da una particolare distribuzione , quindi non sono sicuro di quanto "killer" sia questa funzionalità.
j_random_hacker,

3
BST e BST bilanciato sembrano essere usati in modo intercambiabile. Dovrebbe essere chiaro che la risposta si riferisce al BST bilanciato per evitare confusione.
Gkalpak,

2
@Bulat Sento che stiamo divagando un po ', ma se vogliamo sia il massimo sia il minimo allo stesso tempo, potremmo incorrere in problemi con il mantenimento di due cumuli se non stiamo attenti - stackoverflow.com/a/1098454/7154924 . Probabilmente è meglio usare un heap max-min (grazie ad Atkinson et al.), Che è specificamente progettato per questo scopo.
flow2k

1
@CiroSantilli 新疆 改造 中心 六四 事件 法轮功: Non capisco perché l'operazione di cancellazione di un heap binario sia O (log n). Funziona solo se hai un puntatore all'elemento nell'heap, ma nella maggior parte dei casi d'uso hai la chiave e devi prima trovare l'elemento che prende O (n).
Ricola,

5
l'inserimento dell'heap è log (n) not o (1)
Bobo

78

Heap garantisce solo che gli elementi ai livelli più alti sono maggiori (per heap max) o più piccoli (per heap min) rispetto agli elementi ai livelli inferiori, mentre BST garantisce l'ordine (da "sinistra" a "destra"). Se vuoi elementi ordinati, scegli BST.


8
"L'heap garantisce solo che gli elementi ai livelli più alti sono maggiori (per heap max) o più piccoli (per heap min) rispetto agli elementi ai livelli inferiori, ..." - l'heap non applica questo per livello , ma solo in parent-child- Catene. [1, 5, 9, 7, 15, 10, 11]rappresenta un min-heap valido, ma il 7livello 3 è inferiore rispetto 9al livello 2. Per una visualizzazione, vedere ad esempio gli elementi 25e nell'immagine di esempio di Wikipedia per gli heap . (Si noti inoltre che le relazioni di disuguaglianza tra gli elementi non sono rigide, poiché gli elementi non sono necessariamente unici.)19
Daniel Andersson,

Ci scusiamo per l'ingresso in ritardo, ma voglio solo avere chiarezza. Se l'heap binario è ordinato, il caso peggiore per la ricerca sarebbe log n corretto. Quindi, in tal caso, vengono ordinati i cumuli binari meglio degli alberi di ricerca binaria (BST rosso-nero). Grazie
Krishna il

50

Quando utilizzare un heap e quando utilizzare un BST

Heap è meglio su findMin / findMax ( O(1)), mentre BST è buono in tutti i find ( O(logN)). L'inserto è O(logN)per entrambe le strutture. Se ti interessa solo findMin / findMax (ad es. Relativo alla priorità), vai con heap. Se vuoi tutto ordinato, vai con BST.

Le prime diapositive da qui spiegano le cose molto chiaramente.


3
Mentre insert è logaritmico per entrambi nel caso peggiore, l'inserimento heap medio richiede tempo costante. (Dal momento che la maggior parte degli elementi esistenti sono in fondo, nella maggior parte dei casi un nuovo elemento dovrà solo saltare su uno o due livelli, se non del tutto.)
johncip

1
@xysun Credo BST è migliore in findMin & FindMax stackoverflow.com/a/27074221/764592
Yeo

2
@Yeo: Heap è meglio per findMin xor findMax. Se hai bisogno di entrambi , allora BST è migliore.
Mooing Duck

1
Penso che questo sia solo un malinteso comune. Un albero binario può essere facilmente modificato per trovare min e max come indicato da Yeo. Questa è in realtà una limitazione dell'heap: l' unica ricerca efficiente è min o max. Il vero vantaggio del mucchio è O (1) inserto media come spiego: stackoverflow.com/a/29548834/895245
Ciro Santilli郝海东冠状病六四事件法轮功

1
La risposta di Ciro Santilli è molto meglio: stackoverflow.com/a/29548834/2873507
Vic Seedoubleyew

9

Come accennato da altri, Heap può fare findMin o findMax in O (1) ma non entrambi nella stessa struttura di dati. Tuttavia non sono d'accordo sul fatto che Heap sia migliore in findMin / findMax. In effetti, con una leggera modifica, il BST può fare entrambe le cose findMin e findMax in O (1).

In questo BST modificato, si tiene traccia del nodo min e del nodo max ogni volta che si esegue un'operazione che può potenzialmente modificare la struttura dei dati. Ad esempio, durante l'operazione di inserimento è possibile verificare se il valore minimo è maggiore del valore appena inserito, quindi assegnare il valore minimo al nodo appena aggiunto. La stessa tecnica può essere applicata sul valore massimo. Quindi, questo BST contiene queste informazioni che è possibile recuperarle in O (1). (uguale all'heap binario)

In questo BST (BST bilanciato), quando tu pop mino pop max, il prossimo valore minimo da assegnare è il successore del nodo minimo, mentre il prossimo valore massimo da assegnare è il predecessore del nodo massimo. Quindi si esibisce in O (1). Tuttavia, è necessario riequilibrare l'albero, quindi eseguirà comunque O (log n). (uguale all'heap binario)

Sarei interessato a sentire il tuo pensiero nel commento qui sotto. Grazie :)

Aggiornare

Riferimenti incrociati a domande simili Possiamo usare l'albero di ricerca binario per simulare il funzionamento dell'heap? per ulteriori discussioni sulla simulazione di Heap utilizzando BST.


Perché non sei d'accordo? ti dispiacerebbe condividere con il tuo pensiero qui sotto?
Yeo,

Potresti sicuramente memorizzare il valore massimo e / o minimo di un BST, ma cosa succede se vuoi farlo scoppiare? Devi cercare l'albero per rimuoverlo, quindi cercare di nuovo il nuovo max / min, entrambi i quali sono operazioni O (log n). È lo stesso ordine di inserimenti e rimozioni in un heap prioritario, con una costante peggiore.
Justin il

@JustinLardinois Siamo spiacenti, ho dimenticato di evidenziare questo nella mia risposta. In BST, quando si pop min, il valore minimo successivo da assegnare è il successore del nodo min. e se si apre il massimo, il prossimo valore massimo da assegnare è il predecessore del nodo massimo. Quindi si esibisce ancora in O (1).
Yeo,

Correzione: per popMino popMaxnon è O (1), ma è O (log n) perché deve essere un BST bilanciato che deve essere ribilanciato ad ogni operazione di cancellazione. Quindi è lo stesso dell'heap binario popMino popMaxche esegue O (log n)
Yeo

2
Puoi ottenere il primo min / max, ma ottenere kth min / max tornerebbe alla normale complessità BST.
Caos

3

Un albero di ricerca binario usa la definizione: che per ogni nodo, il nodo alla sua sinistra ha un valore minore (chiave) e il nodo alla sua destra ha un valore maggiore (chiave).

Dove come heap, essendo un'implementazione di un albero binario utilizza la seguente definizione:

Se A e B sono nodi, dove B è il nodo figlio di A, il valore (chiave) di A deve essere maggiore o uguale al valore (chiave) di B. Cioè, chiave (A) ≥ chiave (B ).

http://wiki.answers.com/Q/Difference_between_binary_search_tree_and_heap_tree

Ho fatto la stessa domanda oggi per il mio esame e ho capito bene. Sorridi ... :)


"heap, essendo un'implementazione di un albero binario" - solo sottolineando che un heap è una specie di albero binario, non una specie di BST
Saad,

3

Un altro uso di BST su Heap; a causa di un'importante differenza:

  • trovare successore e predecessore in un BST richiederà O (h) tempo. (O (logn) in BST bilanciato)
  • mentre in Heap, impiegherebbe O (n) tempo per trovare il successore o il predecessore di qualche elemento.

Uso di BST su un mucchio : ora, supponiamo che utilizziamo una struttura di dati per memorizzare il tempo di atterraggio dei voli. Non possiamo programmare un volo a terra se la differenza nei tempi di atterraggio è inferiore a "d". E supponiamo che molti voli siano programmati per atterrare in una struttura di dati (BST o Heap).

Ora, vogliamo programmare un altro volo che atterrerà a t . Quindi, dobbiamo calcolare la differenza di t con il suo successore e predecessore (dovrebbe essere> d). Pertanto, avremo bisogno di un BST per questo, che lo fa velocemente, cioè in O (logn) se bilanciato.

Modificato:

L'ordinamento di BST richiede O (n) tempo per stampare gli elementi in ordine ordinato (Inorder traversal), mentre Heap può farlo in tempo O (n logn). Heap estrae l'elemento min e ri-heap l'array, il che lo rende in grado di eseguire l'ordinamento nel tempo O (n logn).


1
Sì. È dalla sequenza non ordinata a quella ordinata. O (n) tempo per l'attraversamento interno di un BST, che fornisce una sequenza ordinata. In Heaps, si estrae l'elemento min e quindi si ricombina in O (log n) time. Quindi, ci vorrà O (n logn) per estrarre n elementi. E ti lascerà con una sequenza ordinata.
CODError

from unsorted to sorted sequence. O(n) time for inorder traversal of a BST, which gives sorted sequence.Bene, dalla sequenza non ordinata al BST non conosco un metodo basato sul confronto chiave con tempo inferiore a O (n logn), che domina il BST alla parte della sequenza. (Considerando che esiste la costruzione di heap O (n).). Considererei giusto (se inutile) affermare che i cumuli sono vicini all'indifferenza e ai BST ordinati.
greybeard

Quello che sto cercando di spiegare qui è che se hai un BST e anche un mucchio di n elementi => allora tutti gli elementi potrebbero essere stampati in ordine ordinato da entrambe le strutture di dati e BST può farlo in tempo O (n) (Inorder traversal ), mentre Heap richiederebbe tempo O (n logn). Non capisco cosa stai cercando di dire qui. How do you say BST ti darà una sequenza ordinata in O (n logn).
CODError

Penso che anche tu stia considerando il tempo impiegato per costruire un BST e un Heap. Ma suppongo che tu l'abbia già, che l'hai costruito nel tempo e ora vuoi ottenere il risultato ordinato. Non capisco il tuo punto?
CODError

1
Modificato ... Spero che tu sia soddisfatto ora; p e dai un +1 se è corretto.
CODError

1

Inserire tutti gli n elementi da un array in BST richiede O (n logn). n elemnts in un array possono essere inseriti in un heap in O (n) time. Il che conferisce all'heap un vantaggio decisivo


0

Heap garantisce solo che gli elementi ai livelli più alti sono maggiori (per heap max) o più piccoli (per heap min) rispetto agli elementi ai livelli inferiori

Adoro la risposta di cui sopra e mettere il mio commento solo più specifico per le mie necessità e utilizzo. Ho dovuto ottenere la lista n posizioni per trovare la distanza da ogni posizione a punto specifico dire (0,0) e quindi restituire le posizioni am con distanza minore. Ho usato la coda di priorità che è Heap. Per trovare le distanze e mettere in heap mi ci sono voluti n (log (n)) n-locations log (n) ogni inserimento. Quindi per ottenere m con le distanze più brevi ci sono volute m (log (n)) m-locations log (n) eliminazioni di accumulo.

Se avessi dovuto farlo con BST, mi ci sarebbe voluto n (n) inserimento nel caso peggiore (supponiamo che il primo valore sia molto più piccolo e tutti gli altri vengano sequenzialmente sempre più lunghi e l'albero si estende solo dal figlio destro o dal figlio sinistro in caso di minori e minori. Il minimo avrebbe richiesto O (1) tempo ma di nuovo dovevo bilanciarmi. Quindi dalla mia situazione e dalle risposte di cui sopra ciò che ho ottenuto è quando sei solo dopo che i valori alla base di priorità minima o massima vanno per mucchio.

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.