- هذا المقال تكملة للجزء الأول الذي تجده هنا، لكن في الواقع الجزء الثاني يتضمن كل المعلومات المطلوبة ولا تحتاج لقراءة الجزء الأول لتفهمه.
- مسألة الـACM نفسها هنا
ما المطلوب من المسألة؟ كتابة برنامج به وصف لمجموعة من المبان، وحذف الخطوط المتقاطعة بحيث يظهر فقط المحيط العام لتلك المبان، كما توضح الصورة:
كيف نحل هذه المسألة؟ من حسن الحظ أن شرح السؤال نفسه يلمح لنا بالحل!
The skyline vector
should represent the "path" taken, for example, by a bug starting at
the minimum x-coordinate and traveling horizontally and vertically over
all the lines that define the skyline.
يقول لنا البرنامج أن الحل يصف تحركات حشرة تتحرك عبر صف المبان. تعال نتخيل أن هذه الحشرة هي نملة (بدلاً من البقة المذكورة في المسألة). الحركة في الصور التالية من اليسار لليمين:
في البداية ستسير النملة أفقياً حتى تصطدم بجدار بمبنى:
فإن وجدت المبنى ستبدأ في التحرك رأسياً لتتسلقه
إن وجدت نفسها عند سفح مبنى من جديد، فستبدأ في تسلقه، وإن وجدت نفسها عند هضبة فستنزل
في هذه الظروف، تكون النملة دائماً في حالة من أربع: واقفة على الأرض وأمامها مبنى (تحتاج أن تسير إليه)، أو واقفة على سطح مبنى (بدايته أو وسطه) تسير عليه، أو عند سفح مبنى (فتصعده)، أو عند نهاية سطح مبنى، سنسمي هذه الحالة هضبة، (فتهبط).
وكيف تنتقل بين الحالات؟
إن كانت واقفة على الأرض:
- تسير إلى سفح أول مبنى أمامها
إن كانت واقفة على سطح مبنى:
- يمكن أن يكون هناك مبنى يعوق سيرها، في تلك الحالة تسير إلى وجه المبنى العائق ثم تصبح حالتها "عند سفح".
- أو تكون حرة أن تسير لآخر المبنى الذي تقف عليه، وفي تلك الحالة تفعل ذلك وتكون حالتها "على هضبة"
إن كانت عند سفح:
- تصعد لأعلى المبنى، ثم تكون حالتها "على سطح مبنى"
إن كانت عند هضبة:
- إن كان هناك سطح مبنى يعوق هبوطها، تنزل إلى سطح ذلك المبنى ثم تصبح حالتها "على سطح مبنى"
- إن لم يكن هناك عائق تنزل إلى الأرض (ص = 0) وتصبح حالتها "على الأرض"
الملخص:
لم يبق سوى كتابة الكود التي تمثل هذا الرسم، ثم نستطيع جميعنا أن نعود إلى البيت ونأكل الساندوتشات التي أعدتها أمهاتنا :)
شيء نلاحظه: نحن نعلم ان المباني المدخلة مرتبة ترتيبا تصاعدياً حسب الحافة اليسرى للمبنى، لذلك حين نبحث عن عوائق افقية لا يوجد معنى لأن نبدأ البحث من أول مبنى، بل نريد البحث من "أول مبنى على يمين النملة"، لذلك سيكون لدينا متغير اسمه "بداية.البحث.الأفقي" يدل على رقم المبنى الذي سنبدأ منه البحث. هذا المتغير سيأخذ أولاً القيمة 1، ثم نحدثه كلما تحركت النملة لليمين.
الآن إلى الكوووود!!
كما نعلم، مجموعة البيانات المدخلة هي مصفوفة من ثلاثيات، مثل [[12، 10، 20]، [15، 8، 22]، [20، 12، 30]]، وكل ثلاثية تعبر عن مبنى [الحافة اليسرى، الارتفاع، الحافة اليمنى]. فلنبدأ بشيء بسيط: دوال قصيرة لاستخراج بيانات كل مبنى مما يجعل البرنامج أسهل في القراءة:
دالة يسار ( مبنى ) :
ارجع ب: مبنى [ 1 ]
نهاية
دالة سطح ( مبنى ) :
ارجع ب: مبنى [ 2 ]
نهاية
دالة يمين ( مبنى ) :
ارجع ب: مبنى [ 3 ]
نهاية
الآن نرى الإجراء الأساسي لحل المسألة:
بداية.البحث مشترك
إجراء إجابة ( صف.المباني ) :
بداية.البحث = 1
على.الأرض ( 0 ، 0 ، صف.المباني )
نهاية
ببساطة: بداية البحث (الذي يدل على رقم أول مبنى على يمين النملة) نحددها بواحد، ثم ننادي إجراءاً يعبر عن الحالة المبدئية للنمية (أنها على الأرض). سوف نصنع إجراءاً يعبر عن كل حالة وننتقل بين الحالات عن طريق استدعاء تلك الإجراءات.
ماذا أخذ
الإجراء على.الأرض؟ أخذ قيم س، ص، ومصفوفة المباني.
جميل جداً. الآن ننظر للإجراء نفسه:
إجراء على.الأرض(س ، ص ، المباني) :
لكل أ من بداية.البحث إلى عدد(المباني) :
إذا يسار(المباني [أ]) > س :
س = يسار(المباني[أ])
اطبع س
حدث.بداية.البحث(س، المباني)
وكل إلى عند.سفح(س، ص، المباني[أ] ، المباني)
تم
تابع
-- إن وصل التنفيذ هنا فمعنى ذلك
-- أن النملة على الأرض ولا يوجد
-- مبان على يمينها: أي أن الرحلة
-- قد انتهت
نهاية
الإجراء مكون من جزئين: الجزء الأول (بين لكل...تابع) يبحث عن أول مبنى على يمين النملة، فإن وجده:
- يغير قيمة "س" ليحرك النملة عند سفح المبنى، ويطبع القيمة الجديدة كما هو مطلوب في المخرجات
-
ينادي حدث.بداية.البحث(س، المباني) لكي
يبحث عن أول مبنى على يمين س الجديدة ويجعل رقم هذا المبنى هو بداية.البحث
- ينفذ السطر
وكل إلى عند.سفح (____ ) ويعطيه بعض القيم، مما يجعل البرنامج الآن في حالة جديدة.
ما معنى وكل إلى؟ انها تجعل السطر من "استدعاء إجراء" إلى "توكيل لإجراء". ما الفرق بينهما؟ في حالة التوكيل فإنه قبل
استدعاء عند.سفح(...) سوف يتم أولاً حذف الstack frame الخاصة بـ
على.الأرض( ) من الذاكرة، وهذا طبيعي لأن هذا الجزء من البرنامج لا نحتاج أن نرجع إليه.
الآن نرى عند.سفح:
إجراء عند.سفح ( س ، ص ، مبنى ، المباني ) :
ص = سطح ( مبنى )
اطبع ص
وكل إلى عند.سطح ( س ، ص ، مبنى ، المباني )
نهاية
إذا كانت النملة عند سفح مبنى، فإنها تتسلقه (بتغيير قيمة ص وطباعتها)، ثم توكل سائر العمل إلى الحالة الجديدة: عند.سطح
سهلة دي مش كدة؟
إجراء عند.سطح(س، ص، مبنى، المباني) :
لكل أ من بداية.البحث إلى عدد(المباني) :
م = المباني [أ]
-- لو وجدنا مبنى خارجاً تماماً عن حدود المبنى الحالي،
-- فكل المباني التي تليه هي الأخرى خارجة عن الحدود
-- وبالتالي لا داع للاستمرار في البحث
إذا يسار(م) > يمين(مبنى) : اذهب إلى نهاية.البحث
-- هل م أعلى من المبنى الذي تقف عليه النملة؟
إذا سطح(م) > سطح(مبنى) :
-- في هذه الحالة فهو عقبة. هيا نتحرك إلى سفحه
س = يسار (م)
اطبع س
حدث.بداية.البحث(س ، المباني)
وكل إلى عند.سفح(س، ص، م، المباني)
تم
تابع
علامة نهاية.البحث
-- إن وصلنا هنا ولم نجد شيئاً، نسير إلى نهاية المبنى الحالي
س = يمين(مبنى)
اطبع س
حدث.بداية.البحث(س، المباني)
وكل إلى عند.هضبة( س ، ص ، مبنى ، المباني)
نهاية
لا تنزعج! الكود أبسط مما يبدو عليها. هي ببساطة تقوم بالآتي:
1- نفذ لكل مبنى م في المباني التي على يمين النملة:
- لو كان يسار م أكبر من يمين المبنى الذي نقف عليه، إذا فكل المباني بعيدة ولا توجد عقبات. سوف نذهب للخطوة 2
- لو كان الأمر غير هذا، فقد وجدنا مبنى يقطع مبنانا! لو كان أعلى من مبنانا أيضاً فيجب أن نصل إلى سفحه. حرك س إلى سفح المبنى، واطبعها، وانتقل لحالة عند.سفح
2- لو انتهت الحلقة ولم نجد عقبة، إذاً فنحن على سطح مبنى بلا عقبات. هيا نسير إلى نهاية السطح (نغير س ونطبعها)، ثم ننتقل إلى حالة
عند.هضبة
لو كنت لاتزال تجد صعوبة، انظر إلى الصور مرة أخرى.
الآن نصل إلى عند.هضبة:
إجراء عند.هضبة(س، ص، مبنى، المباني) :
أعلى.عقبة = لاشيء
لكل أ من 1 إلى عدد(المباني) :
م = المباني[أ]
إذا م <> مبنى وأيضا يمين(م) > س وأيضا يسار(م) < س :
-- لقد وجدنا مبنى يقطع مبنانا
-- وهو بالتأكيد ليس أعلى منه لأنه لو كان أعلى
-- لكنا عليه بدلا من المبنى الحالي
-- لو كنا مازلنا في بداية البحث، نعطي
-- قيمة مبدئية للمتغير أعلى.عقبة
إذا أعلى.عقبة = لاشيء :
أعلى.عقبة = م
تم
-- سوف نختار أعلى مبنى فيهم لنهبط إليه
إذا سطح(م) > سطح(أعلى.عقبة):
أعلى.عقبة = م
تم
تم
تابع
إذا أعلى.عقبة = لاشيء :
ص = 0
اطبع ص
وكل إلى على.الأرض(س، ص، المباني)
وإلا :
ص = سطح(أعلى.عقبة)
اطبع ص
وكل إلى عند.سطح(س، ص، أعلى.عقبة، المباني)
تم
نهاية
مرة أخرى: الأمر أبسط مما يبدو عليه. الدالة جزءان:
الجزء الأول بين "لكل...تابع" يبحث عن أعلى مبنى يحتوي النقطة س التي تقف فيها النملة (يمينه أكبر من س ويساره اقل منها). لأن هذا الشرط ينطبق على المبني الحالي الذي تقف النملة عليه؛ لابد أن نضع جزءاً من الشرط يتأكد أن م هو مبنى مختلف عن المبنى الحالي.
الجزء الأول بين "لكل...تابع" له دور آخر: من بين كل المباني التي تحتوي س، فإنه يختار أعلاها. هذا يتم عن طريق ثلاثة أجزاء:
- خارج الـloop، يجعل قيمة المتغير أعلى.عقبة تساوي لاشيء.
- داخل الـloop، يعطي هذا المتغير قيمة أولية هي أول مبنى يصلح يجده، وذلك عن طريق الشرط إذا أعلى.عقبة = لاشيء :... هذا الجزء يتم تنفيذه مرة واحدة فقط.
- داخل الـloop أيضاً، يتم كل مرة مقارنة م بأعلى عقبة، لحساب قيمة max في المتغير أعلى.عقبة بالطريقة المعروفة.
الجزء الثاني: لو كانت قيمة المتغير أعلى.عقبة تساوي
لاشيء، فمعنى هذا أنه لا توجد عقبات، فتنزل النملة على الأرض (تغير ص وتغير حالتها).
لو كان هناك قيمة للمتغير أعلى.عقبة، فهناك إذاً عقبة أمام نزولنا. بدلاً من النزول للأرض ننزل إلى سطح تلك العقبة (المبنى) ونغير الحالة.
أخيراً هذا هو الإجراء
حدث.بداية.البحث
إجراء حدث.بداية.البحث (س، المباني) :
لكل أ من بداية.البحث إلى عدد( المباني ) :
إذا يسار (المباني [أ]) > س :
بداية.البحث = أ
اذهب إلى النهاية
تم
تابع
علامة النهاية
نهاية
الإجراء يستخدم القيمة القديمة لـ
بداية.البحث، حتى يجد أول مبنى على يمين النملة (يمينه أكبر من س)، وهنا يسجل رقم ذلك المبنى في
بداية.البحث ويخرج.
هل يعمل البرنامج؟ القيم المطبوعة هي نفس النتيجة المكتوبة في موقع المسألة (ولكني لم أختبره على مدخلات أخرى)
يمكنك أن تختبره أنت إن أردت! الكود كاملةً تجدها هنا:
-- هذا البرنامج حل لمسألة Skyline problem من مسائل الـACM
-- تجد المسألة هنا: http://uva.onlinejudge.org/external/1/105.html
-- اختبارات()
حل.المسألة ( )
إجراء حل.المسألة ( ) :
صف.المباني = [ ]
ملف.المدخلات = افتح.ملف ( "input.txt" )
كرر مادام ليس ملف.المدخلات : منته ( ) :
س = ملف.المدخلات : اقرأ.سطر ( )
الثلاثية = تفصيص ( س ، " " )
الثلاثية [ 1 ] = كعدد ( الثلاثية [ 1 ] )
الثلاثية [ 2 ] = كعدد ( الثلاثية [ 2 ] )
الثلاثية [ 3 ] = كعدد ( الثلاثية [ 3 ] )
صف.المباني = صف.المباني + [ الثلاثية ]
تابع
اغلق.ملف ( ملف.المدخلات )
إجابة ( صف.المباني )
نهاية
بداية.البحث مشترك
إجراء إجابة ( صف.المباني ) :
بداية.البحث = 1
على.الأرض ( 0 ، 0 ، صف.المباني )
نهاية
بداية.البحث مشترك
إجراء على.الأرض ( س ، ص ، المباني ) :
لكل أ من بداية.البحث إلى عدد ( المباني ) :
إذا يسار ( المباني [ أ ] ) > س :
س = يسار ( المباني [ أ ] )
اطبع س
حدث.بداية.البحث ( س ، المباني )
وكل إلى عند.سفح ( س ، ص ، المباني [ أ ] ، المباني )
تم
تابع
-- إن وصل التنفيذ هنا فمعنى ذلك
-- أن النملة على الأرض ولا يوجد
-- مبان على يمينها: أي أن الرحلة
-- قد انتهت
نهاية
إجراء عند.سفح ( س ، ص ، مبنى ، المباني ) :
ص = سطح ( مبنى )
اطبع ص
وكل إلى عند.سطح ( س ، ص ، مبنى ، المباني )
نهاية
إجراء عند.سطح ( س ، ص ، مبنى ، المباني ) :
-- اطبع "سطح"
لكل أ من بداية.البحث إلى عدد ( المباني ) :
م = المباني [ أ ]
-- لو وجدنا مبنى خارجاً تماماً عن حدود المبنى الحالي،
-- فكل المباني التي تليه هي الأخرى خارجة عن الحدود
-- لاحظ أن الأمر التالي غير مختوم بكلمة "تم" بل
-- الأمر كله سطر واحد
إذا يسار ( م ) > يمين ( مبنى ) : اذهب إلى نهاية.البحث
-- هل م أعلى من المبنى الذي تقف عليه النملة؟
إذا سطح ( م ) > سطح ( مبنى ) :
-- في هذه الحالة فهو عقبة. هيا نتحرك إلى سفحه
س = يسار ( م )
اطبع س
حدث.بداية.البحث ( س ، المباني )
وكل إلى عند.سفح ( س ، ص ، م ، المباني )
تم
تابع
علامة نهاية.البحث
-- إن وصلنا هنا ولم نجد شيئاً، نسير إلى نهاية المبنى الحالي
س = يمين ( مبنى )
اطبع س
حدث.بداية.البحث ( س ، المباني )
وكل إلى عند.هضبة ( س ، ص ، مبنى ، المباني )
نهاية
إجراء عند.هضبة ( س ، ص ، مبنى ، المباني ) :
أعلى.عقبة = لاشيء
لكل أ من 1 إلى عدد ( المباني ) :
م = المباني [ أ ]
إذا م <> مبنى وأيضا يمين ( م ) > س وأيضا يسار ( م ) < س :
-- لقد وجدنا مبنى مختلف يقطع مبنانا
-- وهو بالتأكيد ليس أعلى منه لأنه لو كان أعلى
-- لكنا عليه بدلا من المبنى الحالي
-- لو كنا مازلنا في بداية البحث، نعطي
-- قيمة مبدئية للمتغير أعلى.عقبة
إذا أعلى.عقبة = لاشيء :
أعلى.عقبة = م
تم
عقبة2 = م
-- سوف نختار أعلى مبنى فيهم لنهبط إليه
إذا سطح ( عقبة2 ) > سطح ( أعلى.عقبة ) :
أعلى.عقبة = عقبة2
تم
تم
تابع
إذا أعلى.عقبة = لاشيء :
ص = 0
اطبع ص
وكل إلى على.الأرض ( س ، ص ، المباني )
وإلا :
ص = سطح ( أعلى.عقبة )
اطبع ص
وكل إلى عند.سطح ( س ، ص ، أعلى.عقبة ، المباني )
تم
نهاية
إجراء حدث.بداية.البحث ( س ، المباني ) :
لكل أ من بداية.البحث إلى عدد ( المباني ) :
إذا يسار ( المباني [ أ ] ) > س :
بداية.البحث = أ
اذهب إلى النهاية
تم
تابع
علامة النهاية
نهاية
دالة يسار ( مبنى ) :
ارجع ب: مبنى [ 1 ]
نهاية
دالة سطح ( مبنى ) :
ارجع ب: مبنى [ 2 ]
نهاية
دالة يمين ( مبنى ) :
ارجع ب: مبنى [ 3 ]
نهاية