Per trovare l'elemento più vecchio / più nuovo in un heap

5

Voglio trovare l'elemento più vecchio / più recente aggiunto in un heap di dimensioni k. In qualsiasi momento, se devo trovare dire l'elemento più vecchio nell'heap, esiste un approccio con lo spazio O (1) e il tempo O (1).

Stavo pensando ad un approccio per avere una coda della stessa dimensione di quella dell'heap e aggiungere tutti gli elementi nella coda man mano che vengono aggiunti all'heap. Questo produce una soluzione a tempo costante ma ciò richiede uno spazio O (k) extra. Vorrei sapere se questo problema può essere risolto con spazio costante.

    
posta redDragon 08.09.2014 - 00:23
fonte

3 risposte

3

Puoi utilizzare una coda a priorità doppia per questo. Sono disponibili numerosi metodi di implementazione e la struttura è disponibile di routine in molte librerie. La maggior parte delle librerie Java lo chiamano MinMaxPriorityQueue. Offre O (1) recupero di Min e Max (più recente / più vecchio) in tempo O (1).

Per soddisfare il tuo requisito di spazio, devi utilizzare un heap di intervalli in cui ogni nodo ha due valori. La contabilità è un po 'complicata ma non più lenta del normale.

    
risposta data 08.09.2014 - 00:34
fonte
2

Se mantieni una struttura dati separata lungo il tuo heap, questo probabilmente risolverà il tuo problema. Questo è l'approccio che utilizza la LinkedHashMap (una lista collegata lungo il lato HashMap) per consentire una cache LRU e un ben noto ordinamento delle voci in LinkedHashMap (ordine di inserimento) (grepcode LinkedHashMap.Entry ).

Questa struttura dati potrebbe apparire come Deque o qualcosa di simile ed è ordinato per ordine di inserimento. Nessun altro ordine è imposto su questa struttura.

Il problema più grande con questo, però, è che le due strutture (l'heap e il deque) non sono sincronizzate, si dovrebbe fare la conservazione del libro per rimuovere dal deque quando qualcosa viene rimosso dall'heap.

Un approccio a questo sarebbe utilizzare un riferimento debole (Java Riferimento , C # WeakRefrence classe). Quando percorri la deque, esegui il polling del Reference per get() per vedere se è effettivamente lì, e in tal caso restituiscilo, altrimenti scarti quell'elemento nel deque e continua a camminare fino a trovare un elemento Riferimento a cui è collegato un oggetto.

Il trucco qui è che se stai puntando all'oggetto su cui punta l'heap, è possibile che un'altra parte del codice abbia preso una strong presa sull'oggetto e pensi che sia ancora nell'Heap .

Per evitare ciò, il deque dovrebbe invece puntare all'elemento heap piuttosto che all'oggetto a cui punta l'elemento heap, ed essere molto pulito con la rimozione dell'elemento dall'heap quando viene eliminato (e non lasciarlo trapelare al di fuori dell'heap - tranne che per la deque (che dovrebbe probabilmente essere all'interno dell'heap)). Ammetto di non sapendo per quanto tempo il Reference continuerà a puntare a qualcosa e quali condizioni sono necessarie per deterministicamente risolverlo.

Questo permetterebbe al garbage collector di fare il lavoro per te di tenere una contabilità. Il trade off è invece di ogni volta che l'inserto o la rimozione viene eseguito dall'heap e la conservazione del libro viene eseguita, quindi la conservazione del libro viene eseguita quando il deque viene interrogato o sbirciato. Qual è il modo in cui il trade off va considerato in base all'inserimento, alla rimozione e al polling che l'applicazione fa sullo heap.

Un pensiero di incollare un'altra struttura di dati all'interno dell'heap potrebbe consentire problemi di riferimenti deterministici. Se si dispone di una HashMap nell'heap che contiene anche tutti gli oggetti (sebbene si noti che in realtà non è in grado di contenere copie dei propri forti riferimenti agli oggetti). Quando qualcosa viene aggiunto o rimosso dall'heap, viene anche aggiunto o rimosso da HashMap. Quindi, la deque, invece di contenere riferimenti deboli all'oggetto, ha un strong riferimento. Quando il deque viene interrogato, controlla HashMap per vedere se l'oggetto si trova ancora nell'heap.

In ogni caso, ciò che in definitiva fornisce è una deque tale che il primo elemento restituito dalla deque è il più vecchio nell'heap e l'ultimo elemento restituito dalla deque è il più nuovo oggetto nell'heap.

    
risposta data 08.09.2014 - 05:07
fonte
1

Ho copiato la mia risposta da questa domanda: Quale struttura dati posso utilizzare per implementare una coda con priorità doppia terminata con log (n) inserzione e find? l'ho ripetuta qui perché queste domande non sembrano realmente essere duplicati, ma semplicemente due domande che possono essere - ma non devono essere - risolte con la stessa tecnica:

Ecco cosa ho fatto per un'implementazione free-list con allocatore di blocchi che necessitava di una coda con priorità doppia:

  • Utilizza algoritmi albero rosso-nero standard , con un miglioramento piccolo :
  • Durante l'inserimento tieni traccia dell'elemento minimo e massimo nell'albero mentre fai degli inserimenti o delle eliminazioni. Questo aggiunge solo al massimo O (log n) confronti come overhead durante il processo di inserimento già O (log n), quindi non modifica la complessità algoritmica complessiva.

Questo produce:

  • O (log n): inserimento
  • O (1): find-min e delete-min
  • O (1): find-max e delete-max

Poiché quelle erano le sole operazioni di cui avevo bisogno per la mia particolare applicazione, questa era praticamente la migliore struttura dati possibile. Anche se avevi bisogno di altre operazioni con le code di priorità (come cambiare le priorità, ecc.) Praticamente tutto sarebbe ancora O (log n) nel caso peggiore a causa dell'albero rosso-nero.

Il motivo per cui ottieni O (1) sia per find-min / max che delete-min / max è che hai tenuto traccia dell'elemento minimo e massimo durante l'inserimento, così puoi immediatamente trovarlo, eliminando O (log n) per la ricerca. Quindi l'algoritmo albero rosso-nero garantisce le operazioni O (1) per riequilibrare l'albero dopo che l'elemento è stato rimosso. (Questa è una proprietà ben nota e molto importante degli alberi rosso-neri. Questo non sarebbe il caso della maggior parte degli altri alberi di ricerca binari, come gli alberi AVL, che richiedono operazioni O (log n) durante il ribilanciamento.)

    
risposta data 25.11.2015 - 05:45
fonte

Leggi altre domande sui tag