เหตุใดการวนซ้ำรายการจึงเร็วกว่าการจัดทำดัชนีผ่านรายการ


125

การอ่านเอกสาร Java สำหรับรายการ ADTระบุว่า:

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

นี่หมายความว่าอย่างไร? ฉันไม่เข้าใจข้อสรุปที่วาดขึ้น


12
อีกตัวอย่างหนึ่งที่อาจช่วยให้คุณเข้าใจกรณีทั่วไปคือบทความ "Back to Basics"ของJoel Spolsky - ค้นหา "Shlemiel the painter's algorithm"
CVn

คำตอบ:


211

ในรายการที่เชื่อมโยงแต่ละองค์ประกอบมีตัวชี้ไปยังองค์ประกอบถัดไป:

head -> item1 -> item2 -> item3 -> etc.

ในการเข้าถึงitem3คุณจะเห็นได้อย่างชัดเจนว่าคุณต้องเดินจากหัวผ่านทุกโหนดจนกว่าจะถึงข้อ 3 เนื่องจากคุณไม่สามารถกระโดดโดยตรงได้

ดังนั้นหากฉันต้องการพิมพ์ค่าของแต่ละองค์ประกอบถ้าฉันเขียนสิ่งนี้:

for(int i = 0; i < 4; i++) {
    System.out.println(list.get(i));
}

สิ่งที่เกิดขึ้นคือ:

head -> print head
head -> item1 -> print item1
head -> item1 -> item2 -> print item2
head -> item1 -> item2 -> item3 print item3

สิ่งนี้ไม่มีประสิทธิภาพอย่างมากเพราะทุกครั้งที่คุณจัดทำดัชนีจะรีสตาร์ทจากจุดเริ่มต้นของรายการและดำเนินการทุกรายการ ซึ่งหมายความว่าความซับซ้อนของคุณมีประสิทธิภาพO(N^2)เพียงแค่สำรวจรายการเท่านั้น!

ถ้าฉันทำสิ่งนี้แทน:

for(String s: list) {
    System.out.println(s);
}

แล้วสิ่งที่เกิดขึ้นคือ:

head -> print head -> item1 -> print item1 -> item2 -> print item2 etc.

O(N)ทั้งหมดในการสำรวจเส้นทางเดียวซึ่งเป็น

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


29
ข้อสังเกตเล็กน้อย: LinkedList จะค้นหาจากท้ายรายการหากดัชนีอยู่ในครึ่งหลังของรายการ แต่นั่นไม่ได้เปลี่ยนความไร้ประสิทธิภาพพื้นฐาน มันทำให้ปัญหาน้อยลงเพียงเล็กน้อย
Joachim Sauer

8
นี้จะไม่มีประสิทธิภาพอย่างน่ากลัว สำหรับ LinkedList ขนาดใหญ่ - ใช่สำหรับขนาดเล็กสามารถทำงานได้เร็วขึ้น REVERSE_THRESHOLDตั้ง 18 นิ้วjava.util.Collectionsเป็นเรื่องแปลกที่จะเห็นคำตอบที่ได้รับการโหวตโดยไม่ต้องพูด
bestsss

1
@DanDiplo ถ้าโครงสร้างเชื่อมโยงใช่มันถือเป็นจริง อย่างไรก็ตามการใช้โครงสร้าง LinkedS เป็นเรื่องลึกลับเล็กน้อย พวกเขามักจะทำงานได้แย่กว่าอาร์เรย์ที่สำรองไว้ (รอยเท้าหน่วยความจำเพิ่มเติม gc ไม่เป็นมิตรและพื้นที่ที่แย่มาก) รายการมาตรฐานใน C # ได้รับการสนับสนุนอาร์เรย์
bestsss

3
ข้อสังเกตเล็กน้อย: หากคุณต้องการตรวจสอบว่าควรใช้ประเภทการวนซ้ำแบบใด (ดัชนีเทียบกับ Iterator / foreach) คุณสามารถทดสอบได้เสมอว่ารายการใช้RandomAccess หรือไม่ (อินเทอร์เฟซเครื่องหมาย):List l = unknownList(); if (l instanceof RandomAccess) /* do indexed loop */ else /* use iterator/foreach */
afk5min

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

35

คำตอบโดยนัยที่นี่:

โปรดสังเกตว่าการดำเนินการเหล่านี้อาจดำเนินการตามเวลาตามสัดส่วนกับค่าดัชนีสำหรับการนำไปใช้งานบางอย่าง (เช่นคลาส LinkedList)

รายการที่เชื่อมโยงไม่มีดัชนีโดยธรรมชาติ การเรียก.get(x)ใช้จะต้องใช้รายการเพื่อค้นหารายการแรกและเรียกใช้.next()x-1 ครั้ง (สำหรับ O (n) หรือการเข้าถึงเวลาเชิงเส้น) โดยที่รายการที่สำรองด้วยอาร์เรย์สามารถทำดัชนีได้backingarray[x]ใน O (1) หรือเวลาคงที่

หากคุณดูJavaDoc สำหรับLinkedListคุณจะเห็นความคิดเห็น

การดำเนินการทั้งหมดดำเนินการตามที่คาดไว้สำหรับรายการที่เชื่อมโยงแบบทวีคูณ การดำเนินการที่จัดทำดัชนีในรายการจะสำรวจรายการจากจุดเริ่มต้นหรือจุดสิ้นสุดแล้วแต่ว่าสิ่งใดจะใกล้เคียงกับดัชนีที่ระบุ

ในขณะที่JavaDoc สำหรับArrayListมีไฟล์

ปรับขนาดอาร์เรย์ของอินเทอร์เฟซรายการ ดำเนินการรายการทางเลือกทั้งหมดและอนุญาตองค์ประกอบทั้งหมดรวมทั้ง null นอกเหนือจากการใช้อินเทอร์เฟซ List แล้วคลาสนี้ยังมีวิธีการจัดการกับขนาดของอาร์เรย์ที่ใช้ภายในเพื่อจัดเก็บรายการ (คลาสนี้เทียบเท่ากับ Vector โดยประมาณยกเว้นว่าจะไม่ซิงโครไนซ์)

size, isEmpty, get, set, iteratorและlistIteratorการดำเนินงานที่ทำงานในเวลาคง การดำเนินการเพิ่มจะทำงานในเวลาคงที่ที่ตัดจำหน่ายนั่นคือการเพิ่มองค์ประกอบ n ต้องใช้เวลา O (n) การดำเนินการอื่น ๆ ทั้งหมดทำงานในเวลาเชิงเส้น (พูดโดยประมาณ) ปัจจัยคงที่ต่ำเมื่อเทียบกับการLinkedListใช้งาน

คำถามที่เกี่ยวข้องเรื่อง "Big-O สรุปสำหรับคอลเลกชัน Java กรอบ"มีคำตอบที่ชี้ไปยังทรัพยากรนี้"คอลเลกชัน Java JDK6"ที่คุณอาจพบที่เป็นประโยชน์


7

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

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

นี่ไม่เป็นความจริงอย่างสมบูรณ์ ความจริงก็คือว่า

ด้วย ArrayList การนับวนซ้ำที่เขียนด้วยมือจะเร็วขึ้นประมาณ 3 เท่า

ที่มา: Designing for Performance, Android doc ของ Google

โปรดทราบว่าการวนซ้ำที่เขียนด้วยลายมือหมายถึงการทำดัชนีซ้ำ ฉันสงสัยว่ามันเป็นเพราะตัววนซ้ำซึ่งใช้กับการปรับปรุงสำหรับลูป มันสร้างประสิทธิภาพเล็กน้อยในการลงโทษในโครงสร้างซึ่งได้รับการสนับสนุนโดยอาร์เรย์ที่ต่อเนื่องกัน ฉันยังสงสัยว่านี่อาจเป็นจริงสำหรับคลาส Vector

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

ฉันแค่อยากจะชี้ให้เห็นว่าในบริบทของการพัฒนาใน Android การข้ามผ่านของ ArrayLists ทั้งสองไม่จำเป็นต้องเทียบเท่ากัน เพียงแค่อาหารสำหรับความคิด


แหล่งที่มาของคุณคือ Anndroid เท่านั้น สิ่งนี้ถือไว้สำหรับ JVM อื่น ๆ ด้วยหรือไม่?
Matsemann

ไม่แน่ใจว่า tbh อย่างสมบูรณ์ แต่อีกครั้งการใช้การปรับปรุงสำหรับลูปควรเป็นค่าเริ่มต้นในกรณีส่วนใหญ่
Dhruv Gairola

มันสมเหตุสมผลสำหรับฉันการกำจัดตรรกะตัววนซ้ำทั้งหมดเมื่อเข้าถึงโครงสร้างข้อมูลที่ใช้อาร์เรย์ทำงานได้เร็วขึ้น ฉันไม่รู้ว่าเร็วขึ้น 3 เท่า แต่เร็วกว่าแน่นอน
Setzer22

7

iterating กว่ารายการที่มีชดเชยสำหรับการค้นหาเช่นiเป็นคล้ายกับShlemiel อัลกอริทึมของจิตรกร

Shlemiel รับงานเป็นจิตรกรข้างถนนโดยวาดเส้นประที่กลางถนน ในวันแรกเขาเอากระป๋องสีออกไปที่ถนนและทำถนนให้เสร็จ 300 หลา “ ดีงาม!” เจ้านายของเขาพูดว่า "คุณเป็นคนทำงานเร็ว!" และจ่ายเงินให้เขา

วันรุ่งขึ้น Shlemiel ทำได้เพียง 150 หลา “ นั่นยังไม่ดีเท่าเมื่อวาน แต่คุณยังเป็นคนทำงานที่รวดเร็ว 150 หลานั้นน่านับถือ” และจ่ายเงินให้เขาเป็นจำนวนมาก

วันรุ่งขึ้น Shlemiel วาดภาพถนน 30 หลา "เพียง 30!" ตะโกนเจ้านายของเขา "นั่นเป็นเรื่องที่ยอมรับไม่ได้! ในวันแรกคุณทำงานหนักขนาดนั้นเป็นสิบเท่า! เกิดอะไรขึ้น?"

"ฉันไม่สามารถช่วยได้" Shlemiel กล่าว "ทุกๆวันฉันจะอยู่ห่างจากกระป๋องสีมากขึ้นเรื่อย ๆ !"

แหล่ง

เรื่องราวเล็ก ๆ น้อย ๆ นี้อาจทำให้เข้าใจได้ง่ายขึ้นว่าเกิดอะไรขึ้นภายในและเหตุใดจึงไม่มีประสิทธิภาพ


4

ในการค้นหาองค์ประกอบ i-th ของLinkedListการนำไปใช้ต้องผ่านองค์ประกอบทั้งหมดจนถึง i-th

ดังนั้น

for(int i = 0; i < list.length ; i++ ) {
    Object something = list.get(i); //Slow for LinkedList
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.