Độ phức tạp về thời gian của chuỗi lặp có thực sự là thêm O (n ^ 2) hay O (n) không?


88

Tôi đang giải quyết một vấn đề của CTCI.

Vấn đề thứ ba của chương 1 có bạn lấy một chuỗi chẳng hạn như

'Mr John Smith '

và yêu cầu bạn thay thế các khoảng trắng trung gian bằng %20:

'Mr%20John%20Smith'

Tác giả đưa ra giải pháp này bằng Python, gọi nó là 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

Câu hỏi của tôi:

Tôi hiểu rằng đây là O (n) theo nghĩa quét qua chuỗi thực tế từ trái sang phải. Nhưng không phải các chuỗi trong Python là bất biến? Nếu tôi có một chuỗi và tôi thêm một chuỗi khác vào nó bằng +toán tử, nó không phân bổ không gian cần thiết, sao chép qua bản gốc và sau đó sao chép qua chuỗi thêm vào?

Nếu tôi có một tập hợp các nchuỗi có độ dài 1, thì điều đó sẽ mất:

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

hoặc O (n ^ 2) thời gian , có? Hay tôi nhầm lẫn trong cách Python xử lý việc nối thêm?

Ngoài ra, nếu bạn sẵn sàng dạy tôi cách câu cá: Làm thế nào để tôi tự tìm hiểu điều này? Tôi đã không thành công trong nỗ lực tìm kiếm nguồn chính thức của Google. Tôi đã tìm thấy https://wiki.python.org/moin/TimeComplexity nhưng điều này không có bất kỳ thứ gì trên chuỗi.


17
Ai đó nên nói với tác giả vềurllib.urlencode
wim

10
@wim Nó có nghĩa là một vấn đề thực tiễn về mảng và chuỗi
user5622964

3
Mục đích của cuốn sách là dạy các câu hỏi phỏng vấn, thường yêu cầu bạn phát minh lại bánh xe để xem quá trình suy nghĩ của người được phỏng vấn.
James Wierzba

1
Vì nó là Python, tôi nghĩ làm một rtrimreplacesẽ được ưa thích hơn và trong sân bóng O(n). Sao chép qua chuỗi dường như là cách kém hiệu quả nhất.
OneCricketeer

2
@RNar Bạn có thể giải thích cách sao chép có thể mất thời gian liên tục không?
James Wierzba

Câu trả lời:


83

Trong CPython, cách triển khai tiêu chuẩn của Python, có một chi tiết triển khai khiến điều này thường là O (n), được triển khai trong mã mà vòng lặp đánh giá bytecode gọi +hoặc +=với hai toán hạng chuỗi . Nếu Python phát hiện đối số bên trái không có tham chiếu nào khác, nó sẽ gọi reallocđể cố gắng tránh một bản sao bằng cách thay đổi kích thước chuỗi tại chỗ. Đây không phải là thứ bạn nên dựa vào, bởi vì nó là chi tiết triển khai và bởi vì nếu realloccuối cùng cần phải di chuyển chuỗi thường xuyên, hiệu suất sẽ giảm xuống còn O (n ^ 2).

Nếu không có chi tiết triển khai kỳ lạ, thuật toán là O (n ^ 2) do lượng sao chép bậc hai liên quan. Mã như thế này sẽ chỉ có ý nghĩa trong một ngôn ngữ có các chuỗi có thể thay đổi, như C ++ và thậm chí trong C ++ mà bạn muốn sử dụng +=.


2
Tôi đang xem mã bạn đã liên kết ... có vẻ như một phần lớn của mã đó đang dọn dẹp / xóa con trỏ / tham chiếu đến chuỗi đang được nối, đúng không? Và sau đó về cuối nó thực hiện _PyString_Resize(&v, new_len)để cấp phát bộ nhớ cho chuỗi được nối và sau memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);đó sẽ thực hiện sao chép. Nếu thay đổi kích thước tại chỗ không thành công PyString_Concat(&v, w);(tôi cho rằng điều này có nghĩa là khi bộ nhớ liền kề ở cuối địa chỉ chuỗi ban đầu không còn trống). Làm thế nào để hiển thị tốc độ?
dùng5622964

Tôi đã hết dung lượng trong nhận xét trước đây của mình, nhưng câu hỏi của tôi là liệu tôi có hiểu đúng mã đó hay không và cách diễn giải việc sử dụng / thời gian chạy bộ nhớ của những phần đó.
user5622964,

1
@ user5622964: Rất tiếc, đã ghi nhầm chi tiết triển khai kỳ lạ. Không có chính sách thay đổi kích thước hiệu quả; nó chỉ kêu gọi reallocvà hy vọng những điều tốt nhất.
user2357112 hỗ trợ Monica

Làm thế nào để memcpy(PyString_AS_STRING(v) + v_len, PyString_AS_STRING(w), w_len);hoạt động? Theo cplusplus.com/reference/cstring/memcpy, nó có định nghĩa void * memcpy ( void * destination, const void * source, size_t num );và mô tả: "Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination."Số trong trường hợp này là kích thước của chuỗi nối, và nguồn là địa chỉ của chuỗi thứ hai, tôi giả sử? Nhưng tại sao lại là đích (chuỗi đầu tiên) + len (chuỗi đầu tiên)? Kỉ niệm nhân đôi?
user5622964,

7
@ user5622964: Đó là số học con trỏ. Nếu bạn muốn hiểu mã nguồn CPython đến các chi tiết triển khai kỳ lạ, bạn sẽ cần biết C. Phiên bản siêu cô đọng PyString_AS_STRING(v)là địa chỉ của dữ liệu của chuỗi đầu tiên và việc thêm vào v_lensẽ giúp bạn có địa chỉ ngay sau chuỗi dữ liệu kết thúc.
user2357112 hỗ trợ Monica

39

Tác giả dựa vào một tối ưu hóa xảy ra ở đây, nhưng không đáng tin cậy một cách rõ ràng. strA = strB + strCthường O(n), làm cho chức năng O(n^2). Tuy nhiên, khá dễ dàng để đảm bảo rằng toàn bộ quá trình là như vậy O(n), hãy sử dụng một mảng:

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

Tóm lại, appendhoạt động được khấu hao O(1) , (mặc dù bạn có thể làm cho nó mạnh hơn O(1)bằng cách phân bổ trước mảng cho đúng kích thước), tạo ra vòng lặp O(n).

Và sau đó join cũng có O(n), nhưng không sao vì nó nằm ngoài vòng lặp.


Câu trả lời này là tốt vì nó cho biết cách nối các chuỗi.
user877329

câu trả lời chính xác trong bối cảnh tính toán thời gian chạy.
Intesar Haider

25

Tôi đã tìm thấy đoạn văn bản này trên Python Speed> Sử dụng các thuật toán tốt nhất và công cụ nhanh nhất :

Việc nối chuỗi được thực hiện tốt nhất với ''.join(seq)một O(n)quá trình. Ngược lại, việc sử dụng toán tử '+'hoặc '+='có thể dẫn đến một O(n^2)quá trình vì các chuỗi mới có thể được tạo cho mỗi bước trung gian. Trình thông dịch CPython 2.4 giảm nhẹ vấn đề này phần nào; tuy nhiên, ''.join(seq)vẫn là phương pháp hay nhất


3

Đối với khách truy cập trong tương lai: Vì nó là một câu hỏi CTCI, bất kỳ tài liệu tham khảo nào để học urllib gói không bắt buộc ở đây, cụ thể là theo OP và cuốn sách, câu hỏi này là về Mảng và Chuỗi.

Đây là một giải pháp hoàn chỉnh hơn, lấy cảm hứng từ giả của @ 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'))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.