Складність часу ітеративного рядка додавання насправді O (n ^ 2), або O (n)?


89

Я працюю над проблемою поза CTCI.

Третя проблема глави 1 дозволяє вам взяти такий рядок, як

'Mr John Smith '

і просить Вас замінити проміжні простори на %20:

'Mr%20John%20Smith'

Автор пропонує це рішення на Python, називаючи його 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

Моє запитання:

Я розумію, що це O (n) з точки зору сканування фактичного рядка зліва направо. Але хіба рядки в Python не є незмінними? Якщо у мене є рядок і я додаю до нього ще один рядок за допомогою +оператора, чи не виділяє він необхідний простір, копіює над оригіналом, а потім копіює над доданим рядком?

Якщо у мене є колекція nрядків, кожна довжиною 1, то це займає:

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

або час O (n ^ 2) , так? Або я помиляюся в тому, як Python обробляє додавання?

Як варіант, якщо ви хочете навчити мене риболовлі: як я можу знайти це для себе? Я не мав успіху в своїх спробах отримати офіційне джерело в Google. Я знайшов https://wiki.python.org/moin/TimeComplexity, але це не містить нічого в рядках.


17
Хтось повинен розповісти автору проurllib.urlencode
wim

11
@wim Це має бути практичною проблемою щодо масивів та рядків
user5622964

3
Мета книги - навчити запитань на співбесіді, які зазвичай пропонують вам заново винайти колесо, щоб побачити процес мислення співрозмовника.
James Wierzba

1
Оскільки це Python, я думаю, що робити rtrimта replaceбуло б більш переважним і в базі O(n). Копіювання над рядками здається найменш ефективним способом.
OneCricketeer

2
@RNar Чи можете ви пояснити, як копія може зайняти постійний час?
James Wierzba

Відповіді:


83

У CPython, стандартній реалізації Python, є деталь реалізації, яка робить це, як правило, O (n), реалізоване в коді, цикл оцінки байт-коду викликає +або +=з двома рядковими операндами . Якщо Python виявляє, що лівий аргумент не має інших посилань, він закликає reallocспробувати уникнути копії, змінивши розмір рядка на місці. Це не те, на що вам коли-небудь слід покладатися, оскільки це деталь реалізації і тому, що якщо в reallocкінцевому підсумку потрібно часто переміщати рядок, продуктивність в будь-якому випадку погіршується до O (n ^ 2).

Без дивної деталі реалізації алгоритм має значення O (n ^ 2) через квадратичну кількість копіювання. Такий код мав би сенс лише в мові зі змінними рядками, як C ++, і навіть у C ++, який ви хотіли б використовувати +=.


2
Я розглядаю код, який ви зв'язали ... схоже, велика частина цього коду очищає / видаляє покажчики / посилання на рядок, що додається, правильно? А потім до кінця він виконує _PyString_Resize(&v, new_len)виділення пам’яті для об’єднаного рядка, а потім memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);виконує копію. Якщо зміна розміру на місці не вдається, це відбувається PyString_Concat(&v, w);(я припускаю, це означає, що суцільна пам’ять у кінці вихідної адреси рядка не є вільною). Як це показує прискорення?
user5622964

У моєму попередньому коментарі не вистачило місця, але моє запитання полягає в тому, чи правильно я розумію цей код і як інтерпретувати використання / пам’ять цих фрагментів пам’яті.
user5622964

1
@ user5622964: На жаль, неправильно запам'ятав дивну деталь реалізації. Не існує ефективної політики зміни розміру; воно просто кличе reallocі сподівається на краще.
user2357112 підтримує Моніку

Як memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);працює? Відповідно до cplusplus.com/reference/cstring/memcpy він має визначення void * memcpy ( void * destination, const void * source, size_t num );та опис: число "Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."в цьому випадку - це розмір рядка, що додається, а джерело - це адреса другого рядка, я припускаю? Але чому ж тоді пункт призначення (перший рядок) + len (перший рядок)? Подвійна пам’ять?
user5622964

7
@ user5622964: Це арифметика покажчика. Якщо ви хочете зрозуміти вихідний код CPython до дивних деталей реалізації, вам потрібно знати C. Супер згущена версія - PyString_AS_STRING(v)це адреса даних першого рядка, а додавання v_lenотримує адресу відразу після рядка дані закінчуються.
user2357112 підтримує Моніку

40

Автор покладається на оптимізацію, яка трапляється тут, але не є явно надійною. strA = strB + strCяк правило O(n), робить функцію O(n^2). Однак досить легко переконатися, що це весь процес O(n), використовуючи масив:

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

У двох словах, appendоперація амортизується O(1) (хоча її можна зробити сильною O(1), попередньо розподіливши масив до потрібного розміру), зробивши цикл O(n).

І тоді joinце також O(n), але це нормально, тому що воно знаходиться поза циклом.


Ця відповідь хороша, оскільки вона розповідає, як об’єднати рядки.
user877329

точна відповідь у контексті розрахунку часу роботи.
Intesar Haider

25

Я знайшов цей фрагмент тексту в розділі " Швидкість Python"> Використовуйте найкращі алгоритми та найшвидші інструменти :

Конкатенацію рядків найкраще робити, якщо ''.join(seq)це O(n)процес. На відміну від цього, використання операторів '+'або '+='може призвести до O(n^2)процесу, оскільки для кожного проміжного кроку можуть створюватися нові рядки. Інтерпретатор CPython 2.4 дещо пом’якшує цю проблему; однак, ''.join(seq)залишається найкращою практикою


3

Для майбутніх відвідувачів: Оскільки це питання CTCI, будь-яке посилання на вивчення пакету urllib тут не потрібне, зокрема, відповідно до OP та книги, це питання стосується масивів та рядків.

Ось більш повне рішення, натхнене псевдо @ 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'))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.