محتويات الدورة

هياكل البيانات والخوارزميات / براهين التحسين والتجميعات المحفوظة / الدرس 13

البرمجة الديناميكية: الحالة والترتيب واستعادة الحل

عرّف مسائل فرعية قابلة لإعادة الاستخدام وترتيبا صحيحا لاعتمادها، قبل ضغط التخزين أو استعادة حل منها.

وقت القراءة التقريبي: 6 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • تحتاج كل خانة في الجدول إلى وصف دقيق لمعناها
  • اختر الأدنى عبر كل اختيار أخير صالح
  • يظل التخزين المضغوط ممثلا لطبقات مفهومية

تحتاج كل خانة في الجدول إلى وصف دقيق لمعناها

تعيد البرمجة الديناميكية استخدام حلول مسائل فرعية متداخلة. ابدأ بتحديد معنى كل حالة بالضبط، ووسائطها المقبولة، والحالات الأصغر التي تعتمد عليها. ثم حدّد حالات الأساس وترتيب تقييم يحترم تلك الاعتماديات. فالجدول المملوء بالأعداد ليس نموذجا في حد ذاته. في مسألة أقل عدد عملات، قد تعني state[a] أقل عدد من العملات الموجبة المسموحة التي يساوي مجموع قيمها المقدار a بالضبط، مع السماح بإعادة استخدام كل فئة دون حد. يختلف ذلك عن عد التركيبات، أو اختيار كل عملة مرة واحدة، أو قبول مجموع لا يزيد على a. وتغيّر كل صيغة من هذه الصيغ العلاقة العودية.

اختر الأدنى عبر كل اختيار أخير صالح

ينظر البرنامج الأول في العملة الأخيرة اللازمة لتكوين مقدار دقيق. تترك إزالة عملة موجبة مقدارا أصغر حصرا؛ لذلك يصح تقييم المقادير بترتيب متزايد. تظل الحالات غير القابلة للوصول مساوية للانهاية، بينما يحتاج المقدار صفر إلى صفر عملات. وتسجل خانة العملة السابقة اختيارا فقط عندما يحسن عدد العملات. يعيد اتباع هذه الاختيارات بناء حل أمثل واحد، وتضمن الإيجابية تناقص المقدار المتبقي حتى الصفر. ترفض الشيفرة فئات العملات الصفرية أو السالبة لأنها تبطل حجة الاعتماد هذه. وتعيد None للهدف غير القابل للتكوين، بدلا من خلط غياب الحل بحل مقبول لا يستخدم أي عملة.

python مثال مستقل لخوارزمية بلغة Python 3، يستخدم بيانات محلية مصطنعة.
from math import inf

def minimum_coins(coins, amount):
    if type(amount) is not int or amount < 0:
        raise ValueError("Amount must be a nonnegative integer")
    if any(type(coin) is not int or coin <= 0 for coin in coins):
        raise ValueError("Coins must be positive integers")
    best = [inf] * (amount + 1)
    parent = [None] * (amount + 1)
    best[0] = 0
    for current in range(1, amount + 1):
        for coin in coins:
            if coin <= current and best[current - coin] + 1 < best[current]:
                best[current] = best[current - coin] + 1
                parent[current] = coin
    if best[amount] == inf:
        return None
    chosen, remaining = [], amount
    while remaining:
        coin = parent[remaining]
        chosen.append(coin)
        remaining -= coin
    return best[amount], chosen

print(minimum_coins([1, 3, 4], 6))
print(minimum_coins([4, 6], 5))
print(minimum_coins([1, 3], 0))
الناتج المتوقع
(2, [3, 3])
None
(0, [])

يُكوَّن المقدار 6 بالضبط باستخدام عملتين من فئة 3. ولا يمكن تكوين المقدار 5 من الفئتين 4 و6، بينما يملك الصفر الحل الفارغ.

يظل التخزين المضغوط ممثلا لطبقات مفهومية

في حقيبة صفر-واحد يمكن استخدام كل عنصر مرة واحدة على الأكثر. تشمل الحالة المفهومية كلا من سابقة العناصر المعالجة والسعة. يخزن البرنامج الثاني صف سعات واحدا فقط، ويحدث السعات تنازليا لكي يقرأ الانتقال طبقة العناصر السابقة. أما التكرار تصاعديا فيقرأ قيمة حسّنها العنصر نفسه بالفعل، فيسمح بإعادة استخدامه عرضا. تعني الحالة هنا أفضل قيمة بوزن لا يتجاوز السعة؛ ولذلك تتيح التهيئة بالصفر الاختيار الفارغ. وتحتاج صيغة الوزن الدقيق إلى تهيئة مختلفة للحالات غير القابلة للوصول. يغيّر الضغط التخزين، وليس عقد الاعتماد الأساسي.

python مثال مستقل لخوارزمية بلغة Python 3، يستخدم بيانات محلية مصطنعة.
def zero_one_knapsack(items, capacity):
    if type(capacity) is not int or capacity < 0:
        raise ValueError("Capacity must be a nonnegative integer")
    best = [0] * (capacity + 1)
    for weight, value in items:
        if type(weight) is not int or weight <= 0:
            raise ValueError("Weights must be positive integers")
        for current in range(capacity, weight - 1, -1):
            best[current] = max(best[current], best[current - weight] + value)
    return best[capacity]

print(zero_one_knapsack([(2, 3), (3, 4)], 4))
print(zero_one_knapsack([(2, 3)], 0))
الناتج المتوقع
4
0

لا يمكن استخدام العنصر ذي الوزن 2 مرتين. ويحافظ التكرار التنازلي على أن خلية المصدر تمثل طبقة العناصر السابقة.

قد تجعل الحدود العددية التعقيد شبه متعدد الحدود

عندما توجد c فئات عملات ويكون الهدف A، يستخدم الجدول O(cA) انتقالا وO(A) مساحة، دون ناتج استعادة الحل. وتستخدم الحقيبة O(nC) انتقالا لعدد n من العناصر وسعة C. تعتمد هذه الحدود على المقدار العددي، بينما لا يحتاج تمثيل A أو C بالثنائي إلا إلى عدد لوغاريتمي من البتات. لذلك تكون الخوارزميات شبه متعددة الحدود بالنسبة إلى الترميز العام للمدخل، ولا تصبح متعددة الحدود لمجرد استعمال حلقات متداخلة على أعداد صحيحة. قد تجعل القيم الكبيرة الجدول غير عملي رغم قلة العناصر. اذكر افتراضات الحساب وحدود الموارد المقبولة، دون فرض قيمة قصوى عامة.

يجب أن توافق استعادة الاختيارات القيمة المثلى

لا تضمن صحة قيمة الهدف صحة قائمة الاختيارات المستعادة. يجب أن تقابل كل سابقة مخزنة الانتقال الذي أنشأ تلك الحالة، وأن تنتهي سلسلتها وفقا لمقياس الاعتماد. قد يكتب ضغط الذاكرة فوق معلومات لازمة للاستعادة، خصوصا في المسائل القائمة على العناصر. احفظ طبقات منفصلة للسابقات، أو أعد حساب القرارات عن قصد، أو استخدم استراتيجية استعادة أخرى مبرهنة. وحدّد سلوك التعادل أيضا: فقد توجد حلول مثلى متعددة، وتختلف إعادة أمثل صحيح واحد عن عدها كلها أو اختيار أمثل مفضل معجميا. لا تستنتج تفرد الحل من كون القيمة العددية الدنيا واحدة.

قارن الحالات الصغيرة بعدّ مستقل للحلول

يوفر BFS على المقادير القابلة للوصول مرجعا مستقلا لأقل عدد حواف عندما تكون أهداف العملات صغيرة. تحقّق من انتماء العملات المستعادة إلى المجموعة المقبولة، ومساواة مجموعها للهدف، ومطابقة عددها للعدد المعلن. ولعينات الحقيبة الصغيرة، عد المجموعات الجزئية وقارن أفضل قيمة مقبولة. أدرج مقادير دقيقة غير قابلة للتكوين، والهدف صفر، والفئات المكررة، وعنصرا أثقل من السعة، والمثال المضاد لإعادة الاستخدام. هذه الاختبارات حسابات محلية محدودة عمدا. تكمل العلاقة وثابت الطبقات، ولا تدّعي أن العد المحدود يثبت كل مدخل أو أن جداول كبيرة خضعت لقياس أداء.

الأخطاء الشائعة وإصلاحها

إعادة استخدام عنصر صفر-واحد داخل تحديث واحد

لماذا يحدث؟
مُر على السعات المضغوطة بترتيب تصاعدي.
كيف تصلحه؟
مر تنازليا لكي تقرأ طبقة العناصر السابقة.
كيف تتجنب تكراره؟
اختبر عنصرا واحدا وزنه 2 وقيمته 3 عند سعة 4.

ظهور المقدار غير القابل للتكوين كأنه حل فارغ

لماذا يحدث؟
هُيئت جميع الحالات على أنها كلفة صفرية مقبولة.
كيف تصلحه؟
ميّز غير القابل للوصول، وهيّئ حالات الأساس الصحيحة فقط.
كيف تتجنب تكراره؟
اختبر الصفر منفصلا عن مقدار موجب غير قابل للتكوين.

دوران استعادة الحل أو إرجاع اختيارات غير متسقة

لماذا يحدث؟
لا تتبع السابقات انتقالا صحيحا يتناقص وفق مقياس الاعتماد.
كيف تصلحه؟
احفظ الاختيارات مع التحسينات المثبتة، وتحقّق من سلسلة السابقات.
كيف تتجنب تكراره؟
افحص انتماء العملات، ومجموعها، وعددها، وانتهاء الاستعادة.

اختبر فهمك

1. لماذا تفشل قاعدة اختيار أكبر عملة أولا للفئات 1 و3 و4 عند المقدار 6؟

اعرض الحل المشروح

تختار 4 ثم 1 ثم 1، فتستخدم ثلاث عملات، بينما يستخدم الحل 3+3 عملتين فقط. لا يضمن اختيار فئة كبيرة محليا أقل عدد على مستوى الحل الكامل. تنظر البرمجة الديناميكية في كل عملة أخيرة مسموح بها، وتقارن الحلول الكاملة للحالات الأصغر المتبقية بعد إزالة تلك العملة.

2. لماذا تختلف نتيجة الحقيبة 4 عن النتيجة 6 عند السماح بعدد غير محدود من النسخ؟

اعرض الحل المشروح

عند السعة 4، لا يستطيع العنصر ذو الوزن 2 والقيمة 3 إنتاج القيمة 6 إلا إذا اختير مرتين. يسمح عقد صفر-واحد باستخدامه مرة واحدة، وأفضل عنصر منفرد مسموح هو ذو الوزن 3 والقيمة 4. تشفر التحديثات التنازلية قيد الاستخدام مرة واحدة.

أهم ما تعلمته

  • تحتاج كل خانة في الجدول إلى وصف دقيق لمعناها
  • قارن الحالات الصغيرة بعدّ مستقل للحلول

المراجع الرسمية