Complessità di ArrayList di LinkedHashSet

0

Ottengo le stringhe di input dalla console in questo modo:

while ((currentLine = bufferedReader.readLine()) != null ) {
    StringTokenizer string = new StringTokenizer(currentLine, " ");
    while (string.hasMoreTokens()) {
        // Create a new LinkedHashSet for every token and then add it to the ArrayList.
        LinkedHashSet<String> linkedHashSet = new LinkedHashSet<String>();
        linkedHashSet.add(string.nextToken());
        setOfStrings.add(linkedHashSet);
    }
}

Ricevo sempre stringhe diverse dall'input, mai lo stesso. Dopo aver terminato di compilare le strutture dati, ho questa situazione:

  • Un ArrayList<LinkedHashSet<String>> che contiene un LinkedHashSet per ogni divisione di stringhe.
  • All'interno di ogni LinkedHashSet ho una stringa che è diversa da qualsiasi altra stringa presente negli altri LinkedHashSet - in altre parole, è unica. Ad esempio, non posso avere questo all'interno di ArrayList:

    Set x : [foo]
    ...
    Set y : [foo] 
    

Dopo averlo fatto, chiamo la funzione qui sotto molte volte per fare diverse fusioni.

public void mergeSets(ArrayList<String> operations, ArrayList<LinkedHashSet<String>> setOfStrings) {
    String toMerge = operations.get(1);
    String fromMerge = operations.get(2);
    boolean enteredFirstToMerge = false;
    boolean enteredFirstFromMerge = false;
    // Temporary LinkedHashSet reference used to merge two sets.
    LinkedHashSet<String> subSetToMerge = null;
    LinkedHashSet<String> subSetFromMerge = null;
    for (Iterator<LinkedHashSet<String>> iterator = setOfStrings.iterator();
            iterator.hasNext(); ) {
        LinkedHashSet<String> subSet = iterator.next();
        if (subSet.contains(toMerge) && subSet.contains(fromMerge))
            break;
        else {
            if (subSet.contains(toMerge) && !enteredFirstToMerge) {
                enteredFirstToMerge = true;
                subSetToMerge = subSet;
                iterator.remove();
            } else if (subSet.contains(fromMerge) && !enteredFirstFromMerge) {
                enteredFirstFromMerge = true;
                subSetFromMerge = subSet;
            }
        }
        if (enteredFirstFromMerge && enteredFirstToMerge)
            break;
        }
        if (enteredFirstFromMerge && enteredFirstToMerge) {
            subSetFromMerge.addAll(subSetToMerge);
        }
    }

Spiegazione:

Ad esempio, se ho come operazione merge foo bar , devo fare questi passaggi:

  • Prima di tutto, devo trovare dove si trovano foo e bar :

    • Dentro setOfStrings , posso avere questa situazione:

          position x : [bar, tree, hotel]
          ...
          position y : [foo, lemon, coffee] 
      

Quando li trovo, devo combine the set which contains foo with the set that contains bar in questo modo:

            position x : {bar tree hotel foo lemon coffee}
            ...
            position y : {} -> deleted from the arrayList

Questa funzione prende come parametri un arrayList di operazioni e un arrayList<LinkedHashSet<String>> :

Per il ArrayList di operations , ottengo sempre una posizione specifica:

  • operations.get(1) fa riferimento al set da unire (foo in questo esempio)
  • operations.get(2) fa riferimento al set in cui aggiungere il set foo (barra in questo esempio)

Con questo ciclo for, I iterate su ArrayList per cercare i set da, for (Iterator<LinkedHashSet<String>> iterator = setOfStrings.iterator(); iterator.hasNext(); )

Questa dichiarazione if controlla se l'iteratore si trova nel set specifico:

if (subSet.contains(toMerge) && !enteredFirstToMerge) {
    enteredFirstToMerge = true;
    subSetToMerge = subSet;
    iterator.remove();
} else if (subSet.contains(fromMerge) && !enteredFirstFromMerge) {
    enteredFirstFromMerge = true;
    subSetFromMerge = subSet;
}

La mia domanda è: potrei avere collisioni con questo tipo di algoritmo che ho implementato?

Se non la complessità temporale è solo O (n) - > la dimensione di arrayList.

    
posta OiRc 19.06.2014 - 17:40
fonte

1 risposta

1

La tua domanda principale è se potresti avere una collisione, ma è prima importante determinare dove potresti avere una collisione. Un ArrayList non ha collisioni perché è un elenco ordinato in cima a un array. La tua preoccupazione sarebbe maggiore su quanto spesso deve estendere la capacità dell'array sottostante. Lo sentiresti quando leggi solo l'input, però.

Dove potresti avere collisioni si trova in LinkedHashSet s. Poiché le collisioni di hash sono una funzione della capacità del negozio di hash sottostante, dell'algoritmo di hashing e dei dati stessi, c'è sempre una possibilità per una collisione e non c'è modo di sapere se ne avrete una tranne che per eseguire tutti dei tuoi dati attraverso. Alla fine, poiché si tratta di stringhe diverse e l'algoritmo di hashing per le stringhe incorporato in Java è piuttosto efficace, il fattore rimanente per determinare il numero di collisioni che si potrebbero potenzialmente avere è la capacità del negozio di hash sottostante.

Un LinkedHashSet predefinito ha una capacità iniziale di 16 e un fattore di carico di 0,75. Ciò significa che una volta che ci sono 12 elementi in LinkedHashSet , raddoppierà la sua capacità di ospitare più elementi. Se possiamo assumere che gli hash siano equamente distribuiti, una maggiore capacità significa una minore probabilità di collisione. Il ridimensionamento di LinkedHashSet è in realtà l'attività più costosa. Per ridimensionare l'archivio hash sottostante, è necessario eseguire iterazioni sugli elementi correnti, eseguirne il rehash e reinserirli nell'archivio hash più grande. La migliore strategia per evitare il collisione e delle collisioni consiste nell'assegnare un grande% diLinkedHashSet s quando le istanziate. Ciò che "grande" dipende dalla quantità di dati che si sta inserendo e dalla quantità di dati che verranno combinati. Se si conoscono le dimensioni e la forma generale dei dati prima della mano, si potrebbe provare a stimare una dimensione iniziale adeguata abbastanza grande da contenere il set più grande (dimensione necessaria / 0,75 per tenere conto del fattore di carico). Questo in sostanza diventa il compromesso tra tempo vecchio e spazio, poiché dimensioni iniziali più grandi significano più consumo di memoria.

Altri suggerimenti generali

StringTokenizer è deprecato e string.split(" ") è generalmente raccomandato al suo posto. Tuttavia, dal momento che stai ricevendo il tuo input dalla console, ti suggerisco di eliminare completamente il lettore bufferizzato e di avvolgere System.in in Scanner . Scanner ha un metodo next() che recupera il token successivo, assegnando implicitamente tokenizzazione dell'input per te con meno sforzo.

Inoltre, mentre stai utilizzando ArrayList e LinkedHashSet per le loro caratteristiche di performance, è probabilmente ancora meglio fare affidamento sulle loro interfacce ( List e Set rispettivamente) rispetto a quelle specifiche implementazioni.

In terzo luogo, invece di scrivere tutto il codice per ottenere un iteratore e quindi eseguirne il looping, puoi usare il costrutto for-each per eseguire il loop su qualsiasi Iterable .

In quarto luogo, non hai bisogno di flag booleani quando puoi facilmente ottenere le stesse informazioni da un controllo Null. Possiamo eliminare due variabili immediatamente.

Ecco una versione leggermente riscritta che incorpora questi suggerimenti:

Main.java

import java.io.*;
import java.util.*;

public class Main
{
    public static void main(String[] args) throws IOException {
        Scanner input = new Scanner(System.in);
        List<Set<String>> setOfStrings = new ArrayList<>();

        while (input.hasNext()) {
            Set<String> tokenSet = new LinkedHashSet<>();
            tokenSet.add(input.next());
            setOfStrings.add(tokenSet);
        }

        SetMerger sm = new SetMerger();
        sm.mergeSets("foo", "bar", setOfStrings);
        sm.mergeSets("bar", "baz", setOfStrings);
        System.out.println(setOfStrings.toString());
    }
}

SetMerger.java

import java.util.*;

public class SetMerger
{
    public void mergeSets(String toMerge, String fromMerge, List<Set<String>> stringSets) {
        Set<String> setToMerge = null, targetSet = null;

        for (Set<String> subset : stringSets) {
            if(subset.contains(toMerge)) {
                if(subset.contains(fromMerge)) {
                    return;
                }

                setToMerge = subset;
                break;
            }
        }

        for (Set<String> subset : stringSets) {
            if(subset.contains(fromMerge)) {
                targetSet = subset;
                break;
            }
        }

        if(setToMerge != null && targetSet != null) {
            targetSet.addAll(setToMerge);
            stringSets.remove(setToMerge);
        }
    }
}
    
risposta data 28.06.2014 - 07:36
fonte

Leggi altre domande sui tag