Vertical Order Traversal of a Binary Tree
تُخزَّن شجرة ثنائية في المصفوفة root بترتيب الكومة: يكون الجذر عند الفهرس 0، ويكون ابنا الفهرس i عند 2*i+1 (الأيسر) و2*i+2 (الأيمن)، وتشير -1 إلى موضع فارغ، وقد تنتهي المصفوفة بعناصر -1 إضافية.
يكون الجذر في العمود 0؛ ويكون الابن الأيسر على بُعد عمود واحد إلى يسار أبيه، والابن الأيمن على بُعد عمود واحد إلى يمينه. أعد قائمة لكل عمود، من اليسار إلى اليمين. داخل كل عمود، رتّب العقد من الأعلى إلى الأسفل، والعقد التي تشترك في العمود والعمق رتّبها تصاعديًا حسب القيمة.
الدالة
- rootinteger-array
- الشجرة بترتيب الكومة، مع -1 للمكان الفارغ
- تُرجعinteger-2d-array
- قائمة واحدة من القيم لكل عمود، بدءًا بالعمود الأيسر، وكل قائمة من الأعلى إلى الأسفل
القيود
1 ≤ root.length ≤ 32767- كل
root[i]إما-1أو قيمة تحقق0 ≤ root[i] ≤ 5000، ولا تظهر أي قيمة مرتين. root[0]لا تساوي أبدًا-1، لذا تحتوي الشجرة على عقدة واحدة على الأقل.- قد تنتهي المصفوفة بعناصر
-1إضافية بعد العقدة الأخيرة. - كلا ابني الموضع الفارغ فارغان أيضًا، والشجرة لها بحد أقصى
14مستوى.
أمثلة
- المدخلات
- root = [5, 2, 9, 4, 7, 1, 6]
- المخرجات
- [[4], [2], [5, 1, 7], [9], [6]]
- الشرح
- يحتوي العمود 0 على الجذر
5، ثم7و1على بُعدين أسفل منه. يشترك هذان العنصران في العمود والعمق، لذا يأتي1قبل7، رغم أن7يقع إلى اليسار أكثر.
- المدخلات
- root = [1, 2, 3, -1, 4, -1, -1, -1, -1, 5]
- المخرجات
- [[2, 5], [1, 4], [3]]
- الشرح
- يحتوي العمود -1 على
2عند العمق 1 و5عند العمق 3 (الابن الأيسر لـ4). يحتوي العمود 0 على الجذر1و4عند الفهرس 4. يحتوي العمود 1 على3فقط.
- المدخلات
- root = [6, -1, -1]
- المخرجات
- [[6]]
- الشرح
- ليس لـ
6أبناء، لذا تتكوّن الإجابة من عمود واحد،[6].
سؤال إضافي
كل قيمة لا تتجاوز 5000. هل يمكنك الاستفادة من ذلك للاستغناء عن الفرز بالمقارنة وإنهاء التنفيذ في زمن O(n + 5000)؟
تلميحات
افتحها واحدًا تلو الآخر. كل تلميح يكشف أكثر قليلًا.
لكل عقدة عمود وعمق وقيمة. إذا كانت لديك كل هذه الثلاثيات في قائمة واحدة، فكيف سترتبها وفق ترتيب الإجابة؟
يُخرج البحث بالعرض العقد بالفعل من الأعلى إلى الأسفل. ما الجزء الذي لا يزال مفقودًا من الترتيب عندما تشترك عدة عقد في عمود ومستوى واحد؟
عالج الشجرة مستوىً واحدًا في كل مرة. رتّب كل مستوى حسب العمود، ثم حسب القيمة، وأضف كل قيمة إلى قائمة عمودها، وفي النهاية اقرأ القوائم بدءًا من أصغر عمود وصولًا إلى أكبرها.
الحل
لكل عقدة ثلاثة مفاتيح: العمود والعمق والقيمة. الإجابة هي العقد مرتبة حسب هذه المفاتيح، في قائمة لكل عمود. عند التعادل في العمود والعمق، يكون الترتيب حسب القيمة، وليس حسب الترتيب من اليسار إلى اليمين الذي تصادفها به عملية الاجتياز، لذا يجب الفرز: إما جميع العقد دفعة واحدة، أو كل مستوى من البحث بالعرض أولًا.
سجّل كل عقدة، ثم رتّبها
الفكرة
زُر كل عقدة مرة واحدة، بأي ترتيب، وسجّل الثلاثية (العمود، العمق، القيمة). تؤدي الخطوة إلى ابن إلى زيادة العمق بمقدار 1 وتحريك العمود خطوة واحدة إلى اليسار أو اليمين. ثم رتّب الثلاثيات حسب العمود والعمق والقيمة. تصبح عقد كل عمود الآن متجاورة، بالترتيب المطلوب. مرّ على القائمة المرتبة وابدأ قائمة داخلية جديدة كلما تغيّر العمود.
في المثال 1، تُرتّب ثلاثيات العمود 0 كما يلي: (0, 0, 5)، (0, 2, 1)، (0, 2, 7). تهيمن عملية الترتيب، O(n log n)، على المرور ذي التعقيد O(n)، وتشغل الثلاثيات مساحة O(n).
الخوارزمية
- أضِف (الفهرس 0، العمود 0، العمق 0) إلى المكدس.
- أزِل عقدة، وسجّل (العمود، العمق، القيمة)، وأضِف كل ابن حقيقي باستخدام العمود
col-1أوcol+1والعمقdepth+1. - عندما يصبح المكدس فارغًا، رتّب الثلاثيات حسب العمود، ثم العمق، ثم القيمة.
- استعرضها، وابدأ قائمة جديدة كلما تغيّر العمود.
def verticalTraversal(root):
n = len(root)
entries = [] # (column, depth, value) for every node
stack = [(0, 0, 0)] # (node index, column, depth); the root is never empty
while stack:
i, col, depth = stack.pop()
entries.append((col, depth, root[i]))
for child, child_col in ((2 * i + 1, col - 1), (2 * i + 2, col + 1)):
if child < n and root[child] != -1:
stack.append((child, child_col, depth + 1))
# Column, then depth, then value: exactly the order of the answer.
entries.sort()
result = []
for k, (col, depth, value) in enumerate(entries):
if k == 0 or col != entries[k - 1][0]:
result.append([]) # a new column starts
result[-1].append(value)
return resultرتّب داخل كل مستوى، مستوىً تلو الآخر
الفكرة
يتعامل البحث بعرض الشجرة مع مستوى واحد في كل مرة. إذا أضافت كل عقدة قيمتها إلى قائمة عمودها عند معالجتها، فستكون كل قائمة مرتبة من الأعلى إلى الأسفل، لذا لا حاجة إلى فرز الأعماق. تحدث حالات التعادل ضمن المستوى نفسه فقط، لذا رتّب كل مستوى حسب العمود، ثم حسب القيمة، قبل معالجته.
في المثال 1، يصل المستوى 2 بالشكل 4, 7, 1, 6 في الأعمدة -2, 0, 0, 2، ويُفرز إلى 4, 1, 7, 6، لذا يصبح العمود 0 بالشكل [5, 1, 7]. تشكّل الأعمدة المشغولة نطاقًا متصلًا، لذا يكفي تتبّع أصغرها وأكبرها.
يستغرق فرز مستوى يضم m من العقد وقتًا قدره O(m log m). وتظل الحالة الأسوأ O(n log n)، لأن المستوى الأخير من شجرة كاملة يضم نحو نصف العقد. أما المساحة فهي O(n).
الخوارزمية
- ابدأ بمستوى يحتوي على (الفهرس 0، العمود 0).
- رتّب المستوى حسب العمود، ثم القيمة.
- وبهذا الترتيب، أضف كل قيمة إلى قائمة عمودها، واجمع أبناءها الفعليين، ذوي العمودين
col-1وcol+1، في المستوى التالي. - كرّر ذلك حتى يصبح المستوى فارغًا، مع تتبّع أصغر عمود وأكبره.
- أعِد القوائم من أصغر عمود إلى أكبره.
def verticalTraversal(root):
n = len(root)
columns = {} # column -> its values, top to bottom
level = [(0, 0)] # (node index, column) of every node on the current level
while level:
# Every node here has the same depth: order them by column, then value.
level.sort(key=lambda node: (node[1], root[node[0]]))
next_level = []
for i, col in level:
columns.setdefault(col, []).append(root[i])
for child, child_col in ((2 * i + 1, col - 1), (2 * i + 2, col + 1)):
if child < n and root[child] != -1:
next_level.append((child, child_col))
level = next_level
# The columns that hold a node form one unbroken range.
return [columns[col] for col in range(min(columns), max(columns) + 1)]
أخطاء شائعة وحالات حدّية
- ترك العناصر المتعادلة بترتيب من اليسار إلى اليمين. يُنتج البحث بعرض الشجرة من دون الفرز في كل مستوى
[5, 7, 1]للعمود 0 في المثال 1، وليس[5, 1, 7]. - إلحاق القيم أثناء اجتياز الشجرة بعمق من دون فرز. في
[1, 2, 3, -1, 4, -1, -1, -1, -1, -1, 5]، يُلحق الاجتياز الذي يبدأ باليسار5(عمق 3) بالعمود 1 قبل3(عمق 1). - الفرز حسب العمود والقيمة، ولكن ليس حسب العمق. عندها تصبح قراءة العمود 0 في المثال 1 هي
[1, 5, 7]. - استخدام السلاسل النصية كمفاتيح للأعمدة. عند الفرز كنص، تأتي
"-1"قبل"-2"، لذا تظهر الأعمدة الواقعة إلى يسار 0 بترتيب خاطئ.
أسئلة شائعة3
ما التعقيد الزمني لل traversal العمودي لشجرة ثنائية؟
ترتيب جميع العقد حسب العمود والعمق والقيمة يتطلب O(n log n)، وهو ما يهيمن على المرور ذي التعقيد O(n). والترتيب مستوىً بمستوى له أسوأ حالة مماثلة، لأن المستوى الأخير من الشجرة الممتلئة يحتوي على نحو نصف العقد. وتبلغ المساحة O(n).
هل يمكن إجراء اجتياز الترتيب العمودي دون فرز؟
فقط إذا كانت حالات التعادل تُرتَّب من اليسار إلى اليمين. عندئذٍ، تعطي عملية بحث بالعرض تُضيف كل قيمة إلى قائمة عمودها الإجابة في O(n). أما إذا كانت حالات التعادل تُرتَّب حسب القيمة، فستحتاج إلى بعض الفرز، ولكن داخل المستوى فقط، إذ لا يمكن أن تتعادل إلا العقد الموجودة في المستوى نفسه.
ما علاقة اجتياز الترتيب العمودي بالمنظر العلوي والمنظر السفلي؟
تجمع العروض الثلاثة العُقد بحسب أرقام الأعمدة نفسها. يحتفظ العرض العلوي بالعقدة الأقل عمقًا في كل عمود، ويحتفظ العرض السفلي بالعقدة الأعمق. أما اجتياز الترتيب العمودي فيحتفظ بكل عقدة، وعندما تشترك عدة عقد في المستوى نفسه، يرتبها بحسب القيمة، بينما يختار العرضان العقد بحسب موضعها.
مسائل مشابهة
مسائل تعتمد على الأفكار نفسها. حلّ اثنتين أو ثلاث منها يثبّت النمط.
def verticalTraversal(root):
# اكتب الكود هنااختبار #1
اختبار #2
اختبار #3
وسيط
root = [5, 2, 9, 4, 7, 1, 6]
الإخراج المتوقع
[[4], [2], [5, 1, 7], [9], [6]]