هياكل البيانات والخوارزميات / البنى المرتبة والاستدلال على الرسوم / الدرس 8
أشجار البحث الثنائية وعمليات المرور
اعتمد ترتيب الأشجار الفرعية بدلا من التخمين المحلي، وعبّر عن كلفة البحث بالارتفاع الفعلي للشجرة.
في هذه الصفحة
أهداف الدرس
- تشمل قاعدة الترتيب الأشجار الفرعية كاملة
- أدرج وابحث عبر طريق واحد يبرره الثابت
- يكشف المرور الوسطي العضوية بالترتيب
تشمل قاعدة الترتيب الأشجار الفرعية كاملة
تربط شجرة البحث الثنائية كل عقدة بمفتاح وابنين على الأكثر. وفقا لعقد المجموعة في هذا الدرس، يكون كل مفتاح في الشجرة الفرعية اليسرى أصغر حصرا من مفتاح العقدة، وكل مفتاح في اليمنى أكبر حصرا منه. لا يكفي فحص الأبناء المباشرين فقط: فقد يحقق مفتاح عميق في موضع خاطئ مقارنته بأبيه، مع مخالفته حدا يفرضه أحد أسلافه. حدّد سياسة التكرار قبل الإدراج. تتجاهل الأمثلة المفاتيح المتساوية المكررة، فتمثل عضوية فريدة بدلا من عدد مرات الظهور. ويمكن لمجموعة متعددة العناصر حفظ عداد عند كل مفتاح، لكن ذلك نموذج مختلف له قواعد تحديث تناسبه.
أدرج وابحث عبر طريق واحد يبرره الثابت
ينزل البرنامج الأول وفقا للمقارنات حتى يجد ابنا فارغا أو مفتاحا موجودا. في كل خطوة، لا يمكن للشجرة الفرعية المستبعدة أن تحوي المفتاح المطلوب تحت الثابت. وتجعل إعادة الجذر إنشاء شجرة كانت فارغة في البداية أمرا صريحا. يستخدم البحث الحجة نفسها، وينتهي عندما يبلغ المساواة أو ابنا غائبا. لا تعيد الشيفرة موازنة الشجرة؛ لذلك تكون كلفة العملية O(h) لارتفاع h، ولا تصبح O(log n) تلقائيا. وقد ينتج عن إدراج مفاتيح مختلفة مرتبة سلسلة ذات ارتفاع خطي.
class Node:
def __init__(self, key):
self.key = key
self.left = self.right = None
def insert(root, key):
if root is None:
return Node(key)
node = root
while True:
if key == node.key:
return root
side = "left" if key < node.key else "right"
child = getattr(node, side)
if child is None:
setattr(node, side, Node(key))
return root
node = child
def contains(root, key):
while root is not None:
if key == root.key:
return True
root = root.left if key < root.key else root.right
return False
root = None
for key in [5, 2, 7, 2]:
root = insert(root, key)
print(contains(root, 7))
print(contains(root, 8))
True
False
لا ينشئ تكرار المفتاح 2 عقدة أخرى. ويتبع البحث طريقا واحدا يعتمد طوله على الشجرة التي بُنيت.
يكشف المرور الوسطي العضوية بالترتيب
يستخدم البرنامج الثاني مكدسا صريحا بدلا من عودية Python. ينزل يسارا مع تذكر الأسلاف، ثم يصدر أقرب عقدة لم ينته منها، ويستكشف بعدها شجرتها الفرعية اليمنى. الثابت أن المكدس يسجل طريق العقد التي يُستكمل جانبها الأيسر قبل إصدارها هي. تُدفع كل عقدة إلى المكدس وتُسحب منه مرة واحدة، فيستغرق المرور O(n) زمنا وO(h) مساحة للمكدس، دون احتساب مساحة الناتج المعاد O(n). وتكون المفاتيح المصدرة متزايدة في شجرة بحث صارمة صحيحة. يعمل المرور أيضا على شجرة ثنائية عادية، لكن لا ضمان لترتيب الناتج عندئذ.
class Node:
def __init__(self, key, left=None, right=None):
self.key, self.left, self.right = key, left, right
def inorder(root):
output, stack = [], []
current = root
while current is not None or stack:
while current is not None:
stack.append(current)
current = current.left
current = stack.pop()
output.append(current.key)
current = current.right
return output
root = Node(5, Node(2, Node(1), Node(3)), Node(7))
print(inorder(root))
print(inorder(None))
[1, 2, 3, 5, 7]
[]
يمثل المكدس الصريح الأسلاف الذين لم ينته المرور منهم، ويجنب هذه العملية الاعتماد على عمق العودية.
يجب أن يحفظ الحذف أكثر من مؤشر واحد
يفصل حذف ورقة رابط ابن واحد. ويمكن عند حذف عقدة ذات ابن واحد إعادة وصل أبيها بذلك الابن. وعندما يكون لها ابنان، يستطيع التنفيذ استبدال المفتاح بخلفه أو سلفه في الترتيب الوسطي، ثم حذف العقدة المقابلة التي أصبحت حالتها أبسط. ويجب تحديث روابط الآباء أو العدادات أو الارتفاعات أو أحجام الأشجار الفرعية إذا كان التمثيل يحتفظ بها. لا تصف الحذف بأنه تبديل قيم اعتباطية حتى تبدو الصورة معقولة. يجب أن يحفظ الاستبدال جميع الحدود التي تفرضها الأسلاف، وأي عقد خارجي للهوية، خصوصا إذا كان المستدعون يحتفظون بمراجع للعقد لا للمفاتيح.
الموازنة ثابت إضافي يجب الحفاظ عليه
تقيد AVL وغيرها من أشجار البحث المتوازنة الارتفاع بقواعد محددة للتحديث والدوران. ولا يوفر الشكل الثنائي وحده تلك الضمانات. يحفظ الدوران ترتيب المفاتيح في المرور الوسطي، مع تغيير علاقات الأسلاف محليا، ويجب أن تتبع الحقول المضافة التغيير البنيوي. تعلّم ثابت مخطط الموازنة المختار وبرهانه قبل إعلان تحديثات لوغاريتمية في أسوأ حالة. ولبيانات ثابتة مرتبة، قد تكون مصفوفة ببحث ثنائي أبسط وأقل مساحة. اختر الشجرة عندما يبرر مزيج عملياتها الكامل وواجهة ترتيبها تعقيد التعديل الإضافي، لا لمجرد أن اسم الشجرة يوحي بالكفاءة.
تحقّق من حدود الأسلاف والحالات المتأثرة بشكل الشجرة
اختبر الأشجار الفارغة والمنفردة، والمفاتيح المكررة وفق سياسة المجموعة، وترتيب إدراج متوازنا، وترتيب إدراج مرتب القيم. قارن ناتج المرور الوسطي بفرز مفاتيح المدخل الفريدة، وقارن العضوية بمجموعة مرجعية. ينبغي للمدقق تمرير الحدين الأدنى والأعلى اللذين تفرضهما الأسلاف، لا مقارنة الأب بأبنائه فقط. وافحص أيضا عدم مشاركة العقد أو وجود دورات عندما يعد التمثيل بشجرة. قد تكشف الاختبارات ارتفاعا منحطا دون أن يكون ذلك خطأ صحة في هذا التنفيذ غير المتوازن؛ أما الخطأ فهو ادعاء ضمان للارتفاع لا تحافظ عليه الشيفرة أصلا.
الأخطاء الشائعة وإصلاحها
مخالفة مفتاح عميق لحد أحد أسلافه رغم اجتياز الفحوص المحلية
- لماذا يحدث؟
- اقتصر التحقق على مقارنة الأبناء المباشرين.
- كيف تصلحه؟
- مرّر كل الحدود الدنيا والعليا ذات الصلة خلال التحقق.
- كيف تتجنب تكراره؟
- أدرج عقدة تابعة في موضع خاطئ على عمق مستويين.
ادعاء بحث لوغاريتمي في شجرة على شكل سلسلة
- لماذا يحدث؟
- اختلط التفرع الثنائي بالموازنة المحفوظة.
- كيف تصلحه؟
- عبّر عن الكلفة بـ O(h)، واذكر هل يوجد حد مضمون لـ h.
- كيف تتجنب تكراره؟
- اختبر ترتيب الإدراج المرتب، وافحص ارتفاع الشجرة.
تغيير الإدراج المكرر لما وُعد بأنه مجموعة
- لماذا يحدث؟
- لم تُحدد دلالة التكرار، أو طُبقت بصورة غير متسقة.
- كيف تصلحه؟
- اختر التجاهل أو الاستبدال أو العد، وطبّق السياسة في جميع العمليات.
- كيف تتجنب تكراره؟
- كرر المفتاح نفسه في اختبارات البناء والعضوية.
اختبر فهمك
1. لماذا يمكن لعقدة حفيدة في يسار الجذر، ذات مفتاح أكبر منه، أن تبطل الشجرة؟
اعرض الحل المشروح
يجب أن يكون كل مفتاح في الشجرة الفرعية اليسرى للجذر أصغر من مفتاح الجذر، بصرف النظر عن أبيه المباشر. قد تصح المقارنة المحلية بينما يفشل هذا الشرط الذي يفرضه السلف. يحتاج التحقق إلى حدود موروثة، أو إلى حجة مكافئة تثبت الترتيب على مستوى الشجرة كلها.
2. هل يجعل استبدال العودية بمكدس البحث في شجرة غير متوازنة لوغاريتميا؟
اعرض الحل المشروح
لا. يغير ذلك كيفية تخزين العمل غير المنتهي، ولا يغير عدد العقد على طريق البحث. تظل السلسلة ذات ارتفاع خطي. تحتاج الموازنة إلى ثابت بنيوي إضافي وقواعد تحديث؛ أما المكدس الصريح فيجنب الاعتماد على مكدس استدعاءات اللغة فقط.
أهم ما تعلمته
- تشمل قاعدة الترتيب الأشجار الفرعية كاملة
- تحقّق من حدود الأسلاف والحالات المتأثرة بشكل الشجرة