greedy algorithms

כאשר דיברנו על אלגוריתמים מהסגנון של תכנות דינמי, ראינו שאם ניתן להביע את פתרון הבעיה כפונקציה ריקורסיבית (עם כמה קריאות ריקורסיביות) כאשר ישנה חזרה על קריאות שהופיעו קודם (במצב של אלגוריתם פיבונאצ׳י אין חזרות כאלה) אז ניתן להקטין את זמן הריצה על ידי שימוש בזכרון נוסף.
אבל מה אם היינו יכולים להבטיח שמספיק להסתכל על קריאה ריקורסיבית אחת? זה יאפשר לעץ הריקורסיה להראות יותר כמו ״מסלול״ מובטח לפתרון הנכון ואולי אפילו לייעל את זמן הריצה של התוכנית.
אינטואיבית זה המשמעות של אלגוריתמים חמדנים- אלגוריתם שמכיל קריאה ריקורסיבית אחת בלבד לתת מופע שעליו נפתור את אותה הבעיה.
השימוש במילה חמדן בא לידי ביטוי בגלל הבחירה החמדנית שהאלגוריתם מבצע: האלגוריתם מבצע החלטה מקומית (החלטה שמבוצעת כחלק מהאלגוריתם עצמו) אחרי אבחון מסויים לגבי הפלט, והוא דבק בהחלטה הזאת לכל אורך האלגוריתם שבעקבותה הוא יודע לאיזה תת מופע ללכת כדי להמשיך את הריקורסיה.

דוגמה חיפוש בינארי :
הקלט הוא מערך ממויין של 𝑛 מספרים ומספר 𝑥 , ורוצים להחליט אם 𝑥 נמצא במערך . האלגוריתם הקלאסי פותר את הבעיה בזמן O(logn), האלגוריתם הזה הוא אלגוריתם חמדן : הרקורסיה מתחייבת לחפש רק בחצי אחד , ואז האלגוריתם ממשיך ברקורסיה רק עבור החלק הזה . איך מוכיחים שאלגוריתם חיפש בינארי הוא נכון? כיוון שהוא אלגוריתם רקורסיבי , אז הכיוון הוא הוכחה באינדוקציה , נתבונן בצעד האינדוקציה , אנחנו צריכים להוכיח שהחצי של המערך שלא מסתכלים עליו , לו מונע אותנו מלהגיע לתשובה הנכונה , וכך יכולים להוכיח שהאלגוריתם מחזיר את התשובה הנכונה (בשימוש בהנחת האינדוקציה ).

הוכחת נכונות של אלגוריתם חמדני

הבחירה החמדנית

נרצה להוכיח את ה greedy choice של האלגוריתם על ידי כך שנראה שההחלטה החמדנית המקומית שהאגוריתם החמדני עושה , לא מונע האלגוריתם מלהגיע לפתרון נכון (או אופטימלי). למשל בחיפוש הבינארי נוכל להגיד שהבחירה החמדנית היא ״אם האוסף שלנו ממויין והאיבר שאנחנו מחפשים גדול מהחציון, אז בוודאות הוא נמצא בתת המופע הימני של האוסף או שהוא לא נמצא בכלל, אחרת, בשמאלי או שהוא לא נמצא בכלל .״

תת- המבנה האופטימלי

צריכים להוכיח שהקומבינציה של הבחירה החמדנית והפתרון של הקריאה הרקורסיבית על תת-המופע שהוגדר ע"י הבחירה החמדנית נותנת הפתרון הנכון (אופטימלי). תת המבנה האופטימלי זאת תכונה ידועה במדעי המחשב שמגדירה מבנה כאופטימלי לבעיה אם ניתן להגיע לפתרון אופטימלי של אותה הבעיה על ידי חיבור של הפתרונות האופטימלים של תתי המופעים של הבעיה.

אלגוריתמים חמדנים מוכרים

א. Prefix Code - Huffman Code
ב. Minimum Spanning Tree

בעיית בחירת הפעילויות

פוליטקיאי מתמודד בבחירות ורוצה להספיק להגיע להופיע בכמה שיותר אירועים. ישנה חפיפה בין אירועים, והמתמודד לא יכול להגיע לאירוע באופן חלקי, כיוון שבכך הוא יביך את מארחיו ויפסיד מצביעים. מנגד, למתמודד יש כח על של התעתקות ממקום למקום, כך שהמעבר בין מקומות אינו לוקח לו זמן.
באופן פורמלי נגדיר את הבעיה ככה
בהינתן שתי פעילויות שמיוצגות על ידי זמן התחלה וסיום [si,fi] הפעילות ה Ai ו [sj,fj] הפעילות ה Aj נאמר שהפעילויות מתיישבות זו עם זו אמ״מ

fisjsifj

זה מתאר מצב של חוסר חפיפה דוגמה לכך על ציר הזמן תיראה ככה
Pasted image 20221127184816.png|300

קלט: מערך של פעילויות S{A1,A2,A3,,An} כך שלכל פעילות Ai יש זמן התחלה וסיום כמתואר למעלה.
פלט:תת הקבוצה גדולה ביותר AS של כל הפעילויות שמתיישבות זו עם זו.

פתרון
נרצה לבנות אלגוריתם חמדני - ישנה איזושהי פעילות Ai שזאת הפעילות הראשונה אליה הפוליטיקאי ילך בפתרון אופטימלי.
נשים לב שבהינתן שניקח Ai כזאת לא ייתכן שנמצא פעילות Aj שמסתיימת לפני תחילת הפעילות Ai אחרת היינו יכולים להוסיף גם את Aj לקבוצת הפעילויות ואז Ai אינה הפעילות הראשונה בפתרון האופטימלי.
מההבחנה הזו, נשים לב לכך שבבואנו לבחור את הפעילות הראשונה, הפרמטר שהכי מעניין אותנו אינו מתי הפעילות מתחילה אלא דווקא מתי היא מסתיימת.
נרצה את הפעילות שמסתיימת מוקדם ביותר, כי זאת תשאיר לנו כמה שיותר אפשרויות לבניית הקבוצה הכי גדולה. לכן הבחירה החמדנית שלנו תהיה שקיים פתרון אופטימלי לבעיית בחירת הפעילויות בו בוחרים פעילות שמסתיימת מוקדם ביותר.
בכל שלב ניקח את הפעילות שמסתיימת מוקדם ככל הניתן מבין הפעילויות שעוד אפשר לקחת, ונמחק מרשימת האפשרויות את כל אלה שחופפות לה.

Activity-Selector(S)
	sort(S, by: f_i)
	A = {A_1}
	j=1
	for i=2 to n
	if s_i >= f_j
		A = A union {A_i}
		j = i
	return A	

נוכל לתאר אותו גם בצורה ריקורסיבית

  1. בחר את הפעילות שמסיימת מוקדם ביותר
  2. מחק את הפעילויות שלא מתיישבות איתה (אלו שמתחילות לפני סיומה)
  3. הוסף באופן ריקורסיבי בחירה גדולה ביותר של פעילויות מהפעילויות הנותרות.

הוכחת נכונות

תכונת הבחירה החמדנית
קיים פתרון אופטימלי לבעיית בחירת הפעילויות בו בוחרים את הפעילות שמסתיימת מוקדם ביותר

ניקח פתרון אופטימלי כלשהו נסמנו OPT
אם הפעילות שמסתיימת מוקדם נמצאת שם סיימנו.
אחרת נסתכל על הפעילות שמסתיימת ראשונה נסמן אותה כ j=minOPT כמו כן נגדיר את 1 להיות הפעילות שמסתיימת ראשונה באופן כללי (בגלל שגם ככה מסתכלים על הפעילויות ממוינות לפי זמני סיום).
אנחנו יודעים שהפעילות j מסתיימת בוודאות אחרי 1 ולכן נבנה את OPT בצורה הבאה

OPT=OPT/{j}{1}

נוכיח כי הוא פתרון חוקי :
עבור שתי פעילויות x,yOPT אם הן היו גם ב OPT אז סיימנו. אם אחת מהן היא 1 (בלי הגבלת הכלליות נניח כי x=1) אזי מחוקי אי שיוויון יתקיים

f1fjsy

כי j,y פעילויות שמתיישבות זו עם זו בפתרון אופטימלי OPT ו1 בוודאות מסתיימת לפניה אז גם אם 1,j לא מתיישבות , בוודאות מתקיים ש 1,y כן מתיישבות.

נוכיח כי הוא פתרון אופטימלי:

|OPT|=|OPT|+11=|OPT|

ו OPT הוא פתרון אופטימלי.

בעזרת תכונת הבחירה החמדנית, נוכיח את נכונות האלגוריתם, בהוכחת תכונת תת־המבנה האופטימלי ־ זוהי הוכחת הנכונות של האלגוריתם (מקבילה לצעד האינדוקציה בהוכחת נכונות המתבססת על אינדוקציה).

תכונת תת המבנה האופטימלי
פתרון המורכב מבחירה של הפעילות שמסתיימת מוקדם ביותר, בתוספת פתרון אופטימלי לבעיית בחירת הפעילויות עם כל הפעילויות שמתחילות אחרי סיום הפעילות הנ"ל הוא פתרון אופטימלי לבעיה.

יהי פתרון A כפי במוצג בהגדרת הפתרון למעלה ונב״שׁ שאינו אופטימלי ופתרון B אופטימלי שמכיל בתוכו את 1 לפי למת הבחירה החמדנית.
מהנתון מתקיים

|B|>|A|

אנחנו יודעים שמתקיים מאיך ש A בנוי ש 1A כי זאת הפעילות שמסתיימת מוקדם ביותר. כמו כן אנחנו יודעים ש A/{1} הוא פתרון אופטימלי לבעיה כיוון שA בנוייה מתת פתרון אופטימלי איחוד עם ֿ{1} .
כלומר נקבל שגם A/{1} וגם B/{1} הם פתרונות אופטימלים עם הפעילויות {iS | sif1} . כלומר יתקיים

|B/{1}||A/{1}||B|1|A|1|A||B|

בסתירה לכך ש A לא אופטימלית.

בעיית תרמיל הגב בשברים

ב בעית תרמיל הגב אנו חושבים על גנב שצריך לבחור אילו פריטים לגנוב תוך אילוץ של משקל מקסימלי. בגרסת השברים, אנו מניחים שהגנב פרץ לבית מרקחת וניתן לקחת גם חלקים של מוצר, והשווי וגם המחיר של המוצר הנגנב פרופורציונאליים לחלק אותו בוחרים לגנוב.

קלט: מערך של מחירים V=(v1,v2,,vn) ומערך של משקלים W=(w1,w2,,wn) ומספר B .
פלט: החפצים או החלק שאנחנו לוקחים מכל חפץ (1- לקחת את כל החפץ , 0- לא לקחת בכלל , כל מספר בין לבין- לקחת את החלק היחסי). הפלט ייראה כך X=(x1,x2,x3,,xn) כך ש xi[0,1] ומתקיים

1in:i=1nxiwiBi=1nxivi   is the maximum possible sum 

נציע אלגוריתם חמדני הפותר את הבעיה הזו.
בעוד שבבעיית תרמיל הגב הרגילה לעבוד עם יחס עלות ליחידת משקל מסויימת לא היה עובד, כאן אכן נוכל לעבוד איתו.
נסמן ai=viwi ונמיין את החפצים לפי יחס הסדר מהגדול להקטן על ai. כלומר במקום ה1 יהיה את היחס עלות למשקל הכי גבוה.
כעת נבחר באופן חמדני את הערכים שם היחס הגדול ביותר כל עוד יש מקום, עד לזה שערכו הקטן ביותר.

Knapsack(V,W,B)
	define A[n]
	for i=1 to n
		A[i] = a_i = v_i / w_i
	sort V and W from biggest to smallest using A
	define X[n]
	for i=1 to n
		x[i] = min(B,w_i)/w_i
		B = B - x_i * w_i
	return X	 

באופן ריקורסיבי נוכל להסתכל על האלגוריתם כך:

  1. ניקח את המקסימום האפשרי מהחפץ עם היחס עלות-משקל הגבוהה ביותר.
  2. משמיטים את כמה שלקחנו מהמשקל
  3. מפעילים את הריקורסיה עם משקל מופחת ובלי האיבר שהוספנו

הוכחת נכונות

תכונת הבחירה החמדנית
קיים פתרון אופטימלי לבעיית תרמיל הגב בשברים בו לוקחים את החלק הגדול ביותר שניתן מהחפץ שמתאים ל־a1) החפץ עם השווי הגדול ביותר ליחידת משקל).

יהי פתרון אופטימלי כלשהו (x1,x2,,xn)
אם קיים xi כך שהוא מכיל בתוכו את החפץ הגבוה ביותר ליחידת משקל סיימנו.
אחרת ישנו בלי הגבלת הכלליות נניח x1 הוא מתאר את הכמות שלקחנו בפתרון האופטימלי הזה עם היחס הטוב ביותר מבין כל האחרים שלקחנו. נחלק למקרים :

  1. לא לקחנו בכלל מv שהוא מתאר את הכמות שלקחנו מהחפץ עם a המשתלם ביותר מבין כל החפצים כלומר x=0 - נכניס את x לאחר שחישבנו אותו מחדש עבור B המקורי (נזכיר x=min(B,w)/w) לאוסף ונבדוק האם אין חריגה במשקל, אם אין אז סיימנו כי הוספנו עוד ערך בלי לחרוג. אם יש חריגה נתחיל להשמיט כמה משקל שאנחנו יכולים מהסוף להתחלה , כלומר מ xn עד ל x1 ההשמטה תהיה ביחס לחריגה מהמשקל. החריגה תבוא לידי ביטוי בצורה מספר שלילי כלומר B=W<0 וכעת מה שאפשר לעשות זה
Balance(B)
	assert B < 0
	
	for i=n to 1
		B =  B + x_i * w_i
		if B < 0
			continue
		if B = 0
			return 
		x_i = min(B, w_i)/w_i		
		return
  1. אם לקחנו מv אבל לא בכמות המקסימלית, נעשה בידיוק אותו דבר, נעשה חישוב מחדש לx נכניס אותו לוקטור ונריץ את אותו אלגוריתם.

נשים לב שברור שהסכום החדש יניב יותר כסף , קל להוכיח את זה באופן הבא, נפרק את המשקל שלקחנו מ x , נסמנו w כסכום של כל המשקלים שהשלמנו
w=wn+wn1++w1
אם לא לקחנו מ wi אז ערכו 0. כעת יתקיים

i[1,n]:vwi>viwii=1nvwi>i=1nviwi

נשים לב שבוודאות ניתן יהיה לאזן על ידי הורדת משקלים בלי שנגיד למצב ש״אין מספיק להוריד״ כי בסוף בוודאות סכום כל מי שאינו x היה B כלומר יש לנו עד B להוריד מהמשקל.

תכונת תת המבנה האופטימלי
פתרון המורכב מבחירה של החלק הגדול ביותר שניתן מהחפץ הראשון, בתוספת בחירה אופטימלית כלשהי לתת הבעיה המוגדרת ע"פ שאר החפצים בבית, עם תרמיל שמשקלו קטן מ־B עם המשקל הגדול ביותר שניתן לקחת מהחפץ הראשון ־ הוא פתרון אופטימלי לבעיה.

נב״ש שהפתרון המתואר בלמה (x1,x2,,xn) אינו אופטימלי ונניח ש (y1,,y2) פתרון אופטימלי. מהנחת הבחירה החמדנית נקבל ש

y1=x1i=1nxi<i=1nyi

אם נוריד את x1,y1 נקבל שתי פתרונות אופטימליים לבעיה תרמיל הגב בשברים עבור תרמיל במשקל Bx1w1. אבל מכאן יתקיים בגלל שכעת אנחנו עובדים עם תת פתרון אופטימלי x2,,xn

i=2nyii=2nxi

ומכאן

i=2nyi+x1i=2nxi+x1

שזה בסתירה לאי שיוויון הראשון.