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

يمكننا النظر إلى مشكلة الحقيبة 0-1 على أنها عملية تتكوّن من جولة من القرارات، حيث يوجد لكل عنصر قراران: عدم وضعه ووضعه، وبذلك تحقق المسألة نموذج شجرة القرار.
هدف هذه المسألة هو إيجاد «أقصى قيمة يمكن وضعها في الحقيبة ضمن حد السعة»، لذا يُرجَّح أنها مسألة برمجة ديناميكية.
الخطوة 1: فكّر في القرارات في كل جولة، وحدّد الحالة، وبذلك تحصل على جدول
لكل عنصر، إذا لم يُوضع في الحقيبة تبقى سعة الحقيبة دون تغيير؛ وإذا وُضع فيها تنقص سعة الحقيبة. ومن ذلك يمكننا اشتقاق تعريف الحالة: رقم العنصر الحالي وسعة الحقيبة ، ويُرمز إليهما بـ.
تقابل الحالة المسألة الفرعية: أقصى قيمة يمكن تحقيقها باستخدام أول من العناصر في حقيبة سعتها ، ويُرمز إليها بـ.
وما نحتاج إلى إيجاده هو ، لذا نحتاج إلى جدول ثنائي الأبعاد بحجم .
الخطوة 2: حدّد البنية الفرعية المثلى، ثم استنتج معادلة انتقال الحالة
بعد اتخاذ القرار بشأن العنصر ، يتبقى المسألة الفرعية الخاصة بأول من العناصر، ويمكن تقسيمها إلى الحالتين التاليتين.
- عدم وضع العنصر : تبقى سعة الحقيبة دون تغيير، وتنتقل الحالة إلى .
- وضع العنصر : تنقص سعة الحقيبة بمقدار ، وتزيد القيمة بمقدار ، وتنتقل الحالة إلى .
يكشف التحليل أعلاه البنية الفرعية المثلى لهذه المسألة: أقصى قيمة تساوي الأكبر بين القيمتين الناتجتين عن عدم وضع العنصر في الحقيبة ووضعه فيها. ومن ذلك يمكن اشتقاق معادلة انتقال الحالة:
لاحظ أنه إذا تجاوز وزن العنصر الحالي السعة المتبقية للحقيبة ، فالخيار الوحيد هو عدم وضعه في الحقيبة.
الخطوة 3: حدّد الشروط الحدية وترتيب انتقال الحالة
عندما لا توجد عناصر أو تكون سعة الحقيبة ، تكون أقصى قيمة ، أي أن العمود الأول والصف الأول يساويان .
تنتقل الحالة الحالية من الحالة التي فوقها ومن الحالة الواقعة في أعلى اليسار ، لذا يمكننا اجتياز جدول بأكمله بالترتيب الأمامي باستخدام حلقتين متداخلتين.
واستناداً إلى التحليل أعلاه، سننفّذ تالياً حلول البحث بالقوة الغاشمة والحفظ والبرمجة الديناميكية بالترتيب.
الطريقة 1: البحث بالقوة الغاشمة
تتضمن شيفرة البحث العناصر التالية.
- المعاملات التعاودية: الحالة .
- القيمة المُعادة: حل المسألة الفرعية .
- شرط التوقف: عند نفاد العناصر () أو عندما تكون السعة المتبقية للحقيبة ، يتوقف التعاود وتُعاد القيمة .
- التشذيب: إذا تجاوز وزن العنصر الحالي السعة المتبقية للحقيبة، فلا يتوفر إلا خيار عدم وضعه.
كما يوضح الشكل أدناه، بما أن كل عنصر يولّد فرعي بحث: استبعاده وتضمينه، يكون التعقيد الزمني .
وبمراقبة شجرة التعاود، يسهل ملاحظة مسائل فرعية متداخلة، مثل . وعندما تكون العناصر كثيرة وسعة الحقيبة كبيرة، ولا سيما عندما تكون العناصر ذات الوزن نفسه كثيرة، يزداد عدد المسائل الفرعية المتداخلة زيادة كبيرة.

الطريقة 2: الحفظ
لضمان ألا تُحسب المسائل الفرعية المتداخلة إلا مرة واحدة، نستخدم قائمة الحفظ mem لتسجيل حلول المسائل الفرعية، حيث يقابل mem[i][c] القيمة .
بعد إدخال الحفظ، يعتمد التعقيد الزمني على عدد المسائل الفرعية، وهو . وتكون شيفرة التنفيذ كما يلي:
يوضح الشكل أدناه فروع البحث التي شُذّبت في الحفظ.

الطريقة 3: البرمجة الديناميكية
البرمجة الديناميكية في جوهرها عملية ملء جدول أثناء انتقالات الحالة. وتكون الشيفرة كما يلي:
كما يوضح الشكل أدناه، يُحدَّد التعقيد الزمني والتعقيد المكاني معاً بحجم المصفوفة dp، وهو .
تحسين المساحة
بما أن كل حالة مرتبطة فقط بالحالة في الصف الذي فوقها، يمكننا استخدام مصفوفتين متدحرجتين لتقليل التعقيد المكاني من إلى .
وبالتفكير أكثر، هل يمكننا تحقيق التحسين المكاني باستخدام مصفوفة واحدة فقط؟ بالملاحظة، نرى أن كل حالة تنتقل من الخلية التي فوقها مباشرة أو الخلية الواقعة في أعلى اليسار. وإذا كانت هناك مصفوفة واحدة فقط، فحين نبدأ اجتياز الصف ، لا تزال تلك المصفوفة تخزّن حالة الصف .
- إذا استخدمنا الاجتياز الأمامي، فحين نصل إلى ، تكون قيم أعلى اليسار ~ قد كُتب فوقها بالفعل، مما يمنع انتقال الحالة الصحيح.
- إذا استخدمنا الاجتياز العكسي، فلن تظهر مشكلة الكتابة فوق القيم، ويمكن لانتقال الحالة أن يجري بصورة صحيحة.
يوضح الشكل أدناه عملية الانتقال من الصف إلى الصف باستخدام مصفوفة واحدة. يرجى التفكير في الفرق بين الاجتياز الأمامي والاجتياز العكسي.
في تنفيذ الشيفرة، نحتاج ببساطة إلى حذف البعد الأول من المصفوفة dp وتغيير الحلقة الداخلية إلى اجتياز عكسي: