Domanda
Ho una classe che rappresenta un intervallo. Questa classe ha due proprietà "start" e "fine" di un tipo comparabile. Ora sto cercando un algoritmo efficiente per prendere l'unione di un insieme di tali intervalli.
Grazie in anticipo.
Soluzione
Ordinali in base a uno dei termini (inizia, ad esempio), quindi controlla le sovrapposizioni con il vicino (a destra) mentre scorri l'elenco.
class tp():
def __repr__(self):
return '(%d,%d)' % (self.start, self.end)
def __init__(self,start,end):
self.start=start
self.end=end
s=[tp(5,10),tp(7,8),tp(0,5)]
s.sort(key=lambda self: self.start)
y=[ s[0] ]
for x in s[1:]:
if y[-1].end < x.start:
y.append(x)
elif y[-1].end == x.start:
y[-1].end = x.end
Altri suggerimenti
Utilizza l'algoritmo sweep line . Fondamentalmente, ordinate tutti i valori in un elenco (mantenendo sia l'inizio che la fine dell'intervallo insieme a ciascun elemento). Questa operazione è O (n log n). Quindi esegui il ciclo in un unico passaggio lungo gli elementi ordinati e calcoli gli intervalli O (n).
O (n registro n) + O (n) = O (n registro n)
Si scopre che questo problema è stato risolto, molte volte - a vari livelli di fantasia, andando sotto la nomenclatura (s): http://en.wikipedia.org/wiki/Interval_tree , http: //en.wikipedia.org/wiki/Segment_tree e anche 'RangeTree'
(poiché la domanda di OP comporta un numero elevato di intervalli importanti per queste strutture di dati)
in termini di scelta personale della selezione della libreria Python:
-
Dai test, sto scoprendo che ciò che più inchioda in termini di funzionalità complete e corrente di pitone (non marcio): le classi 'Interval' e 'Union' di SymPy, vedi: http://sympystats.wordpress.com/2012/03/30/simplifying-sets/
-
Un'altra bella scelta, una prestazione più alta ma un'opzione meno ricca di funzionalità (ad es. non ha funzionato sulla rimozione della gamma in virgola mobile): https://pypi.python.org/pypi/Banyan
Infine: cerca in giro su SO stesso, sotto uno qualsiasi di IntervalTree, SegmentTree, RangeTree e troverai risposte / hook più in abbondanza
L'algoritmo di geocar fallisce quando:
s=[tp(0,1),tp(0,3)]
Non ne sono molto sicuro, ma penso che questo sia il modo corretto:
class tp():
def __repr__(self):
return '(%.2f,%.2f)' % (self.start, self.end)
def __init__(self,start,end):
self.start=start
self.end=end
s=[tp(0,1),tp(0,3),tp(4,5)]
s.sort(key=lambda self: self.start)
print s
y=[ s[0] ]
for x in s[1:]:
if y[-1].end < x.start:
y.append(x)
elif y[-1].end == x.start:
y[-1].end = x.end
if x.end > y[-1].end:
y[-1].end = x.end
print y
L'ho anche implementato per la sottrazione:
#subtraction
z=tp(1.5,5) #interval to be subtracted
s=[tp(0,1),tp(0,3), tp(3,4),tp(4,6)]
s.sort(key=lambda self: self.start)
print s
for x in s[:]:
if z.end < x.start:
break
elif z.start < x.start and z.end > x.start and z.end < x.end:
x.start=z.end
elif z.start < x.start and z.end > x.end:
s.remove(x)
elif z.start > x.start and z.end < x.end:
s.append(tp(x.start,z.start))
s.append(tp(z.end,x.end))
s.remove(x)
elif z.start > x.start and z.start < x.end and z.end > x.end:
x.end=z.start
elif z.start > x.end:
continue
print s
Ordina tutti i punti. Quindi scorrere l'elenco incrementando un contatore per " start " punti e decrementandolo per "fine" punti. Se il contatore raggiunge 0, allora è davvero un endpoint di uno degli intervalli nell'unione.
Il contatore non diventerà mai negativo e raggiungerà 0 alla fine dell'elenco.
Per trovare il totale dell'unione degli intervalli in c ++
#include <iostream>
#include <algorithm>
struct interval
{
int m_start;
int m_end;
};
int main()
{
interval arr[] = { { 9, 10 }, { 5, 9 }, { 3, 4 }, { 8, 11 } };
std::sort(
arr,
arr + sizeof(arr) / sizeof(interval),
[](const auto& i, const auto& j) { return i.m_start < j.m_start; });
int total = 0;
auto current = arr[0];
for (const auto& i : arr)
{
if (i.m_start >= current.m_end)
{
total += current.m_end - current.m_start;
current = i;
}
else if (i.m_end > current.m_end)
{
current.m_end = i.m_end;
}
}
total += current.m_end - current.m_start;
std::cout << total << std::endl;
}