موضوع مقاله: برنامه‌نویسی پویا | مخاطب: عمومی | لحن: دانشنامه‌ای | ساختار: عنوان، مقدمه، 3 بخش، جمع‌بندی | مقاله: | # برنامه‌نویسی پویا | مقدمه: در علوم رایانه و ریاضیات، برنامه‌ریزی پویا یا داینامیک، روشی کارآمد برای حل مسائل جستجو و بهینه‌سازی با استفاده از دو ویژگی زیرمسئله‌های هم‌پوشان و زیرساخت‌های بهینه است. | ## بخش 1: برنامه‌نویسی پویا | برخلاف برنامه‌ریزی خطی، چارچوب استانداردی برای فرموله کردن مسائل برنامه‌ریزی پویا وجود ندارد. در واقع، آنچه برنامه‌ریزی پویا انجام می‌دهد ارائه روش برخورد کلی جهت حل این نوع مسائل است. در هر مورد، باید معادلات و روابط ریاضی مخصوصی که با شرایط آن مسئله تطبیق دارد نوشته شود.هیلیر، ج 2، ص 65 تاریخچه این روش در سال ۱۹۵۳ توسط ریاضی‌دانی به نام ریچارد بلمن معرفی شد. برنامه‌ریزِی پویا در ریاضی و علوم رایانه روشی شناخته شده‌است که از آن در نوشتن الگوریتم‌های بهینه با استفاده از حذف اجرای چند بارهٔ یک زیر مسئله یکسان استفاده می‌شود. نشان داده شده‌است که روش علوم رایانه‌ای برای برنامه‌ریزی پویا کارایی بالاتری دارد زیرا محاسبات تکراری را حذف می‌کند در حالی که در روش ریاضی برنامه‌ریزی پویا امکان کاهش فضای حافظه بیشتر است. برنامه‌ریزی پویا شبیه روش تقسیم و حل، مسائل را با استفاده از ترکیب کردن جواب زیرمسئله‌ها حل می‌کند. الگوریتم تقسیم و حل، مسئله را به زیر مسئله‌های مستقل تقسیم می‌کند و پس از حل زیر مسئله‌ها به صورت بازگشتی، نتایج را با هم ترکیب کرده و جواب مسئله اصلی را به‌دست می‌آورد. به عبارت دقیق تر، برنامه‌ریزی پویا بهتر است در مسائلی استفاده شود که زیرمسئله‌ها مستقل نیستند؛ یعنی زمانی که زیرمسئله‌ها، خود دارای چندین زیر مسئلهٔ یکسان هستند. در این حالت روش تقسیم و حل با اجرای مکرر زیرمسئله‌های یکسان، بیشتر از میزان لازم محاسبات انجام می‌دهد. یک الگوریتم برنامه‌ریزی پویا زیرمسئله‌ها را یک بار حل و جواب آن‌ها را در یک جدول ذخیره می‌کند و با این کار از تکرار اجرای زیرمسئله‌ها در زمانی که مجدداً به جواب آن‌ها نیاز است جلوگیری می‌کند. مثلا اگر مسئله فیبوناچی با برنامه ریزی پویا حل شود باعث اجرای مکرر یک زیرمسئله نمی شود. ویژگی‌ها یک مسئله باید دارای دو مشخصهٔ کلیدی باشد تا بتوان برنامه‌نویسی پویا را برای آن به کار برد: زیرساختار بهینه و زیرمسئله‌های هم‌پوشان. در طرف مقابل، به حل یک مسئله با ترکیب جواب‌های بهینهٔ زیرمسئله‌های ناهم‌پوشان، «تقسیم و حل» گفته‌می‌شود. به همین علت است که مرتب‌سازی ادغامی و سریع به عنوان مسائل برنامه‌نویسی پویا شناخته‌نمی‌شوند. اصل بهینگی اگر بنا باشد پرانتزبندی کل عبارت بهینه شود، پرانتزبندی زیرمسئله‌ها هم باید بهینه باشند. حل بهینه، سومین مرحله از بسط یک الگوریتم برنامه‌نویسی پویا برای مسائل بهینه‌سازی است. مراحل بسط چنین الگوریتمی به شرح زیر است: ارائه یک ویژگی بازگشتی که حل بهینهٔ نمونه‌ای از مسئله را به دست می‌دهد. تمام مسائل بهینه‌سازی را نمی‌توان با برنامه‌نویسی پویا حل کرد چرا که باید اصل بهینگی در مسئله صدق کند. اصل بهینگی در یک مسئله صدق می‌کند اگر یک حل بهینه برای نمونه ای از مسئله، همواره حاوی حل بهینه برای همهٔ زیر نمونه‌ها باشد. در روش برنامه‌نویسی پویا اغلب از یک آرایه برای ذخیره نتایج جهت استفاده مجدد استفاده شده، و زیرمسائل به صورت جزء به کل حل می‌شوند. در مورد این مسئله، ابتدا زیرمسائلی که تنها از دو ماتریس تشکیل شده‌اند محاسبه می‌شوند. این دسته از زیرمسائل به زیرمسائلی متشکل از دو ماتریس تجزیه می‌شوند که قبلاً محاسبات آن‌ها صورت گرفته، و نتایج آن‌ها در آرایه ذخیره شده‌اند. درختهای جستجوی دودویی بهینه درخت جستجوی دودویی یک درخت دودویی از عناصر است که معمولاً کلید نامیده می‌شوند به قسمی که: هر گره حاوی یک کلید است. کلیدهای موجود در زیردرخت راست هر گره، بزرگتر یا مساوی کلید آن گره هستند زیرسازه بهینه زیرساختار بهینه به این معناست که جواب یک مسئلهٔ بهینه‌سازی را بتوان از ترکیب جواب‌های بهینهٔ زیرمسئله‌های آن به دست آورد. چنین زیرساختارهای بهینه‌ای معمولاً با رویکرد بازگشتی به دست می‌آیند؛ برای مثال، در گراف (G=(V,E، کوتاه‌ترین مسیرِ م از رأس آ به رأس ب، زیرساختار بهینه از خود نشان می‌دهد: هر رأس میانیِ ث از مسیرِ م را در نظر بگیرید. اگر م واقعاً کوتاه‌ترین مسیر باشد، آنگاه می‌توان آن را به دو زیرمسیر م۱ از آ تا ث و م۲ از ث تا ب در نظر گرفت، به نحوی که هر کدام از این‌ها کوتاه‌ترین مسیر بین دو سر خود هستند (به همان طریق برش و چسباندن کتاب مقدمه‌ای بر الگوریتم‌ها). بر این اساس، می‌توان جواب را به صورت بازگشتی بیان کرد، درست مانند الگوریتم‌های بلمن-فورد و فلوید-وارشال. استفاده از روش زیرسازه بهینه به این معناست که مسئله را به زیرمسئله‌هایی کوچک‌تر بشکنیم و برای هر یک از این زیرمسئله‌ها پاسخی بهینه بیابیم و پاسخ بهینه مسئلهٔ کلی را از کنار هم قرار دادن این پاسخ‌های بهینهٔ جزئی به دست آوریم. مثلاً در هنگام حل مسئلهٔ یافتن کوتاه‌ترین مسیر از یک رأس یک گراف به رأسی دیگر، می‌توانیم کوتاه‌ترین مسیر تا مقصد از همهٔ رئوس مجاور را به دست آوریم و از آن برای جواب کلی استفاده کنیم. شکستن مسئله به بخش‌های کوچک‌تر حل خود این زیرمسئله‌ها با شکاندن آن‌ها به صورت بازگشتی استفاده از پاسخ‌های جزئی برای یافتن پاسخ کلی زیرمسئله‌های هم‌پوشان زیرمسئله‌های هم‌پوشان به معنای کوچک بودن فضای زیرمسئله‌هاست، به این معنا که هر الگوریتم بازگشتی برای حل این مسئله، باید به جای ایجاد زیرمسئله‌های جدید، زیرمسئله‌های تکراری را بارها حل کند. در صورت اتخاذ چنین رویکرد ساده‌انگارانه‌ای، نهایتاً زیرمسئله‌های یکسانی را بارها حل می‌کنیم، در صورتی که تعداد کل زیرمسئله‌ها در واقعیت کم است (تنها ۴۳تا). برنامه‌نویسی پویا به این حقیقت دقت می‌کند و هر زیرمسئله را تنها یک بار حل می‌کند. گفته می‌شود مسئله‌ای دارای زیرمسئله‌های هم‌پوشان است، اگر بتوان مسئله را به زیرمسئله‌های کوچکتری شکست که پاسخ هرکدام چند بار در طول فرایند حل مورد استفاده قرار بگیرد. برنامه‌ریزی پویا کمک می‌کند تا هر کدام از این پاسخ‌ها فقط یک بار محاسبه شوند و فرایند حل از بابت دوباره‌کاری هزینه‌ای را متحمل نشود. برای مثال در دنباله فیبوناچی برای محاسبهٔ عدد چهارم دنباله به دانستن عدد سوم نیاز داریم. حال اگر مثلاً در شرایطی بخواهیم عدد ششم دنبالهٔ فیبوناچی را حساب کنیم، در این محاسبه هم مقدار عدد پنجم را می‌خواهیم و هم مقدار عدد چهارم را. اگر تصمیم بگیریم اعداد چهارم و پنجم را به نوبت حساب کنیم در هنگام محاسبهٔ هرکدام به مقدار عدد سوم نیاز پیدا می‌کنیم و باید دوباره آن را محاسبه کنیم. برای جلوگیری از این محاسبات چندباره، الگوریتمهایی که مبتنی بر برنامه‌ریزی پویا هستند، معمولاً یکی از دو راه زیر را استفاده می‌کنند. اگر جواب مسئله‌ای را بتوان به صورت بازگشتی با جواب زیرمسئله‌های آن به دست آورد، و در صورت هم‌پوشانی زیرمسئله‌ها، می‌توان جواب زیرمسئله‌ها را در یک جدول به خاطر سپرد. هر گاه که برای حل یک زیرمسئله اقدام می‌کنیم، ابتدا بررسی می‌کنیم که آیا این زیرمسئله قبلاً حل شده یا نه؛ اگر جواب آن را داشتیم، می‌توانیم آن را مستقیماً استفاده کنیم؛ در غیر این صورت، زیرمسئله را حل می‌کنیم و جواب آن را به جدول اضافه می‌کنیم. در این رویکرد مسئله به زیرمسئله‌هایی شکسته می‌شود و پاسخ هر زیرمسئله پس از محاسبه در جایی ذخیره می‌شود. رویکرد پایین به بالا: پس از آن که جواب یک مسئله را به صورت بازگشتی با استفاده از زیرمسئله‌های آن صورت‌بندی کردیم، می‌توانیم مسئله را از پایین به بالا نگاه کنیم: ابتدا زیرمسئله‌ها را حل می‌کنیم و سپس جواب آن‌ها را برای به دست آوردن جواب زیرمسئله‌های بزرگ‌تر استفاده می‌کنیم تا نهایتاً به مسئلهٔ اصلی برسیم. این روش نیز معمولاً به کمک یک جدول با تولید مرحله به مرحلهٔ زیرمسئله‌های بزرگ‌تر و بزرگ‌تر به کمک جواب زیرمسئله‌های کوچک‌تر انجام می‌شود؛ برای مثال، اگر مقادیر F41 و F40 را بدانیم، می‌توانیم مستقیماً مقدار F42 را به دست آوریم. در این رویکرد همهٔ زیرمسئله‌های مورد نیاز از کوچک به بزرگ حل می‌شوند و از جواب‌ها «بلافاصله» برای محاسبهٔ بعدی‌ها استفاده می‌شود و فرایند محاسبه تا رسیدن به زیرمسئلهٔ مورد نیاز (که در واقع مسئلهٔ اصلی ماست) ادامه می‌یابد. برخی از زبان‌های برنامه‌نویسی می‌توانند به‌طور خودکار جواب صدا زدن یک تابع با ورودی‌های مشخص را به خاطر بسپارند تا صدا زدن با نام را سرعت ببخشند (این فرایند با نام صدا زدن با نیاز شناخته‌می‌شود). برخی زبان‌های نیز به صورت خودکار به‌خاطرسپاری را در خود دارند: مانند Prolog جدول‌دار و J، که به‌خاطرسپاری را با قید .M پشتیبانی می‌کند. به‌خاطرسپاری به عنوان یک الگوی طراحی در دسترس نیز در زبان‌های بازنویسی جملات مانند زبان ولفرام یافت‌می‌شود. زمانی که به یک مسئلهٔ برنامه‌نویسی پویا می‌اندیشیم، باید مجموعهٔ زیرمسئله‌های موجود و چگونگی وابستگی آن‌ها را درک کنیم. | ## بخش 2: برنامه‌نویسی پویا | گراف زیرمسئله‌ها یک گراف جهت‌دار، شامل یک رأس به ازای هر زیرمسئلهٔ متمایز است و در صورتی یک یال جهت‌دار از رأس زیرمسئلهٔ x به رأس زیرمسئلهٔ y دارد که برای تعیین یک جواب بهینه برای زیرمسئلهٔ x مستقیماً نیاز به در نظر گرفتن یک جواب بهینه برای زیرمسئلهٔ y داشته‌باشیم. می‌توان گراف زیرمسئله‌ها را یک نسخهٔ کاهش‌یافتهٔ درخت بازگشتی برای روش بازگشتی بالا به پایین در نظر گرفت، به گونه‌ای که همهٔ رئوس مربوط به یک زیرمسئله را یکی کنیم و یال‌ها را از والد به فرزند جهت‌دار کنیم. روش پایین به بالا برای برنامه‌نویسی پویا رئوس گراف زیرمسئله‌ها را به ترتیبی در نظر می‌گیرد که همهٔ زیرمسئله‌های مجاور یک زیرمسئله، پیش از آن حل شوند. در یک الگوریتم برنامه‌نویسی پویای پایین به بالا، رئوس گراف زیرمسئله‌ها را به صورتی در نظر می‌گیریم که «معکوس مرتب توپولوژیکی» یا «مرتب توپولوژیک وارون» زیرگراف مسئه‌ها است. به زبان دیگر، هیچ زیرمسئله‌ای تا زمانی که همهٔ زیرمسئله‌هایی که به آن‌ها وابسته است حل نشده‌اند، در نظر گرفته‌نمی‌شود. به‌طور مشابه، می‌توانیم رویکرد بالا به پایین (همراه به‌خاطرسپاری) برای برنامه‌نویسی پویا را به شکل جستجوی ژرفانخست گراف زیرمسئله‌ها ببینیم. اندازهٔ گراف زیرمسئله‌ها می‌تواند ما را در تعیین زمان اجرای الگوریتم برنامه‌نویسی پویا یاری کند. از آنجایی که هر زیرمسئله را فقط یک بار حل می‌کنیم، زمان اجرا برابر است با مجموع تعداد بارهایی که نیاز است هر زیرمسئله را حل کنیم. به‌طور معمول، زمان محاسبهٔ جواب یک زیرمسئله، متناسب با درجهٔ رأس متناظر در گراف، و تعداد زیرمسئله‌ها برابر با تعداد رئوس گراف است. مثال یک پیاده‌سازی ساده از یک تابع برای یافتن عدد فیبوناچی nام می‌تواند به شکل زیر باشد. حال فرض کنید ما یک آرایه دوتایی map داریم که عدد n را به مقدار عدد فیبوناچی nام مربوط کرده و ذخیره می‌کند. در فرایند پایین به بالا برای حل چنین مسئله‌ای از عدد فیبوناچی یکم شروع می‌کنیم تا به عدد خواسته‌شده برسیم. علت این که همیشه از رویکرد پایین به بالا استفاده نمی‌کنیم این است که گاهی از قبل نمی‌دانیم باید کدام زیرمسئله‌ها را حل کنیم تا به مرحله اصلی برسیم، یا این که مجبوریم زیرمسئله‌هایی که استفاده نمی‌شوند را هم حل کنیم. زمانی که یک مسئله به دو یا چند زیرمسئله تقسیم می‌شود، دو حالت ممکن است پیش بیاید: داده‌های زیرمسئله‌ها هیچ اشتراکی با هم نداشته و کاملاً مستقل از هم هستند. نمونه چنین مواردی مرتب‌سازی آرایه‌ها با روش ادغام یا روش سریع است که داده‌ها به دو قسمت تقسیم شده و به صورت مجزا مرتب می‌شوند. در این حالت داده‌های یکی از بخش‌ها هیچ ارتباطی با داده‌های بخش دیگر نداشته و در نتیجهٔ حاصل از آن بخش، اثری ندارند. روش برنامه‌نویسی پویا غالباً برای الگوریتم‌هایی به کار برده می‌شود که در پی حل مسئله‌ای به صورت بهینه می‌باشند. در روش تقسیم و غلبه ممکن است برخی از زیرمسائلِ کوچکتر، با هم برابر باشند که در این صورت زیرمسائلِ برابر، به‌طور تکراری چندین مرتبه حل می‌شوند که این یکی از معایب روش تقسیم و غلبه است. ایده‌ای که در فرایِ روش برنامه‌نویسی پویا نهفته‌است، جلوگیری از انجام این محاسبات تکراری است و روشی که معمولاً برای این عمل به کارگرفته می‌شود استفاده از جدولی برای نگهداری نتایج حاصل از حل زیرمسائل است. در این صورت اگر الگوریتم به زیرمسئله‌ای برخورد کرد که پیش از این حل شده است، به جای حل مجدد آن، نتیجه محاسبه قبلی را از جدول برداشته و کار را با زیرمسئله بعدی دنبال می‌کند. روش برنامه‌نویسی پویا یک روش پایین به بالا است (البته همان‌طور که گفته شد، اگر از قبل بدانیم باید کدام زیرمسئله‌ها را حل کنیم تا به مرحله اصلی برسیم، یا این که مجبور نباشیم زیرمسئله‌هایی که استفاده نمی‌شوند را هم حل کنیم) به این معنی که از حل مسائل کوچکتر به حل مسائل بزرگتر می‌رسیم. درحالی‌که از نظر ساختاری، روش تقسیم و غلبه روشی است از بالا به پایین، یعنی به‌طور منطقی (نه عملی) پردازش از مسئله اولیه آغاز شده و به سمت پایین و حل مسائلِ کوچکتر، به پیش می‌رود. مجموع زیرمجموعه و کوله پشتی: اضافه کردن یک متغیر ما بیشتر و بیشتر می‌بینیم که مسائل مربوط به برنامه‌ریزی منبع خیلی خوبی از مسایل الگوریتمی را با انگیزهٔ عملی ارائه می‌دهد. تاکنون مشکلاتی را در نظر گرفتیم که در آن درخواست‌ها با فاصلهٔ زمانی مشخص از یک منبع مشخص‌شده و همچنین مشکلاتی که در آن درخواست‌ها دارای مدت و مهلت هستند، اما فواصل خاصی را که در طی آن باید انجام شود تعیین نشده‌است. در این بخش، ما نسخه‌ای از نوع دوم مشکل را با مدت زمان و مهلت در نظر می‌گیریم، که حل مستقیم با استفاده از تکنیک‌هایی که تاکنون دیده‌ایم، دشوار است. ما برای حل مسئله از برنامه‌نویسی پویا استفاده خواهیم کرد، اما با یک تغییر: مشخص می‌شود مجموعه زیرمسئله‌ها به اندازه کافی نخواهدبود، و بنابراین ما در نهایت به ایجاد یک مجموعه غنی‌تر از زیرمسئله‌ها می‌پردازیم. همان‌طور که خواهیم‌دید، این کار با اضافه کردن یک متغیر جدید به بازگشت در زیربرنامهٔ پویا انجام می‌شود. مسئله در مسئلهٔ برنامه‌ریزی که در اینجا مورد توجه قرار می‌دهیم، ما یک ماشین داریم که می‌تواند دستورها را پردازش کند همچنین یک سری درخواست‌ها داریم که می‌توانیم از آن در بازهٔ زمانی تا استفاده کنیم. اگر هدف ما این باشد که دستورها را پردازش کنیم به طوری که ماشین را تا زمان مشغول نگه داریم، کدام دستورها را باید انتخاب کنیم؟ هدف ما انتخاب یک زیرمجموعه از این آیتم‌ها است به طوری که مجموع های آیتم‌های انتخاب شده از کمتر مساوی باشند و با توجه به این محدودیت، مجموع این وزن‌ها بیشترین مقدار ممکن باشد. این مسئله یک حالت خاص رایج از مسئلهٔ کوله پشتی است، که هر درخواست یک ارزش و یک‌وزن دارد. هدف در این مسئلهٔ کلی‌تر این است که زیرمجموعه‌ای با بیشترین مجموع ارزش انتخاب کنیم با توجه به اینکه نباید مجموع وزن‌ها از بیشتر باشد. نام «کوله پشی» اشاره به مسئلهٔ پر کردن یک کوله پشتی با ظرفیت دارد که باید آن را تا جای ممکن با استفاده از مجموعه ای از آیتم‌ها پرکرد. چون این مسئله، مسائل برنامه‌ریزی دیگری که قبلاً دیده‌ایم را شبیه‌سازی می‌کند، طبیعی است که از خود بپرسیم آیا الگوریتم حریصانه‌ای برای راه‌حل بهینه وجود دارد یا خیر. به نظر می‌آید که پاسخ خیر باشد، حداقل تا کنون هیچ روش حریصانهٔ کارایی شناخته نشده‌است که آیتم‌ها را به صورت نزولی مرتب کند؛ یا حداقل این کار را برای آیتم‌های با وزن حداکثر انجام دهد، و سپس ما شروع به انتخاب آیتم‌ها با این ترتیب کنیم تا جایی که مجموع وزن آیتم‌های انتخاب شده کمتر از باشد. اما اگر مضرب دو باشد، و ما سه آیتم با وزن‌های داشته‌باشیم، خواهیم دید که الگوریتم حریصانه جواب بهینه را تولید نخواهد کرد. به‌طور جایگزین می‌توانستیم آیتم‌ها را بر اساس وزنشان به صورت صعودی مرتب کنیم و کار مشابه را انجام دهیم، ولی این بار در ورودی‌های شکست می‌خورد. هدف ما این است که نشان دهیم که چگونه با استفاده از برنامه‌نویسی پویا این مسئله را حل کنیم. اصول اولیهٔ برنامه‌نویسی پویا را به یاد بیاورید: ما باید تعداد کمی از زیرمسئله‌ها را داشته باشیم به طوری که هر زیرمسئله را بتوان به راحتی با استفاده از زیرمسئله‌های «کوچکتر» حل کرد و پاسخ به مسئلهٔ اصلی به‌راحتی به‌دست می‌آید، هنگامی که ما پاسخ همهٔ زیر مسئله‌ها را بدانیم. در واقع برای هر مجموعهٔ اولیه از آیتم‌ها و هر مقدار ممکن w برای بیشینه وزن ممکن، زیرمسئله‌ای در نظر می‌گیریم تا با استفاده از آنها بتوانیم سریعتر مسئله را حل کنیم. ما را به عنوان مقدار پاسخ بهینه برای آیتم‌های و بیشینه وزن مجاز تعریف می‌کینم که برابر است با: که روی زیرمجموعهٔ است که را ارضا می‌کند. استفاده از این مجموعهٔ جدید از زیرمسئله‌ها، این اجازه را به ما می‌دهد که مقدار به عنوان یک عبارت ساده بر حسب مقادیر به‌دست آمده از مسائل کوچکتر بیان کرد. در غیر این صورت، ما راه‌حل بهینه را از این طریق با توجه به اینکه برای هر n درخواست کدام یک از گزینه‌های بالا بهتر است به‌دست می‌آوریم. با استفاده از استدلال مشابه برای زیرمسئلهٔ آیتم‌های و بیشینه وزن ، رابطهٔ بازگشتی روبرو را می‌دهد: (*) اگر آنگاه. در غیر اینصورت: مانند قبل، ما می‌خواهیم الگوریتمی طراحی کنیم که جدولی از همهٔ مقادیر ممکن داشته باشیم درحالی که هر کدام از آنها را حداکثر یکبار محاسبه کرده باشیم. با استفاده از لم (*) می‌توان فوراً ثابت کرد که مقدار برگشتی برای در خواست‌های و وزن در دسترس ، پاسخ بهینه‌ای است .( یک آرایهٔ دو بعدی است که مقادیر را در خود ذخیره کرده‌است) آنالیز آلگوریتم برای الگوریتمی که در این شرایط طراحی کرده‌ایم، اما به یک جدول دو بعدی نیاز داریم، که بازتاب دو بعدی جدول زیرسوالات است که در حال ساخته شدن هستند. به عنوان یک مثال از اجرای این الگوریتم، به یک نمونه با وزن و و آیتم‌هایی با اندازه و توجه کنید. ما در خواهیم یافت که مقدار بهینه خواهدبود (که با استفاده از سومین آیتم و یکی از دو آیتم اول آن را به‌دست آورده‌ایم). | ## بخش 3: برنامه‌نویسی پویا | قبل از آن، در مورد برنامه‌ریزی فواصل زمانی موزون، جدول راه حل‌های را ساخته‌ایم، و مقادیر را با استفاده از مقادیر قبلی در زمان اجرای محاسبه می‌کنیم؛ بنابراین مدت زمان اجرا متناسب با تعداد ورودی‌ها به جدول می‌باشد. (**) الگوریتم مجموع زیرمجموعه بهینه جواب مسئله را محاسبه می‌کند و زمان اجرای آن است. توجه کنید که زمان اجرای برنامه تابعی از چندجمله‌ای بر حسب n نیست، در واقع یک تابع چندجمله ای از و است. الگوریتم‌های شبه‌چندجمله‌ای می‌توانند به‌طور قابل توجهی کارا باشند هنگامی که اعداد ی که به عنوان ورودی دریافت می‌کنیم به‌طور قابل توجهی کوچک باشند. برای به‌دست آوردن یک مجموعه بهینه از آیتم‌ها، می‌توانیم آرایهٔ را با استفاده از روشی که در قبل گفته شد ردیابی کنیم. مسئلهٔ کوله پشتی مسئلهٔ کوله پشتی کمی از مسئلهٔ برنامه‌ریزی که در قبل بحث کردیم سخت‌تر است. شرایطی را در نظر بگیرید که مانند قبل آیتم یک‌وزن نامنفی دارد و همچنین یک ارزش جداگانه نیز دارد. هدف ما پیدا کردن مجموعه با بیشینه ارزش با محدودیت مجموع وزن این آیتم‌ها نباید از بیشتر شود. ما از مجموعه زیرمسئله‌های مشابه استفاده می‌کنیم، تا مقدار راه‌حل بهینه را مشخص کنیم با استفاده از زیرمجموعه از آیتم‌های و بیشینه وزن دردسترس . ما یک راه‌حل بهینه در نظرمی‌گیریم و دوحالت را با توجه به اینکه هست یا خیر، مشخص می‌کنیم: ۱- اگر آنگاه ۲- اگر آنگاه استفاده از این خط از استدلال برای زیرمسئله‌ها نتیجهٔ مشابه (*) را می‌دهد. در غیر این صورت: با استفاده از این روش بازگشت، می‌توانیم یه الگوریتم پویای کاملاً مشابه پیاده‌سازی کنیم، که نتیجهٔ روبرو را می‌دهد: مسئلهٔ کوله پشتی در زمان اجرای قابل حل است. الگوریتم ضریب دو جمله‌ای با استفاده از برنامه‌ریزی پویا ورودی‌ها : اعداد صحیح و مثبت و که در آن است. خوب است که علت این موضوع را بدانیم تا از اینکه در دام الگوریتم‌های نادرست و نابهینه بیفتیم، خودداری کنیم. ابتدا به بررسی مسئلهٔ زیر می‌پردازیم: مسئله: بلندترین مسیر ساده ورودی: یک گراف وزن دار با راس‌های شروع و پایان داریم . خروجی: مسیری از به بیابید که بیشترین وزن را داشته باشد و در آن هیچ رأسی را بیش از یک بار نبینیم. این تفاوت قابل توجه نیست: به راحتی با اضافه کردن یال یک تور بسته به دست می‌آوریم. باز هم این تفاوت خیلی مهم نیست: این مسئله ما را تشویق می‌کند تا جایی که می‌توان راس‌ها را بازدید کرد (در حالت ایدئال تمام رئوس) دقیقاً مانند TSP. درستی الگوریتم‌های برنامه‌نویسی پویا وابسته به روابط بازگشتی‌ای است که بر پایه آن بنا شده‌اند. فرض کنید ما تابع را به عنوان تابعی که نمایانگر طول بلندترین مسیر ساده از به است تعریف کنیم. توجه کنید که بلندترین مسیر ساده از به باید قبل از رسیدن به ، تعدادی رئوس را بازید کند؛ بنابراین، آخرین یال بازدید شده باید به صورت باشد. این موضوع رابطهٔ بازگشتی روبرو را برای محاسبهٔ طول بلندترین مسیر ساده پیشنهاد می‌کند: که در آن وزن یال است. به دو مورد زیر توجه کنید: اولاً، این رابطهٔ بازگشتی منجر به هیچ‌گونه سادگی نمی‌شود. برای جلوگیری از چنین چیزی، ما باید یک تابع بازگشتی متفاوت تعریف کنیم که صراحتاً ذخیره کند که ما در چه مکان‌هایی حضور داشتیم. شاید بتوانیم را تعریف کنیم به عنوان تابعی که نمایانگر بلندترین مسیر ساده از به است و از راس عبور نمی‌کند؟ شاید این یک قدم در جهت درست باشد ولی هنوز ما را به یک تابع بازگشتی قابل اطمینان نمی‌رساند. چون هیچ ترتیب چپ به راست یا بزرگ به کوچکی از رئوس وجود ندارد، واضح نیست که زیرمسئله‌های کوچکتر چه هستند. بدون داشتن چنین ترتیبی، ما در یک دور بینهایت می‌افتیم تا زمانی که بخواهیم کاری انجام دهیم. برنامه‌نویسی پویا را می‌توان در هر مسئله‌ای که اصل بهینه بودن را رعایت می‌کند بکار برد. برای مثال، در تصمیم‌گیری اینکه یک تطابق رشته‌ای را به‌وسیلهٔ جابه‌جایی، اضافه کردن یا حذف کردن گسترش دهیم، ما نیازی نداریم که بدانیم کدام دنباله از عملگرها تا آن زمان انجام شده‌است. در واقع، ممکن است دنباله‌های متفاوت ویرایش وجود داشته باشد که هزینهٔ را بر روی حرف از الگوی و حرف از الگوی اعمال می‌کند. مسئله‌ها هنگامی از اصل بهینگی پیروی نمی‌کنند که به جای هزینهٔ عملیات، مشخصهٔ عملیات مهم می‌شود. چنین چیزی می‌تواند شکلی از مسئلهٔ فاصلهٔ ویرایش که در آن مجاز به استفاده از عملگرها با یک ترتیب خاص نیستیم، باشد. زمان اجرای الگوریتم‌های برنامه‌نویسی پویا تابع دو چیز است :۱) تعداد زیر مسئله‌هایی که ما باید آنها را بررسی کنیم ۲) چه قدر طول می‌کشد تا هر زیرمسئله را بررسی کنیم. تاکنون در مسئله‌هایی که بررسی کردیم، زیر مسئله‌ها به‌وسیلهٔٔ مکان‌های توقف در ورودی مشخص می‌شدند. این به این خاطر است که موضوعات ترکیبیاتی که روی آنها کار می‌شده مانند رشته‌ها، دنباله‌های عددی، چندضلعی‌ها و … به صورت ضمنی در عناصر خود دارای یک ترتیب هستند. هنگامی که ترتیب مشخص شد، مکان‌های توقف یا حالات نسبتاً کمی وجود خواهد داشت، به طوری که ما می‌توانیم الگوریتم‌های بهینه را به‌دست بیاوریم. هنگامی که اشیا به صورت مشخص دارای یک ترتیب نیستند، ما تعداد نمایی از زیر مسئله‌ها را به‌دست می‌آوریم (برخلاف بالا). پس مشخص کنندهٔ بلندترین مسیر ساده از تا است که دنبالهٔ دقیق رئوس میانی بین و است که در مسیر آمده‌است. رابطهٔ بازگشتی روبرو برای محاسبهٔ مقدار بالا است که در آن به معنی اضافه کردن به انتهای است: این رابطه درست است اما چه قدر بهینه است؟ پس ما از چنین مسیرهایی داریم، در واقع، این الگوریتم واقعاً از جستجوی ترکیبیاتی استفاده می‌کند تا تمامی مسیرهای بین را بسازد. فرض کنید که مشخص‌کنندهٔ بلندترین مسیر از به است که رأس‌های میانی روی مسیر دقیقاً در مجموعه قرار دارد. بنابر این اگر ، دقیقاً ۶ مسیر وجود دارد: این فضای حالت حداکثر حالت دارد، بنابراین کوچکتر از مسیرهای محاسبه شده در قسمت قبل است. علاوه بر این، تابع را می‌توان با رابطه بازگشتی روبرو محاسبه کرد: پس می‌توان بلندترین مسیر ساده از به را با بیشینه کردن روی مجموعهٔ تمام رئوس بین این دو رأس به دست آورد. تنها زیر مجموعه از راس وجود دارد، پس این یک بهبود چشمگیر روی شمردن تمام تور ممکن است. همچنان برنامه‌نویسی پویا بیشترین تأثیر را بر اشیایی که به صورت مناسب مرتب شده‌اند دارد. الگوریتم‌هایی که از برنامه‌نویسی پویا استفاده می‌کنند جواب‌های بازگشت‌کننده به مدل‌های مشبک برای به هم پیوستن پروتئین و دی‌ان‌ای استنتاج معکوس به عنوان راه حلی برای مسائل بهینه‌سازی پویای زمان‌گسسته با افق متناهی روش ضرایب نامعین را می‌توان برای حل معادلهٔ بلمن در مسائل بهینه‌سازی پویای زمان‌تغییرناپذیر تنزیلیافتهٔ زمان‌گسسته با افق نامتناهی به کار برد. | ## جمع‌بندی | بسیاری از الگوریتم‌های رشته از جمله بلندترین زیردنبالهٔ مشترک، بلندترین زیردنبالهٔ صعودی، بلندترین زیررشتهٔ مشترک، فاصلهٔ لونشتاین بسیاری از مسائل الگوریتمی گرافها را می‌توان به صورت بهینه برای گراف‌هایی با عرض درخت یا خوشهٔ کران‌دار توسط برنامه‌نویسی پویا روی تجزیهٔ درختی گراف حل کرد.