Yinelemeli dizgenin zaman karmaşıklığı gerçekte O (n ^ 2) veya O (n) 'yu ekliyor mu?


89

CTCI'dan kaynaklanan bir sorun üzerinde çalışıyorum.

1. bölümün üçüncü problemi, aşağıdaki gibi bir dizge almanızı gerektirir:

'Mr John Smith '

ve sizden ara boşlukları aşağıdakilerle değiştirmenizi ister %20:

'Mr%20John%20Smith'

Yazar bu çözümü Python'da O (n) olarak adlandırarak sunuyor:

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

Benim sorum:

Soldan sağa gerçek dizgide tarama açısından bunun O (n) olduğunu anlıyorum. Ancak Python'daki dizeler değişmez değil mi? Bir dizem varsa ve işleçle ona başka bir dize +eklersem, gerekli alanı ayırmaz, orijinalin üzerine kopyalamaz ve ardından eklenen dizenin üzerine kopyalamaz mı?

nHer biri 1 uzunluğunda dizelerden oluşan bir koleksiyonum varsa , o zaman şunu alır:

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

veya O (n ^ 2) zamanı , evet? Yoksa Python'un eklemeyi nasıl işlediği konusunda yanılıyor muyum?

Alternatif olarak, bana balık tutmayı öğretmeye istekli olsaydın: Bunu kendim bulmaya nasıl giderdim? Resmi bir kaynak olarak Google’a girişimlerimde başarısız oldum. Https://wiki.python.org/moin/TimeComplexity buldum ama bu dizelerde hiçbir şey içermiyor.


17
Birisi yazara şunu söylemeliurllib.urlencode
wim

11
@wim Diziler ve dizelerle ilgili bir uygulama problemi olması gerekiyordu
user5622964

3
Kitabın amacı, görüşülen kişinin düşünce sürecini görmek için sizden genellikle tekerleği yeniden icat etmenizi isteyen mülakat sorularını öğretmektir.
James Wierzba

1
Python olduğu için, bir rtrimve replacedaha çok tercih edileceğini düşünüyorum O(n). Dizeleri kopyalamak en az verimli yol gibi görünüyor.
OneCricketeer

2
@RNar Bir kopyanın nasıl sabit zaman alabileceğini açıklayabilir misiniz?
James Wierzba

Yanıtlar:


84

CPython yılında Python standart uygulaması, uygulanan bu genellikle O (n) hale getiren bir uygulama detay, orada baytkodu değerlendirme döngü çağrıları kod +veya +=iki dize işlenenler ile . Python, sol bağımsız değişkenin başka başvurusu olmadığını algılarsa realloc, dizeyi yerinde yeniden boyutlandırarak bir kopyadan kaçınmaya çalışır. Bu, güvenmeniz gereken bir şey değildir, çünkü bu bir uygulama ayrıntısıdır ve eğer reallocsonunda dizeyi sık sık hareket ettirmeniz gerekirse, performans yine de O (n ^ 2) 'ye düşer.

Garip uygulama ayrıntısı olmadan, içerilen ikinci dereceden kopyalama miktarı nedeniyle algoritma O (n ^ 2) 'dir. Bunun gibi bir kod, yalnızca C ++ gibi değiştirilebilir dizelere sahip bir dilde ve hatta kullanmak istediğiniz C ++ dilinde bile mantıklı olacaktır +=.


2
Bağladığınız koda bakıyorum ... bu kodun büyük bir kısmı eklenen dizgeye işaretçileri / referansları temizliyor / kaldırıyor gibi görünüyor, değil mi? Ve sonra sonuna doğru, _PyString_Resize(&v, new_len)birleştirilmiş dizge için bellek ayırmayı gerçekleştirir ve sonra memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);hangisi kopyalamayı yapar. Yerinde yeniden boyutlandırma başarısız olursa, başarısız olur PyString_Concat(&v, w);(Bunun orijinal dize adresinin sonundaki bitişik belleğin boş olmadığı anlamına geldiğini varsayıyorum). Bu hızlanmayı nasıl gösteriyor?
user5622964

Önceki yorumumda yerim kalmadı, ancak sorum şu, bu kodu doğru anlayıp anlamadığım ve bu parçaların bellek kullanımını / çalışma sürelerini nasıl yorumlayacağım.
user5622964

1
@ user5622964: Hay aksi, tuhaf uygulama detayını yanlış hatırladım. Etkin bir yeniden boyutlandırma politikası yoktur; reallocen iyisini arar ve umar.
user2357112, Monica

Nasıl memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);çalışır? Cplusplus.com/reference/cstring/memcpy'ye göre tanımı void * memcpy ( void * destination, const void * source, size_t num );ve açıklaması vardır: "Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."Bu durumda sayı, eklenen dizenin boyutudur ve kaynak ikinci dizenin adresidir, sanırım? Ama o zaman neden hedef (ilk dize) + len (ilk dize)? Çift hafıza?
user5622964

7
@ user5622964: Bu işaretçi aritmetiğidir. CPython kaynak kodunu tuhaf uygulama ayrıntılarına kadar anlamak istiyorsanız, C'yi bilmeniz gerekir. Süper yoğunlaştırılmış sürüm, PyString_AS_STRING(v)ilk dizenin verilerinin v_lenadresidir ve ekleme , size dizenin hemen ardından adresi verir. veriler biter.
user2357112 Monica'yı

41

Yazar, burada olan ancak açıkça güvenilir olmayan bir optimizasyona güveniyor. strA = strB + strCtipik olarak O(n)işlevi yapmaktır O(n^2). Ancak, tüm sürecin tamamlandığından emin olmak oldukça kolaydır O(n), bir dizi kullanın:

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

Özetle, appendişlem amortismana tabi tutulur O(1) ( O(1)diziyi doğru boyuta önceden tahsis ederek güçlü hale getirebilmenize rağmen ), döngüyü oluşturur O(n).

Ve sonra da joinöyle O(n), ama sorun değil çünkü döngünün dışında.


Bu cevap iyidir çünkü dizelerin nasıl birleştirileceğini anlatır.
user877329

çalışma sürelerinin hesaplanması bağlamında kesin cevap.
ihaider

25

Bu metin parçacığını Python Hızında buldum > En iyi algoritmaları ve en hızlı araçları kullanın :

Dize birleştirme en iyi ''.join(seq)olanı bir O(n)işlem olan ile yapılır . Buna karşılık, '+'veya '+='işleçlerini kullanmak bir O(n^2)işlemle sonuçlanabilir çünkü her ara adım için yeni dizeler oluşturulabilir. CPython 2.4 yorumlayıcısı bu sorunu bir şekilde hafifletir; ancak ''.join(seq)en iyi uygulama olmaya devam ediyor


3

Gelecekteki ziyaretçiler için: Bu bir CTCI sorusu olduğundan, urllib paketini öğrenmeye yönelik herhangi bir referans burada gerekli değildir, özellikle OP ve kitap uyarınca, bu soru Diziler ve Dizeler hakkındadır.

İşte @ njzk2'nin sözdiziminden ilham alan daha eksiksiz bir çözüm:

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'))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.