Algoritmo per unire due array ordinati con un numero minimo di confronti


24

Dato sono due matrici ordinate a , b di tipo T con dimensione n e m . Sto cercando un algoritmo che unisce i due array in un nuovo array (di dimensioni massime n + m).

Se hai un'operazione di confronto economica, è abbastanza semplice. Basta prendere dall'array con il primo elemento più basso fino a quando uno o entrambi gli array sono completamente attraversati, quindi aggiungere gli elementi rimanenti. Qualcosa del genere /programming/5958169/how-to-merge-two-sorted-arrays-into-a-sorted-array

Tuttavia, la situazione cambia quando si confrontano due elementi è molto più costoso rispetto alla copia di un elemento dall'array di origine all'array di destinazione . Ad esempio, potresti avere un array di grandi numeri interi di precisione arbitraria, o stringhe, in cui un confronto può essere piuttosto costoso. Supponi solo che la creazione di array e la copia di elementi sia gratuita e l'unica cosa che costa è il confronto di elementi.

In questo caso, si desidera unire i due array con un numero minimo di confronti tra elementi . Ecco alcuni esempi in cui dovresti essere in grado di fare molto meglio del semplice algoritmo di unione:

a = [1,2,3,4, ... 1000]
b = [1001,1002,1003,1004, ... 2000]

O

a = [1,2,3,4, ... 1000]
b = [0,100,200, ... 1000]

Ci sono alcuni casi in cui il semplice algoritmo di unione sarà ottimale, come

a = [1,3,5,7,9,....,999]
b = [2,4,6,8,10,....,1000]

Quindi l'algoritmo dovrebbe idealmente degradarsi con grazia ed eseguire un massimo di n + m-1 confronti nel caso in cui le matrici siano interfogliate, o almeno non essere significativamente peggiori.

Una cosa che dovrebbe fare abbastanza bene per gli elenchi con una grande differenza di dimensioni sarebbe quella di usare la ricerca binaria per inserire gli elementi dell'array più piccolo nell'array più grande. Ma ciò non si degrada con garbo nel caso in cui entrambi gli elenchi abbiano le stesse dimensioni e interfogliati.

L'unica cosa disponibile per gli elementi è una funzione (totale) di ordinamento, quindi non è possibile alcuno schema che renda i confronti più economici.

Qualche idea?

Ho escogitato questo pezzo alla Scala . Credo che sia ottimale per quanto riguarda il numero di confronti, ma va oltre la mia capacità di dimostrarlo. Almeno è molto più semplice delle cose che ho trovato in letteratura.

E dal post originale, ho scritto un post sul blog su come funziona.


2
Non c'è modo di fare meno confronti rispetto al "semplice algoritmo di unione". Puoi provare a gestire i casi limite come il primo che menzioni, ma questo peggiorerà il caso medio.
Mephy,

5
@Mephy: illuminaci e dacci una prova formale, per favore. O se non puoi, considera di eliminare (o almeno perfezionare) il tuo commento.
Doc Brown,

4
@DocBrown se avessi una prova formale, darei una risposta, non un commento. Ad ogni modo, è un problema lineare abbastanza ovvio, perché cercare di trovare una soluzione migliore di quella lineare richiederebbe almeno un tempo lineare.
Mephy,

4
@Mephy: ti suggerisco di dedicare del tempo a leggere la risposta di seguito e pensare due volte a ciò che hai scritto.
Doc Brown,

4
@Mephy La maggior parte delle cose ovvie ("non puoi fare moltiplicazioni in meno di O (n ^ 2)", "se cambio quale porta ho scelto non migliorerò le mie possibilità di vincere un prezzo" , "puoi ordinare in meno di O (n log n) ", ..) sono sbagliati. Ad esempio, l'utilizzo di un approccio di ricerca binaria nell'elenco più breve dovrebbe migliorare il caso medio.
Voo,

Risposte:


31

Il normale algoritmo di ordinamento di tipo merge - Unisci passo con normalmente applica n + m -1 confronti, in cui un elenco ha dimensioni n e l'altro elenco ha dimensioni m. L'uso di questo algoritmo è l'approccio più semplice per combinare due elenchi ordinati.

Se i confronti sono troppo costosi, potresti fare due cose: o minimizzi il numero di confronti o minimizzi il costo dei confronti.

Concentriamoci sulla minimizzazione del costo del confronto. Tu e solo tu puoi decidere se i dati che state confrontando possono essere quantizzati o meno. Se puoi quantizzarli, che è una forma di implementazione di un metodo hash, che mantiene l'ordine. Ad esempio, se i tuoi dati vengono confrontati per nome, quindi il primo tname, ... puoi prendere il primo in Chars del nome "Klaehn, Ruediger" e ridurre / quantizzare il tuo elemento di dati in "Kl.Ru", se lo confronti a "Packer, il" si conserva l'ordine "Pa.Th" - ora è possibile applicare un algoritmo di confronto più economico, confrontando i valori ridotti. Ma se trovi un altro "Kl.Ru", ora hai un valore prossimo e potresti ora passare a un approccio più costoso confrontando questi elementi.

Se riesci a estrarre questo valore quantizzato dai tuoi dati, più velocemente del confronto, questa è la prima cosa da fare, confronta prima il valore quantizzato o con hash. Tieni presente che questo valore deve essere calcolato una sola volta, quindi puoi calcolarlo durante la creazione dell'elemento dati.

Ho anche menzionato un altro modo, per ridurre al minimo i tuoi confronti.

Ho dato un'occhiata al libro classico TAOCP- Volume 3-Ordinamento e ricerca, (pp.197-207, sezione 5.3.2) che contiene 10 pagine complete su questo argomento. Ho trovato due riferimenti ad algoritmi che sono più veloci dei confronti n + m-1.

In primo luogo c'è l'algoritmo di fusione di Hwang-Lin e il secondo un miglioramento di Glenn K Manacher - entrambi sono citati da TAOCP e un algoritmo di Christen, che si avvicina al limite inferiore dei confronti necessari, a condizioni speciali sulla lunghezza n e m delle liste.

L'algoritmo di Manacher è stato presentato nel Journal of ACM Vol. 26 Numero 3 alle pagine 434-440: "Miglioramenti significativi dell'algoritmo di fusione" Hwan-Lin ". l'elenco con m elementi e l'elenco con n elementi possono avere una lunghezza diversa, ma devono anche essere ordinati in base al numero di elementi che contengono m <= n

L'algoritmo di Hwang-Lin rompe gli elenchi per unirli, a parte gli elenchi più piccoli e ordina gli elenchi confrontando il primo elemento di ciascun elenco secondario e per decidere se alcuni elementi dell'elenco secondario devono essere confrontati o meno. Se il primo elenco è più piccolo del secondo elenco, allora la possibilità è alta, che gli elementi consecutivi dell'elenco più lungo possano essere trasferiti nell'elenco risultante senza confronto. Se il primo elemento del piccolo ist è maggiore del primo elemento dell'elenco più grande diviso, tutti gli elementi davanti all'elenco secondario possono essere copiati senza confronto.

Analisi dei casi medi dell'algoritmo di fusione di Hwang e Lin (Vega, Frieze, Santha) nella Sezione 2 è possibile trovare uno pseudocodice dell'algoritmo HL. Che è molto meglio della mia descrizione. E puoi capire perché ci sono meno confronti: l'algoritmo utilizza una ricerca binaria per trovare l'indice, dove inserire l'elemento dall'elenco più breve.

Se gli elenchi non sono interlacciati come nel tuo ultimo esempio, nella maggior parte dei casi dovresti avere un elenco rimanente più piccolo e un elenco più grande rimanente. Questo è quando l'algoritmo HL inizia a funzionare meglio.


Grazie, per il tuo commento su questo: ho controllato la mia risposta e ho scoperto che Knuth ha trascorso 10 pagine complete su questo argomento. E poi ho preso The JACM dalla mia libreria e ho guardato più avanti. Migliorerò la mia risposta. - Non è necessario il downvoting. L'algoritmo hash (quantizzatore) è un'idea semplice, che può essere applicata a molti set di dati, ma solo il Guy che ha chiesto, è l'unico a decidere se è applicabile ai suoi dati o meno.
thepacker il

4
Dopo aver migliorato la tua risposta, tutti coloro che hanno votato per difetto avranno la possibilità di votarti di nuovo ;-)
Doc Brown,

+1 per notare che se le dimensioni sono molto diverse, l'unione standard non è ottimale.
Florian F,

1

Supponiamo che i due array abbiano elementi N e M, N ≥ M, e tutti gli elementi sono diversi.

Se l'array ordinato contiene un elemento x di N seguito da un elemento y di M o viceversa, allora xey devono essere stati confrontati, altrimenti non sapremmo in quale ordine appartengano. (Non può esserci una catena di altri elementi dire a, b, c dove sappiamo che x <a <b <c <y, ad esempio, perché non ci sono elementi tra x e y. Quindi xey devono essere stati confrontati direttamente.

Se N> M, allora è possibile avere un array in cui ogni elemento di M è sia preceduto che seguito da un elemento di N, il che significa che sono necessari almeno 2M confronti - anche se si utilizza un algoritmo di ordinamento non deterministico che può rendere una supposizione perfetta quali numeri confrontare. (Che cosa significa: supponi di avere N grande, M = 1. La ricerca binaria prende O (log2 N) passi; un algoritmo non deterministico indovinerebbe tra quali due elementi appartiene l'elemento del secondo array e fare due confronti con confermare l'ipotesi).

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.