الگوریتم حریصانه
روش حریصانه (Greedy (/ˈɡri:di/)) یکی از روش‌های مشهور و پرکاربرد طراحی الگوریتم‌ها است که با ساختاری ساده در حل بسیاری از مسائل استفاده می‌شود. این روش اغلب در حل مسائل بهینه‌سازی استفاده شده و در پاره‌ای مواقع جایگزین مناسبی برای روش‌هایی مانند برنامه‌ریزی پویا است. در حالت کلی این روش سرعت و مرتبهٔ اجرایی بهتری نسبت به روش‌های مشابه خود دارد؛ اما متناسب با مسئله ممکن است به یک جواب بهینهٔ سراسری ختم نشود. این دسته از الگوریتم‌ها در علوم رایانه کاربرد وسیعی دارند.
در روش حریصانه رسیدن به هدف در هر گام مستقل از گام قبلی و بعدی است. یعنی در هر مرحله برای رسیدن به هدف نهایی، مستقل از این که در مراحل قبلی چه انتخاب‌هایی صورت گرفته و انتخاب فعلی ممکن است چه انتخاب‌هایی در پی داشته باشد، انتخابی که در ظاهر بهترین انتخاب ممکن است صورت می‌پذیرد. به همین دلیل است که به این روش، روش حریصانه گفته می‌شود. به طور مثال زمانی که یک دزد عجول و حریص وارد خانه‌ای می‌شود، در مسیر حرکت خود هر وسیله و کالای با ارزشی را داخل کیسه می‌اندازد. وی در این حالت چندان توجهی نمی‌کند که چه اشیائی را قبلاً برداشته و ممکن است در آینده چه اشیاء گرانبهاتری به دست آورد. او در هر گام تنها از بین اشیاء دم دست خود با ارزش‌ترین آن را انتخاب کرده و به وسایل قبلی اضافه می‌کند.
این روش کاربردهای عمومی نیز دارد. به طور مثال زمانی که در مقابل خرید از یک فروشگاه یک اسکناس تحویل فروشنده داده می‌شود، وی با یک حساب سرانگشتی سعی می‌کند با حداقل اسکناس‌ها و سکه‌های ممکن باقیماندهٔ پول را تولید کرده و به خریدار تحویل دهد. این حساب سرانگشتی ممکن است روش حریصانه باشد.
در بسیاری از موارد مسئله به‌طور مستقیم یا غیر مستقیم از ما می‌خواهند که یک متغیر را کمینه یا بیشینه کنیم. در اکثر مسائل از این دست ما باید تعدادی انتخاب انجام دهیم و مسئله در تعدادی مرحله حل شود. بازهٔ وسیعی از این مسائل توسط الگوریتم‌های حریصانه قابل حل هستند.
نام این روش از شخصیت معروف اسکروج در داستان سرود کریسمس گرفته شده‌است. یکی از تفریحات و انگیزه‌های اسکروج، به دست آوردن پول بیشتر بود؛الگوریتم حریصانه (Greedy) نیز مانند شیوه اسکروج می‌باشد؛ مثلاً فرض کنید اسکروج در یک مسابقه شرکت کرده‌است و باید در مرحله اول مسابقه یک درب را انتخاب کند، به ازای انتخاب هر درب فقط می‌تواند درب‌های بعد آن را باز کند و پول دریافت کند:
در مرحله اول باید بین دو درب، اولی پنج سکه و دومی دو سکه، انتخاب کند؛ چون او حریص است بهترین انتخاب را در لحظه انتخاب درب اول می‌داند.
در مرحله دوم درب بعد از درب اول دارای سه سکه است و او با هشت سکه خارج می‌شود؛ حال درب بعد از درب دوم را بررسی می‌کنیم؛ ممکن است دارای یک سکه باشد که در این صورت انتخاب حریصانه اسکروج بهینه بوده است یا ممکن است ده سکه باشد که در این صورت نشان می‌دهد که انتخاب بهینه در هر مرحله لزوماً منجر به سود بیشینه نمی‌شود.
کلیت روش حریصانه در هر مرحله، انتخاب یک عنصر از عناصر موجود است. کار با یک مجموعه تهی شروع شده و این عنصر قسمتی از جواب مسئله است که یه ترتیبی خاص به مجموعه عناصر نهایی اضافه می‌شود.
به دلیل این که هر الگوریتم حریصانه الزاماً حل بهینه را نمی‌دهد، برای هر مسئلهٔ خاص باید اثبات کنیم که آیا الگوریتم حریصانه برای آن، جواب بهینه می‌دهد یا خیر و این موضوع اغلب سخت‌ترین مرحله کار است.
در طی این مسیر گام‌های زیر اتفاق می‌افتد:
به زبان ساده، در روش حریصانه طی هر مرحله یک عنصر به روش حریصانه به مجموعه جواب اضافه شده، شرط محدودیت‌ها بررسی شده و در صورت نبود مشکل، عنصر و عناصر بعدی به همین ترتیب به مجموعه جواب اضافه می‌شوند. در طی این گام‌ها اگر به یک شرط نهایی خاص برسیم، یا امکان انتخاب عنصر دیگری برای اضافه کردن به مجموعه جواب وجود نداشته باشد، الگوریتم پایان یافته و مجموعه جواب به دست آمده به عنوان جواب بهینه ارائه می‌شود. توجه داشته باشید که ممکن است بر اساس نوع مسئله، ترتیب اضافه شدن عناصر به مجموعه جواب اهمیت داشته باشد.
هرچند روش یکتایی برای اثبات درستی الگوریتم حریصانه وجود ندارد ولی روش زیر در خیلی از مواقع الگوریتم را اثبات می‌کند. روش اثبات به این صورت است که دنبالهٔ تصمیمات اتخاذ شده در هر مرحله را در نظر گرفته، دنباله‌ای بهینه پیدا می‌کنیم که با دنبالهٔ ساخته شده یکسان باشد (منظور از دنبالهٔ بهینه، دنباله‌ای از کارها می‌باشد که جواب بهینه را به ما می‌دهد). برای پیدا کردن دنباله، ابتدا یک دنباله بهینه را درنظر می‌گیریم و در هر مرحله یک دنباله شبیه‌تر به دنباله خودمان پیدا می‌کنیم که بهینه باشد و سرانجام دنباله‌ای که پیدا کردیم با دنبالهٔ ما یکسان می‌شود.
میزان شباهت دنباله formula_1 و formula_2 را بزرگ‌ترین اندیسی مثل formula_3 در نظر می‌گیریم که formula_4
درواقع شباهت دو دنباله، بزرگ‌ترین عددی مثل formula_3 است که formula_3 عضو اول ابتدای هر دو دنباله با هم یکسان باشد.
چون روش این الگوریتم گرفتن بهترین تصمیم در هر مرحله است، در بسیاری از موارد به راه‌حل بهینه سراسری ختم نمی‌شود. عمده مسائلی که این الگوریتم به جواب درست منتهی نمی‌شود، مسائل مربوط به گراف‌های وزن‌دار است؛ مثل مسئله فروشنده دوره‌گرد یا پیدا کردن بزرگ‌ترین مسیر در گراف وزن‌دار.
ما n شی و یک کولهٔ پشتی داریم که هر شی دارای وزنی است؛ ظرفیت کولهٔ پشتی نیز محدود است. ما می‌خواهیم ارزش شی‌های قرار داده شده در کوله‌ٔ پشتی ماکزیمم شود: ما به دنبال formula_7 Max هستیم، طوری که formula_8 کمتر از W (بیشترین وزنی که در کولهٔ پشتی قرار می‌گیرد. حال با توجه به روش حریصانه، راه حل‌های زیر موجودند:
جواب‌های حاصل از مورد ۱ و ۲ لزوماً بهینه نیستند اما جواب حاصل از روش سوم بهینه است.
مسئله: خریداری از یک فروشگاه یک جنس 64 تومانی می‌خرد و 100 تومان به فروشنده می‌دهد و فروشنده باید 36 تومان به او بازگرداند. اگر فروشنده سکه‌های 50 ,25 ,10 ,5 ,1 تومانی (از هر کدام حداقل یک نمونه) داشته باشد، چگونه می‌توان بقیه پول خریدار را برگرداند به نحوی که تعداد سکه‌ها (در کل) کمترین مقدار ممکن باشد؟
پاسخ: فرض کنید می‌خواهیم n تومان را با سکه های ۲۵ تومانی، ۱۰ تومانی، ۵ تومانی و ۱ تومانی پرداخت کنیم؛ به طوری که کمترین تعداد سکه را بپردازیم. ما می‌توانیم یک الگوریتم حریصانه برای حل این مسئله ارائه دهیم که تعداد سکه بهینه را در هر مرحله به دست آورد. برای این کار ما در هر مرحله بزرگترین سکه‌ای را که از پول باقی‌مانده تجاوز نکند انتخاب می‌کنیم در این صورت تعداد سکه‌ها بهینه خواهد بود.
در ابتدا هیچ سکه‌ای در مجموعه جواب نداریم. از بین سکه‌های موجود بزرگ‌ترین ممکن یعنی 25 تومانی را انتخاب می‌کنیم. این مرحله از الگوریتم حریصانه را روال انتخاب (select procedure) گویند. اگر یک سکه 25 تومانی دیگر را انتخاب کنیم حاصل از 36 تومان بیشتر شده، لذا آن را کنار گذاشته به سراغ سکهٔ 10 تومانی می‌رویم. حال بررسی می‌کنیم اگر این سکهٔ 10 تومانی را به مجموعهٔ انتخابی قبلی اضافه کنیم، حاصل از 36 تومان بیشتر می‌شود یا خیر. این مرحله را تحقیق عملی بودن (feasibility check) می‌نامند. حال اگر این 10 تومان را به 25 تومان اضافه کنیم، جمع مجموعهٔ انتخاب‌شده 35 می‌شود که هنوز به 36 نرسیده است. این مرحله را تحقیق حل شدن (Solution check) می‌گوییم. در ادامه سکه‌های دیگر را به ترتیب مقایسه می‌کنیم و در نهایت با انتخاب سکه یک تومانی در کل با 3 سکه (25 تومانی و 10 تومانی و یک تومانی) 36 تومان به دست می‌آید و این حداقل تعداد سکه ممکن است.
توجه کنید در انتخاب فوق، ملاک انتخاب، برای انتخاب بهترین سکه در هر مرحله (بهینهٔ محلی) سکه است و در انتخاب سکه در هر مرحله به انتخاب‌های قبلی و بعدی کاری نداریم. در این شیوه اجازهٔ فکر کردن دربارهٔ یک انتخاب انجام شده را نداریم؛ یعنی هنگامی که سکه‌ای پذیرفته شد به طور دائم جزو حل به حساب می‌آید و هنگامی هم که سکه‌ای رد می‌شود به‌طور دائم از حل کنار گذاشته می‌شود.
کد مربوط به روش اول: procedure change ({c_1}, {c_2}, {...}, {c_r}: value of denominations of coin, where {c_1}> {c_2}> {...}> {c_r}; n : a positive integer) { همان‌طور که مشاهده کردید این روش بسیار ساده است، ولی اصلی‌ترین نکته این است که آیا این روش الزاماً به یک حل بهینه می‌رسد؟ در رابطه با مسئلهٔ خاص فوق می‌توان اثبات کرد که جواب بهینه است ولی با مثال زیر نشان می‌دهیم که ممکن است این گونه نباشد.
فرض کنید قرار است به فردی 16 تومان پس بدهیم. سکه‌های موجود 50 ,25 ,10 ,5 ,1 تومانی است (با این که ما در ایران سکه‌ٔ 12 تومانی نداریم ولی این فرض را بکنید که داریم).
با الگوریتم حریصانهٔ فوق به این نتیجه می‌رسیم که باید به این فرد یک سکه 12 تومانی و 4 سکه 1 تومانی بدهیم؛ یعنی جمعاً 5 سکه. در حالی که حل بهینهٔ مسئلهٔ فوق یک سکهٔ 10 تومانی، یک سکهٔ 5 تومانی و یک سکهٔ یک تومانی است؛ یعنی در کل 3 سکه.
مسئله (کولهٔ پشتی صفر و یک): مثالی از این مسئله مربوط به دزدی می‌شود که با کولهٔ پشتی وارد یک جواهر فروشی می‌شود. اگر وزن قطعات از یک حد بیشینه W فراتر رود، کولهٔ پشتی پاره خواهد شد. هر قطعه دارای ارزش و وزن معینی خواهد بود. مسئله‌ای که دزد با آن مواجه است، تعیین حداکثر ارزش قطعات است در حالی که وزن کل آن‌ها از حد معین W فراتر نرود. این مسئله را کولهٔ پشتی صفر و یک می‌گویند.
پاسخ: فرض کنید n قطعه وجود داشته باشد، به طوری که:
[s = [item1, item2, item3, ..., itemn
وزن Wi = item1
ارزش Pi = itemi
W = حداکثر وزنی که کوله پشتی قادر به تحمل آن است.
و wi ،pi ،w همگی اعداد صحیح هستند. راه حل جستجوی جامع این است که همهٔ زیرمجموعه‌های این n قطعه را در نظر بگیریم، زیرمجموعه‌هایی را که وزن کل آن‌ها از w فراتر نرود، کنار می‌گذاریم و از میان آن‌هایی که باقی می‌ماند، آن را که بیشترین ارزش را دارد، انتخاب می‌کنیم.  void greedy_knap_sack(w, n){
با تعداد m رنگ داده شده، رأس‌های گراف باید به گونه‌ای رنگ شوند که هیچ دو رأس مجاوری دارای رنگ مشابه نباشند. هدف رنگ کردن رأس‌ها به شیوه‌ای است که هیچ دو رأس مجاوری دارای رنگ مشابه نباشند. هیچ الگوریتم کارآمدی برای رنگ‌آمیزی گراف با حداقل رنگ‌های ممکن وجود ندارد و این مسئله از جمله مسائل «ان‌پی کامل» (NP Complete) محسوب می‌شود. «الگوریتم‌های تقریبی» (Approximate Algorithms) گوناگونی برای حل این مسئله وجود دارند، که رنگ‌آمیزی حریصانه از جمله آنهاست. رنگ‌آمیزی حریصانه به این صورت است که رأس اول را با رنگ اول رنگ می‌کنیم و همه رأس‌های بعدی را با اولین رنگی که در رأس‌های مجاور آن وجود نداشت رنگ می‌کنیم. اگر چنین رنگی وجود نداشت، رنگ جدیدی اضافه می‌کنیم.
void greedyColoring(std::vector)  int result[V.size()];  result[0] = 0;  for (int u = 1; u ::iterator i;  for (i = adj[u].begin(); i != adj[u].end(); ++i)  if (result[*i] != -1)  available[result[*i]] = true;  int cr;  for (cr = 0; cr مجموعهٔ {s = {a1, a2, …, an از n فعالیت پیشنهادی داریم که می‌خواهند از یک منبع استفاده نمایند، مانند یک تالار سخنرانی که در یک زمان می‌تواند تنها مورد استفادهٔ یک فعالیت قرار گیرد. هر فعالیت ai دارای زمان شروع si و زمان خاتمهٔ fi است به‌طوری‌که 0 ≤ fi> si ماکزیمم اندازه از فعالیت‌هایی که هم‌پوشانی ندارند. برای مثال زیرمجموعهٔ s از فعالیت‌ها را در نظر بگیرید که آن‌ها را برحسب زمان پایان به ترتیب صعودی یکنواخت مرتب کرده‌ایم:
برای این مثال زیرمجموعهٔ {a3, a9, a11} شامل فعالیت‌هایی است که هم‌پوشانی ندارند.
اگرچه این زیرمجموعه ماکزیمم نیست، چون زیرمجموعهٔ {a1, a4, a9, a11} بزرگ‌تر از آن است. در حقیقت {a1, a4, a9, a11} بزرگ‌ترین زیرمجموعه از فعالیت‌های متقابلاً سازگار است.
حال یک الگوریتم حریصانه بازگشتی برای حل مسئلهٔ زمان‌بندی فعالیت‌ها ارائه خواهیم نمود.
با تعریف مجموعهٔ {sij = {ak € s : fi ≤ sk زیرمجموعه‌ای از فعالیت‌ها در S است که می‌توانند بعد از خاتمه فعالیت ai شروع شده و قبل از شروع فعالیت aj خاتمه یابند. در حقیقت sij شامل همه فعالیت‌هایی است که با ai و aj و همچنین با همه فعالیت‌هایی که بعد از خاتمه ai خاتمه نیافته و همه فعالیت‌هایی که قبل از شروع aj شروع نمی‌شوند، سازگارند. به منظور بیان کل مسئله، فعالیت‌های ساختگی a0 و an+1 را اضافه کرده و توافق می‌کنیم که f0 = 0 و ∞ = sn+1 پس s = s0, n+1 و محدودهٔ تغییر i و j به صورت زیر خواهد بود:
0 ≤ i, j ≤ n+1
دامنهٔ تغییر i و j را به صورت زیر می‌توانیم بیشتر محدود کنیم. فرض می‌کنیم که فعالیت‌ها در یک ترتیب یکنواخت صعودی از زمان‌های خاتمه مرتب شده‌اند:
f0 ≤ f1 ≤ f2 ≤ … ≤ fn ≤ fn+1 ادعا می‌کنیم که sij = ᴓ هر گاه i ≥ j باشد. فرض کنید یک فعالیت ak € sij برای i ≥ j وجود دارد به‌طوری‌که ai بعد از aj در ترتیب مرتب قرار دارد. پس خواهیم داشت fi ≤ sk کسر مثبتی را می‌توان به صورت مجموع چند کسر واحد متفاوت نوشت. (کسر واحد به کسری گفته می‌شود که صورت آن یک و مخرجش یک عدد طبیعی است.)
ورودی: صورت و مخرج کسری که می‌خواهیم به صورت مجموع چند کسر واحد بنویسیم.
خروجی: مخرج کسرهای واحدی که کسر ورودی را تولید می‌کنند. def egyptianFraction(nr, dr):
روش‌های پریم و کروسکال دو روش مشهور تولید درخت پوشای کمینه از یک گراف وزن‌دار هستند که از روش حریصانه بهره می‌برند. منظور از درخت پوشای کمینه، درخت پوشایی از گراف است که مجموع وزن یال‌های آن کمتر یا مساوی مجموع وزن یال‌های سایر درخت‌های پوشای آن گراف است.
کدگذاری هافمن یک الگوریتم مقایسهٔ داده است که از الگوریتم حریصانه برای پیاده‌سازی استفاده می‌کند و بر اساس تعداد تکرارهای یک کاراکتر در یک فایل است. این کدگذاری در فشرده‌سازی اطلاعات به کار می‌رود و با کدگذاری مجدد کاراکترهای موجود در اطلاعات بر اساس میزان استفاده‌ی آن‌ها، سعی در کم کردن حجم فایل می‌کند. بر اساس این روش، کاراکتری با استفاده‌ی بالا با کد کوتاهتر و کاراکتری با استفاده‌ی کم با کد طولانی‌تر جایگزین می‌شود.
این الگوریتم در سال 1956 توسط ادسخر دکسترا (Edsger Dijkstra) اثبات شد؛ که جزو الگوریتم‌های جستجوی گراف است، و سؤالاتی چون کوتاه‌ترین مسیر از یک رأس در گراف‌های بدون وزن منفی را حل می‌کند. همچنین در مسیریابی (routing) و به عنوان یک زیرروال (subroutine) در الگوریتم‌های دیگر گراف استفاده می‌شود.  def dijkstra(Adj, w, s):