2014-05-05 14 views
5

Ho bisogno di una struttura dati Java che possa aggiungere, eliminare e accedere in modo efficiente a un oggetto casuale.Struttura dati Java con aggiunta, eliminazione e random efficienti

Questo è ciò che non funziona:

ArrayList ha efficiente add (costante di tempo), e ad accesso casuale (solo "ottenere" con un numero intero casuale), ma elimina può richiedere tempo lineare, perché deve potenzialmente cerca l'intero elenco per questo.

TreeSet o HashSet ha un'aggiunta ed eliminazione efficiente, ma non riesco a capire come ottenere un oggetto casuale.

Qualche idea?

In teoria, un Albero B funzionerebbe, se potessi attraversare l'albero io stesso con Sinistra o Diritti casuali, ma non penso che una classe Java standard mi dia questa abilità.

Sono disposto ad utilizzare una libreria di terze parti se nulla nelle classi Java standard funzionerà.

Non è necessario supportare duplicati o null, né è necessario essere thread-safe.

Grazie.

+0

'ArrayList.remove (int index)' viene eseguito in tempo costante ammortizzato. Per quanto posso dire, questo significa che le chiamate individuali sono di tempo lineare, ma il tempo medio su una serie di chiamate si avvicina al tempo costante. – Aarowaim

+0

Grazie per questo Aarowaim, ma non saprei l'indice dell'oggetto da rimuovere. Suppongo che potrei memorizzare queste informazioni separatamente in una HashMap o qualcosa del genere, ma non appena ho rimosso un oggetto da ArrayList, gli altri oggetti nella lista cambierebbero gli indici, il che significherebbe che alcuni dei valori nella HashMap sarebbero errati, e mi ci vorrà del tempo lineare per sistemarli. – Magmatic

+0

@Magmatic È necessario consentire elementi duplicati? – Boann

risposta

4

È possibile ottenere questo con una coppia ArrayList/HashMap (in cui la mappa memorizzerà l'indice degli oggetti nell'elenco) se si effettua la rimozione degli elenchi non ordinati. Alla rimozione, invece di spostare tutti gli elementi successivi nell'elenco in basso di una posizione, basta spostare l'ultimo elemento per riempire lo spazio dell'elemento rimosso. Poi ci sono al massimo due diversi elementi che devono essere toccati durante la rimozione. Un esempio veloce (che non ho ancora testato e la speranza non ho bungle):

class SpecialSet<E> extends AbstractSet<E> implements Set<E> { 
    private final List<E> list = new ArrayList<>(); 
    private final Map<E,Integer> indexMap = new HashMap<>(); 

    @Override 
    public boolean add(E e) { 
     if (contains(e)) return false; 
     indexMap.put(e, list.size()); 
     list.add(e); 
     return true; 
    } 

    @Override 
    public boolean remove(Object o) { 
     Integer indexBoxed = indexMap.remove(o); 
     if (indexBoxed == null) return false; 
     int index = indexBoxed; 
     int last = list.size() - 1; 
     E element = list.remove(last); 
     if (index != last) { 
      indexMap.put(element, index); 
      list.set(index, element); 
     } 
     return true; 
    } 

    public E removeRandom() { 
     E element = list.get((int)(Math.random() * size())); 
     remove(element); 
     return element; 
    } 

    @Override 
    public boolean contains(Object o) { 
     return indexMap.containsKey(o); 
    } 

    @Override 
    public Iterator<E> iterator() { 
     return list.iterator(); 
    } 

    @Override 
    public int size() { 
     return list.size(); 
    } 
} 

A seconda di quale oggetto comportamento test di uguaglianza si desidera, è forse potrebbe scambiare la HashMap per un IdentityHashMap per un po 'meglio velocità.

+0

Wow, grazie per il codice! L'ho appena buttato nel mio progetto. (Ho solo cambiato "removeRandom" in "getRandom" e ho eliminato la linea che lo rimuove). Ha funzionato perfettamente! Stavo usando un ArrayList con la rimozione inefficiente, ma quando ho cambiato questo codice, il tempo di elaborazione è sceso da 1900 ms a 628 ms. Wow wow wow! Grazie, grazie, grazie! – Magmatic

+0

Qui non vedo l'importanza di 'HashMap'. Il trucco più importante è qui: la rimozione non rimuove l'elemento direttamente, sta rimuovendo l'ultimo elemento e l'ultimo elemento nella posizione che deve essere rimosso. In tal caso, solo un singolo 'ArrayList' come struttura interna aiuterà –

+0

@AdrianShum Devi trovare un elemento prima di poterlo rimuovere; questa è la parte che rende veloce HashMap. – Boann

0

Suggerirei di utilizzare una mappa, con un tasto intero. In questo modo puoi generare un numero casuale per la rimozione.

Problema lasciato al lettore: nelle operazioni di rimozione successive (o qualsiasi operazione) ci saranno degli spazi vuoti.

+0

Ma come puoi rimuovere un valore se non conosci la sua chiave? Avresti comunque bisogno di fare una ricerca lineare inefficiente per trovarlo. – Magmatic

1

Si noti che un semplice array farà il lavoro, se si pre-allocare alla dimensione massima di cui avrete bisogno. Mantieni un valore di indice "top" e inserisci incrementando "top" e memorizzandolo in quell'elemento dell'array. Quando elimini, copia il valore "in alto" nell'elemento eliminato e decrementa "in alto".

Se non si conosce la dimensione massima è ancora possibile allocare una matrice di matrici e utilizzare lo stesso algoritmo di base, solo è necessario calcolare prima i valori dell'indice di array maggiore e minore prima di accedere a un elemento.

Problemi correlati