Ist die zeitliche Komplexität des iterativen String-Anhängens tatsächlich O (n ^ 2) oder O (n)?

87

Ich arbeite an einem Problem mit CTCI.

Das dritte Problem in Kapitel 1 besteht darin, dass Sie eine Zeichenfolge wie z

'Mr John Smith '

und fordert Sie auf, die Zwischenräume durch Folgendes zu ersetzen %20:

'Mr%20John%20Smith'

Der Autor bietet diese Lösung in Python an und nennt sie 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

Meine Frage:

Ich verstehe, dass dies O (n) ist, wenn die tatsächliche Zeichenfolge von links nach rechts durchsucht wird. Aber sind Strings in Python nicht unveränderlich? Wenn ich eine Zeichenfolge habe und mit der eine weitere Zeichenfolge hinzufüge+ Operator , weist sie dann nicht den erforderlichen Speicherplatz zu, kopiert sie über das Original und kopiert sie dann über die anhängende Zeichenfolge?

Wenn ich eine Sammlung von nZeichenfolgen mit der Länge 1 habe, dann dauert das:

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

oder O (n ^ 2) Zeit , ja? Oder irre ich mich darin, wie Python mit dem Anhängen umgeht?

Alternativ, wenn Sie mir das Fischen beibringen möchten: Wie würde ich das selbst herausfinden? Ich habe bei meinen Versuchen, eine offizielle Quelle zu googeln, keinen Erfolg gehabt. Ich habe https://wiki.python.org/moin/TimeComplexity gefunden, aber dies hat nichts mit Zeichenfolgen zu tun.

user5622964
quelle
17
Jemand sollte dem Autor davon erzählenurllib.urlencode
wim
10
@wim Es soll ein Übungsproblem über Arrays und Strings sein
user5622964
3
Der Zweck des Buches ist es, Interviewfragen zu vermitteln, bei denen Sie häufig aufgefordert werden, das Rad neu zu erfinden, um den Denkprozess des Befragten zu sehen.
James Wierzba
1
Da es Python ist, denke ich, ein rtrimund replacewäre bevorzugter und im Baseballstadion vonO(n) . Das Kopieren über Zeichenfolgen scheint der am wenigsten effiziente Weg zu sein.
OneCricketeer
2
@RNar Können Sie erklären, wie eine Kopie eine konstante Zeit in Anspruch nehmen kann?
James Wierzba

Antworten:

81

In CPython, der Standardimplementierung von Python, gibt es ein Implementierungsdetail, das dies normalerweise zu O (n) macht, das in dem Code++= implementiert ist, den die Bytecode-Auswertungsschleife für oder mit zwei Zeichenfolgenoperanden benötigt . Wenn Python feststellt, dass das linke Argument keine anderen Referenzen enthält, wird reallocversucht, eine Kopie zu vermeiden, indem die Größe der Zeichenfolge geändert wird. Darauf sollten Sie sich niemals verlassen, da es sich um ein Implementierungsdetail handelt und reallocdie Leistung ohnehin auf O (n ^ 2) abnimmt, wenn die Zeichenfolge häufig verschoben werden muss.

Ohne das seltsame Implementierungsdetail ist der Algorithmus aufgrund des quadratischen Kopieraufwands O (n ^ 2). Code wie dieser wäre nur in einer Sprache mit veränderlichen Zeichenfolgen wie C ++ und sogar in C ++, die Sie verwenden möchten, sinnvoll+= .

user2357112 unterstützt Monica
quelle
2
Ich schaue auf den Code, den Sie verlinkt haben ... es sieht so aus, als würde ein großer Teil dieses Codes Zeiger / Verweise auf die angehängte Zeichenfolge bereinigen / entfernen, richtig? Gegen Ende _PyString_Resize(&v, new_len)wird dann der Speicher für die verkettete Zeichenfolge zugewiesen, und dann memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);wird die Kopie erstellt. Wenn die direkte Größenänderung fehlschlägt, ist dies der Fall PyString_Concat(&v, w);(ich gehe davon aus, dass dies bedeutet, wenn der zusammenhängende Speicher am Ende der ursprünglichen Zeichenfolgenadresse nicht frei ist). Wie zeigt dies die Beschleunigung?
user5622964
In meinem vorherigen Kommentar ist mir der Speicherplatz ausgegangen, aber meine Frage ist, ob ich diesen Code richtig verstehe oder nicht und wie die Speichernutzung / Laufzeit dieser Teile zu interpretieren ist.
user5622964
1
@ user5622964: Hoppla, ich habe mich an das seltsame Implementierungsdetail erinnert. Es gibt keine effiziente Richtlinie zur Größenänderung. es ruft nur reallocund hofft auf das Beste.
user2357112 unterstützt Monica
Wie funktioniert das memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);? Laut cplusplus.com/reference/cstring/memcpy hat es Definition void * memcpy ( void * destination, const void * source, size_t num );und Beschreibung: "Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."Die Zahl ist in diesem Fall die Größe der anhängenden Zeichenfolge, und die Quelle ist die Adresse der zweiten Zeichenfolge, nehme ich an? Aber warum ist dann das Ziel (erste Zeichenfolge) + len (erste Zeichenfolge)? Doppelter Speicher?
user5622964
7
@ user5622964: Das ist Zeigerarithmetik. Wenn Sie den CPython-Quellcode bis auf die seltsamen Implementierungsdetails verstehen möchten, müssen Sie C kennen. Die super-komprimierte Version PyString_AS_STRING(v)ist die Adresse der Daten der ersten Zeichenfolge. Durch Hinzufügen erhalten v_lenSie die Adresse direkt nach der der Zeichenfolge Daten enden.
user2357112 unterstützt Monica
38

Der Autor verlässt sich auf eine Optimierung, die zufällig hier ist, aber nicht explizit zuverlässig ist. strA = strB + strCist in der Regel O(n)die Funktion zu machen O(n^2). Es ist jedoch ziemlich einfach sicherzustellen, dass der gesamte Prozess ausgeführt wird O(n). Verwenden Sie ein Array:

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

Kurz gesagt, die appendOperation wird amortisiert O(1) (obwohl Sie sie stark machen können, O(1)indem Sie das Array der richtigen Größe vorab zuweisen), wodurch die Schleife entsteht O(n).

Und dann joinist das auch O(n), aber das ist okay, weil es außerhalb der Schleife liegt.

njzk2
quelle
Diese Antwort ist gut, weil sie erklärt, wie Zeichenfolgen verkettet werden.
user877329
genaue Antwort im Zusammenhang mit der Berechnung der Laufzeiten.
Intesar Haider
25

Ich habe diesen Textausschnitt in Python Speed ​​gefunden.> Verwenden Sie die besten Algorithmen und schnellsten Tools :

Die Verkettung von Zeichenfolgen erfolgt am besten mit ''.join(seq)einem O(n)Prozess. Im Gegensatz dazu kann die Verwendung der Operatoren '+'oder zu '+='einem O(n^2)Prozess führen, da für jeden Zwischenschritt neue Zeichenfolgen erstellt werden können. Der CPython 2.4-Interpreter verringert dieses Problem etwas. jedoch ''.join(seq)bleibt die beste Praxis

OneCricketeer
quelle
2

Für zukünftige Besucher: Da es sich um eine CTCI-Frage handelt, ist hier kein Verweis auf das Lernen des Urllib- Pakets erforderlich, insbesondere gemäß OP und dem Buch. Bei dieser Frage handelt es sich um Arrays und Strings.

Hier ist eine vollständigere Lösung, die vom Pseudo von @ njzk2 inspiriert ist:

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'))
geekidharsh
quelle