La complessità temporale della stringa iterativa è in realtà O (n ^ 2) o O (n)?


89

Sto lavorando a un problema fuori dal CTCI.

Il terzo problema del capitolo 1 ti fa prendere una stringa come

'Mr John Smith '

e ti chiede di sostituire gli spazi intermedi con %20:

'Mr%20John%20Smith'

L'autore offre questa soluzione in Python, chiamandola O (n):

def urlify(string, length):
    '''function replaces single spaces with %20 and removes trailing spaces'''
    counter = 0
    output = ''
    for char in string:
        counter += 1
        if counter > length:
            return output
        elif char == ' ':
            output = output + '%20'
        elif char != ' ':
            output = output + char
    return output

La mia domanda:

Capisco che questo è O (n) in termini di scansione attraverso la stringa effettiva da sinistra a destra. Ma le stringhe in Python non sono immutabili? Se ho una stringa e aggiungo un'altra stringa con l' +operatore, non alloca lo spazio necessario, copia sull'originale e quindi copia sulla stringa aggiunta?

Se ho una raccolta di nstringhe ciascuna di lunghezza 1, allora ciò richiede:

1 + 2 + 3 + 4 + 5 + ... + n = n(n+1)/2

o O (n ^ 2) volta , sì? O mi sbaglio nel modo in cui Python gestisce l'aggiunta?

In alternativa, se tu fossi disposto a insegnarmi a pescare: come potrei scoprirlo da solo? Non ho avuto successo nei miei tentativi di Google una fonte ufficiale. Ho trovato https://wiki.python.org/moin/TimeComplexity ma questo non ha nulla sulle stringhe.


17
Qualcuno dovrebbe parlare all'autore diurllib.urlencode
wim

11
@wim È pensato per essere un problema pratico su array e stringhe
user5622964

3
Lo scopo del libro è insegnare le domande dell'intervista, che comunemente ti chiedono di reinventare la ruota per vedere il processo di pensiero dell'intervistato.
James Wierzba

1
Dato che è Python, penso che fare un rtrime replacesarebbe più preferito e nel campo da baseball di O(n). Copiare le stringhe sembra il modo meno efficiente.
OneCricketeer

2
@RNar Puoi spiegare come una copia può richiedere tempo costante?
James Wierzba

Risposte:


83

In CPython, l'implementazione standard di Python, c'è un dettaglio di implementazione che lo rende solitamente O (n), implementato nel codice che il ciclo di valutazione del bytecode richiede +o +=con due operandi stringa . Se Python rileva che l'argomento a sinistra non ha altri riferimenti, chiama reallocper tentare di evitare una copia ridimensionando la stringa in posizione. Questo non è qualcosa su cui dovresti mai fare affidamento, perché è un dettaglio di implementazione e perché se reallocfinisce per dover spostare la stringa frequentemente, le prestazioni degradano comunque a O (n ^ 2).

Senza gli strani dettagli di implementazione, l'algoritmo è O (n ^ 2) a causa della quantità quadratica di copie coinvolte. Un codice come questo avrebbe senso solo in un linguaggio con stringhe mutabili, come C ++, e anche in C ++ che vorresti usare +=.


2
Sto guardando il codice che hai collegato ... sembra che una grande parte di quel codice stia ripulendo / rimuovendo puntatori / riferimenti alla stringa aggiunta, corretto? E poi verso la fine esegue _PyString_Resize(&v, new_len)per allocare la memoria per la stringa concatenata, e poi memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);chi esegue la copia. Se il ridimensionamento sul posto fallisce, lo fa PyString_Concat(&v, w);(presumo che questo significhi quando la memoria contigua alla fine dell'indirizzo della stringa originale non è libera). In che modo questo mostra l'accelerazione?
user5622964

Ho esaurito lo spazio nel mio commento precedente, ma la mia domanda è se sto comprendendo correttamente quel codice e come interpretare l'utilizzo della memoria / i tempi di esecuzione di quei pezzi.
user5622964

1
@ user5622964: Ops, ho ricordato male i dettagli strani dell'implementazione. Non esiste una politica di ridimensionamento efficiente; chiama realloce spera solo per il meglio.
user2357112 supporta Monica

Come memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);funziona? Secondo cplusplus.com/reference/cstring/memcpy ha definizione void * memcpy ( void * destination, const void * source, size_t num );e descrizione: "Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."il num in questo caso è la dimensione della stringa aggiunta e la fonte è l'indirizzo della seconda stringa, presumo? Ma allora perché la destinazione (prima stringa) + len (prima stringa)? Doppia memoria?
user5622964

7
@ user5622964: Questa è l'aritmetica dei puntatori. Se vuoi capire il codice sorgente di CPython fino ai dettagli strani di implementazione, devi sapere C.La versione super condensata è che PyString_AS_STRING(v)è l'indirizzo dei dati della prima stringa e l'aggiunta v_lenti fa ottenere l'indirizzo subito dopo la stringa i dati finiscono.
user2357112 supporta Monica

40

L'autore fa affidamento su un'ottimizzazione che sembra essere qui, ma non è esplicitamente affidabile. strA = strB + strCè tipicamente O(n), rendendo la funzione O(n^2). Tuttavia, è abbastanza facile assicurarsi che l'intero processo sia O(n), utilizzare un array:

output = []
    # ... loop thing
    output.append('%20')
    # ...
    output.append(char)
# ...
return ''.join(output)

In poche parole, l' appendoperazione viene ammortizzata O(1) , (sebbene tu possa renderla forte O(1)pre-allocando l'array alla giusta dimensione), facendo il ciclo O(n).

E poi lo joinè anche O(n), ma va bene perché è fuori dal ciclo.


Questa risposta è buona perché spiega come concatenare le stringhe.
user877329

risposta precisa nel contesto del calcolo dei tempi di esecuzione.
Intesar Haider

25

Ho trovato questo frammento di testo su Python Speed> Usa i migliori algoritmi e gli strumenti più veloci :

La concatenazione di stringhe viene eseguita meglio con ''.join(seq)che è un O(n)processo. Al contrario, l'utilizzo degli operatori '+'o '+='può dare come risultato un O(n^2)processo perché è possibile creare nuove stringhe per ogni passaggio intermedio. L'interprete CPython 2.4 attenua in qualche modo questo problema; tuttavia, ''.join(seq)rimane la migliore pratica


3

Per i futuri visitatori: poiché è una domanda CTCI, non è richiesto alcun riferimento al pacchetto learning urllib qui, in particolare come per OP e il libro, questa domanda riguarda gli array e le stringhe.

Ecco una soluzione più completa, ispirata allo pseudo di @ njzk2:

text = 'Mr John Smith'#13 
special_str = '%20'
def URLify(text, text_len, special_str):
    url = [] 
    for i in range(text_len): # O(n)
        if text[i] == ' ': # n-s
            url.append(special_str) # append() is O(1)
        else:
            url.append(text[i]) # O(1)

    print(url)
    return ''.join(url) #O(n)


print(URLify(text, 13, '%20'))
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.