מכללת אורט כפר-סבא מבני נתונים ויעילות אלגוריתמים מימוש עץ בינארי בסביבת העבודה ייצוג ביטוי חשבוני באמצעות עץ בינארי סריקת עץ בינארי לפי רמות מציאת זוג איברים במערך ממוין שסכומם נתון 24.12.14 אורי וולטמן [email protected] חידה לחימום יש מאה חכמים שעומדים בטור :החכם האחרון בטור רואה את כל החכמים שלפניו ,החכם הלפני-אחרון רואה את כולם פרט לאחרון ,וכך הלאה – עד לחכם העומד בראש הטור ,שלא רואה איש. כל חכם חובש כובע בצבע לבן או שחור .כל חכם יודע את צבעי הכובעים של כל האנשים העומדים לפניו בטור ,אולם הוא אינו יודע את צבע כובעו שלו. אדם מסוים עובר על פני הטור ושואל כל אחד ממאה החכמים ,מה צבע הכובע שעל ראשו .לחכם מותר לענות רק "שחור" או "לבן". הראשון שנשאל הוא החכם העומד אחרון בטור ,ומשם ממשיכים לפי הסדר, עד לחכם העומד בראש הטור. אותם חכמים ששוגים כאשר הם נשאלים לגבי צבע כובעם – מוצאים להורג. מצאו אסטרטגיה בה יש לנקוט כדי להבטיח שכמה שיותר מהחכמים ישרדו. מימוש עץ בינארי מימוש טבעי לטנ"מ עץ בינארי יעשה שימוש בחוליה ,אשר תכיל שדה ערך ( ,)infoבדומה לחוליה ברשימה מקושרת. להבדיל מברשימה מקושרת ,כאן יכיל כל צומת שני מצביעים left :ו,right- המצביעים לתת-עץ השמאלי ולתת-עץ הימני ,בהתאמה: ;typedef int tree_item { struct node_type ;tree_item info ;struct node_type *left, *right ;} נגדיר עץ בינארי בתור מצביע למבנה הזה: ;typedef struct node_type *tree עץ בינארי תזכורת – אלו הן פעולות הממשק של טנ"מ 'עץ בינארי': אתחל-עץ פעולה המחזירה עץ בינארי ריק בנה-עץ )(L,R,x פעולה המחזירה עץ בינארי שבשורשו האיבר ,xהתת-עץ השמאלי שלו ,Lוהתת-עץ הימני שלו .R הנחות :העצים Lו R-מאותחלים. תת-עץ-שמאלי )(T פעולה המחזירה את התת-עץ השמאלי של .T הנחות T :מאותחל ואינו ריק. תת-עץ-ימני )(T פעולה המחזירה את התת-עץ הימני של .T הנחות T :מאותחל ואינו ריק. החלף-תת-עץ-שמאלי )(T,new_tree פעולה המחליפה את התת-עץ השמאלי של Tבעץ הבינארי .new_tree הנחות :העצים Tו new_tree -מאותחלים T ,איננו ריק. עץ בינארי תזכורת – אלו הן פעולות הממשק של טנ"מ 'עץ בינארי': החלף-תת-עץ-ימני )(T,new_tree פעולה המחליפה את התת-עץ הימני של Tבעץ הבינארי .new_tree הנחות :העצים Tו new_tree -מאותחלים T ,איננו ריק. אחזר-שורש )(T פעולה המחזירה את האיבר שבשורשו של .TהנחותT : מאותחל ואיננו ריק. עדכן-שורש )(T,x פעולה המשנה את התוכן של שורש Tלהיות .x הנחות T :מאותחל ואיננו ריק עץ-ריק?)(T פעולה המחזירה 'אמת' אם העץ הבינארי Tהוא עץ ריק, ו'שקר' אחרת. הנחה T :מאותחל. מימוש עץ בינארי :tree.h-נתונות הכותרות של הפונקציות ב tree tree_init (void); tree tree_build (tree lsub, tree rsub, tree_item data); tree tree_lsub (tree t); tree tree_rsub (tree t); void tree_change_lsub (tree *t, tree new_tree); void tree_change_rsub (tree *t, tree new_tree); tree_item tree_root_retrieve (tree t); void tree_root_modify (tree *t, tree_item data); int tree_empty (tree t); מימוש עץ בינארי :tree.c ובקובץ המימוש tree tree_init (void) { return NULL; } tree tree_build (tree lsub, tree rsub, tree_item data) { tree t; t = malloc(sizeof(struct node_type)); t->info = data; t->left = lsub; t->right = rsub; return t; } מימוש עץ בינארי tree tree_lsub (tree t) { return t->left; } tree tree_rsub (tree t) { return t->right; } נכתוב,tree_change_lsub - וtree_change_rsub לצורך מימוש פעולות הממשק : המקבלת מצביע לעץ ומשחררת את כל צמתיו,tree_delete פונקצית עזר בשם void tree_delete (tree *t) { if (!tree_empty(*t)) { tree_delete(&((*t)->left)); tree_delete(&((*t)->right)); free(*t); } } מימוש עץ בינארי void tree_change_lsub (tree *t, tree new_tree) { tree_delete(&((*t)->left)); (*t)->left = new_tree; } .right- מוחלף בleft- פרט לכך ש, זהה לגמריtree_change_rsub הפונקציה tree_item tree_root_retrieve (tree t) { return t->info; } void tree_root_modify (tree *t, tree_item data) { (*t)->info = data; } int tree_empty (tree t) { return (t == NULL); } מה סיבוכיות זמן הריצה של כל אחת ?מפעולות הממשק עץ ביטוי בינארי לעיתים קרובות ,כאשר קומפיילר נתקל בביטוי חשבוני בעת סריקה של קוד מקור בשפת תכנות ,הוא מייצג את הביטוי החשבוני באמצעות עץ בינארי. נניח שמדובר בביטוי המורכב ממספרים חד-ספרתיים שיקראו אופרנדים ( ,)operandsומפעולות החשבון הבסיסיות -חיבור ,חיסור ,כפל וחילוק - הנקראות אופרטורים (.)operators כמו כן ,כדי לחסוך את הטיפול בקדימויות שבין האופרטורים ,נניח שהביטוי החשבוני "ממוסגר לחלוטין" ,כלומר -סביב כל אופרטור ושני האופרנדים שעליהם הוא פועל ,מופיעים סוגריים .נסכים גם כי האופרנדים תמיד יהיו אי-שליליים. קבעו אילו מהביטויים החשבוניים הבאים הם "ממוסגרים לחלוטין": עץ ביטוי בינארי נגדיר באופן פורמלי: A ביטוי חשבוני הוא: )(X op Y או: כאשר Aהוא אופרנד כאשר Xו Y-הם ביטויים חשבוניים ו op-הוא אופרטור. נשים לב כי זו הגדרה רקורסיבית X .ו Y-הם ביטויים חשבוניים בעצמם. כיצד נייצג ביטויים כאלה? אם נעיין בהגדרה של עץ בינארי ,נראה דימיון ברור להגדרה של ביטוי חשבוני .לכן, טבעי לייצג ביטוי חשבוני באמצעות עץ בינארי. צומת בעץ יוכל להכיל אחד משני סוגי נתונים :אופרטור או אופרנד. צומת עם בנים יכיל אופרטור ,שאותו צריך להפעיל על הביטויים המיוצגים על-ידי התת-עצים השמאלי והימני של אותו הצומת. עלים בעץ יכילו אופרנדים. עץ ביטוי בינארי נביט בדוגמאות הבאות ,לעצים בינאריים המייצגים ביטויים חשבוניים: כיצד ייוצג הביטוי ) ) ? ( ( 4 + ( 7 * 8 ) ) – ( 9 / 3 עץ ביטוי בינארי נכתוב אלגוריתם רקורסיבי המקבל עץ בינארי מאותחל ולא ריק ,Tהמייצג ביטוי חשבוני ,ומחשב את ערכו של הביטוי .האלגוריתם יעשה שימוש במשתנים r_valו l_val-שיאחסנו ערכים מספריים ,ובמשתנה op שיאחסן אופרטור. חשב-ערך-ביטוי ()T אם עלה? ( , )Tאזי: החזר את אחזר-שורש ()T אחרת: חשב-ערך-ביטוי (תת-עץ-שמאלי (l_val ) )T חשב-ערך-ביטוי (תת-עץ-ימני (r_val ) )T אחזר-שורש (op )T החזר את ))l_val op r_val עץ ביטוי בינארי מהו ערכו של עץ הביטוי הבא? * - + 8 2 * 1 4 3 עץ ביטוי בינארי * - + 8 2 * 1 4 3 עץ ביטוי בינארי * - + 8 2 * 1 4 3 עץ ביטוי בינארי * - + 8 2 * 1 4 3 עץ ביטוי בינארי * - + 8 2 * 1 4 3 עץ ביטוי בינארי * - + 8 2 * 1 4 3 עץ ביטוי בינארי * 1 + * 8 1 4 עץ ביטוי בינארי * 1 + * 8 1 4 עץ ביטוי בינארי * 1 + * 8 1 4 עץ ביטוי בינארי * 1 + * 8 1 4 עץ ביטוי בינארי * 1 + * 8 1 4 עץ ביטוי בינארי * 1 + * 8 1 4 עץ ביטוי בינארי * 1 + 8 4 עץ ביטוי בינארי * 1 + 8 4 עץ ביטוי בינארי * 12 1 עץ ביטוי בינארי * 12 1 עץ ביטוי בינארי 12 עץ ביטוי בינארי לאחר שראינו כיצד אפשר לחשב את ערכו של ביטוי חשבוני המיוצג באמצעות עץ ,נפנה לאלגוריתם הבונה עץ שכזה. האלגוריתם הרקורסיבי בנה-עץ-ביטוי מקבל כקלט ביטוי חשבוני בתור מחרוזת תווים ,ומחזיר את העץ הבינארי המתאים. האלגוריתם יקרא את תווי הקלט זה אחרי זה. אם התו הנקרא הוא אופרנד ,נבנה עלה המכיל אותו. אם התו הוא סוגר שמאלי ,זוהי תחילתו של ביטוי חשבוני מהצורה ( ,)X op Yואז יבוצעו הפעולות האלה: .1בניית עץ המתאים לביטוי החשבוני השמאלי ,Xבאמצעות קריאה רקורסיבית לבנה-עץ-ביטוי. .2קריאת התו הבא (שחייב להיות אופרטור). .3בניית עץ המתאים לביטוי החשבוני הימני ,Yבאמצעות קריאה רקורסיבית לבנה-עץ-ביטוי. .4בניית עץ ששורשו opוהתת-עצים השמאלי והימני שלו הם ,בהתאמה ,העצים שנבנו על פי הביטויים החשבוניים Xו.Y- משנסתיימה בניית העץ המתאים לביטוי שבתוך הסוגריים ,התו הבא במחרוזת צריך להיות סוגר ימני. עץ ביטוי בינארי נניח כי הביטוי שמתקבל בקלט הוא ביטוי חשבוני תקין .האלגוריתם יעשה שימוש במשתנים T1 ,Tו T2-שהם עצים ,ובתו .ch בנה-עץ-ביטוי קרא תו ch אם chהוא ספרה ,אזי: בנה-עץ ( ,chאתחל-עץ ,אתחל-עץ) T אחרת ,אם ‘(‘ = ,chאזי: בנה-עץ-ביטוי T1 קרא תו ch בנה-עץ-ביטוי T2 בנה-עץ (T )T1, T2, ch קרא תו ch החזר את T האם התנאי שמופיע מיד לאחר ה"-אחרת" הוא הכרחי? עץ ביטוי בינארי ))((3-2)*((4*1)+8 עץ ביטוי בינארי = ch ))((3-2)*((4*1)+8 עץ ביטוי בינארי (= ch ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch (==ch ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch ((==ch ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch ( (== ch = ch ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch ( (== ch ch =3 3 ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch ((==ch 3 ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch (ch==- 3 ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch (==ch = ch 3 ))((3-2)*((4*1)+8 עץ ביטוי בינארי ch (==ch ch =2 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch (ch==- 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch (ch==- 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי (= ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי *= ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch *==ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch *(==ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch (*== ch = ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch (= ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch ch (==ch 2 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch ch ( ch==4 2 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch (= ch 2 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch *= ch 2 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch ch *==ch 2 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch ch * ch==1 2 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch *= ch 2 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch **== ch *= ch 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch *(==ch 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch * ch==+ 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch ==*+ ch = ch 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch ==*+ ch ch =8 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch * ch==+ 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי ch * ch==+ - + 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי *= ch - + 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי * *= ch - + 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי * - + 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי * - + 8 2 * 1 4 ))((3-2)*((4*1)+8 3 עץ ביטוי בינארי נביט בעץ הביטוי הבינארי הבא. הביטוי שיתקבל בעקבות סריקה בסדר תחילי ( )preorder traversalשל העץ, הוא ביטוי תחילי ( )prefix expressionהמתאים לביטוי החשבוני המקורי. הביטוי שיתקבל בעקבות סריקה בסדר תוכי ( )inorder traversalשל העץ ,הוא ביטוי תוכי ( )infix expressionהזהה לביטוי המקורי (אך ללא סוגריים). הביטוי שיתקבל בעקבות סריקה בסדר סופי * ( )postorder traversalשל העץ ,הוא ביטוי סופי ( )postfix expressionהמתאים לביטוי החשבוני המקורי. - + 8 2 * 1 4 3 עץ בינארי בעיה אלגוריתמית :פתחו אלגוריתם המקבל ביטוי חשבוני ממוסגר לחלוטין ומחשב את ערכו. בעיה אלגוריתמית :פתחו אלגוריתם המקבל ביטוי חשבוני ,ממוסגר לחלוטין ,ומחזיר ביטוי סופי ( )postfixמתאים. האם תוכלו להיעזר באלגוריתמים שכבר הכרנו כדי להרכיב אלגוריתם הפותר בעיה זו? האם תוכלו להיעזר באלגוריתמים שכבר הכרנו כדי להרכיב אלגוריתם הפותר בעיה זו? בעיה אלגוריתמית :פתחו אלגוריתם המקבל ביטוי חשבוני ,ממוסגר לחלוטין ,ומחזיר ביטוי תחילי ( )prefixמתאים. האם תוכלו להיעזר באלגוריתמים שכבר הכרנו כדי להרכיב אלגוריתם הפותר בעיה זו? רמות בעץ בינארי -תזכורת רמה ( )levelשל צומת מסוים בעץ היא אורך המסלול מהשורש אל צומת זה, כלומר – המרחק של הצומת מהשורש. רמת השורש היא ,0והרמה של כל צומת אחר בעץ גדולה באחד מהרמה של ההורה שלו. גובה עץ ( )tree heightהוא המרחק הגדול ביותר מהשורש לעלה כלשהו של העץ ,כלומר – זוהי הרמה הגבוהה ביותר של העץ. מעוניינים לסרוק את איברי העץ לרוחב ,ולא לעומק. כלומר – לסרוק לפי רמות. סריקה לפי רמות תחילה נבקר ב ,A-ואחר כך בשני בניו D ,ו.H- לאחר הביקור ב H-עלינו לבקר ב ,E-איך כיצד נעבור מהצומת Hל ?E-הרי באמצעות פעולות הממשק שהגדרנו ,ניתן לגשת לבן רק מאביו. כדי לבצע את המעברים הללו ,נשמור את הצמתים שטיפלנו בהם בתור ,המכיל את תת-העצים שטרם נסרקו. תחילה נכניס לתור הריק את העץ כולו .נמשיך על-ידי הוצאת העץ שבראש התור, והכנסת שני התת-עצים של עץ זה לסוף התור ,ונמשיך כך עד שיתרוקן התור. בכל פעם שנוציא עץ מהתור ,נבקר בשורשו. סריקה לפי רמות להלן האלגוריתם המקבל עץ בינארי ,Tוסורק את צמתי העץ ,לפי רמות, משמאל לימין .האלגוריתם עושה שימוש בתור Qשל עצים. סרוק-לפי-רמות ()T אתחל-תור Q אם לא עץ-ריק? ( , )Tאזי: הכנס-לתור ()Q,T כל עוד לא תור-ריק? ( , )Qבצע: הוצא-מתור (T1 )Q בקר בשורש של T1 אם לא עץ-ריק? (תת-עץ-שמאלי ( , ))T1אזי: הכנס-לתור (תת-עץ-שמאלי ()Q,)T1 אם לא עץ-ריק? (תת-עץ-ימני ( , ))T1אזי: הכנס-לתור (תת-עץ-ימני ()Q,)T1 הסבירו מדוע סיבוכיות זמן הריצה של אלגוריתם זה היא לינארית. סריקה לפי רמות A H Y D G B E Z T סריקה לפי רמות A H Y D G B A E Z T סריקה לפי רמות A D D,H E T A H G Z Y B סריקה לפי רמות A D H,E,G E T A,D H G Z Y B סריקה לפי רמות A D E,G,Y E T A,D,H H G Z Y B סריקה לפי רמות A D G,Y,T E T A,D,H,E H G Z Y B סריקה לפי רמות A D Y,T,Z,B E T A,D,H,E,G H G Z Y B סריקה לפי רמות A D T,Z,B E T A,D,H,E,G,Y H G Z Y B סריקה לפי רמות A D Z,B E T A,D,H,E,G,Y,T H G Z Y B סריקה לפי רמות A D B E T A,D,H,E,G,Y,T,Z H G Z Y B סריקה לפי רמות A D E T A,D,H,E,G,Y,T,Z,B H G Z Y B סריקה לפי רמות A D E T A,D,H,E,G,Y,T,Z,B H G Z Y B מציאת זוג איברים במערך שסכומם נתון נתון מערך ממוין aהמכיל nמספרים ,ונתון מספר נוסף .xהמטרה היא לפתח אלגוריתם שייקבע האם קיימים במערך שני איברים שונים שסכומם הוא .xלדוגמא ,עבור המערך aהבא (שמקיים :)n = 8 20 18 15 8 7 4 אם ,x = 11אז האלגוריתם ידפיס תשובה חיובית. אם ,x = 13אז האלגוריתם ידפיס תשובה שלילית. אם ,x = 35אז האלגוריתם ידפיס תשובה חיובית. 3 1 מציאת זוג איברים במערך שסכומם נתון פתרון ראשון :נעבור ,בעזרת שתי לולאות ,forעל כל זוגות האיברים במערך ,ונבדוק עבור כל זוג אם סכומו הוא .x )void find_sum (int a[], int n, int x { ;int i,j )for (i = 0; i < n; i++ )for (j = i+1; j < n; j++ { )if (a[i]+a[j] == x ;)printf (“The sum of %d and %d is %d”, a[i],a[j],x ;return } ;)printf (“There are no two elements whose sum is %d”, x } מהי סיבוכיות זמן הריצה של האלגוריתם? )(n-1) + (n-2) + (n-3) + ... + 1 = n*(n-1)/2 = Θ(n2 אלגוריתם זה כלל לא משתמש בעובדה שהמערך ממוין! מה נעשה? אם נתקלנו בזוג iו j-שעבורו ,a[i]+a[j] > xאין טעם לבדוק את שאר ה-j-ים ,ואפשר לעצור את הלולאה הפנימית ,להגדיל את ,iולהריץ את הפנימית שוב. מציאת זוג איברים במערך שסכומם נתון פתרון שני :אם נתקלנו בזוג איברים שסכומם עולה על ,xנשבור מהלולאה הפנימית ,ונתקדם לאיטרציה הבאה בלולאה החיצונית: )void find_sum (int a[], int n, int x { ;int i,j )for (i = 0; i < n; i++ )for (j = i+1; j < n; j++ { )if (a[i]+a[j] == x ;)printf (“The sum of %d and %d is %d”, a[i],a[j],x ;return ;} else if (a[i]+a[j] > x) break ;)printf (“There are no two elements whose sum is %d”, x } אלגוריתם זה אכן חוסך בצעדים עבור קלטים מסוימים ,אבל האם מה שהושג הוא שיפור בקבוע או שיפור בסדר גודל? השיפור שהושג הוא רק שיפור בקבוע .סיבוכיות זמן הריצה של האלגוריתם נותרה ).Θ(n2 מציאת זוג איברים במערך שסכומם נתון פתרון שלישי :ננצל בצורה משמעותית את העובדה שהמערך ממוין .עבור כל איבר ] ,a[iנחפש בעזרת חיפוש בינארי איבר במערך aשערכו ] .x-a[iאם נמצא איבר כזה ,ברור שסכומם של שני האיברים שווה ל.x- )void find_sum (int a[], int n, int x { ;int i, index { )for (i = 0; i < n; i++ */פונקציה לחיפוש בינארי *index = binary(a,0,n-1,x-a[i]); / && )if (index != -1 { ){ index != i ;)printf (“The sum of %d and %d is %d”, a[i],a[index],x ;return } } ;)printf (“There are no two elements whose sum is %d”, x } ישנה טעות באלגוריתם ...מהי? מה סיבוכיות זמן הריצה של אלגוריתם זה? זמן הריצה של binaryהוא ).Θ(logn לולאת ה for-מתבצעת nפעמים במקרה הגרוע ביותר. )Θ(nlogn מציאת זוג איברים במערך שסכומם נתון פתרון רביעי :ניתן לכתוב גרסה יעילה מעט יותר ,המסתמכת על כך שבהינתן ],a[i אנחנו יודעים באיזה תת-מערך יש להפעיל חיפוש בינארי על מנת למצוא את ].x-a[i )void find_sum (int a[], int n, int x { ;int i, index { )for (i = 0; i < n; i++ index = (x-a[i] < a[i]) ? binary(a,0,i-1,x-a[i]) : ;)]binary(a,i+1,n-1,x-a[i { )if (index != -1 ;)printf (“The sum of %d and %d is %d”, a[i],a[index],x ;return } } ;)printf (“There are no two elements whose sum is %d”, x } מדוע בפתרון זה לא היה צורך לבצע את הבדיקה ? index != i האם השיפור בזמן הריצה הוא שיפור בקבוע או שיפור בסדר גודל? יעילות האלגוריתם נותרה ).Θ(nlogn האם ייתכן ובכלל נאלץ להפעיל את החיפוש הבינארי על המחצית השמאלית של המערך? מציאת זוג איברים במערך שסכומם נתון פתרון חמישי :נבנה מערך דו-מימדי בן nשורות ו n-עמודות ,אשר התא ה- ) (i,jשלו ,יכיל את הערך ] .a[i]+a[jמכיוון שהמערך aממוין ,הרי שכל שורה וכל עמודה במערך הדו-מימדי תהיה אף היא ממוינת. מדובר בטבלת יאנג ( .)Young Tableauאנחנו מכירים אלגוריתם יעיל למציאת איבר בטבלת יאנג ,ונוכל להפעיל אלגוריתם זה עם הערך .x )void find_sum (int a[], int n, int x { ;int i,j */מקצה זיכרון למערך דו-מימדי *int **b = init_matrix(n,n); / )for (i = 0; i < n; i++ )for (j = 0; j < n; j++ ;]b[i][j] = a[i] + a[j ;)find_in_young_tableau(b,n,n,x,&i,&j )if (i != -1 ;)printf (“The sum of %d and %d is %d”, a[i], a[j], x else ;)printf (“There are no two elements whose sum is %d”, x */האם זה מספיק כדי לשחרר את כל הזיכרון שהוקצה? *free(b); / } מציאת זוג איברים במערך שסכומם נתון בפתרון האחרון ישנה שגיאה ...מהי? איברי האלכסון הראשי של המטריצה bאינם מייצגים סכום של שני איברים שונים (אלא של איבר עם עצמו) ,ולכן איננו מעוניינים שהם יוחזרו על-ידי הפונקציה .find_in_young_tableau כיצד ניתן לפתור זאת? כמה זמן אורכת בניית טבלת היאנג המתאימה? Θ(n2) כמה זמן אורך חיפוש בטבלת היאנג? Θ(n) לכן ,האם משתלם ליישם פתרון זה על פני הפתרון הרביעי שסיבוכיות זמן הריצה שלו היא )?Θ(nlogn אם ידוע לנו כי יתבצעו שאילתות רבות על אותו המערך ,אז במקום לבצע פעמים רבות קטע קוד של ) ,Θ(nlognייתכן וישתלם לבנות פעם אחת טבלת יאנג בזמן ) Θ(n2ואח"כ לבצע כל שאילתא בזמן יעיל של ).Θ(n מציאת זוג איברים במערך שסכומם נתון פתרון שישי :באלגוריתם זה נבצע מעבר אחד בלבד על איברי המערך ,אבל נעשה שימוש בשני אינדקסים i :ינוע מ 0-ומעלה ,ו j-ינוע מ n-1-ומטה. נניח ,שנתון המערך הבא ,ושמחפשים שני איברים שסכומם .x = 78 1 2 3 20 25 26 38 40 45 50 55 60 65 נקדם את iכל עוד מתקיים .a[i]+a[j] < x מרגע שנתקלנו בזוג איברים המקיימים ,a[i]+a[j] > xברור שאין טעם להמשיך ולהגדיל את ,iשכן המערך ממוין וגם אז יתקיים .a[i]+a[j] > x לכן ,נקטין את הערך של jכל עוד מתקיים .a[i]+a[j] > x אם במהלך ריצת האלגוריתם ניתקל בזוג איברים שסכומם – xאזי סיימנו .אחרת – יכילו iו j-בשלב מסוים את אותו הערך ,ואז נדווח על כשלון ונעצור. עקבו בעצמכם אחרי ריצת האלגוריתם עבור המערך הנ"ל. מציאת זוג איברים במערך שסכומם נתון void find_sum (int a[], int n, int x) { int i = 0, j = n-1; while (i < j) { if (a[i] + a[j] == x) { printf (“The sum of %d and %d is %d”, a[i],a[j],x); return; } if (a[i] + a[j] < x) i++; else j--; } printf (“There are no two elements whose sum is %d”, x); } אז, ובכל איטרציה ההפרש קטן באחד,n הואj- לi מאחר שההפרש ההתחלתי בין ועל כן סיבוכיות זמן הריצה של, פעמיםn תתבצע לכל היותרwhile-לולאת ה .Θ(n) האלגוריתם היא מציאת זוג איברים במערך שסכומם נתון )Θ(n2 )Θ(n2 )Θ(n )Θ(nlogn )Θ(nlogn )Θ(n2)+Θ(n
© Copyright 2024