Deque ใน STL คืออะไร


194

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

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

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


4
เป็นไปได้ที่ซ้ำกันของSTL deque เข้าถึงโดยดัชนีเป็น O (1)?
fredoverflow

1
@ Graham“ dequeue” เป็นชื่อสามัญอีกชื่อหนึ่งสำหรับ“ deque” ฉันยังคงอนุมัติการแก้ไขเนื่องจาก "deque" มักเป็นชื่อมาตรฐาน
Konrad Rudolph

@ Konrad ขอบคุณ คำถามนี้เกี่ยวกับ C ++ STL deque โดยเฉพาะซึ่งใช้การสะกดที่สั้นกว่า
เกรแฮม Borland

2
dequeย่อมาจากคิวสิ้นสุดวันที่สองแต่เห็นได้ชัดว่าความต้องการที่เข้มงวดของ O (1) การเข้าถึงองค์ประกอบกลางโดยเฉพาะอย่างยิ่ง C + +
Matthieu M.

คำตอบ:


186

deque ค่อนข้างกำหนดซ้ำ: ภายในก็ยังคงเป็นคิวสองครั้งที่สิ้นสุดของชิ้นที่มีขนาดคงที่ แต่ละอันคือเวกเตอร์และคิว ("แผนที่" ในภาพด้านล่าง) ของตัวมันเองก็เป็นเวกเตอร์

แผนผังของรูปแบบหน่วยความจำของ deque

มีการวิเคราะห์ที่ดีของลักษณะการปฏิบัติงานและวิธีการที่จะเปรียบเทียบไปvectorมากกว่าที่CodeProject

การใช้งานไลบรารีมาตรฐาน GCC ภายในใช้T**เพื่อแสดงแผนที่ แต่ละบล็อคข้อมูลเป็นสิ่งT*ที่ถูกจัดสรรด้วยขนาดคงที่บางส่วน__deque_buf_size(ขึ้นอยู่กับsizeof(T))


28
นั่นคือคำจำกัดความของ deque ตามที่ฉันได้เรียนรู้ แต่ด้วยวิธีนี้มันไม่สามารถรับประกันการเข้าถึงเวลาที่แน่นอนดังนั้นต้องมีบางสิ่งที่ขาดหายไป
stefaanv

14
@stefaanv, @Konrad: การใช้งาน C ++ ที่ฉันได้เห็นใช้อาร์เรย์ของพอยน์เตอร์ในอาร์เรย์ขนาดคงที่ สิ่งนี้หมายความว่า push_front และ push_back ไม่ได้เป็นค่าคงที่จริง ๆ แต่ด้วยปัจจัยที่เพิ่มขึ้นอย่างชาญฉลาดคุณยังคงได้รับค่าคงที่เวลาดังนั้น O (1) จึงไม่ผิดพลาดและในทางปฏิบัติมันเร็วกว่าเวกเตอร์เพราะคุณเปลี่ยน พอยน์เตอร์เดียวแทนที่จะเป็นทั้งวัตถุ (และพอยน์เตอร์น้อยกว่าวัตถุ)
Matthieu M.

5
การเข้าถึงเวลาคงที่ยังคงเป็นไปได้ เพียงแค่ถ้าคุณต้องการจัดสรรบล็อกใหม่ที่ด้านหน้าให้ดันพอยน์เตอร์ใหม่บนเวกเตอร์หลักและเลื่อนพอยน์เตอร์ทั้งหมด
Xeo

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

15
@ JeremyWest ทำไมไม่? การเข้าถึงดัชนีจะไปที่องค์ประกอบ i% B-th ในบล็อก i / B-th (B = ขนาดบล็อก) นั่นคือ O (1) อย่างชัดเจน คุณสามารถเพิ่มบล็อกใหม่ในค่าตัดจำหน่าย O (1) ดังนั้นการเพิ่มองค์ประกอบจะถูกตัดจำหน่าย O (1) ในตอนท้าย การเพิ่มองค์ประกอบใหม่ที่จุดเริ่มต้นคือ O (1) เว้นแต่จะต้องเพิ่มบล็อกใหม่ การเพิ่มบล็อกใหม่ที่จุดเริ่มต้นไม่ใช่ O (1) จริงมันเป็น O (N) แต่ในความเป็นจริงมันมีปัจจัยคงที่ที่มีขนาดเล็กมากเนื่องจากคุณเพียงต้องการย้ายพอยน์เตอร์ N / B มากกว่าองค์ประกอบ N
Konrad Rudolph

22

ลองจินตนาการว่ามันเป็นเวกเตอร์ของเวกเตอร์ เพียง แต่พวกเขาไม่ได้มาตรฐานstd::vectors

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

dequeพฤติกรรมเวกเตอร์ภายในต้องมีการเปลี่ยนแปลงขึ้นอยู่กับว่ามันจะอยู่ที่ด้านหน้าหรือด้านหลังของ ที่ด้านหลังมันสามารถทำงานเป็นมาตรฐานstd::vectorที่มันเติบโตในตอนท้ายและpush_backเกิดขึ้นในเวลา O (1) ที่ด้านหน้ามันต้องทำสิ่งที่ตรงกันข้ามการเติบโตในตอนแรกpush_frontๆ ในทางปฏิบัติสามารถทำได้อย่างง่ายดายโดยการเพิ่มตัวชี้ไปยังองค์ประกอบด้านหน้าและทิศทางของการเติบโตพร้อมกับขนาด ด้วยการดัดแปลงอย่างง่ายนี้push_frontสามารถใช้เวลา O (1)

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


1
คุณสามารถอธิบายเวกเตอร์ด้านในว่ามีความจุ
Caleth

18

deque = คิวสิ้นสุดวันที่สองครั้ง

ภาชนะบรรจุที่สามารถเติบโตได้ในทิศทางใดทิศทางหนึ่ง

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


6
มันไม่ได้เป็นเวกเตอร์ภายใน โครงสร้างภายในสามารถจัดสรรได้ แต่กำลังการผลิตที่ไม่ได้ใช้ในตอนเริ่มต้นและสิ้นสุด
Mooing Duck

@MooingDuck: มันมีการกำหนดการใช้งานจริง ๆ มันสามารถเป็นอาร์เรย์ของอาร์เรย์หรือเวกเตอร์ของเวกเตอร์หรืออะไรก็ตามที่สามารถให้พฤติกรรมและความซับซ้อนที่ได้รับคำสั่งจากมาตรฐาน
Alok บันทึก

1
@ แอล: ฉันไม่คิดว่าarrayอะไรหรือvectorอะไรก็ตามที่สามารถสัญญาO(1)push_front ค่าตัดจำหน่าย ภายในของเค้าสองคนอย่างน้อยต้องสามารถที่จะมีO(1)push_front ซึ่งไม่ใช่การarrayหรือมิได้vectorรับประกันกระป๋อง
Mooing Duck

4
@MooingDuck ความต้องการนั้นเป็นไปตามความต้องการได้ง่ายถ้าอันแรกโตขึ้นจากบนลงล่างมากกว่าจากล่างขึ้นบน เห็นได้ชัดว่ามาตรฐานvectorไม่ได้ทำเช่นนั้น แต่เป็นการดัดแปลงที่ง่ายพอที่จะทำเช่นนั้น
Mark Ransom

3
@ Mooing Duck ทั้ง push_front และ push_back สามารถทำได้อย่างง่ายดายใน O (1) ที่ตัดจำหน่ายด้วยโครงสร้างเวกเตอร์เดียว มันเป็นเพียงการทำบัญชีบัฟเฟอร์วงกลมเพิ่มขึ้นอีกนิด สมมติว่าคุณมีเวกเตอร์ความจุปกติ 1,000 โดยมี 100 องค์ประกอบในนั้นที่ตำแหน่ง 0 ถึง 99 ตอนนี้เมื่อ push_Front เกิดขึ้นคุณเพิ่งกดที่จุดสิ้นสุดเช่นที่ตำแหน่ง 999 จากนั้น 998 เป็นต้นจนกระทั่งทั้งสองจบลง จากนั้นคุณจัดสรรใหม่ (พร้อมการเติบโตแบบเลขชี้กำลังเพื่อรับประกันเวลาคงที่ของค่าตัดจำหน่าย) เช่นเดียวกับที่คุณทำกับเวกเตอร์สามัญ อย่างมีประสิทธิภาพคุณเพียงแค่ต้องการตัวชี้เพิ่มเติมไปที่ el ก่อน
plamenko

14

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

ในคำตอบนี้ฉันไม่ได้พยายามระบุการใช้งานที่ดีฉันแค่พยายามช่วยเราตีความความซับซ้อนในมาตรฐาน C ++ ฉันอ้างอิงจากN3242ซึ่งก็คือตามมาตรฐานWikipediaเอกสารล่าสุดที่เป็นมาตรฐาน C ++ 11 (ดูเหมือนว่าจะมีการจัดระเบียบแตกต่างจากมาตรฐานสุดท้ายและด้วยเหตุนี้ฉันจะไม่พูดหมายเลขหน้าแน่นอนแน่นอนกฎเหล่านี้อาจมีการเปลี่ยนแปลงในมาตรฐานสุดท้าย แต่ฉันไม่คิดว่าจะเกิดขึ้น)

สามารถดำเนินการได้อย่างถูกต้องโดยใช้deque<T> vector<T*>องค์ประกอบทั้งหมดจะถูกคัดลอกไปยังกองและตัวชี้ที่เก็บไว้ในเวกเตอร์ (เพิ่มเติมเกี่ยวกับเวกเตอร์ในภายหลัง)

ทำไมT*แทนT? เพราะมาตรฐานกำหนดไว้ว่า

"การแทรกที่ปลายทั้งสองด้านของ deque จะยกเลิกการทำซ้ำตัววนซ้ำทั้งหมดไปยัง deque แต่ไม่มีผลต่อความถูกต้องของการอ้างอิงถึงองค์ประกอบของ deque "

(ความสำคัญของฉัน) T*ช่วยในการตอบสนองความว่า นอกจากนี้ยังช่วยให้เราพึงพอใจในสิ่งนี้:

"การแทรกองค์ประกอบเดียวทั้งที่จุดเริ่มต้นหรือจุดสิ้นสุดของ deque เสมอ ..... ทำให้การเรียกเพียงครั้งเดียวไปยังตัวสร้างของ T "

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

มาตรฐานพูดถึง "จำนวนการดำเนินการกับวัตถุที่มีอยู่" สำหรับdeque::push_frontสิ่งนี้ชัดเจน 1 เพราะTวัตถุหนึ่งชิ้นถูกสร้างขึ้นอย่างแน่นอนและศูนย์ของTวัตถุที่มีอยู่นั้นถูกอ่านหรือสแกนในทางใดทางหนึ่ง หมายเลขนี้ 1 เป็นค่าคงที่อย่างชัดเจนและไม่ขึ้นกับจำนวนของวัตถุที่อยู่ใน deque สิ่งนี้ทำให้เราสามารถพูดได้ว่า:

'สำหรับเราdeque::push_frontจำนวนของการดำเนินการกับวัตถุที่บรรจุอยู่ (Ts) ได้รับการแก้ไขและไม่ขึ้นอยู่กับจำนวนของวัตถุที่อยู่ใน deque'

แน่นอนจำนวนของการดำเนินการเกี่ยวกับT*จะไม่เป็นอย่างดี เมื่อการvector<T*>เติบโตใหญ่เกินไปมันจะถูกจัดสรรใหม่และT*จะถูกคัดลอกหลายรอบ ใช่จำนวนการดำเนินการT*จะแตกต่างกันอย่างมาก แต่จำนวนการดำเนินการTจะไม่ได้รับผลกระทบ

ทำไมเราดูแลเกี่ยวกับความแตกต่างระหว่างการนับการดำเนินงานเกี่ยวกับเรื่องนี้TและการดำเนินงานในการนับT*? เป็นเพราะมาตรฐานพูดว่า:

ข้อกำหนดความซับซ้อนทั้งหมดในข้อนี้มีการระบุเพียงอย่างเดียวในแง่ของจำนวนการดำเนินการบนวัตถุที่มีอยู่

สำหรับdequeวัตถุที่มีอยู่เป็นTไม่ได้T*หมายถึงเราสามารถละเว้นการดำเนินการใด ๆ ซึ่งสำเนา (หรือ reallocs T*บริการ)

ฉันไม่ได้พูดอะไรมากมายเกี่ยวกับเวกเตอร์ที่จะทำงานใน deque บางทีเราอาจตีความว่ามันเป็นบัฟเฟอร์แบบวงกลม (โดยเวกเตอร์ใช้ค่าสูงสุดcapacity()เสมอและจากนั้นจัดสรรใหม่ทุกอย่างให้เป็นบัฟเฟอร์ที่ใหญ่กว่าเมื่อเวกเตอร์เต็มรายละเอียดไม่สำคัญ

ในวรรคสามที่ผ่านมาเรามีการวิเคราะห์deque::push_frontและความสัมพันธ์ระหว่างจำนวนของวัตถุใน deque ที่มีอยู่แล้วและจำนวนของการดำเนินงานที่ดำเนินการโดย push_front บนมีT-objects และเราพบว่าพวกเขาเป็นอิสระจากกัน ในฐานะที่เป็นมาตรฐานบังคับว่าความซับซ้อนนั้นอยู่ในรูปแบบของการปฏิบัติการบนTดังนั้นเราสามารถพูดได้ว่ามันมีความซับซ้อนอย่างต่อเนื่อง

ใช่Operations-On-T * -Complexityจะถูกตัดจำหน่าย (เนื่องจากvector) แต่เราสนใจเพียงแค่ในOperations-On-T-Complexityและนี่คงที่ (ไม่ตัดจำหน่าย)

ความซับซ้อนของ vector :: push_back หรือ vector :: push_front นั้นไม่เกี่ยวข้องในการดำเนินการนี้ ข้อพิจารณาเหล่านั้นเกี่ยวข้องกับการดำเนินการT*และด้วยเหตุนี้ไม่เกี่ยวข้อง หากมาตรฐานนั้นอ้างถึงแนวคิดทางทฤษฎี 'ตามธรรมเนียม' ของความซับซ้อนดังนั้นพวกเขาจะไม่ จำกัด ตัวเองอย่างชัดเจนถึง "จำนวนการดำเนินการบนวัตถุที่บรรจุอยู่" ฉันแปลความหมายประโยคนั้นมากเกินไปหรือไม่


8
ดูเหมือนจะโกงฉันมาก! เมื่อคุณระบุความซับซ้อนของการดำเนินการคุณจะไม่ดำเนินการกับข้อมูลบางส่วนเท่านั้น: คุณต้องการทราบถึงความคาดหวังของการดำเนินการที่คุณกำลังเรียกใช้โดยไม่คำนึงถึงสิ่งที่ดำเนินการ ถ้าฉันทำตามตรรกะของคุณเกี่ยวกับการดำเนินการบน T ก็หมายความว่าคุณสามารถตรวจสอบว่าค่าของ T * แต่ละรายการเป็นจำนวนเฉพาะในแต่ละครั้งที่มีการดำเนินการและยังคงเคารพมาตรฐานเนื่องจากคุณไม่ได้แตะ Ts คุณสามารถระบุว่าคำพูดของคุณมาจากไหน?
Zonko

2
ฉันคิดว่าผู้เขียนมาตรฐานรู้ว่าพวกเขาไม่สามารถใช้ทฤษฎีความซับซ้อนแบบเดิมได้เพราะเราไม่มีระบบที่ระบุอย่างครบถ้วนที่เรารู้ตัวอย่างเช่นความซับซ้อนของการจัดสรรหน่วยความจำ ไม่ใช่ความจริงที่จะแสร้งว่าสามารถจัดสรรหน่วยความจำสำหรับสมาชิกใหม่ของรายการlistโดยไม่คำนึงถึงขนาดปัจจุบัน หากรายการมีขนาดใหญ่เกินไปการจัดสรรจะช้าหรือจะล้มเหลว ดังนั้นเท่าที่ฉันเห็นคณะกรรมการได้ตัดสินใจที่จะระบุเฉพาะการดำเนินงานที่สามารถนับจำนวนและวัดอย่างเป็นกลาง (PS: ฉันมีทฤษฎีอื่นเกี่ยวกับเรื่องนี้สำหรับคำตอบอื่น)
Aaron McDaid

ฉันค่อนข้างแน่ใจO(n)ว่าจำนวนการดำเนินการเป็นสัดส่วนกับจำนวนองค์ประกอบแบบไม่เชิงเส้น IE, การดำเนินการนับเมตาดาต้า มิฉะนั้นก็จะทำให้รู้สึกไม่มีที่จะ จำกัด O(1)การค้นหาไป ดังนั้น, รายการที่เชื่อมโยงไม่ได้มีคุณสมบัติ
Mooing Duck

8
นี่เป็นการตีความที่น่าสนใจมาก แต่ด้วยตรรกะนี้ a listสามารถนำไปใช้เป็นพvectorอยน์เตอร์ได้เช่นกัน (การแทรกที่อยู่ตรงกลางจะทำให้เกิดการเรียกใช้ตัวสร้างสำเนาเดียวโดยไม่คำนึงถึงขนาดของรายการและการO(N)สับของพอยน์เตอร์สามารถละเว้นได้ พวกเขาไม่ได้ดำเนินการเกี่ยวกับ T)
Mankarse

1
นี่เป็นข้อกฎหมายภาษาที่ดี (แม้ว่าฉันจะไม่พยายามที่จะคาดเดาได้ว่ามันถูกต้องจริงหรือหากมีบางจุดในมาตรฐานที่ห้ามการใช้งานนี้) แต่ไม่มีข้อมูลที่เป็นประโยชน์ในทางปฏิบัติเพราะ (1) การใช้งานทั่วไปไม่ได้ใช้dequeวิธีนี้และ (2) "การโกง" ด้วยวิธีนี้ (แม้ว่าจะได้รับอนุญาตตามมาตรฐาน) เมื่อการคำนวณความซับซ้อนของอัลกอริทึมไม่เป็นประโยชน์ในการเขียนโปรแกรมที่มีประสิทธิภาพ .
Kyle Strand

13

จากภาพรวมคุณสามารถคิดว่าdequeเป็นdouble-ended queue

ภาพรวม deque

ข้อมูลในdequeจะถูกเก็บไว้โดย chuncks ของเวกเตอร์ขนาดคงที่ซึ่ง ได้แก่

ตัวชี้โดยmap(ซึ่งเป็นอันของเวกเตอร์ แต่ขนาดอาจเปลี่ยนแปลงได้)

deque โครงสร้างภายใน

รหัสชิ้นส่วนหลักของdeque iteratorดังต่อไปนี้:

/*
buff_size is the length of the chunk
*/
template <class T, size_t buff_size>
struct __deque_iterator{
    typedef __deque_iterator<T, buff_size>              iterator;
    typedef T**                                         map_pointer;

    // pointer to the chunk
    T* cur;       
    T* first;     // the begin of the chunk
    T* last;      // the end of the chunk

    //because the pointer may skip to other chunk
    //so this pointer to the map
    map_pointer node;    // pointer to the map
}

รหัสชิ้นส่วนหลักของdequeดังต่อไปนี้:

/*
buff_size is the length of the chunk
*/
template<typename T, size_t buff_size = 0>
class deque{
    public:
        typedef T              value_type;
        typedef T&            reference;
        typedef T*            pointer;
        typedef __deque_iterator<T, buff_size> iterator;

        typedef size_t        size_type;
        typedef ptrdiff_t     difference_type;

    protected:
        typedef pointer*      map_pointer;

        // allocate memory for the chunk 
        typedef allocator<value_type> dataAllocator;

        // allocate memory for map 
        typedef allocator<pointer>    mapAllocator;

    private:
        //data members

        iterator start;
        iterator finish;

        map_pointer map;
        size_type   map_size;
}

ด้านล่างฉันจะให้รหัสหลักของdequeส่วนใหญ่เกี่ยวกับสามส่วน:

  1. iterator

  2. วิธีการสร้าง deque

1. ตัววนซ้ำ ( __deque_iterator)

ปัญหาหลักของ iterator คือเมื่อ ++, - iterator มันอาจข้ามไปที่ chunk อื่น ๆ (ถ้ามันชี้ไปที่ edge of chunk) chunk 1ตัวอย่างเช่นมีสามชิ้นข้อมูล: chunk 2, chunk 3,

pointer1ตัวชี้ไปยังเริ่มต้นของchunk 2เมื่อผู้ประกอบการ--pointerก็จะชี้ไปยังจุดสิ้นสุดของการเพื่อให้เป็นไปchunk 1pointer2

ป้อนคำอธิบายรูปภาพที่นี่

ด้านล่างฉันจะให้ฟังก์ชั่นหลักของ__deque_iterator:

ประการแรกข้ามไปที่ก้อนใด ๆ :

void set_node(map_pointer new_node){
    node = new_node;
    first = *new_node;
    last = first + chunk_size();
}

โปรดทราบว่าchunk_size()ฟังก์ชั่นที่คำนวณขนาดก้อนคุณสามารถคิดว่ามันคืนค่า 8 เพื่อทำให้ง่ายขึ้นที่นี่

operator* รับข้อมูลในกลุ่มข้อมูล

reference operator*()const{
    return *cur;
}

operator++, --

// คำนำหน้ารูปแบบของการเพิ่มขึ้น

self& operator++(){
    ++cur;
    if (cur == last){      //if it reach the end of the chunk
        set_node(node + 1);//skip to the next chunk
        cur = first;
    }
    return *this;
}

// postfix forms of increment
self operator++(int){
    self tmp = *this;
    ++*this;//invoke prefix ++
    return tmp;
}
self& operator--(){
    if(cur == first){      // if it pointer to the begin of the chunk
        set_node(node - 1);//skip to the prev chunk
        cur = last;
    }
    --cur;
    return *this;
}

self operator--(int){
    self tmp = *this;
    --*this;
    return tmp;
}
ตัววนซ้ำข้ามขั้นตอน / การเข้าถึงแบบสุ่ม
self& operator+=(difference_type n){ // n can be postive or negative
    difference_type offset = n + (cur - first);
    if(offset >=0 && offset < difference_type(buffer_size())){
        // in the same chunk
        cur += n;
    }else{//not in the same chunk
        difference_type node_offset;
        if (offset > 0){
            node_offset = offset / difference_type(chunk_size());
        }else{
            node_offset = -((-offset - 1) / difference_type(chunk_size())) - 1 ;
        }
        // skip to the new chunk
        set_node(node + node_offset);
        // set new cur
        cur = first + (offset - node_offset * chunk_size());
    }

    return *this;
}

// skip n steps
self operator+(difference_type n)const{
    self tmp = *this;
    return tmp+= n; //reuse  operator +=
}

self& operator-=(difference_type n){
    return *this += -n; //reuse operator +=
}

self operator-(difference_type n)const{
    self tmp = *this;
    return tmp -= n; //reuse operator +=
}

// random access (iterator can skip n steps)
// invoke operator + ,operator *
reference operator[](difference_type n)const{
    return *(*this + n);
}

2. วิธีการสร้าง deque

ฟังก์ชั่นทั่วไปของ deque

iterator begin(){return start;}
iterator end(){return finish;}

reference front(){
    //invoke __deque_iterator operator*
    // return start's member *cur
    return *start;
}

reference back(){
    // cna't use *finish
    iterator tmp = finish;
    --tmp; 
    return *tmp; //return finish's  *cur
}

reference operator[](size_type n){
    //random access, use __deque_iterator operator[]
    return start[n];
}


template<typename T, size_t buff_size>
deque<T, buff_size>::deque(size_t n, const value_type& value){
    fill_initialize(n, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::fill_initialize(size_t n, const value_type& value){
    // allocate memory for map and chunk
    // initialize pointer
    create_map_and_nodes(n);

    // initialize value for the chunks
    for (map_pointer cur = start.node; cur < finish.node; ++cur) {
        initialized_fill_n(*cur, chunk_size(), value);
    }

    // the end chunk may have space node, which don't need have initialize value
    initialized_fill_n(finish.first, finish.cur - finish.first, value);
}

template<typename T, size_t buff_size>
void deque<T, buff_size>::create_map_and_nodes(size_t num_elements){
    // the needed map node = (elements nums / chunk length) + 1
    size_type num_nodes = num_elements / chunk_size() + 1;

    // map node num。min num is  8 ,max num is "needed size + 2"
    map_size = std::max(8, num_nodes + 2);
    // allocate map array
    map = mapAllocator::allocate(map_size);

    // tmp_start,tmp_finish poniters to the center range of map
    map_pointer tmp_start  = map + (map_size - num_nodes) / 2;
    map_pointer tmp_finish = tmp_start + num_nodes - 1;

    // allocate memory for the chunk pointered by map node
    for (map_pointer cur = tmp_start; cur <= tmp_finish; ++cur) {
        *cur = dataAllocator::allocate(chunk_size());
    }

    // set start and end iterator
    start.set_node(tmp_start);
    start.cur = start.first;

    finish.set_node(tmp_finish);
    finish.cur = finish.first + num_elements % chunk_size();
}

สมมติว่าi_dequeมี 20 องค์ประกอบ0~19ที่มีขนาดก้อนเป็น 8 และตอนนี้องค์ประกอบ push_back 3 (0, 1, 2) เพื่อi_deque:

i_deque.push_back(0);
i_deque.push_back(1);
i_deque.push_back(2);

มันเป็นโครงสร้างภายในเช่นด้านล่าง:

ป้อนคำอธิบายรูปภาพที่นี่

จากนั้น push_back อีกครั้งระบบจะเรียกใช้การจัดสรรก้อนใหม่:

push_back(3)

ป้อนคำอธิบายรูปภาพที่นี่

ถ้าเราpush_frontมันจะจัดสรรก้อนใหม่ก่อนหน้า prevstart

ป้อนคำอธิบายรูปภาพที่นี่

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


คุณพูดว่า "หมายเหตุเมื่อองค์ประกอบ push_back เป็น deque หากแผนที่และชิ้นส่วนทั้งหมดถูกเติมเต็มจะทำให้เกิดการจัดสรรแผนที่ใหม่และปรับชิ้นส่วน" ฉันสงสัยว่าทำไมมาตรฐาน C ++ บอกว่า "[26.3.8.4.3] การแทรกองค์ประกอบเดียวทั้งที่จุดเริ่มต้นหรือจุดสิ้นสุดของ deque มักใช้เวลาคงที่" ใน N4713 การจัดสรรเชยข้อมูลใช้เวลามากกว่าค่าคงที่ ไม่มี?
HCSF

7

ฉันอ่าน "โครงสร้างข้อมูลและอัลกอริธึมใน C ++" โดย Adam Drozdek และพบว่ามีประโยชน์ HTH

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

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

ภาพมีค่าหนึ่งพันคำ

ป้อนคำอธิบายรูปภาพที่นี่


1
ขอบคุณสำหรับการอ้างอิงหนังสือ ฉันอ่านdequeตอนนี้และมันก็ค่อนข้างดี
Rick

@Rick ดีใจที่ได้ยินเช่นนั้น ฉันจำได้ว่าการขุดเข้าไปใน deque ในบางครั้งเพราะฉันไม่เข้าใจว่าคุณสามารถเข้าถึงตัวดำเนินการแบบสุ่ม ([]) ใน O (1) ได้อย่างไร นอกจากนี้ยังพิสูจน์ได้ว่า (push / pop) _ (back / front) มีการตัดจำหน่าย O (1) ความซับซ้อนเป็นช่วงเวลาที่น่าสนใจ 'aha'
Keloo

6

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


4
หรือการลบตรงกลางต้องการการย้ายที่มาก
Mark Hendrickson

หากinsertต้องใช้จำนวนมากของการย้ายถิ่นฐานวิธีการที่ไม่ทดลอง 4 นี่แสดงส่ายความแตกต่างระหว่างvector::insert()และdeque::insert()?
Bula

1
@Bula: อาจเกิดจากการสื่อสารผิดพลาดของรายละเอียด? ความซับซ้อนของการแทรกแบบ deque คือ "เส้นตรงในจำนวนองค์ประกอบที่แทรกบวกกับระยะทางที่น้อยกว่าถึงจุดเริ่มต้นและจุดสิ้นสุดของการหักมุม" หากต้องการรู้สึกถึงค่าใช้จ่ายนี้คุณจะต้องแทรกตรงกลางปัจจุบัน นั่นคือสิ่งที่มาตรฐานของคุณกำลังทำอะไร
Kerrek SB

@ KerrekSB: บทความที่มีเกณฑ์มาตรฐานถูกอ้างอิงในคำตอบของ Konrad อันที่จริงฉันไม่ได้สังเกตเห็นส่วนความคิดเห็นของบทความด้านล่าง ในเธรด 'แต่ deque มีเวลาการแทรกแบบเชิงเส้น?' ผู้เขียนพูดถึงว่าเขาใช้การแทรกที่ตำแหน่ง 100 ผ่านการทดสอบทั้งหมดซึ่งทำให้ผลลัพธ์เข้าใจได้ง่ายขึ้นเล็กน้อย
Bula
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.