A complexidade de tempo do acréscimo de string iterativa é realmente O (n ^ 2) ou O (n)?

89

Estou trabalhando em um problema fora do CTCI.

O terceiro problema do capítulo 1 exige que você pegue uma string como

'Mr John Smith '

e pede que você substitua os espaços intermediários por %20:

'Mr%20John%20Smith'

O autor oferece essa solução em Python, chamando-a de 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

Minha pergunta:

Eu entendo que este é O (n) em termos de digitalização através da string real da esquerda para a direita. Mas as strings em Python não são imutáveis? Se eu tiver uma string e adicionar outra string a ela com o +operador, isso não aloca o espaço necessário, copia sobre o original e, em seguida, copia sobre a string anexada?

Se eu tiver uma coleção de nstrings de comprimento 1, isso leva:

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

ou O (n ^ 2) vez , sim? Ou estou enganado em como o Python lida com a adição?

Alternativamente, se você estiver disposto a me ensinar a pescar: como eu faria para descobrir isso sozinho? Não tive sucesso em minhas tentativas de pesquisar uma fonte oficial no Google. Encontrei https://wiki.python.org/moin/TimeComplexity, mas não tem nada sobre strings.

user5622964
fonte
17
Alguém deveria contar ao autor sobreurllib.urlencode
wim
11
@wim Deve ser um problema prático sobre matrizes e strings
user5622964
3
O objetivo do livro é ensinar as perguntas da entrevista, que normalmente pedem que você reinvente a roda para ver o processo de pensamento do entrevistado.
James Wierzba
1
Uma vez que é Python, acho que fazer um rtrime replaceseria mais preferível e próximo de O(n). Copiar strings parece a maneira menos eficiente.
OneCricketeer
2
@RNar Você pode explicar como uma cópia pode demorar um tempo constante?
James Wierzba

Respostas:

84

Em CPython, a implementação padrão do Python, há um detalhe de implementação que geralmente torna isso O (n), implementado no código que o loop de avaliação de bytecode chama para +ou +=com dois operandos de string . Se o Python detectar que o argumento esquerdo não tem outras referências, ele chamará reallocpara tentar evitar uma cópia redimensionando a string no local. Isso não é algo em que você deve confiar, porque é um detalhe de implementação e porque se reallocacabar precisando mover a string com frequência, o desempenho cai para O (n ^ 2) de qualquer maneira.

Sem o estranho detalhe de implementação, o algoritmo é O (n ^ 2) devido à quantidade quadrática de cópias envolvida. Um código como esse só faria sentido em uma linguagem com strings mutáveis, como C ++, e até mesmo em C ++ que você gostaria de usar +=.

user2357112 suporta Monica
fonte
2
Estou olhando para o código que você vinculou ... parece que uma grande parte desse código está limpando / removendo ponteiros / referências à string que está sendo anexada, correto? E então, no final, ele _PyString_Resize(&v, new_len)aloca a memória para a string concatenada, e então memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);quem faz a cópia. Se o redimensionamento local falhar, ele falhará PyString_Concat(&v, w);(presumo que isso signifique quando a memória contígua no final do endereço da string original não estiver livre). Como isso mostra a aceleração?
user5622964
Eu fiquei sem espaço em meu comentário anterior, mas minha dúvida é se estou ou não entendendo esse código corretamente e como interpretar o uso de memória / tempos de execução dessas peças.
user5622964
1
@ user5622964: Opa, esqueci o estranho detalhe de implementação. Não há uma política de redimensionamento eficiente; apenas chama realloce espera o melhor.
user2357112 suporta Monica
Como memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);funciona? De acordo com cplusplus.com/reference/cstring/memcpy, ele tem definição void * memcpy ( void * destination, const void * source, size_t num );e descrição: "Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."O num, neste caso, é o tamanho da string anexada e a origem é o endereço da segunda string, presumo? Mas então por que o destino (primeira string) + len (primeira string)? Memória dupla?
user5622964
7
@ user5622964: Isso é aritmética de ponteiro. Se você quiser entender o código-fonte do CPython até os detalhes de implementação estranhos, você precisará saber C. A versão supercondensada é que PyString_AS_STRING(v)é o endereço dos dados da primeira string, e adicionar v_lenfornece o endereço logo após a string dados termina.
user2357112 suporta Monica em
41

O autor conta com uma otimização que está aqui, mas não é explicitamente confiável. strA = strB + strCé tipicamente O(n), fazer a função O(n^2). No entanto, é muito fácil ter certeza de que todo o processo é O(n), use uma matriz:

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

Em suma, a appendoperação é amortizada O(1) (embora você possa torná-la forte O(1)pré-alocando o array para o tamanho certo), criando o loop O(n).

E então o joiné também O(n), mas tudo bem porque está fora do loop.

njzk2
fonte
Essa resposta é boa porque diz como concatenar strings.
user877329
resposta precisa no contexto do cálculo de tempos de execução.
ihaider
25

Encontrei este snippet de texto em Python Speed> Use os melhores algoritmos e ferramentas mais rápidas :

A concatenação de strings é melhor feita com o ''.join(seq)que é um O(n)processo. Em contraste, o uso dos operadores '+'ou '+='pode resultar em um O(n^2)processo porque novas strings podem ser criadas para cada etapa intermediária. O interpretador CPython 2.4 mitiga esse problema um pouco; no entanto, ''.join(seq)continua a ser a melhor prática

OneCricketeer
fonte
3

Para futuros visitantes: Como é uma questão CTCI, qualquer referência ao pacote de aprendizagem urllib não é necessária aqui, especificamente de acordo com o OP e o livro, esta questão é sobre Arrays e Strings.

Esta é uma solução mais completa, inspirada no pseudo de @ 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'))
geekidharsh
fonte