ความซับซ้อนของเวลาของสตริงซ้ำต่อท้าย 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น่าจะเป็นที่ต้องการมากกว่าและใน ballpark ของO(n). การคัดลอกสตริงดูเหมือนจะเป็นวิธีที่มีประสิทธิภาพน้อยที่สุด
OneCricketeer

2
@RNar คุณอธิบายได้ไหมว่าการทำสำเนาใช้เวลาคงที่ได้อย่างไร?
James Wierzba

คำตอบ:


84

ใน CPython การดำเนินงานมาตรฐานของงูใหญ่มีรายละเอียดการดำเนินงานที่ทำให้นี้มัก O (n) ดำเนินการในรหัสการประเมินผล bytecode สายห่วงสำหรับ+หรือ+=สองตัวถูกดำเนินการสตริง หาก 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 รองรับ Monica

วิธีการ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 รองรับ Monica

41

ผู้เขียนอาศัยการเพิ่มประสิทธิภาพที่เกิดขึ้นที่นี่ แต่ไม่สามารถพึ่งพาได้อย่างชัดเจน 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

คำตอบที่แม่นยำในบริบทของการคำนวณเวลาทำงาน
ihaider

25

ฉันพบตัวอย่างข้อความนี้เกี่ยวกับPython Speed> ใช้อัลกอริทึมที่ดีที่สุดและเครื่องมือที่เร็วที่สุด :

การต่อสายอักขระทำได้ดีที่สุด''.join(seq)ซึ่งเป็นO(n)กระบวนการ ในทางตรงกันข้ามการใช้'+'หรือ'+='ตัวดำเนินการอาจทำให้เกิดO(n^2)กระบวนการได้เนื่องจากสตริงใหม่อาจถูกสร้างขึ้นสำหรับแต่ละขั้นตอนกลาง ล่าม CPython 2.4 ช่วยบรรเทาปัญหานี้ได้บ้าง อย่างไรก็ตาม''.join(seq)ยังคงเป็นแนวทางปฏิบัติที่ดีที่สุด


3

สำหรับผู้เยี่ยมชมในอนาคต:เนื่องจากเป็นคำถาม CTCI จึงไม่จำเป็นต้องอ้างอิงถึงแพ็คเกจการเรียนรู้urllibที่นี่โดยเฉพาะตาม OP และหนังสือคำถามนี้เกี่ยวกับ Arrays และ Strings

นี่เป็นวิธีแก้ปัญหาที่สมบูรณ์ยิ่งขึ้นโดยได้รับแรงบันดาลใจจากหลอกของ @ 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'))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.