هياكل البيانات والخوارزميات / العقود والتسلسلات والبحث / الدرس 3
العقد المرتبطة والملكية والمرور
افصل تغييرات المؤشرات ثابتة الزمن من البحث اللازم للعثور على موضعها، وحافظ على ثوابت إمكان الوصول.
في هذه الصفحة
أهداف الدرس
- يمثل الرابط إمكان الوصول، وليس إزاحة داخل مصفوفة
- احفظ الرأس القديم قبل اعتماد الرأس الجديد
- يتطلب عكس السلسلة حفظ الرابط الأمامي
يمثل الرابط إمكان الوصول، وليس إزاحة داخل مصفوفة
تحفظ القائمة المرتبطة الأحادية قيمة ورابط next في كل عقدة، ويحدد مرجع head العقدة الأولى. ويتطلب بلوغ الموضع k اتباع الروابط بدءا من الرأس، ما لم يتوفر مرجع آخر مناسب بالفعل. يختلف ذلك عن الوصول بالفهرس إلى مصفوفة. قد يغيّر الإدراج بعد عقدة معروفة عددا ثابتا من الروابط، لكن العثور على تلك العقدة قد يستغرق زمنا خطيا. اذكر المرجع الذي تستقبله العملية قبل إعلان كلفتها. ويمكن أن تجعل كلفة تخصيص الذاكرة، والكلفة الإضافية للكائنات، ومحلية الوصول في الذاكرة العقد المرتبطة أقل جاذبية من المصفوفات لكثير من أنماط العمل الفعلية، رغم أناقة عمليات المؤشرات.
احفظ الرأس القديم قبل اعتماد الرأس الجديد
يضيف البرنامج الأول القيم إلى بداية السلسلة. تشير العقدة الجديدة إلى الرأس القديم، ثم يصبح head مرجعا إلى تلك العقدة. وإذا عُكس ترتيب الخطوتين دون عناية، فقد تشير العقدة إلى نفسها، أو تصبح بقية السلسلة غير قابلة للوصول. الثابت هو أن اتباع next من head يزور جميع العقد المدرجة مرة واحدة بالضبط، وينتهي إلى None. ويترك المدخل الفارغ الرأس غائبا بصورة طبيعية. أما الدالة المساعدة to_list فزمنها خطي وتخصص قائمة للناتج؛ ولذلك لا تشملها دعوى أن الإضافة في البداية ثابتة الزمن.
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
def to_list(head):
result = []
while head is not None:
result.append(head.value)
head = head.next
return result
head = None
for value in [1, 2, 3]:
head = Node(value, head)
print(to_list(head))
[3, 2, 1]
تغيّر كل إضافة إلى البداية المرجع الأمامي، وتحفظ اللاحقة الموجودة بالفعل. ويُظهر المرور النهائي ترتيب الإدراج معكوسا.
يتطلب عكس السلسلة حفظ الرابط الأمامي
يعكس البرنامج الثاني السلسلة بطريقة تكرارية. وقبل تغيير current.next يحفظ العقدة التالية التي لم تُعالج بعد. تكون السابقة المعالجة سلسلة معكوسة ينتهي الجزء المعالج فيها عند previous، بينما يحدد current اللاحقة الأصلية المتبقية. ينقل كل تكرار عقدة واحدة بالضبط من اللاحقة إلى السابقة المعكوسة. وإذا لم يُحفظ following أولا، فإن استبدال current.next يفقد الطريق إلى العقد غير المعالجة. تستخدم الخوارزمية عددا ثابتا من المراجع المساعدة، وتزور العقد عددا خطيا من المرات، لكنها تعدّل الروابط الأصلية. ويمكن للمستدعين الذين يحتفظون بمراجع مشتركة إلى عقد داخلية ملاحظة هذا التعديل، ولذلك يجب أن يوافق استعمالهم عقد الملكية.
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
def reverse(head):
previous = None
current = head
while current is not None:
following = current.next
current.next = previous
previous = current
current = following
return previous
def values(head):
result = []
while head is not None:
result.append(head.value)
head = head.next
return result
head = Node(1, Node(2, Node(3)))
head = reverse(head)
print(values(head))
print(reverse(None))
[3, 2, 1]
None
يعيد العكس استخدام العقد الموجودة، بدلا من نسخ القيم إلى عقد جديدة. وتبقى السلسلة الفارغة فارغة.
تضيف مراجع الرأس والذيل ثوابت مترابطة
يمكن أن يجعل الاحتفاظ بمرجع tail الإلحاق بالنهاية ثابت الزمن عندما تكون السلسلة صحيحة. عندئذ يجب أن يغيب كل من head وtail في القائمة الفارغة، وأن يشيرا إلى العقدة نفسها في قائمة من عقدة واحدة. ويجب أن يمحو حذف العقدة الأخيرة المرجعين معا. وقد يجعل ذيل متقادم الإلحاقات اللاحقة تتصل بسلسلة لم تعد قابلة للوصول من head. تضيف القوائم الثنائية روابط إلى العقد السابقة، وتدعم عمليات أخرى على عقد معروفة، لكن كل تعديل يجب أن يحدث الاتجاهين باتساق. تمنح المراجع الإضافية عمليات مريحة مقابل عبء إضافي للحفاظ على الثوابت.
ميّز الدورات من مشاركة لاحقة مسموح بها
تعني الدورة أن تكرار اتباع next لا يصل أبدا إلى النهاية الغائبة، ولذلك قد يستمر مرور بسيط إلى ما لا نهاية. وليس وجود رأسين مختلفين يشتركان في لاحقة دليلا على دورة بالضرورة، لكن التعديل عبر أحد الرأسين يؤثر في البنية القابلة للوصول من الآخر. حدّد هل تسمح بالمشاركة. تستخدم معالجة الدورات لاحقا تقنيات تختلف عن عد القيم داخل مجموعة، لأن تساوي قيمتين لا يعني أنهما في العقدة نفسها؛ فالمهم هو هوية العقدة. وعند تصميم واجهة إنتاج، فكّر هل تفضّل deque أو list قياسية على كشف روابط قابلة للتعديل وإلزام كل مستدع بالحفاظ على إمكان الوصول.
تحقّق من التحويلات الحساسة لهوية العقد
اختبر السلسلة الفارغة، وعقدة واحدة، وعقدتين، وسلاسل أطول. وفي العكس افحص ترتيب القيم، وNone النهائية الجديدة، وظهور هويات العقد نفسها مرة واحدة بالضبط. ويجب أن يعيد العكس مرتين الروابط الأصلية لسلسلة صحيحة غير دورية ذات ملكية واضحة. قد يفوت اختبار القيم وحدها تكرار العقد عند تكرر قيمها؛ لذا أدرج قيما متساوية داخل عقد مختلفة الهوية. أبق العينات صغيرة، وضع حدا لأي مرور تشخيصي أثناء التحقيق في دورة محتملة. هذا تحقق محلي آمن من الخوارزمية، وليس اختبارا معياريا للذاكرة أو توجيها لتشغيل بنى مرتبطة إنتاجية.
الأخطاء الشائعة وإصلاحها
اختفاء بقية السلسلة أثناء العكس
- لماذا يحدث؟
- كُتب فوق next قبل حفظ المرجع الأمامي.
- كيف تصلحه؟
- احفظ following قبل إعادة ربط current.next.
- كيف تتجنب تكراره؟
- تتبّع مثالا من ثلاث عقد اعتمادا على هويتها.
استخدام ذيل متقادم للإلحاق بعد حذف العقدة الأخيرة
- لماذا يحدث؟
- حدّث ثابت الحالة الفارغة head وحده.
- كيف تصلحه؟
- امح head وtail معا عند إزالة العقدة الأخيرة.
- كيف تتجنب تكراره؟
- اختبر الانتقال من الفراغ إلى عقدة واحدة، ومن عقدة واحدة إلى الفراغ.
اعتبار القيم المتساوية عقدة واحدة
- لماذا يحدث؟
- استُخدمت مساواة القيمة بدلا من هوية الكائن.
- كيف تصلحه؟
- قارن هوية العقدة عند فحص الخصائص البنيوية.
- كيف تتجنب تكراره؟
- أدرج قيما مكررة في عقد مختلفة.
اختبر فهمك
1. هل يكون الإدراج بعد قيمة ثابت الزمن دائما في قائمة مرتبطة أحادية؟
اعرض الحل المشروح
لا. يمكن للإدراج بعد عقدة مقدمة بالفعل أن يغيّر عددا ثابتا من الروابط. لكن العثور على العقدة التي تحمل القيمة المطلوبة قد يحتاج إلى مرور كامل، كما أن تكرار القيمة يحتاج إلى قاعدة لتحديد العقدة المختارة. اذكر كلفتي البحث والتعديل بصورة منفصلة.
2. لماذا يفيد فحص العكس مرتين، مع بقائه غير كاف؟
اعرض الحل المشروح
يمكنه كشف الروابط المفقودة والمعالجة الخاطئة للنهاية، لكن قد ينجح تنفيذ معيب رغم ذلك في عينات قيم مختارة. تحقّق أيضا من الهويات، وعدد العقد الدقيق، والترتيب المتوقع بعد عكس واحد، وحالتي الفراغ والعقدة الواحدة. ويبقى ثابت الخوارزمية وحجة انتهائها ضروريين.
أهم ما تعلمته
- يمثل الرابط إمكان الوصول، وليس إزاحة داخل مصفوفة
- تحقّق من التحويلات الحساسة لهوية العقد