Esiste una coda prioritaria con estratti ?


46

Esistono moltissime strutture dati che implementano l'interfaccia della coda di priorità:

  • Inserisci: inserisce un elemento nella struttura
  • Get-Min: restituisce l'elemento più piccolo nella struttura
  • Estrai-Min: rimuove l'elemento più piccolo nella struttura

Strutture di dati comuni che implementano questa interfaccia sono heap (min) .

Di solito, i tempi di esecuzione (ammortizzati) di queste operazioni sono:

  • Inserisci: (a volte )O ( log n )O(1)O(logn)
  • Get-Min:O(1)
  • Extract-Min:O(logn)

L' heap di Fibonacci raggiunge ad esempio questi tempi di funzionamento. Ora, la mia domanda è la seguente:

Esiste una struttura di dati con i seguenti tempi di esecuzione (ammortizzati)?

  • Inserisci:O(logn)
  • Get-Min:O(1)
  • Extract-Min:O(1)

Se possiamo costruire una struttura del genere in tempo dato input ordinato, allora possiamo trovare intersezioni di linea su input pre-ordinati con intersezioni strettamente più veloci rispetto a quando utilizziamo le "normali" code di priorità.o ( nO(n)o(nlogn)


Penso che usando un BST bilanciato, che non si riequilibrerebbe quando si fa Extract-Min potrebbe funzionare. O forse un elenco di salto.
svick

@svick: saltare gli elenchi è randomizzato, che non è quello che sto cercando. Se riesci a farlo con un BST, allora è fantastico, ma penso che dovrai fare una sorta di bilanciamento.
Alex ten Brink

Una nota a margine: questa è una domanda di base e conosco la risposta, ma è bello vedere che non è così facile da risolvere. Se qualcuno conosce la risposta, non esitare a darla :)
Alex ten Brink

Se si accettano tempi di aggiornamento ammortizzati, è possibile mantenere le strutture heap standard e apportare solo piccole modifiche all'analisi. Vedi la mia risposta qui sotto.
Joe,

Risposte:


27

La nostra idea è quella di utilizzare alberi di splay filettati . A parte l'articolo di Wikipedia, inseriremo gli alberi in modo che ogni nodo abbia un puntatore al suo successore nell'attraversamento in ordine; teniamo anche un puntatore all'elemento più piccolo nella struttura.nextstart

È facile vedere che è possibile estrarre l'elemento più piccolo nel tempo (nel peggiore dei casi) : basta seguire il puntatore, rimuovere il minimo e cambiare il puntatore al minimo . Il minimo non può mai avere un figlio sinistro; se ha un figlio giusto, lo mettiamo nella posizione minima nell'albero. Noi non eseguiamo gli alberi operazione strombo strombo solito farebbe. Il risultato è un albero di ricerca che è ancora ragionevolmente bilanciato: poiché rimuoviamo solo i nodi sul fianco sinistro, sappiamo che quando il numero di nodi (in una sottostruttura interessata) scende a circa la metà del numero originale a causa delle eliminazioni, il (sub ) l'altezza dell'albero è ridotta di uno.O(1)startnext

Sono possibili inserimenti in tempo ammortizzato ; anche le operazioni a zig-zag (e cosa no) riequilibreranno bene l'albero.O(logn)

Questo è uno schizzo approssimativo al massimo. I crediti vanno a F. Weinberg, che ha confuso la domanda con me e il nostro consulente M. Nebel, che ha menzionato gli alberi splay, sull'unica variante di albero che non avevamo provato.


2
Non mi è chiaro come far funzionare l'analisi ammortizzata se non si esegue lo splay su extractMin. Puoi dare un suggerimento?
jbapple,

Non l'abbiamo fatto in dettaglio. L'idea è che una serie di operazioni extract-min non sbilanciano l'albero, quindi non è necessario lo splaying e l'analisi normale dovrebbe funzionare per gli inserimenti.
Raffaello

9
! attento Gli alberi Splay non sono necessariamente bilanciati. I nodi a cui non si è avuto accesso da molto tempo potrebbero essere molto profondi nell'albero. Per rendere l'analisi passare attraverso, si deve discutere in termini della stessa funzione potenziale utilizzato per analizzare sguanci.
JeffE,

20
  • Inserisci:O(logn)
  • Get-Min:O(1)
  • Extract-Min:O(1)

Tempo ammortizzato

Semplici implementazioni di una coda prioritaria (ad es. Qualsiasi BST bilanciato o min-heap binario standard) possono raggiungere questi tempi (ammortizzati) semplicemente caricando il costo di Extract-Min da inserire e mantenendo un puntatore all'elemento minimo. Ad esempio, potresti avere una potenziale funzione che è . Quindi l'inserimento di un nuovo elemento aumenta il potenziale di , quindi il costo ammortizzato dell'inserto è ancora , ma Extract-Min () riduce il potenziale di e quindi il costo ammortizzato è solo .O ( log n ) O ( log n ) Ω ( log n ) O ( 1 )cnlognO(logn)O(logn)Ω(logn)O(1)

Nel caso peggiore

È possibile utilizzare una struttura di dati esistente in letteratura: alberi di ricerca delle dita e mantenere semplicemente un puntatore all'elemento minimo. Guarda questo sondaggio per una panoramica e l' articolo del 1988 di Levcopoulos e Overmars per una versione implementabile che soddisfa le tue esigenze.


1
Molto subdolo. Hai ragione, immagino che avrei dovuto chiedere qualcosa di più forte per escluderlo. Bella idea :)
Alex ten Brink,

@AlextenBrink Potresti richiedere le eliminazioni caso peggiore . (che sembra essere quello che cercavano alcune delle altre risposte) Ho aggiunto un paragrafo alla mia risposta per affrontare quel caso. O(1)
Joe,

14

2-4 alberi hanno ammortizzato modifiche in posizioni note. Vale a dire, se si dispone di un puntatore a una posizione nella struttura, è possibile rimuovere o aggiungere un elemento lì nel tempo ammortizzato .O ( 1 )O(1)O(1)

È quindi possibile mantenere un puntatore all'elemento minimo e al nodo radice in un albero 2-4. Gli inserti devono passare attraverso il nodo principale. Aggiornare il puntatore al minimo è banale dopo un deleteMin, e deleteMins è il tempo (ammortizzato).O(1)

Una nota a margine interessante: gli alberi rosso-neri sono solo un modo di guardare 2-4 alberi. I progettisti degli implementatori di librerie standard C ++ 98 prevedevano di fornire un contenitore basato su albero rosso-nero e lo standard specifica che inserire ed eliminare dovrebbe essere il tempo ammortizzato in posizioni note (che chiamano "iteratori" ). Tuttavia, questo è in realtà molto più complicato per gli alberi rosso-neri che per 2-4 alberi, poiché richiede nodi che segnano pigramente che devono essere ricolorati. Per quanto ne so, nessuna implementazione della libreria standard C ++ 98 ha soddisfatto quel particolare requisito.O(1)


8

Su richiesta, ecco la struttura che ho trovato dopo aver formulato la domanda:

L'idea di base è quella di utilizzare un albero capro espiatorio filettato insieme a un puntatore al minimo (e per buona misura, anche il massimo). Un'alternativa più semplice al threading consiste nel mantenere i puntatori predecessore e successore in ogni nodo (che è equivalente, più semplice, ma ha più sovraccarico). Sono venuto per chiamarlo un mucchio di capri espiatori , solo per dargli un nome.

Proprio questa struttura di base ti dà queste operazioni:

  • Cerca: data una chiave, restituisce un puntatore al nodo corrispondente in tempo .O(logn)
  • Inserisci: data una chiave, inserisce la chiave nella struttura, restituendo un puntatore a quel nodo in tempo .O(logn)
  • Predecessore / successore: dato un puntatore, restituisce il successore o il predecessore in tempo.O(1)
  • Get-Min / Max: riporta il puntatore al minimo o al massimo.

Nell'analisi degli alberi del capro espiatorio, il sovraccarico di bilanciamento dell'eliminazione viene analizzato come , ma l'analisi fornisce effettivamente un sovraccarico di equilibrio di (che viene ignorato nel documento poiché contano anche il tempo necessario per trovare il nodo da eliminare). Quindi, se abbiamo un puntatore a un nodo, possiamo eliminarlo in tempo costante (puoi farlo nell'albero di ricerca binario threaded in tempo ) e combinato con sovraccarico di bilanciamento, questo dà un time delete:O ( 1 ) O ( log n ) O ( 1 ) O ( 1 ) O ( 1 )O(logn)O(1)O(logn)O(1)O(1)O(1)

  • Elimina: dato un puntatore, elimina il nodo in volta.O(1)

Combinando questo:

  • Estrai-Min / Max: elimina il nodo minimo / massimo in tempo .O(1)

Puoi fare un po 'di più con i puntatori: ad esempio non è difficile mantenere un puntatore alla mediana o qualche altra statistica dell'ordine, quindi puoi mantenere un numero costante di tali puntatori se ne hai bisogno.

Alcune altre cose:

  • Costruisci: dato chiavi in ​​ordine, costruisci un heap Capro espiatorio in tempo .nO(n)
  • Equilibrio: bilancia l'albero in modo che formi un albero di ricerca binario perfettamente bilanciato (riduce il sovraccarico della ricerca) in tempo (puoi farlo un fattore costante più velocemente di quanto la carta suggerisce tra l'altro, facendo uso di puntatori predecessore / successore).O(n)

E infine, sono abbastanza sicuro che puoi supportare queste operazioni, ma ho bisogno di pensarci un po 'di più prima di saperlo con certezza:

  • Inserisci-Nuovo-Min / Max: data una chiave più piccola / più grande di qualsiasi chiave già presente nella struttura, inserisce la chiave nella struttura, restituendo un puntatore a quel nodo in tempo .O(1)

L'intuizione chiave è che gli alberi capro espiatorio assicurano che l'eliminazione di qualsiasi nodo senza riequilibrare non influisce sulle prestazioni di altre operazioni a lungo termine, anche se si eliminano molti nodi.
Raffaello

Conosco due modi per fare le eliminazioni negli alberi del capro espiatorio. Un modo rispecchia gli inserti ed è il tempo ammortizzato . L'altro modo in cui ho sentito parlare di usa la ricostruzione globale ed è ammortizzato, ma non so come mantenere il threading in quel caso. Immagina di inserire una nuova chiave in una parte dell'albero che contiene tutte le chiavi cancellate che devono ancora essere rimosse. Come si trova il predecessore della chiave da inserire nel tempo ? O(lgn)O(1)O(lgn)
jbapple,

2
@jbapple: ci sono due varianti su come fare le eliminazioni in tempo per gli alberi del capro espiatorio. Uno è lasciare il nodo dentro, contrassegnarlo come cancellato e rimuovere tutti questi nodi eliminati con la ricostruzione globale, e l'altro è davvero rimuovere il nodo. Il primo è più facile da analizzare (e ti dà anche il limite sul secondo, motivo per cui di solito è spiegato) ma il secondo è quello che sto cercando : puoi cancellare in tempo in un albero di ricerca binario vaniglia se è possibile eseguire query predecessore / successore in tempo e il bilanciamento in tempo ammortizzato si ottiene il resto del limite. O(1)O(1)O(1)O(1)
Alex ten Brink,

Ah, adesso capisco.
jbapple,

2

Un soft heap è una sottile modifica di una coda binomiale. La struttura dei dati è approssimativa con un parametro di errore . Supporta insert, delete, meld e findmin. La complessità ammortizzata di ciascuna operazione è , ad eccezione di insert che richiede tempo . La novità del soft heap sta nel battere il limite logaritmico sulla complessità di un heap nel modello basato sul confronto. Al fine di rompere la barriera teorica dell'informazione, l'entropia della struttura dei dati viene ridotta aumentando artificialmente i valori di alcune chiavi. Questo si chiama corrompere le chiavi. La struttura dei dati è completamente basata su puntatori (senza array o ipotesi numeriche) ed è ottimale per qualsiasi valore diϵO(1)log(1/ϵ)ϵ nel modello basato sul confronto.

Le applicazioni del soft heap includono il calcolo dell'albero di spanning minimo per un grafico, mantenendo in modo dinamico percentili e statistiche di ordine temporale lineare. Può anche essere usato per il calcolo approssimativo, come l'ordinamento approssimativo in cui il rango di un elemento non differisce mai di più di dal rango reale.ϵn

Per il documento originale, chiaro e ben scritto, vedi Bernard Chazelle, The Soft Heap: una coda di priorità approssimativa con tasso di errore ottimale, Journal of ACM, 47 (6), pp. 1012-1027, 2000 . Per un'implementazione e un'analisi alternative che affermano di essere più semplici e intuitive da SODA'09, vedi Kaplan H. & Zwick U., Un'implementazione e un'analisi più semplici dei mucchi molli di Chazelle, 2009 .


Sebbene una struttura di dati molto interessante, gli heap non sono esatti: findmin può restituire un valore che non è il minimo, ma è solo un minimo approssimativo. Grazie comunque per i collegamenti :)
Alex ten Brink,

1
@AlextenBrink: il punto della struttura dei dati (come in molti algoritmi probabilistici) è che puoi usare una struttura approssimativa dei dati per ottenere risposte esatte. In effetti, la natura approssimativa dei cumuli morbidi non ha impedito che venisse utilizzato nell'unico algoritmo del tempo lineare noto per lo spanning tree minimo.
Jérémie,

2

Bene, finalmente ti ho procurato la complessità che stavi cercando, e la cosa migliore, l'ho trovata in letteratura:

Complessità peggiore

Elimina :O(1)

Delete-min :O(1)

Find-min :O(1)

Inserisci :O(log n)

Riferimento

Se a MELD è consentito prendere un tempo lineare, è possibile supportare DELETE-MIN nel tempo costante nel peggiore dei casi utilizzando gli alberi di ricerca delle dita di Dietz e Raman [3]. Usando la loro struttura di dati MAKEQUEUE , FINDMIN , DELETEMIN , DELETE può essere supportato nel peggiore dei casi , INSERT nel peggiore dei casi e MELD nel peggiore dei casi .O(1)O(log n)O(n)

Brodal, Gerth Stølting. "Code di priorità di fusione rapida". In Atti del 4 ° Workshop internazionale su algoritmi e strutture dati, 282–290. WADS '95. Londra, Regno Unito, Regno Unito: Springer-Verlag, 1995.

[3]: Dietz, Paul F e Rajeev Raman. "Un albero di ricerca dito per l'ora di aggiornamento costante". Lettere per l'elaborazione delle informazioni 52, n. 3 (1994): 147-154.

Anche se questo utilizza il modello di calcolo RAM :

La nostra struttura di dati utilizza il modello di macchina ad accesso casuale (RAM) con misura del costo unitario e dimensione della parola logaritmica;

Più recentemente, è stato fornito un modello di soluzione di calcolo Pointer-Machine[1] .

[1]: Brodal, Gerth Stølting, George Lagogiannis, Christos Makris, Athanasios Tsakalidis e Kostas Tsichlas. "Alberi di ricerca delle dita ottimali nella macchina del puntatore". J. Comput. Syst. Sci. 67, n. 2 (settembre 2003): 381–418.


2

Affrontare questo problema mantenendo due strutture di dati: una matrice e un albero binario.

Per mantenere l'indicizzazione nell'array, in precedenza si avrebbe il ; ma più recentemente questo è stato superato modificando l'analisi dalla tecnica del cronogramma. Il nuovo limite [inferiore] è stato dimostrato per problemi simili nel modello 1 di sonda cellulare . Dalla lettura di quell'articolo; è mia comprensione che tale limite si applica anche al problema della rappresentazione dell'elenco .Ω(lognloglogn)Ω(logn)

Ora, se si inserisce un albero binario nell'array e si riequilibria + reindicizza ogni aggiornamento di , si avranno: complessità.O(logn)O(logn)

La tua corsa più lunga, tra gli nullelementi eliminati, sarà . Ciò chiaramente non lascia alcun vantaggio teorico rispetto al riequilibrio + reindicizzazione di ogni aggiornamento.O(logn)

A seconda della distribuzione, è possibile supporre di riequilibrare solo ogni inserto; quindi estrarre la complessità dall'estratto. Estrai — da entrambe le estremità — prenderà quindi solo ; poiché non è necessario eseguire reindex (basta tenere traccia degli offset dell'indice per tenerlo in ).O(1)O(1)

Se non riesci a fare questo presupposto, il mio approccio ti lascerà con inserire, riequilibrare ed estrarre. Ha comunque un vantaggio rispetto ad alcuni altri approcci, in quanto è possibile ottenere min / max e qualsiasi punto intermedio, ad esempio: dammi il valore mediano, in . Inoltre ha funzionalità.O(logn)O(1)delete_at(idx)


1 Patrascu, Mihai ed Erik D. Demaine. "Limiti logaritmici inferiori nel modello della sonda cellulare." SIAM J. Comput. 35, n. 4 (aprile 2006): 932-963. DOI: 10,1137 / S0097539705447256.


1
Vuoi dire che usi un albero AVL o un albero bilanciato simile? Come si fa ad accertarsi che la rimozione del minimo non causi più che costantemente molte rotazioni più lontano di quanto costantemente molti passi (dal sito di rimozione o dalla radice)? In particolare, la rimozione di elementi dagli alberi AVL può causare rotazioni , quindi è necessario discutere su come impedirlo. O(logn)
Raffaello

Che cosa significa "thread un albero di ricerca binario in un array"?
jbapple,

@AT: condivido il sentimento di jbapple.
Raffaello

La memorizzazione di un albero binario in un array in quel modo (come il classico heap binario) fa in modo che ogni rotazione impieghi tempo in cui è la dimensione della sottostruttura radicata nel nodo ruotato. In tal caso, eseguire "solo" rotazioni su un aggiornamento può comunque richiedere molto tempo. Ω(k)kO(1)
jbapple,

Il tuo aggiornamento, in cui spieghi come implementare le rotazioni in tempo costante, non funziona negli array. Questa risposta è ancora errata. Il documento di Tarjan a cui fai riferimento riguarda gli alberi memorizzati con nodi e puntatori.
jbapple,

-2

Trova-min in con tempo di aggiornamento previsto diO(1)O(log log n)

Vedi l'articolo del 2007: Equivalenza tra le code prioritarie e l'ordinamento di Mikkel Thorup.

Nota: fa riferimento all'articolo del 2002 di Han & Thorup: ordinamento intero in Tempo previsto e spazio lineareO(n log log n) .


Sebbene il documento che hai collegato sia interessante, la coda di priorità che presentano non ha cancellazioni di tempo costanti (se ho letto l'abstract correttamente), e quindi non è quello che sto chiedendo.
Alex ten Brink,

-2

Analisi

Inserisci :o(n log log n)

Cerca :o(log log n)

Elimina :O(1)

Spazio :O(n)

Get-Min :O(1)

Extract-Min :O(1)

Implementazione

  1. Forma un elenco da un numero arbitrario (costante) di elementi, diciamo 6:O(1)
  2. Ordina l'elenco: =O(6)O(1)
  3. Il punto di inserimento per ogni successivo nodo , saranno i secondi elementi (') pos (-1 per <, +1 per> e con due arg dipende da quale si inizia / finisce in: e sarà trovato usando l' interpolazione dinamica Cerca [1] in:k±
    ((k>nsize1)(k<n0)((k<ni)(k>ni+1)))
    o(log log n)

[1]: Andersson, Arne e Christer Mattsson. "Ricerca di interpolazione dinamica nel tempo O (log log n)". In automi, lingue e programmazione, a cura di Andrzej Lingas, Rolf Karlsson e Svante Carlsson, 700: 15–27. Dispense in Informatica. Springer Berlin / Heidelberg, 1993. http://dx.doi.org/10.1007/3-540-56939-1_58 .


2
Bene, il tempo di inserimento è lontano dal segno.
Raffaello

Questo è troppo impreciso. Ad esempio, che cos'è , , e ? nsize1n0nini+1
Juho,

Leggendo l'abstract del documento che colleghi, sembra che questi limiti siano limiti attesi per input di una particolare distribuzione, che non è quindi quello che sto cercando: voglio i limiti che menziono su qualsiasi input.
Alex ten Brink,

@Raphael: No non lo è. mrm: posizioni nell'elenco. AlextenBrink: può essere facilmente modificato in peggiore dei casi su qualsiasi distribuzione utilizzando l' algoritmo di ricerca binaria per trovare il punto di inserimento. O(log n)
AL

@AT La ricerca binaria logaritmica richiede un accesso casuale. Come viene implementato l'elenco sottostante? Dovresti davvero discutere per i tuoi limiti dichiarati. Inoltre, "posizioni nell'elenco" è vago: a quali posizioni e a cosa si riferiscono i simboli? Non tutti hanno accesso al documento che hai collegato. Prova a rendere la tua risposta (più) autonoma e almeno a riassumere i fatti. A questo punto non credo che la tua risposta sia corretta.
Juho,
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.