เหตุใด push_back ใน C ++ เวกเตอร์จึงตัดจำหน่ายอย่างต่อเนื่อง


23

ฉันกำลังเรียนรู้ C ++ และพบว่าเวลาทำงานของฟังก์ชัน push_back สำหรับเวกเตอร์นั้นคงที่ "ตัดจำหน่าย" เอกสารอธิบายเพิ่มเติมว่า "หากการจัดสรรใหม่เกิดขึ้นการจัดสรรใหม่จะเป็นแบบเชิงเส้นในขนาดทั้งหมด"

นี่ไม่ควรหมายความว่าฟังก์ชัน push_back เป็นโดยที่คือความยาวของเวกเตอร์ ท้ายที่สุดเราสนใจการวิเคราะห์กรณีที่เลวร้ายที่สุดใช่ไหม?O(n)n

ฉันเดาว่าสำคัญไม่เข้าใจว่าคำคุณศัพท์ "ตัดจำหน่าย" เปลี่ยนแปลงเวลาทำงานอย่างไร


ด้วยเครื่อง RAM การจัดสรรหน่วยความจำไบต์ไม่ใช่การดำเนินการ - ซึ่งถือว่าเป็นเวลาที่ค่อนข้างคงที่ nO(n)
usul

คำตอบ:


24

คำสำคัญที่นี่คือ "ตัดจำหน่าย" การวิเคราะห์ค่าตัดจำหน่ายเป็นเทคนิคการวิเคราะห์ที่ตรวจสอบลำดับของการดำเนินงานหากลำดับวิ่งทั้งในเวลาแล้วการดำเนินการในแต่ละลำดับวิ่งใน n แนวคิดคือแม้ว่าการดำเนินการบางอย่างในลำดับอาจมีค่าใช้จ่ายสูง แต่ก็ไม่สามารถเกิดขึ้นได้บ่อยครั้งพอที่จะทำให้โปรแกรมมีน้ำหนัก สิ่งสำคัญคือให้สังเกตว่าสิ่งนี้แตกต่างจากการวิเคราะห์กรณีโดยเฉลี่ยในการกระจายอินพุตหรือการวิเคราะห์แบบสุ่ม การวิเคราะห์ค่าตัดจำหน่ายสร้างกรณีที่เลวร้ายที่สุดสำหรับประสิทธิภาพของอัลกอริทึมโดยไม่คำนึงถึงอินพุต ส่วนใหญ่จะใช้ในการวิเคราะห์โครงสร้างข้อมูลซึ่งมีสถานะถาวรตลอดทั้งโปรแกรมnT(n)T(n)/n

หนึ่งในตัวอย่างที่พบบ่อยที่สุดที่ได้รับคือการวิเคราะห์สแต็กที่มีการดำเนินการหลายตัวที่ปรากฏองค์ประกอบการวิเคราะห์ที่ไร้เดียงสาของ Multipop จะบอกว่าในกรณีที่เลวร้ายที่สุด Multipop ต้องใช้เวลาเพราะมันอาจจะต้องปิดองค์ประกอบทั้งหมดของสแต็ค อย่างไรก็ตามหากคุณดูลำดับของการปฏิบัติงานคุณจะสังเกตเห็นว่าจำนวนของป็อปไม่สามารถเกินจำนวนการกดได้ ดังนั้นในการดำเนินการตามลำดับของการดำเนินงานจำนวนครั้งของป๊อปจึงไม่สามารถเกินและดังนั้นการเรียกใช้มัลติพอยท์ในเวลาตัดสิทธิ์แม้ว่าบางครั้งการโทรครั้งเดียวอาจใช้เวลามากขึ้นkO(n)nO(n)O(1)

ทีนี้สิ่งนี้เกี่ยวข้องกับเวกเตอร์ C ++ อย่างไร เวกเตอร์ถูกนำไปใช้กับอาร์เรย์ดังนั้นเพื่อเพิ่มขนาดของเวกเตอร์คุณต้องจัดสรรหน่วยความจำใหม่และคัดลอกอาร์เรย์ทั้งหมด เห็นได้ชัดว่าเราไม่ต้องการทำสิ่งนี้บ่อยนัก ดังนั้นหากคุณดำเนินการ push_back และเวกเตอร์จะต้องมีการจัดสรรพื้นที่มากขึ้นก็จะเพิ่มขนาดโดยปัจจัยเมตรตอนนี้ต้องใช้หน่วยความจำมากขึ้นซึ่งคุณอาจไม่สามารถใช้งานได้เต็มรูปแบบ แต่การดำเนินการ push_back สองสามครั้งถัดไปทั้งหมดจะทำงานในเวลาคงที่ม.

ตอนนี้ถ้าเราทำการวิเคราะห์ค่าตัดจำหน่ายของการดำเนินงาน push_back (ที่ฉันพบที่นี่ ) เราจะพบว่ามันทำงานในเวลาตัดจำหน่ายคงที่ สมมติว่าคุณมีรายการและปัจจัยการคูณของคุณเป็นเมตรจากนั้นจำนวนการย้ายคือประมาณ(n) การจัดสรรใหม่จะมีค่าใช้จ่ายตามสัดส่วนกับเกี่ยวกับขนาดของอาร์เรย์ปัจจุบัน ดังนั้นเวลาทั้งหมดสำหรับการกดคือเนื่องจากมันเป็นอนุกรมเรขาคณิต หารสิ่งนี้ด้วยการปฏิบัติการและเราได้รับการดำเนินการแต่ละอย่างnม.เข้าสู่ระบบม.(n)ผมม.ผมnΣผม=1เข้าสู่ระบบม.(n)ม.ผมnม.ม.-1nม.ม.-1ค่าคงที่ สุดท้ายคุณจะต้องระมัดระวังเกี่ยวกับการเลือกปัจจัยคุณ หากใกล้เกินไปถึงค่าคงที่นี้จะใหญ่เกินไปสำหรับแอปพลิเคชั่นที่ใช้งานได้จริง แต่ถ้ามีขนาดใหญ่เกินไปให้พูด 2 คุณจะเริ่มเสียความทรงจำมากมาย อัตราการเจริญเติบโตที่เหมาะจะแตกต่างกันโดยการประยุกต์ใช้ แต่ฉันคิดว่าการใช้งานบางคนใช้1.5ม.1ม.1.5


12

แม้ว่า @Marc จะให้การวิเคราะห์ที่ยอดเยี่ยม (สิ่งที่ฉันคิดว่า) เป็นบางคนอาจต้องการพิจารณาสิ่งต่าง ๆ จากมุมที่แตกต่างกันเล็กน้อย

วิธีหนึ่งคือการพิจารณาวิธีการจัดสรรที่แตกต่างกันเล็กน้อย แทนที่จะคัดลอกองค์ประกอบทั้งหมดจากที่เก็บข้อมูลเก่าไปยังที่จัดเก็บใหม่ทันทีพิจารณาคัดลอกเพียงหนึ่งองค์ประกอบต่อครั้ง - เช่นแต่ละครั้งที่คุณทำ push_back จะเพิ่มองค์ประกอบใหม่ไปยังพื้นที่ใหม่และคัดลอกหนึ่งที่มีอยู่ องค์ประกอบจากพื้นที่เก่าไปยังพื้นที่ใหม่ สมมติว่าปัจจัยการเจริญเติบโตของ 2 มันค่อนข้างชัดเจนว่าเมื่อพื้นที่ใหม่เต็มเราจะทำการคัดลอกองค์ประกอบทั้งหมดจากพื้นที่เก่าไปยังพื้นที่ใหม่เสร็จแล้วและ push_back แต่ละแห่งก็มีเวลาคงที่ ณ จุดนี้เราจะทิ้งพื้นที่เก่าจัดสรรบล็อกหน่วยความจำใหม่ที่เพิ่มขึ้นเป็นสองเท่าและทำซ้ำกระบวนการ

เห็นได้ชัดว่าเราสามารถดำเนินการต่อไปเรื่อย ๆ (หรือตราบใดที่มีหน่วยความจำอยู่แล้ว) และทุก ๆ push_back จะเกี่ยวข้องกับการเพิ่มองค์ประกอบใหม่และคัดลอกองค์ประกอบเก่าหนึ่งองค์ประกอบ

การนำไปใช้โดยทั่วไปยังคงมีจำนวนสำเนาเท่ากัน - แต่แทนที่จะทำสำเนาทีละรายการจะทำการคัดลอกองค์ประกอบที่มีอยู่ทั้งหมดในคราวเดียว ในอีกด้านหนึ่งคุณพูดถูก: หมายความว่าถ้าคุณดูข้อความเชิญของ push_back แต่ละรายการบางข้อความจะช้ากว่าคนอื่นมาก หากเราดูค่าเฉลี่ยระยะยาวจำนวนการคัดลอกที่ทำต่อการเรียกใช้ของ push_back จะคงที่ไม่ว่าขนาดของเวกเตอร์จะเป็นเท่าใด

แม้ว่ามันจะไม่เกี่ยวข้องกับความซับซ้อนในการคำนวณ แต่ฉันคิดว่ามันคุ้มค่าที่จะชี้ให้เห็นว่าทำไมการทำสิ่งต่าง ๆ ในขณะที่ทำนั้นมีประโยชน์มากกว่าการคัดลอกองค์ประกอบหนึ่งรายการต่อ push_back ดังนั้นเวลาต่อ push_back จึงคงที่ มีเหตุผลอย่างน้อยสามประการที่ต้องพิจารณา

แรกคือความพร้อมหน่วยความจำเพียง หน่วยความจำเก่าสามารถทำให้เป็นอิสระสำหรับการใช้งานอื่น ๆ ได้เฉพาะหลังจากการคัดลอกเสร็จสิ้น หากคุณคัดลอกทีละหนึ่งไอเท็มบล็อกหน่วยความจำเก่าจะยังคงถูกจัดสรรอีกต่อไป ในความเป็นจริงคุณจะมีบล็อกเก่าหนึ่งบล็อกและบล็อกใหม่หนึ่งบล็อกที่จัดสรรเป็นหลักตลอดเวลา หากคุณตัดสินใจเลือกปัจจัยการเจริญเติบโตที่เล็กกว่าสอง (ซึ่งปกติคุณต้องการ) คุณจะต้องจัดสรรหน่วยความจำให้มากขึ้นตลอดเวลา

ประการที่สองถ้าคุณคัดลอกองค์ประกอบเก่าเพียงครั้งเดียวการทำดัชนีในอาร์เรย์จะยุ่งยากกว่าเล็กน้อย - การดำเนินการทำดัชนีแต่ละครั้งจะต้องคิดออกว่าองค์ประกอบที่ดัชนีที่กำหนดนั้นอยู่ในบล็อกหน่วยความจำเก่าหรือ อันใหม่. นั่นไม่ได้ซับซ้อนอะไรมากนัก แต่สำหรับการดำเนินการเบื้องต้นเช่นการจัดทำดัชนีในอาเรย์การชะลอตัวอาจมีความสำคัญมาก

ประการที่สามโดยการคัดลอกทั้งหมดในครั้งเดียวคุณจะได้รับประโยชน์มากขึ้นจากการแคช การคัดลอกทั้งหมดในครั้งเดียวคุณสามารถคาดหวังได้ว่าทั้งต้นทางและปลายทางจะอยู่ในแคชในกรณีส่วนใหญ่ดังนั้นค่าใช้จ่ายของแคชมิสจะถูกตัดจำหน่ายตามจำนวนองค์ประกอบที่จะพอดีกับบรรทัดแคช หากคุณคัดลอกองค์ประกอบหนึ่งครั้งคุณอาจพลาดแคชได้อย่างง่ายดายสำหรับทุกองค์ประกอบที่คุณคัดลอก นั่นเปลี่ยนเพียงปัจจัยคงที่ไม่ใช่ความซับซ้อน แต่ยังคงมีความสำคัญพอสมควร - สำหรับเครื่องทั่วไปคุณสามารถคาดหวังได้ง่ายถึงปัจจัย 10 ถึง 20

มันอาจคุ้มค่าที่จะพิจารณาทิศทางอื่นสักครู่: ถ้าคุณกำลังออกแบบระบบที่มีข้อกำหนดแบบเรียลไทม์มันอาจจะเหมาะสมที่จะคัดลอกเพียงองค์ประกอบเดียวในแต่ละครั้งแทนที่จะเป็นทั้งหมดในคราวเดียว แม้ว่าความเร็วโดยรวมอาจจะ (หรืออาจจะไม่ต่ำกว่า) แต่คุณก็ยังคงมีขอบเขตบนที่ยากสำหรับการเรียกใช้ push_back เพียงครั้งเดียว - โดยสันนิษฐานว่าคุณมีตัวจัดสรรแบบเรียลไทม์ ระบบนั้นห้ามการจัดสรรหน่วยความจำแบบไดนามิกเลยอย่างน้อยก็ในส่วนที่มีข้อกำหนดแบบเรียลไทม์)


2
+1 นี่คือคำอธิบายสไตล์เฟย์แมนที่ยอดเยี่ยม
Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.