طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 101 · تركيب البيانات والخوارزميات
الدرس 13 · الوحدة 7: اختيار تركيب بيانات لحل مشكلة

القائمة المترابطة

نبني بأيدينا أول تركيب بيانات: سلسلة عقد يعرف كل منها جاره. ⏱ نحو 40 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح بنية القائمة المترابطة: العقدة والرأس والمؤشر.
  • تنفذ الإضافة في الأول والآخر والبحث والحذف.
  • تتعامل مع الحالات الخاصة: القائمة الفارغة وحذف الرأس.
  • تقارن تعقيد عملياتها بـ list.

١عقد مربوطة بمؤشرات

القائمة المترابطة Linked List سلسلة من العقد Nodes. كل عقدة تحمل قيمة data ومؤشرًا next إلى العقدة التالية. لا تحتاج خانات متجاورة في الذاكرة؛ يكفي أن تعرف كل عقدة جارتها.

headأحمدdatanextسارةdatanextخالدdatanextمنىdatanextNone
القائمة المترابطة: نحفظ الرأس head فقط، ونصل إلى البقية بتتبع next.
main.py
class Node:    def __init__(self, data):        self.data = data        self.next = None a = Node("أحمد")b = Node("سارة")c = Node("خالد")a.next = bb.next = c cur = awhile cur is not None:    print(cur.data)    cur = cur.next
Shell×
>>> %Run main.py
أحمد
سارة
خالد
>>>

٢الإضافة في الأول: O(1)

هنا تتفوق القائمة المترابطة على list: الإضافة في الأول لا تزيح شيئًا. ننشئ عقدة جديدة تشير إلى الرأس القديم، ثم تصبح هي الرأس:

🔎 تتبّع: push_front
main.py
class Node:    def __init__(self, data):        self.data = data        self.next = None head = Node("سارة")node = Node("أحمد")node.next = headhead = nodeprint(head.data, "←", head.next.data)
Variables · المتغيّرات في الذاكرة
Shell×

٣القائمة المترابطة كاملة

نجمع العمليات في كلاس LinkedList. هذا هو مشروع الخطة الرسمية: «تنفيذ هيكل بيانات بسيط: إضافة وحذف وبحث»:

main.py
class Node:    def __init__(self, data):        self.data = data        self.next = None class LinkedList:    def __init__(self):        self.head = None        self.size = 0     def push_front(self, data):          # O(1)        node = Node(data)        node.next = self.head        self.head = node        self.size += 1     def append(self, data):              # O(n): نمر حتى الآخر        node = Node(data)        if self.head is None:            self.head = node        else:            cur = self.head            while cur.next is not None:                cur = cur.next            cur.next = node        self.size += 1     def find(self, data):                # O(n)        cur, i = self.head, 0        while cur is not None:            if cur.data == data:                return i            cur, i = cur.next, i + 1        return -1     def remove(self, data):              # O(n)        prev, cur = None, self.head        while cur is not None:            if cur.data == data:                if prev is None:                    self.head = cur.next     # حذف الرأس                else:                    prev.next = cur.next     # تخطي العقدة                self.size -= 1                return True            prev, cur = cur, cur.next        return False     def to_list(self):        out, cur = [], self.head        while cur is not None:            out.append(cur.data)            cur = cur.next        return out names = LinkedList()names.append("سارة")names.append("خالد")names.push_front("أحمد")print(names.to_list(), names.size)print("موقع خالد:", names.find("خالد"))names.remove("سارة")print(names.to_list(), names.size)
Shell×
>>> %Run main.py
['أحمد', 'سارة', 'خالد'] 3
موقع خالد: 2
['أحمد', 'خالد'] 2
>>>

٤الحذف: تخطَّ العقدة

لحذف عقدة من الوسط نجعل العقدة السابقة تشير إلى ما بعدها، فتخرج من السلسلة. لذلك نحتاج متغيرين أثناء المرور: prev و cur. وحذف الرأس حالة خاصة: الرأس الجديد هو التالي.

الحالةما نفعل
القائمة فارغةلا شيء: نُرجع False
العنصر في الرأسhead = head.next
العنصر في الوسط أو الآخرprev.next = cur.next
العنصر غير موجودنصل إلى None ونُرجع False

٥مقارنة مع list

العمليةlist (مصفوفة)القائمة المترابطة
الوصول إلى العنصر رقم iO(1)O(n): نمر من الرأس
الإضافة في الأولO(n)O(1)
الإضافة في الآخرO(1)O(n)، أو O(1) إن حفظنا مؤشرًا للذيل tail
الحذف من الأولO(n)O(1)
البحثO(n)O(n)
الذاكرةخانات متجاورةمؤشر إضافي لكل عنصر

عمليًّا في بايثون تبقى list الأسرع لأغلب الاستخدامات. لكن فهم القائمة المترابطة أساس لتراكيب كثيرة: المكدس والطابور والأشجار والرسوم، وهي مستخدمة بكثرة في لغات مثل C و Java.

توقّع وتحقّق

ماذا تحفظ كل عقدة؟

data و next.

الإضافة في أول القائمة المترابطة:

خطوتان.

الوصول إلى العنصر الخامس:

لا عناوين متجاورة.

حذف عقدة من الوسط:

تخطي العقدة.

الخلاصة

تحدٍّ برمجي: اقلب القائمة المترابطة

متقدم

أضف إلى LinkedList دالة reverse تقلب القائمة في المكان بتغيير المؤشرات فقط، دون إنشاء عقد جديدة.

المطلوب:
  • ثلاثة متغيرات: prev و cur و nxt.
  • في كل خطوة: احفظ cur.next، ثم اجعل cur.next = prev، ثم تقدّم.
  • في الآخر: head = prev.
  • اطبع القائمة قبل القلب وبعده.

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
class Node:    def __init__(self, data):        self.data = data        self.next = None class LinkedList:    def __init__(self):        self.head = None        self.size = 0     def push_front(self, data):          # O(1)        node = Node(data)        node.next = self.head        self.head = node        self.size += 1     def append(self, data):              # O(n): نمر حتى الآخر        node = Node(data)        if self.head is None:            self.head = node        else:            cur = self.head            while cur.next is not None:                cur = cur.next            cur.next = node        self.size += 1     def find(self, data):                # O(n)        cur, i = self.head, 0        while cur is not None:            if cur.data == data:                return i            cur, i = cur.next, i + 1        return -1     def remove(self, data):              # O(n)        prev, cur = None, self.head        while cur is not None:            if cur.data == data:                if prev is None:                    self.head = cur.next     # حذف الرأس                else:                    prev.next = cur.next     # تخطي العقدة                self.size -= 1                return True            prev, cur = cur, cur.next        return False     def to_list(self):        out, cur = [], self.head        while cur is not None:            out.append(cur.data)            cur = cur.next        return out     def reverse(self):        pass lst = LinkedList()for x in [1, 2, 3, 4]:    lst.append(x)print(lst.to_list())lst.reverse()print(lst.to_list())
المطلوب×
[1, 2, 3, 4]
[4, 3, 2, 1]
💡 تلميح 1
nxt = cur.next أولًا، وإلا فقدت بقية القائمة.
💡 تلميح 2
بعد الحلقة: self.head = prev

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
class Node:    def __init__(self, data):        self.data = data        self.next = None class LinkedList:    def __init__(self):        self.head = None        self.size = 0     def push_front(self, data):          # O(1)        node = Node(data)        node.next = self.head        self.head = node        self.size += 1     def append(self, data):              # O(n): نمر حتى الآخر        node = Node(data)        if self.head is None:            self.head = node        else:            cur = self.head            while cur.next is not None:                cur = cur.next            cur.next = node        self.size += 1     def find(self, data):                # O(n)        cur, i = self.head, 0        while cur is not None:            if cur.data == data:                return i            cur, i = cur.next, i + 1        return -1     def remove(self, data):              # O(n)        prev, cur = None, self.head        while cur is not None:            if cur.data == data:                if prev is None:                    self.head = cur.next     # حذف الرأس                else:                    prev.next = cur.next     # تخطي العقدة                self.size -= 1                return True            prev, cur = cur, cur.next        return False     def to_list(self):        out, cur = [], self.head        while cur is not None:            out.append(cur.data)            cur = cur.next        return out     def reverse(self):        prev, cur = None, self.head        while cur is not None:            nxt = cur.next            cur.next = prev            prev = cur            cur = nxt        self.head = prev lst = LinkedList()for x in [1, 2, 3, 4]:    lst.append(x)print(lst.to_list())lst.reverse()print(lst.to_list())
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس