Algoritmo unico completamente stabile e in posto in O (n)

4

Esiste un algoritmo che, dato un array ordinato, scambia tutti gli primi elementi unici all'inizio della matrice e i duplicati fino alla fine, pur rimanendo stabile per entrambi il subarray univoco e il sottoarray duplicato e che viene eseguito in O(n) swaps (e preferibilmente in un passaggio)? Dovrebbe restituire la lunghezza della parte univoca dell'array.

Input / risultato di esempio contrato (usando python perché è facile da leggere):

>>> A = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]   # already sorted
>>> r = Unique(A)
>>> r
5
>>> A
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
>>> A[:r] # the sorted portion
[1, 2, 3, 4, 5]
>>> A[r:] # the duplicates portion
[1, 2, 3, 4, 5]

Stabilità significa che anche se due o più chiavi sono uguali, l'ordine delle chiavi nel risultato persiste. Per esempio. se A[i] == A[j] == A[k] and i < j < k nell'array originale, tutte queste proprietà rimangono nell'intero array dopo aver eseguito l'algoritmo Unique. (Anche se A[i] potrebbe essere nel subarray univoco e entrambi A[j] e% A[k] si trovano nel sottoarray dei duplicati.)

Il mio tentativo fallito è un one-pass che traccia l'attuale elemento univoco mentre itera attraverso l'array. Il prossimo elemento univoco viene scambiato con l'elemento dopo la fine del subarray unico corrente:

def Unique1(A):
    if len(A) <= 1:
        return 0
    i = 0
    for j in range(1, len(A)):
        if A[i] < A[j]:
            i += 1
            if i < j:
                A[i], A[j] = A[j], A[i]
    return i + 1

Scambia i primi elementi univoci di un array ordinato all'inizio dell'array, viene eseguito in un unico passaggio (quindi è O(n) ) ed è stabile per il sottoarray univoco, ma questo fa non soddisfa i requisiti perché rimescola il subarray dei duplicati:

>>> A = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
>>> r = Unique1(A)
>>> A[r:]          # duplicates part
[3, 2, 4, 1, 5]    # not sorted

Mentre le modifiche apportate dall'algoritmo al subarray univoco sono alquanto ovvi, non posso "vedere" cosa sta succedendo al subarray dei duplicati, ma ho la sensazione che potrebbe essere invertito se avessimo saputo o memorizzato più informazioni sul duplicati.

Nota, riordinare solo il sottoarray duplicato è un non-starter perché 1: è O(n log n) non O(n) e 2: interrompe la stabilità.

Posso anche pensare ad un altro algoritmo che scambia elementi unici trovati di recente fino alla posizione successiva. Pur soddisfacendo le altre condizioni, avrebbe O(n^2) swap.

È possibile un algoritmo così unico? E se no, perché no?

    
posta infogulch 31.12.2013 - 20:04
fonte

2 risposte

6

Non ne sono sicuro, ma penso che sia necessario eliminare il "preferibilmente un passaggio" dalla descrizione. Puoi farlo in tre passaggi utilizzando il seguente algoritmo:

  • Fai un passaggio sull'array, contando il numero di oggetti unici come unique_count
  • Assegna un nuovo array temporaneo con dimensione len(A) - unique_count
  • Fai una seconda passata sull'array, spostando ogni prima istanza di un oggetto nella parte anteriore dell'array mentre copi la seconda istanza sul primo spazio libero nell'array temporaneo
  • Passa sopra l'array temporaneo, copiando ciascun elemento nella sua posizione finale nell'array di input

Questo è ancora O (n), anche se con un fattore costante leggermente maggiore per articolo rispetto all'implementazione originale.

    
risposta data 31.12.2013 - 20:16
fonte
2

Se la tua unica ragione per il requisito "sul posto" è che gli elementi occupano molto spazio, ma puoi risparmiare O (n) spazio sul lato se i fattori costanti sono basso, allora questo può sicuramente essere fatto. Ti avverto, tuttavia, che il passaggio uno non è sicuramente possibile, e, in pratica, il numero di passaggi necessari sarà molto grande.

Inoltre, conosco solo un modo per fare questo in modo probabilistico , il che significa che alla fine è possibile che c'è qualche valore univoco ancora da qualche parte nella lista che non ha essere stato estratto Quindi, dovresti considerare questo algoritmo come O (n / e) dove e è la probabilità che qualche oggetto unico non sia stato estratto.

Per fare questo devi essere in grado di fare due cose:

  1. Identifica la prima occorrenza di ciascun elemento.
  2. Partiziona l'elenco in modo che tutte quelle prime occorrenze siano all'inizio.

Entrambi richiedono passaggi da eseguire in tempo lineare.

Il primo che possiamo realizzare con un filtro Bloom. Questo sarebbe un array di bit O (k) con dove k è il numero di elementi univoci nell'elenco. Leggi l'articolo di Wikipedia su filtri di fioritura per avere un'idea di come influisce la dimensione dell'array di bit la probabilità di collisioni di hash e come scegliere il numero ottimale di funzioni hash. Per poter ridurre la probabilità di errore a qualsiasi livello desiderato, le funzioni di hash per il filtro dovrebbero essere randomizzate.

Oltre al filtro, avremo anche bisogno di un altro bitgector O (n).

Dovrebbe essere chiaro ora come useremo queste cose:

  1. Ripeti un numero costante di volte preselezionato:
    1. Passa attraverso l'elenco, controllando ogni elemento con il filtro Bloom prima di aggiungerlo. Se il controllo rivela che questo elemento non è mai stato visto prima, segna la sua posizione impostando il bit sullo stesso indice nel secondo bitvector.
    2. Cancella il filtro Bloom, randomizza le sue funzioni hash.

A questo punto, abbiamo una matrice di bit O (n) i cui contrassegni le posizioni delle prime occorrenze dei valori unici nella tua lista con l'alta probabilità desiderata, quindi è sulla seconda fase: partizionare in modo stabile quegli elementi all'inizio della lista. Per questo dovremo usare magic reale . Sto parlando di algoritmi così complicati da richiedere più di un singolo documento per descrivere tutte le sue parti. Non so nemmeno dove trovare un'implementazione! Ma immagino che questo sia il tuo lavoro.

Sì, stiamo andando a partizionare stabilmente i nostri bitvector in modo che tutti siano all'inizio nel tempo lineare. E ogni volta che dobbiamo eseguire uno scambio per farlo, eseguiremo uno scambio corrispondente agli stessi indici dell'array reale. E per fare questa magica partizione lineare, stabile, sul posto ...

Partizione dello spazio minimo stabile in lineare Time - Katajainen and Pasanen, 1992

Non chiedermi come funzioni quella magia. Non ho nemmeno accesso ai documenti sottostanti.

    
risposta data 10.03.2017 - 06:24
fonte

Leggi altre domande sui tag