هياكل البيانات والخوارزميات / البنى المرتبة والاستدلال على الرسوم / الدرس 10
تمثيل الرسوم والبحث بالعرض والعمق
عرّف الرؤوس والحواف صراحة، ثم حافظ على ثوابت الاكتشاف والعمل غير المنتهي في المرور.
في هذه الصفحة
أهداف الدرس
- التمثيل جزء من عقد المدخلات
- يكتشف BFS أقل عدد حواف ضمن طبقات
- يحتاج DFS إلى تذكر موضع استئناف كل إطار
التمثيل جزء من عقد المدخلات
يحوي الرسم رؤوسا وحوافا، وتختلف دلالة الحواف الموجهة وغير الموجهة. تحفظ قائمة المجاورة الجيران الخارجين لكل رأس، وتتيح مسح الحواف الممثلة. أدرج الرؤوس المعزولة صراحة، وتأكد من انتماء معرّفات الجيران إلى مجموعة الرؤوس المقبولة. ولا يمكن اعتبار إدخال مجاورة مفقود فارغا إلا إذا نص العقد المختار على ذلك. تحتاج الحواف المتوازية والحلقات الذاتية أيضا إلى سياسة محددة. وتقدم مصفوفة المجاورة كلف وصول وتخزين مختلفة. اذكر هل V وE يشيران إلى الرسم كله، أم إلى المكوّن الذي يمكن الوصول إليه من مصدر مختار فقط.
يكتشف BFS أقل عدد حواف ضمن طبقات
يسجل البرنامج الأول مسافة الرأس بمجرد إدراجه في الطابور. لذلك يحوي الطابور رؤوسا اكتُشفت ولم تُستكمل معالجتها، بترتيب غير متناقص للمسافة. ويُبلَغ كل جار غير مزور لرأس مسافته d أول مرة عند مسافة d+1، وهي الدنيا عندما تكون الحواف غير موزونة. قد يؤدي تأخير التعليم حتى السحب إلى إدراج الرأس نفسه عدة مرات وتعقيد حدود حفظ الآباء وحجم العمل. لا تظهر الرؤوس غير القابلة للوصول في هذه النتيجة. هذا مثال مجاورة موجهة؛ ولا يوجد الاتجاه العكسي إلا إذا مُثل صراحة.
from collections import deque
def bfs_distances(graph, source):
if source not in graph:
raise ValueError("Unknown source")
distances = {source: 0}
queue = deque([source])
while queue:
vertex = queue.popleft()
for neighbor in graph[vertex]:
if neighbor not in distances:
distances[neighbor] = distances[vertex] + 1
queue.append(neighbor)
return distances
graph = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": [], "E": []}
print(sorted(bfs_distances(graph, "A").items()))
[('A', 0), ('B', 1), ('C', 1), ('D', 2)]
تُدرج D مرة واحدة رغم إشارة أبوين إليها؛ أما E فهي ممثلة في الرسم، لكن لا يمكن الوصول إليها من A.
يحتاج DFS إلى تذكر موضع استئناف كل إطار
يحاكي البرنامج الثاني DFS العودي بمكدس من مكررات الجيران. يتذكر كل إطار الحافة الخارجة التالية المطلوب فحصها، فلا ينتهي الرأس إلا بعد نفاد مكرره. يمكن لمكدس رؤوس بسيط إنتاج مرور للاكتشاف، لكنه لا يعيد تلقائيا ترتيب الإنهاء العودي. ويهم ترتيب الإنهاء في خوارزميات لاحقة للاعتماد والمكونات القوية الاتصال. يزور البرنامج غابة كاملة، إذ يبدأ جذرا جديدا لكل رأس غير مكتشف حسب ترتيب القاموس. ويحسم ترتيب قائمة الجيران تعادلات المرور، فيجعل ناتج المثال محددا دون ادعاء أن للرسم ترتيب DFS وحيدا من الناحية النظرية.
def dfs_finish_order(graph):
seen, finished = set(), []
for root in graph:
if root in seen:
continue
seen.add(root)
stack = [(root, iter(graph[root]))]
while stack:
vertex, neighbors = stack[-1]
neighbor = next(neighbors, None)
if neighbor is None:
stack.pop()
finished.append(vertex)
elif neighbor not in seen:
seen.add(neighbor)
stack.append((neighbor, iter(graph[neighbor])))
return finished
graph = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": [], "E": []}
print(dfs_finish_order(graph))
['D', 'B', 'C', 'A', 'E']
يستخدم هذا العقد رؤوسا نصية؛ لذلك يصح استخدام None علامة على نفاد المكرر. ويشكل الجذر الأخير E مكوّنا مستقلا.
احسب عمليات المسح وفقا للتمثيل المختار
عند استخدام قوائم المجاورة وعضوية مجموعة أو خريطة ثابتة الزمن المتوقع، تستطيع غابة BFS أو DFS كاملة مسح كل رأس وحافة عددا محدودا من المرات، فتكون كلفتها O(V+E). يستخدم المرور من مصدر واحد الجزء القابل للوصول، مع أن التحقق من المدخل قد يظل يمسح الرسم كله. ويضيف فرز كل قائمة جيران لتحديد الناتج كلف فرز؛ أما الأمثلة فتقبل قوائم مرتبة بدلا من ذلك. وقد تتطلب مصفوفة المجاورة مسح V جارا محتملا لكل رأس حتى مع قلة الحواف. لا تقتبس تعقيد قائمة المجاورة لتنفيذ بالمصفوفة دون تغيير تحليل الكلفة.
الاكتشاف لا يساوي تصنيف الدورات
تمنع مجموعة seen تكرار المرور، لكن كشف الدورات الموجهة يحتاج عموما إلى تمييز رؤوس DFS النشطة من الرؤوس المنتهية. تشير حافة إلى سلف نشط إلى حافة عودة، بينما قد تكون حافة إلى رأس منته غير ضارة. وفي الرسم غير الموجه تحتاج حافة الرجوع إلى الأب إلى معالجة مختلفة، وتعقد الحواف المتوازية تلك القاعدة. لا تعتبر كل جار مزور دليلا على دورة دون نموذج الرسم الصحيح. وبالمثل، لا يوفر DFS أقصر المسارات غير الموزونة لمجرد أنه يصل إلى الهدف في النهاية؛ إذ يقدم ثابت الطبقات في BFS هذا الضمان.
اختبر الوصول والطبقات وتغطية الغابة كاملة
استخدم رسوما صغيرة تضم رأسا معزولا، وشكلا معينا يشترك فرعاه في رأس تابع، وحلقة ذاتية، ودورة موجهة، ومكوّنين. قارن مسافات BFS بمرجع صغير بسيط، أو بإرخاء متكرر للحواف ذات الوزن الواحد. تحقّق من ظهور كل رأس قابل للوصول مرة واحدة، ومن انتهاء كل رأس ممثل مرة واحدة في غابة DFS الكاملة. وإذا كان ترتيب الناتج جزءا من العقد، فثبت ترتيب الجيران واختبره منفصلا عن إمكان الوصول. ليس وجود ترتيب مرور آخر صحيح للرسم عيبا تلقائيا. لا تحتاج العينات المحلية إلى خدمة رسوم شبكية أو حمل مرور واسع النطاق.
الأخطاء الشائعة وإصلاحها
إدراج رأس تابع مشترك في الطابور مرارا
- لماذا يحدث؟
- أُجل تسجيل الاكتشاف حتى سحب الرأس.
- كيف تصلحه؟
- علّم الرأس وعيّن مسافته عند إدراجه في الطابور.
- كيف تتجنب تكراره؟
- اختبر رسما على شكل معين.
استخدام DFS برهانا لأقصر مسار غير موزون
- لماذا يحدث؟
- اختلط إمكان الوصول بأقل عدد حواف.
- كيف تصلحه؟
- استخدم طبقات BFS لهذا العقد.
- كيف تتجنب تكراره؟
- أدرج فرع DFS أول طويلا وطريقا بديلا قصيرا.
إهمال مرور المصدر مكوّنا تتطلبه المهمة
- لماذا يحدث؟
- استُخدم جذر واحد قابل للوصول لمهمة تشمل الرسم كله.
- كيف تصلحه؟
- ابدأ غابة على جميع الرؤوس الممثلة التي لم تُكتشف.
- كيف تتجنب تكراره؟
- أدرج رؤوسا معزولة ومكونات منفصلة.
اختبر فهمك
1. لماذا تغيب E عن ناتج BFS، لكنها تظهر في ترتيب إنهاء DFS؟
اعرض الحل المشروح
يجيب BFS عن المسافات من A فقط، ولا يوجد طريق موجه من A إلى E. أما دالة DFS فتمر صراحة على كل جذر ممثل، فتزور E كمكوّن آخر. تجيب الدالتان عمدا عن عقدين مختلفين لتغطية الرسم.
2. هل تثبت حافة إلى أي رأس مزور وجود دورة موجهة؟
اعرض الحل المشروح
لا. ربما انتهت معالجة الجار بالفعل عبر فرع آخر. يحتاج تصنيف الدورات إلى حالة الطريق النشط أو إلى برهان مكافئ. يثبت علم seen الواحد الاكتشاف ومنع التكرار، وليس العلاقة الزمنية الكاملة بين رؤوس DFS.
أهم ما تعلمته
- التمثيل جزء من عقد المدخلات
- اختبر الوصول والطبقات وتغطية الغابة كاملة