สามวิธีในการจัดเก็บกราฟในหน่วยความจำข้อดีและข้อเสีย


90

มีสามวิธีในการจัดเก็บกราฟในหน่วยความจำ:

  1. โหนดเป็นวัตถุและขอบเป็นตัวชี้
  2. เมทริกซ์ที่มีน้ำหนักขอบทั้งหมดระหว่างโหนดที่มีหมายเลข x และโหนด y
  3. รายการขอบระหว่างโหนดที่มีหมายเลข

ฉันรู้วิธีเขียนทั้งสามข้อ แต่ฉันไม่แน่ใจว่าฉันได้คิดถึงข้อดีและข้อเสียทั้งหมดของแต่ละข้อ

ข้อดีและข้อเสียของแต่ละวิธีในการจัดเก็บกราฟในหน่วยความจำคืออะไร?


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

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

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

1
@Dean J: แค่คำถามเกี่ยวกับ "โหนดเป็นวัตถุและขอบเป็นตัวแทนของพอยน์เตอร์" คุณใช้โครงสร้างข้อมูลใดในการจัดเก็บพอยน์เตอร์ในออบเจ็กต์ มันคือรายการ?
Timofey

4
ชื่อทั่วไป: (1) เทียบเท่ากับรายการถ้อยคำ (2) ถ้อยคำเมทริกซ์ (3) รายการขอบ
Evgeni Sergeev

คำตอบ:


51

วิธีหนึ่งในการวิเคราะห์สิ่งเหล่านี้คือในแง่ของความจำและความซับซ้อนของเวลา (ซึ่งขึ้นอยู่กับว่าคุณต้องการเข้าถึงกราฟอย่างไร)

การจัดเก็บโหนดเป็นอ็อบเจ็กต์โดยมีตัวชี้ซึ่งกันและกัน

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

การจัดเก็บเมทริกซ์ของน้ำหนักขอบ

  • นี่จะเป็นความซับซ้อนของหน่วยความจำของ O (n ^ 2) สำหรับเมทริกซ์
  • ข้อดีของโครงสร้างข้อมูลนี้คือความซับซ้อนของเวลาในการเข้าถึงโหนดใด ๆ ที่กำหนดคือ O (1)

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


3
ฉันเชื่อว่าความซับซ้อนของเวลาสำหรับการค้นหาในโมเดลวัตถุ / ตัวชี้นั้นเป็นเพียง O (n) เท่านั้นหากคุณเก็บโหนดไว้ในอาร์เรย์แยกต่างหาก มิฉะนั้นคุณจะต้องสำรวจกราฟเพื่อค้นหาโหนดที่ต้องการไม่ใช่หรือ? การข้ามทุกโหนด (แต่ไม่จำเป็นต้องทุกขอบ) ในกราฟโดยพลการไม่สามารถทำได้ใน O (n) ทำได้หรือไม่?
Barry Fruitman

@BarryFruitman ฉันค่อนข้างมั่นใจว่าคุณถูกต้อง BFS คือ O (V + E) นอกจากนี้หากคุณกำลังค้นหาโหนดที่ไม่ได้เชื่อมต่อกับโหนดอื่นคุณจะไม่พบโหนดนั้น
WilderField

10

อีกสองสิ่งที่ควรพิจารณา:

  1. แบบจำลองเมทริกซ์ช่วยให้กราฟที่มีขอบถ่วงน้ำหนักง่ายขึ้นโดยการจัดเก็บน้ำหนักไว้ในเมทริกซ์ โมเดลวัตถุ / ตัวชี้จะต้องจัดเก็บน้ำหนักขอบในอาร์เรย์คู่ขนานซึ่งต้องมีการซิงโครไนซ์กับอาร์เรย์ตัวชี้

  2. โมเดลวัตถุ / ตัวชี้ทำงานได้ดีกับกราฟที่กำหนดทิศทางมากกว่ากราฟที่ไม่มีทิศทางเนื่องจากตัวชี้จะต้องได้รับการดูแลเป็นคู่ซึ่งอาจทำให้ไม่ซิงโครไนซ์ได้


1
คุณหมายถึงตัวชี้จะต้องได้รับการดูแลเป็นคู่กับกราฟที่ไม่ได้บอกทิศทางใช่หรือไม่? หากมีการชี้นำคุณเพียงแค่เพิ่มจุดยอดลงในรายการ adjacency ของจุดยอดใดจุดยอดหนึ่ง แต่ถ้าไม่ได้กำหนดทิศทางคุณต้องเพิ่มหนึ่งในรายการ adjacency ของจุดยอดทั้งสอง?
FrostyStraw

@FrostyStraw ใช่แน่นอน
Barry Fruitman

8

วิธีการวัตถุและตัวชี้มีปัญหาในการค้นหาอย่างที่บางคนกล่าวไว้ แต่ค่อนข้างเป็นธรรมชาติสำหรับการทำสิ่งต่างๆเช่นการสร้างต้นไม้ค้นหาแบบไบนารีซึ่งมีโครงสร้างพิเศษมากมาย

โดยส่วนตัวแล้วฉันชอบเมทริกซ์ผู้ช่วยเพราะมันทำให้ปัญหาทุกประเภทง่ายขึ้นมากโดยใช้เครื่องมือจากทฤษฎีกราฟพีชคณิต (กำลัง kth ของเมทริกซ์ adjacency ให้จำนวนพา ธ ของความยาว k จากจุดยอด i ถึงจุดยอด j เช่นเพิ่มเมทริกซ์เอกลักษณ์ก่อนนำกำลัง kth เพื่อให้ได้จำนวนพา ธ ของความยาว <= k นำอันดับ n-1 รองของ Laplacian เพื่อรับจำนวนต้นไม้ที่ทอด ... และอื่น ๆ )

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


7

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

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

ความซับซ้อนของปริภูมิสำหรับวิธีเมทริกซ์จะเป็นไปตามแนวของ O (n ^ 2) (สมมติว่าขอบเป็นแบบทิศทางเดียว) หากกราฟของคุณเบาบางคุณจะมีเซลล์ว่างจำนวนมากในเมทริกซ์ของคุณ แต่ถ้ากราฟของคุณเชื่อมต่อกันอย่างสมบูรณ์ (e = n ^ 2) สิ่งนี้จะเปรียบเทียบได้ดีกับแนวทางแรก ตามที่ RG กล่าวไว้คุณอาจพลาดแคชน้อยลงด้วยวิธีนี้หากคุณจัดสรรเมทริกซ์เป็นหน่วยความจำชิ้นเดียวซึ่งอาจทำให้การติดตามขอบรอบ ๆ กราฟเร็วขึ้น

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


รายการขอบเป็นเรื่องปกติสำหรับอัลกอริทึมของ Kruskal ("สำหรับแต่ละขอบให้ค้นหาใน union-find") นอกจากนี้ Skiena (ฉบับที่ 2 หน้า 157) ยังพูดถึงรายการขอบเป็นโครงสร้างข้อมูลพื้นฐานสำหรับกราฟในไลบรารี Combinatorica ของเขา (ซึ่งเป็นไลบรารีที่ใช้งานทั่วไปของอัลกอริทึมจำนวนมาก) เขาพูดถึงว่าหนึ่งในเหตุผลนี้คือข้อ จำกัด ที่กำหนดโดยแบบจำลองการคำนวณของ Mathematica ซึ่งเป็นสภาพแวดล้อมที่ Combinatorica อาศัยอยู่
Evgeni Sergeev


4

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

struct Node {
    ... node payload ...
    Edge *first_in;    // All incoming edges
    Edge *first_out;   // All outgoing edges
};

struct Edge {
    ... edge payload ...
    Node *from, *to;
    Edge *prev_in_from, *next_in_from; // dlist of same "from"
    Edge *prev_in_to, *next_in_to;     // dlist of same "to"
};

ค่าใช้จ่ายของหน่วยความจำมีขนาดใหญ่ (2 พอยน์เตอร์ต่อโหนดและ 6 พอยน์เตอร์ต่อขอบ) แต่คุณจะได้รับ

  • การแทรกโหนด O (1)
  • การแทรกขอบ O (1) (ให้ตัวชี้ไปที่โหนด "จาก" และ "ถึง")
  • O (1) การลบขอบ (กำหนดตัวชี้)
  • การลบโหนด O (deg (n)) (กำหนดตัวชี้)
  • O (deg (n)) ค้นหาเพื่อนบ้านของโหนด

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

คำอธิบายรายละเอียดของวิธีการนี้สามารถใช้ได้ที่นี่


3

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

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

รายการ adjacency - เป็นเพียงรายการของโหนดที่เชื่อมต่อ - ดูเหมือนว่าหน่วยความจำจะมีประสิทธิภาพมากที่สุด แต่ก็อาจจะช้าที่สุดด้วย

การย้อนกลับกราฟกำกับทำได้ง่ายด้วยการแสดงเมทริกซ์และทำได้ง่ายด้วยรายการ adjacency แต่ไม่ค่อยดีนักกับการแสดงวัตถุ / ตัวชี้

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