รายการที่เชื่อมโยงมีประโยชน์ภายใต้สถานการณ์ใดบ้าง


111

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

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

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


34
คำตอบสำหรับย่อหน้าที่สองของคุณใช้เวลาประมาณหนึ่งภาคการศึกษา
Seva Alekseyev

2
สำหรับความคิดของฉันดูpunchlet.wordpress.com/2009/12/27/letter-the-fourth และเนื่องจากนี่เป็นการสำรวจจึงน่าจะเป็น CW

1
@ นีลดีแม้ว่าฉันสงสัยว่า CS Lewis จะอนุมัติ
ทอม

@ นีล: ฉันเดาว่าเป็นแบบสำรวจ ส่วนใหญ่เป็นความพยายามที่จะดูว่ามีใครสามารถหาคำตอบที่มีพื้นฐานได้อย่างน้อยฉันก็สามารถซื้อได้ว่าสมเหตุสมผล @ เซวา: ใช่อ่านซ้ำฉันทำให้ประโยคสุดท้ายกว้างกว่าที่ฉันตั้งใจไว้ตอนแรก
เจอร์รี่โลงศพ

2
@Yar People (รวมถึงฉันฉันขอโทษที่ต้องพูด) เคยใช้รายการที่เชื่อมโยงโดยไม่มีพอยน์เตอร์ในภาษาเช่น FORTRAN IV (ซึ่งไม่มีความคิดของพอยน์เตอร์) เหมือนกับที่พวกเขาทำต้นไม้ คุณใช้อาร์เรย์แทนหน่วยความจำ "จริง"

คำตอบ:


40

สามารถเป็นประโยชน์สำหรับโครงสร้างข้อมูลพร้อมกัน (ตอนนี้มีตัวอย่างการใช้งานในโลกแห่งความเป็นจริงที่ไม่เกิดขึ้นพร้อมกันด้านล่างซึ่งจะไม่มีถ้า@Neilไม่ได้กล่าวถึง FORTRAN ;-)

ตัวอย่างเช่นConcurrentDictionary<TKey, TValue>ใน. NET 4.0 RC ใช้รายการที่เชื่อมโยงกับรายการโซ่ที่แฮชไปยังที่เก็บข้อมูลเดียวกัน

โครงสร้างข้อมูลพื้นฐานสำหรับConcurrentStack<T>ยังเป็นรายการที่เชื่อมโยง

ConcurrentStack<T>เป็นหนึ่งในโครงสร้างข้อมูลที่ทำหน้าที่เป็นรากฐานสำหรับThread Pool ใหม่ใหม่ (โดยใช้ "คิว" แบบโลคัลเป็นสแต็กเป็นหลัก) (โครงสร้างรองรับหลักอื่น ๆ คือConcurrentQueue<T>)

เธรดพูลใหม่จะเป็นพื้นฐานสำหรับการจัดตารางการทำงานของใหม่ ห้องสมุดขนานงาน

ดังนั้นพวกเขาจึงมีประโยชน์อย่างแน่นอน - รายการที่เชื่อมโยงกำลังทำหน้าที่เป็นหนึ่งในโครงสร้างสนับสนุนหลักของเทคโนโลยีใหม่ที่ยอดเยี่ยมอย่างน้อยหนึ่งอย่าง

(รายการที่เชื่อมโยงแบบเดี่ยวทำให้ตัวเลือกที่น่าสนใจโดยไม่ต้องล็อก - แต่ไม่ต้องรอ - ในกรณีเหล่านี้เนื่องจากการดำเนินการหลักสามารถดำเนินการได้ด้วยCASเดียว(+ การลองใหม่) ในสภาพแวดล้อม GC-d ที่ทันสมัยเช่น Java และ. NET - สามารถหลีกเลี่ยงปัญหา ABAได้อย่างง่ายดายเพียงตัดรายการที่คุณเพิ่มในโหนดที่สร้างขึ้นใหม่และอย่าใช้โหนดเหล่านั้นซ้ำ - ปล่อยให้ GC ทำงานหน้าปัญหา ABA ยังให้การใช้งานการล็อก สแต็กฟรี - ที่ใช้งานได้จริงใน. Net (& Java) โดยมีโหนด (GC-ed) ถือรายการ)

แก้ไข : @NET: จริงๆแล้วสิ่งที่คุณพูดถึงเกี่ยวกับ FORTRAN เตือนฉันว่ารายการที่เชื่อมโยงประเภทเดียวกันนี้สามารถพบได้ในโครงสร้างข้อมูลที่ใช้และถูกละเมิดมากที่สุดใน. NET: .NET ธรรมดาทั่วไปDictionary<TKey, TValue>ที่ธรรมดาทั่วไป

ไม่ใช่รายการเดียว แต่รายการที่เชื่อมโยงจำนวนมากจะถูกเก็บไว้ในอาร์เรย์

  • หลีกเลี่ยงการจัดสรร (de) ขนาดเล็กจำนวนมากในการแทรก / ลบ
  • การโหลดตารางแฮชเริ่มต้นนั้นค่อนข้างเร็วเนื่องจากอาร์เรย์เต็มไปตามลำดับ (เล่นได้ดีมากกับแคชของ CPU)
  • ไม่ต้องพูดถึงว่าตารางแฮชแบบโซ่มีราคาแพงในแง่ของหน่วยความจำและ "เคล็ดลับ" นี้จะลดขนาดตัวชี้ "ลงครึ่งหนึ่งบน x64

โดยพื้นฐานแล้วรายการที่เชื่อมโยงจำนวนมากจะถูกเก็บไว้ในอาร์เรย์ (หนึ่งรายการสำหรับแต่ละที่เก็บข้อมูลที่ใช้) รายการโหนดที่ใช้ซ้ำได้ฟรีคือ "การสาน" ระหว่างกัน (หากมีการลบ) อาร์เรย์จะถูกจัดสรรเมื่อเริ่มต้น / ในการรีแฮชและโหนดของโซ่จะถูกเก็บไว้ในนั้น นอกจากนี้ยังมีตัวชี้ที่ว่าง - ดัชนีในอาร์เรย์ - ที่ตามมาจากการลบ ;-) ดังนั้น - เชื่อหรือไม่ - เทคนิค FORTRAN ยังคงมีอยู่ (... และไม่มีที่ไหนอีกแล้วนอกจากโครงสร้างข้อมูล. NET ที่ใช้บ่อยที่สุด ;-)


2
ในกรณีที่คุณพลาดไปนี่คือความคิดเห็นของนีล: "ผู้คน (รวมถึงฉันฉันขอโทษที่ต้องพูด) เคยใช้รายการที่เชื่อมโยงโดยไม่มีคำแนะนำในภาษาเช่น FORTRAN IV (ซึ่งไม่มีคำแนะนำ) เหมือนกับที่พวกเขาทำต้นไม้ . คุณใช้อาร์เรย์แทนหน่วยความจำ "จริง" "
Andras Vass

ฉันควรเพิ่มว่าวิธีการ "รายการที่เชื่อมโยงในอาร์เรย์" ในกรณีที่Dictionaryบันทึกได้มากกว่าอย่างมีนัยสำคัญใน. NET: มิฉะนั้นแต่ละโหนดจะต้องใช้วัตถุแยกต่างหากบนฮีป - และทุกวัตถุที่จัดสรรบนฮีปจะมีค่าใช้จ่ายบางส่วน ( th.csharp-online.net/Common_Type_System%E2%80%94Object_Layout )
Andras Vass

นอกจากนี้ยังควรทราบว่าค่าเริ่มต้นของ C ++ std::listไม่ปลอดภัยในบริบทมัลติเธรดโดยไม่มีการล็อก
หมูปิ้ง

50

รายการที่เชื่อมโยงมีประโยชน์มากเมื่อคุณต้องการทำการแทรกและลบจำนวนมาก แต่ไม่ต้องค้นหามากเกินไปในรายการความยาวตามอำเภอใจ (ไม่ทราบเวลาคอมไพล์)

การแยกและการเข้าร่วมรายการ (เชื่อมโยงแบบสองทิศทาง) มีประสิทธิภาพมาก

คุณยังสามารถรวมรายการที่เชื่อมโยง - เช่นโครงสร้างต้นไม้สามารถนำไปใช้เป็นรายการที่เชื่อมโยง "แนวตั้ง" (ความสัมพันธ์แม่ / ลูก) ที่เชื่อมโยงรายการที่เชื่อมโยงกันในแนวนอน (พี่น้อง)

การใช้รายการตามอาร์เรย์เพื่อวัตถุประสงค์เหล่านี้มีข้อ จำกัด ที่รุนแรง:

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

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

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

1
สมมติว่า? การจัดสรรนั้นรวดเร็วอย่างน่าอัศจรรย์เห็นได้ชัดโดยปกติจะต้องเพิ่มขนาดวัตถุให้กับตัวชี้ ค่าโสหุ้ยรวมสำหรับ GC ต่ำหรือไม่ ครั้งล่าสุดที่ฉันลองวัดผลบนแอปจริงประเด็นสำคัญคือ Java กำลังทำงานทั้งหมดเมื่อโปรเซสเซอร์ไม่ได้ใช้งานอยู่แล้วดังนั้นโดยปกติแล้วจะไม่ส่งผลต่อประสิทธิภาพที่มองเห็นได้มากนัก ในเกณฑ์มาตรฐานของ CPU ที่มีงานยุ่งมันง่ายที่จะทำให้ Java เสียและได้รับเวลาจัดสรรเคสที่แย่มาก เมื่อหลายปีก่อนและการจัดเก็บขยะในแต่ละรุ่นได้ลดต้นทุนรวมของ GC ลงอย่างเห็นได้ชัด
Steve Jessop

1
@ สตีฟ: คุณคิดผิดเกี่ยวกับการจัดสรรให้ "เหมือนกัน" ระหว่างรายการและอาร์เรย์ ทุกครั้งที่คุณต้องจัดสรรหน่วยความจำสำหรับรายการคุณเพียงแค่จัดสรรบล็อกเล็ก ๆ - O (1) สำหรับอาร์เรย์คุณต้องจัดสรรบล็อกใหม่ให้ใหญ่พอสำหรับรายการทั้งหมดจากนั้นคัดลอกรายการทั้งหมด - O (n) ในการแทรกลงในตำแหน่งที่ทราบในรายการคุณต้องอัปเดตจำนวนตัวชี้คงที่ - O (1) แต่เพื่อแทรกลงในอาร์เรย์และคัดลอกรายการใด ๆ ในภายหลังขึ้นหนึ่งตำแหน่งเพื่อให้มีที่ว่างสำหรับการแทรก - O (n) มีหลายกรณีที่อาร์เรย์จึงมีประสิทธิภาพน้อยกว่า LLs มาก
Jason Williams

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

20

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


เป็นไปได้หรือไม่ที่จะกระตุ้นว่าเหตุใดจึงใช้รายการ แต่ไม่ใช่ชุดหรือแผนที่
patrik

14

อาร์เรย์เป็นโครงสร้างข้อมูลที่มักจะเปรียบเทียบรายการที่เชื่อมโยง

โดยปกติรายการที่เชื่อมโยงจะมีประโยชน์เมื่อคุณต้องทำการแก้ไขรายการจำนวนมากในขณะที่อาร์เรย์ทำงานได้ดีกว่ารายการในการเข้าถึงองค์ประกอบโดยตรง

นี่คือรายการของการดำเนินการที่สามารถทำได้ในรายการและอาร์เรย์โดยเปรียบเทียบกับต้นทุนการดำเนินการสัมพัทธ์ (n = ความยาวรายการ / อาร์เรย์):

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

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

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

การทราบความแตกต่างเหล่านี้เป็นสิ่งสำคัญสำหรับนักพัฒนาในการเลือกระหว่างรายการและอาร์เรย์ในการนำไปใช้งาน

โปรดทราบว่านี่เป็นการเปรียบเทียบรายการและอาร์เรย์ มีวิธีแก้ปัญหาที่ดีสำหรับปัญหาที่รายงานไว้ที่นี่ (เช่น SkipLists, Dynamic Arrays ฯลฯ ... ) ในคำตอบนี้ฉันได้พิจารณาโครงสร้างข้อมูลพื้นฐานที่โปรแกรมเมอร์ทุกคนควรรู้


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

คำตอบนี้ไม่ควรครอบคลุมโปรแกรมของหลักสูตร Data Structures University นี่คือการเปรียบเทียบที่เขียนขึ้นโดยคำนึงถึงรายการและอาร์เรย์ที่เชื่อมโยงซึ่งนำไปใช้ในแบบที่คุณฉันและคนส่วนใหญ่รู้จัก อาร์เรย์ที่ขยายทางเรขาคณิตข้ามรายการ ฯลฯ ... เป็นวิธีแก้ปัญหาที่ฉันรู้ฉันใช้และฉันศึกษา แต่จะต้องมีคำอธิบายที่ลึกกว่าและไม่เหมาะกับคำตอบแบบ stackoverflow
Andrea Zilio

1
"จากมุมมองของการจัดสรรหน่วยความจำรายการต่างๆจะดีกว่าเพราะไม่จำเป็นต้องมีองค์ประกอบทั้งหมดติดกัน" ในทางตรงกันข้ามภาชนะบรรจุที่อยู่ติดกันจะดีกว่าเพราะเก็บองค์ประกอบไว้ข้างๆกัน บนคอมพิวเตอร์สมัยใหม่พื้นที่ข้อมูลเป็นสิ่งสำคัญ สิ่งที่กระโดดไปมาในหน่วยความจำจะฆ่าประสิทธิภาพแคชของคุณและนำไปสู่โปรแกรมที่แทรกองค์ประกอบในตำแหน่งสุ่ม (อย่างมีประสิทธิภาพ) ซึ่งทำงานได้เร็วกว่าด้วยอาร์เรย์แบบไดนามิกเช่น C ++ std::vectorมากกว่ารายการที่เชื่อมโยงเช่น C ++ std::listเพียงเพราะการข้ามผ่าน รายการมีราคาแพงมาก
David Stone

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

4

รายการที่เชื่อมโยงแบบเดี่ยวเป็นตัวเลือกที่ดีสำหรับรายการว่างในตัวจัดสรรเซลล์หรือกลุ่มวัตถุ:

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

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

1
เท่าไหร่? มากกว่า 0, น้อยกว่าล้าน ;-) คำถามของเจอร์รี่ "ใช้ประโยชน์จากรายการให้เป็นประโยชน์" หรือ "ใช้ประโยชน์จากรายการที่โปรแกรมเมอร์ทุกคนใช้ในชีวิตประจำวัน" หรืออะไรระหว่างนั้น? ฉันไม่รู้จักชื่ออื่นนอกจาก "ก้าวก่าย" สำหรับโหนดรายการที่มีอยู่ภายในออบเจ็กต์ที่เป็นองค์ประกอบรายการไม่ว่าจะเป็นส่วนหนึ่งของการรวมกัน (ในรูปแบบ C) หรือไม่ก็ตาม จุดที่ 3 ใช้เฉพาะในภาษาที่ให้คุณทำได้ - C, C ++, แอสเซมเบลอร์ดี Java ไม่ดี
Steve Jessop

4

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

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

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


4

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

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

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


3

มีประโยชน์เมื่อคุณต้องการการผลักดันป๊อปและหมุนความเร็วสูงและไม่ต้องคำนึงถึงการจัดทำดัชนี O (n)


คุณเคยใส่ใจกับเวลา C ++ รายการที่เชื่อมโยงเมื่อเปรียบเทียบกับ (พูด) deque หรือไม่?

@ นีล: ไม่ได้ว่าฉันมี.
Ignacio Vazquez-Abrams

@Neil: ถ้า C ++ จงใจก่อวินาศกรรมคลาสรายการที่เชื่อมโยงเพื่อให้ช้ากว่าคอนเทนเนอร์อื่น ๆ (ซึ่งไม่ไกลจากความจริง) จะทำอย่างไรกับคำถามที่ไม่เชื่อเรื่องพระเจ้าเกี่ยวกับภาษา รายการที่เชื่อมโยงที่ล่วงล้ำยังคงเป็นรายการที่เชื่อมโยง
Steve Jessop

@Steve C ++ เป็นภาษา ฉันไม่เห็นว่ามันจะมีความผันผวนได้อย่างไร หากคุณกำลังแนะนำให้สมาชิกของคณะกรรมการ C ++ ก่อวินาศกรรมรายการที่เชื่อมโยง (ซึ่งต้องช้าอย่างมีเหตุผลสำหรับการดำเนินการจำนวนมาก) ให้ตั้งชื่อคนที่มีความผิด!

3
ไม่ใช่การก่อวินาศกรรมจริง ๆ - โหนดรายการภายนอกมีข้อดี แต่ประสิทธิภาพไม่ใช่หนึ่งในนั้น อย่างไรก็ตามแน่นอนว่าทุกคนก็ตระหนักดีเมื่อทำการละเว้นสิ่งเดียวกับที่คุณทราบซึ่งเป็นเรื่องยากที่จะหาประโยชน์มาstd::listใช้ได้ รายการที่ล่วงล้ำไม่สอดคล้องกับปรัชญา C ++ ของข้อกำหนดขั้นต่ำสำหรับองค์ประกอบคอนเทนเนอร์
Steve Jessop

3

รายการที่เชื่อมโยงเดี่ยวเป็นการใช้งานประเภทข้อมูล "รายการ" ทั่วไปอย่างชัดเจนในภาษาโปรแกรมที่ใช้งานได้:

  1. การเพิ่มส่วนหัวทำได้อย่างรวดเร็วและ(append (list x) (L))และ(append (list y) (L))สามารถแบ่งปันข้อมูลได้เกือบทั้งหมด ไม่จำเป็นต้อง copy-on-write ในภาษาที่ไม่มีการเขียน โปรแกรมเมอร์เชิงหน้าที่รู้วิธีใช้ประโยชน์จากสิ่งนี้
  2. น่าเสียดายที่การเพิ่มหางนั้นช้า แต่ก็เป็นการใช้งานอื่น ๆ

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


3

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


2

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


1

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

เห็นได้ชัดว่าแผนที่แฮชสามารถแทรกและลบใน O (1) ได้ แต่จากนั้นคุณจะไม่สามารถวนซ้ำองค์ประกอบตามลำดับได้

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

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


1

พิจารณาว่ารายการที่เชื่อมโยงอาจมีประโยชน์อย่างมากในการใช้งานสไตล์ Domain Driven Design ของระบบที่มีส่วนที่เชื่อมต่อกับการทำซ้ำ

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

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


1

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

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

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

ปัญหาตรงนี้เป็นล้าน std::vectorอินสแตนซ์พยายามจัดเก็บสิ่งที่มีความยาวผันแปรได้เป็นล้านรายการ สิ่งที่มีความยาวตัวแปรมักจะต้องการการจัดสรรฮีปเนื่องจากไม่สามารถจัดเก็บได้อย่างมีประสิทธิภาพอย่างต่อเนื่องและลบออกในเวลาคงที่ (อย่างน้อยก็ตรงไปตรงมาโดยไม่มีตัวจัดสรรที่ซับซ้อนมาก) หากพวกเขาไม่ได้เก็บเนื้อหาไว้ที่อื่นในฮีป

หากเราทำสิ่งนี้แทน:

struct FaceVertex
{
    // Points to next vertex in polygon or -1
    // if we're at the end of the polygon.
    int next;
    ...
};

struct Polygon
{
     // Points to first vertex in polygon.
    int first_vertex;
    ...
};

struct Mesh
{
    // Stores all the face vertices for all polygons.
    std::vector<FaceVertex> fvs;

    // Stores all the polygons.
    std::vector<Polygon> polys;
};

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

นี่คืออีกตัวอย่างหนึ่ง:

ใส่คำอธิบายภาพที่นี่

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

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

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

ใส่คำอธิบายภาพที่นี่

ในกรณีนั้นnextดัชนีจะชี้ไปยังดัชนีว่างถัดไปหากโหนดถูกลบออกหรือดัชนีที่ใช้ถัดไปหากโหนดยังไม่ถูกลบออก

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


0

ฉันเคยใช้รายการที่เชื่อมโยง (แม้กระทั่งรายการที่เชื่อมโยงเป็นสองเท่า) ในอดีตในแอปพลิเคชัน C / C ++ ก่อนหน้านี้. NET และแม้แต่ stl

ฉันอาจจะไม่ใช้รายการที่เชื่อมโยงในภาษา. NET เนื่องจากรหัสการส่งผ่านทั้งหมดที่คุณต้องการมีให้คุณผ่านวิธีการขยาย Linq

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