Menu
Coddy logo textTech

Doubly Linked List (רשימה מקושרת דו כיוונית)

עודכן לאחרונה

רשימה מקושרת דו כיוונית היא רשימה מקושרת שבה כל צומת מחזיק שני מצביעים: next (לצומת הבא) ו-prev (לצומת הקודם). הקישור הנוסף לאחור מאפשר לעבור על הרשימה בשני הכיוונים ולמחוק צומת ב-O(1) כשכבר יש לכם הפניה אליו, כי אפשר להגיע ישירות לצומת שלפניו במקום ללכת מהראש. לחצו על הפעלה למעלה כדי לראות צמתים מתחברים ומתחברים מחדש בשני הכיוונים.

המחיר הוא מצביע נוסף לכל צומת ויותר ניהול: כל הכנסה ומחיקה חייבות לעדכן גם את next וגם את prev אצל השכנים. זה המבנה שמאחורי תורים דו צדדיים (deque) ומטמוני LRU רבים, שבהם הסרה מהירה משני הקצוות חשובה.

סיבוכיות זמן

פעולהסיבוכיותהערות
הכנסה בראש או בזנבO(1)עם מצביעי ראש וזנב
מחיקת צומת ידועO(1)מגיעים ל-prev ישירות, בלי הליכה
חיפושO(n)עדיין סריקה ליניארית
גישה לפי אינדקסO(n)אין גישה אקראית

רשימה מקושרת חד כיוונית מול דו כיוונית

היבטחד כיווניתדו כיוונית
מצביעים לכל צומת1 (next)2 (next + prev)
מעבר לאחורלאכן
מחיקת צומת ידועO(n) (מציאת prev)O(1)
תקורת זיכרוןנמוכה יותרגבוהה יותר

דוגמה מפורטת

בניית [10, 20, 30] על ידי הוספה לסוף, ואז מחיקת הצומת 20:

צעדמבנהפעולה
התחלהnullרשימה ריקה: head ו-tail שניהם null
הוספת 1010הצומת הראשון; head ו-tail מצביעים שניהם אליו, prev = next = null
הוספת 2010 <-> 20קובעים 10.next = 20 ו-20.prev = 10; tail = 20
הוספת 3010 <-> 20 <-> 30קובעים 20.next = 30 ו-30.prev = 20; tail = 30
מחיקת 2010 <-> 30בעזרת 20.prev ו-20.next: קובעים 10.next = 30 ו-30.prev = 10 ב-O(1)

מתי להשתמש ברשימה מקושרת דו כיוונית

השתמשו בה כאשרהימנעו ממנה כאשר
אתם צריכים לעבור על הרשימה או לחבר בה קטעים בשני הכיווניםאתם תמיד זזים רק קדימה: רשימה מקושרת חד כיוונית קלה יותר
אתם מוחקים צמתים כלשהם שכבר יש לכם הפניה אליהם (O(1))אתם ניגשים בעיקר לפי מיקום: השתמשו במערך לגישה של O(1)
אתם מממשים deque, מטמון LRU או היסטוריית ביטול פעולותהזיכרון מוגבל: מצביע ה-prev הנוסף מכפיל את תקורת הקישורים
הכנסות והסרות קורות לעתים קרובות בשני הקצוותהנתונים קטנים ומשתנים לעתים רחוקות: העתקות של מערך זולות יותר

קוד Doubly Linked List

מימוש נקי של Doubly Linked List שאפשר להריץ, ב-Python, JavaScript, Java, C++, C. בחרו שפה, העתיקו את הקוד, או פתחו אותו טעון מראש בעורך האונליין של Coddy.

קוד Doubly Linked List ב-Python

Python
1class Node:2    def __init__(self, value):3        self.value = value4        self.prev = None5        self.next = None6
7
8class DoublyLinkedList:9    def __init__(self):10        self.head = None11        self.tail = None12
13    def append(self, value):14        node = Node(value)15        if self.tail is None:16            self.head = self.tail = node17            return18        # Wire both directions: old tail <-> new node19        node.prev = self.tail20        self.tail.next = node21        self.tail = node22
23    def prepend(self, value):24        node = Node(value)25        if self.head is None:26            self.head = self.tail = node27            return28        node.next = self.head29        self.head.prev = node30        self.head = node31
32    def forward(self):33        values, current = [], self.head34        while current:35            values.append(current.value)36            current = current.next37        return values38
39    def backward(self):40        values, current = [], self.tail41        while current:42            values.append(current.value)43            current = current.prev44        return values45
46
47lst = DoublyLinkedList()48for value in [2, 3, 4]:49    lst.append(value)50lst.prepend(1)51
52print("Forward: ", lst.forward())53print("Backward:", lst.backward())
להריץ את הקוד הזה בעורך ה-Python אונליין

שאלות נפוצות על רשימה מקושרת דו כיוונית

מה ההבדל בין רשימה מקושרת חד כיוונית לדו כיוונית?
ברשימה מקושרת חד כיוונית יש מצביע אחד לכל צומת (next), ולכן אפשר לזוז רק קדימה. רשימה מקושרת דו כיוונית מוסיפה מצביע prev, שמאפשר לעבור אחורה ולמחוק צומת שיש לכם הפניה אליו ב-O(1), במחיר של מצביע נוסף לכל צומת ועדכון של שני קישורים בכל שינוי.
מתי רשימה מקושרת דו כיוונית שווה את הזיכרון הנוסף?
כשצריך מעבר לאחור או מחיקה ב-O(1) של צמתים כלשהם שכבר יש אליהם הפניה. המקרים הקלאסיים הם deque (תורים דו צדדיים) ומטמוני LRU, שבהם רשומות מוזזות ומוצאות משני הקצוות לעתים קרובות.
למה רשימה מקושרת דו כיוונית יכולה למחוק צומת ב-O(1)?
כדי להסיר צומת צריך לחבר את הצומת שלפניו לצומת שאחריו. ברשימה מקושרת חד כיוונית הייתם צריכים ללכת מהראש כדי למצוא את הצומת הקודם (O(n)); ברשימה מקושרת דו כיוונית מצביע ה-prev של הצומת נותן לכם את הקודם ישירות, ולכן החיבור מחדש הוא O(1).
רשימה מקושרת דו כיוונית או מערך: במה להשתמש?
השתמשו במערך כשצריך גישה של O(1) לפי אינדקס ומעבר ידידותי למטמון, וכשההכנסות קורות בעיקר בסוף. השתמשו ברשימה מקושרת דו כיוונית כשמכניסים או מסירים לעתים קרובות באמצע או בשני הקצוות בהינתן הפניה לצומת, כי זה O(1) לעומת הזזה של O(n) במערך. מערכים מנצחים בזיכרון ובמקומיות; הרשימה מנצחת בשינויים מבניים.
מהו צומת זקיף ברשימה מקושרת דו כיוונית?
צומת זקיף (או צומת דמה) הוא ממלא מקום שיושב לפני הראש ו/או אחרי הזנב, כך שהרשימה אף פעם לא באמת ריקה. הוא מבטל את בדיקות ה-null עבור head, tail והכנסות ומחיקות בגבולות, ומאפשר לכל פעולה ללכת באותו מסלול קוד של חיבור מחדש. מימושים רבים של מטמון LRU בסביבת ייצור משתמשים בשני זקיפים כדי לא לטפל בקצוות כמקרה מיוחד.
מהו הבאג הנפוץ ביותר במחיקה מרשימה מקושרת דו כיוונית?
שכחה לעדכן את head או את tail כשמוחקים את הצומת הראשון או האחרון, מה שמשאיר מצביע תלוי לצומת ששוחרר. בדקו תמיד אם node.prev הוא null (עדכנו את head) או אם node.next הוא null (עדכנו את tail) לפני שמחברים מחדש את השכנים.
איור של שפות התכנות ב-Coddy

לשלוט באלגוריתמים עם Coddy

להתחיל