Python ha un elenco ordinato?


128

Con questo intendo una struttura con:

  • O (log n) complessità per le x.push()operazioni
  • O (log n) complessità per trovare un elemento
  • O (n) complessità da calcolare list(x)che verrà ordinata

Ho anche avuto una questione connessa sulle prestazioni di list(...).insert(...)che ora è qui .


memcpyè ancora un'operazione O (n) . Non sono sicuro di come Python implementa esattamente gli elenchi , ma la mia scommessa sarebbe che siano memorizzati in una memoria contigua (certamente non come un elenco collegato). In tal caso, l'inserimento con bisectcui si dimostra avrà complessità O (n) .
Stephan202,

2
Purtroppo non fuori dagli schemi. Ma la biblioteca dei contenitori ordinati di Grant Jenk è eccellente. stackoverflow.com/a/22616929/284795
Colonnello Panic,

Risposte:


52

L'elenco Python standard non è ordinato in nessuna forma. Il modulo heapq standard può essere utilizzato per aggiungere in O (registro n) a un elenco esistente e rimuovere quello più piccolo in O (registro n), ma non è un elenco ordinato nella definizione.

Esistono varie implementazioni di alberi bilanciati per Python che soddisfano le tue esigenze, ad esempio rbtree , RBTree o pyavl .


1
+1 per rbtree, funziona molto bene (ma contiene codice nativo; non puro pitone, forse non così facile da distribuire)
Will

12
sortcontainers è pure-Python e fast-as-C (come rbtree) con un confronto delle prestazioni.
GrantJ

"non è un elenco ordinato nella tua definizione." Come mai?
Colonnello Panic,

4
heapq consente solo di trovare l'elemento più piccolo; l'OP stava chiedendo una struttura che potesse trovare qualsiasi elemento in O (log n), che non sono heap.
Martin v. Löwis,

70

C'è un motivo particolare per i tuoi requisiti big-O? O vuoi solo che sia veloce? Il modulo dei contenitori ordinati è puro-Python e veloce (come nelle implementazioni di tipo C come blist e rbtree).

Il confronto delle prestazioni mostra i benchmark più velocemente o alla pari con il tipo di elenco ordinato di Blist. Si noti inoltre che rbtree, RBTree e PyAVL forniscono i tipi di dict e set ordinati ma non hanno un tipo di elenco ordinato.

Se le prestazioni sono un requisito, ricordati sempre di eseguire il benchmark. Un modulo che conferma l'affermazione di essere veloce con la notazione Big-O dovrebbe essere sospetto fino a quando non mostra anche confronti di benchmark.

Dichiarazione di non responsabilità: sono l'autore del modulo di classificazione dei contenitori Python.


Installazione:

pip install sortedcontainers

Uso:

>>> from sortedcontainers import SortedList
>>> l = SortedList()
>>> l.update([0, 4, 1, 3, 2])
>>> l.index(3)
3
>>> l.add(5)
>>> l[-1]
5

4
In effetti ho confrontato i container ordinati con bisect: 0.0845024989976for SortedList.add () vs 0.596589182518for bisect.insort (), quindi una differenza di 7 volte in termini di velocità! E mi aspetto che il divario di velocità aumenti con la lunghezza dell'elenco poiché l'ordinamento di inserimento dei contenitori ordinati funziona in O (log n) mentre bisect.insort () in O (n).
gaboroso

1
@gaborous perché bisect utilizza ancora un elenco, quindi l'inserimento rimaneO(n)
njzk2

34

Anche se non ho ancora verificato le velocità "big O" delle operazioni di base dell'elenco Python, bisectprobabilmente vale la pena menzionare anche il modulo standard in questo contesto:

import bisect
L = [0, 100]

bisect.insort(L, 50)
bisect.insort(L, 20)
bisect.insort(L, 21)

print L
## [0, 20, 21, 50, 100]

i = bisect.bisect(L, 20)
print L[i-1], L[i]
## 20, 21

PS. Ah, scusa, bisectè menzionato nella domanda di riferimento. Tuttavia, penso che non ci sarà molto danno se queste informazioni saranno qui)

PPS. E gli elenchi di CPython sono in realtà array (non, diciamo, skiplist o ecc.). Beh, immagino che debbano essere qualcosa di semplice, ma per quanto mi riguarda, il nome è un po 'fuorviante.


Quindi, se non sbaglio, la velocità bisect / list sarebbe probabilmente:

  • per un push (): O (n) per il caso peggiore;
  • per una ricerca: se consideriamo la velocità dell'indicizzazione dell'array come O (1), la ricerca dovrebbe essere un'operazione O (log (n));
  • per la creazione dell'elenco: O (n) dovrebbe essere la velocità della copia dell'elenco, altrimenti è O (1) per lo stesso elenco)

Agg. A seguito di una discussione nei commenti, vorrei collegare qui queste domande SO: come viene implementato l'elenco Python e qual è la complessità di runtime delle funzioni dell'elenco python


push () dovrebbe essere in O (log n) poiché l'elenco è già ordinato.
estani,

1
forse avrei dovuto dire "per un inserto op" . comunque, è stato circa un anno fa, quindi ora posso facilmente mescolare le cose o perdere qualcosa
ジ ョ ー ジ

È sempre possibile inserire un valore in un elenco ordinato in O (registro n), consultare la ricerca binaria. push () è definito come un'operazione di inserimento.
estani,

2
Vero. Ma mentre trovare la posizione di inserimento richiederebbe effettivamente O (log n) ops, l'inserimento effettivo (cioè l'aggiunta dell'elemento alla struttura dei dati) probabilmente dipende da quella struttura (si pensi di inserire un elemento in un array ordinato). E poiché le liste di Python sono in realtà matrici , questo può richiedere O (n). A causa del limite di dimensioni per i commenti, collegherò due domande SO correlate dal testo della risposta (vedi sopra).
ジ ョ ー ジ

Buona discussione. Non ero a conoscenza dell'elenco in cui venivano gestiti come array in Python.
estani,

7
import bisect

class sortedlist(list):
    '''just a list but with an insort (insert into sorted position)'''
    def insort(self, x):
        bisect.insort(self, x)

l'inserto implicito () in bisect.insort () è O (n)
j314erre,

6

Sebbene non fornisca (ancora) una funzione di ricerca personalizzata, il heapqmodulo può soddisfare le tue esigenze. Implementa una coda heap utilizzando un elenco regolare. Dovresti scrivere il tuo test di appartenenza efficiente che utilizza la struttura interna della coda (che può essere eseguita in O (log n) , direi ...). C'è un aspetto negativo: l'estrazione di un elenco ordinato ha complessità O (n log n) .


È bello ma difficile da dividere in due.
ilya n.

3
Come può esserci un test di appartenenza O (log n) in un heap? Se stai cercando un valore x, puoi smettere di guardare un ramo se trovi qualcosa di più grande di x, ma per un valore casuale di x è probabile che il 50% sia su una foglia e probabilmente non puoi potare molto.
mercati dal

1

Vorrei usare i moduli biscecto sortedcontainers. Non ho molta esperienza, ma penso che il heapqmodulo funzioni. Contiene aHeap Queue


0

Potrebbe non essere difficile implementare il tuo elenco su Python. Di seguito è una prova di concetto:

import bisect

class sortlist:
    def __init__(self, list):
        self.list = list
        self.sort()
    def sort(self):
        l = []
        for i in range(len(self.list)):
            bisect.insort(l, self.list[i])
        self.list = l
        self.len = i
    def insert(self, value):
        bisect.insort(self.list, value)
        self.len += 1
    def show(self):
        print self.list
    def search(self,value):
        left = bisect.bisect_left(self.list, value)
        if abs(self.list[min([left,self.len-1])] - value) >= abs(self.list[left-1] - value):
            return self.list[left-1]
        else:
            return self.list[left]

list = [101, 3, 10, 14, 23, 86, 44, 45, 45, 50, 66, 95, 17, 77, 79, 84, 85, 91, 73]
slist = sortlist(list)
slist.show()
slist.insert(99)
slist.show()
print slist.search(100000000)
print slist.search(0)
print slist.search(56.7)

========= Risultati ============

[3, 10, 14, 17, 23, 44, 45, 45, 50, 66, 73, 77, 79, 84, 85, 86, 91, 95, 101]

[3, 10, 14, 17, 23, 44, 45, 45, 50, 66, 73, 77, 79, 84, 85, 86, 91, 95, 99, 101]

101

3

50

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.