מבני נתונים ואלגוריתמים - מחברת קורס/שיעורי בית 5/תשובות
1
עריכההוכחה א'
עריכהנגדיר את אורך המסלול הארוך ביותר כ , ונשאל את השאלה הבאה: בעץ שגובהו (אורך המסלול הארוך ביותר משורש לעלה) , כמה צמתים אפשר לשים לכל היותר?
נשים לב שאם יש צומת שאין לו שני ילדים, אפשר לבנות עץ גדול יותר על ידי הוספת ילד אליו. מכאן נקבל שהעץ המכיל את מספר הצמתים המקסימאלי מבין הצמתים שגובהם , הוא עץ שבו כל מסלול משורש לעלה הוא בגובה , ולכל צומת (למעט העלים) יש שני ילדים.
בעץ כזה, ברמה הראשונה יש 1 צומת, ברמה הבאה 2 צמתים, וברמה ה יש צמתים. נקבל שמספר הצמתים הוא . זה המספר המקסימאלי של הצמתים. לכן, עבור כל עץ שגובהו h ומספר צמתיו , בהכרח מתקיים . מלקיחת משני האגפים נקבל .
הוכחה ב' (מבוססת על מגבלות דחיסה)
עריכה
שקלו לדלג על נושא זה תוכל ללמוד את חומר הקורס גם בלי הוכחות מסוג זה. |
נגדיר את אורך המסלול הארוך ביותר כ
נניח ש היא קבוצת שמות שידועה לשולח ולמקבל. השולח רוצה להודיע על שם אחד מתוך הקבוצה הידועה. לדוגמה, קבוצת השמות יכולה להיות רשימת המתמודדים על נשיאות ארה"ב, וכתב עיתון מעוניין לשלוח למערכת את שם הזוכה.
משפט: בכל שיטת קידוד, יש הודעה אחת לפחות שארכה . |
(הטענה נובעת משיקולים קומבינטוריים דומים מאד לאלה של החסם התחתון על מיון מבוסס-השוואות שראינו.)
מצד שני, כל עץ חיפוש בינרי יכול לשמש לקידוד צומת. השולח מחפש את הצומת בעץ, מהצומת למטה (כפי שראינו בחיפוש בעץ בינרי). בכל צומת, אם הוא פונה שמאלה הוא מסמן 0, ואם פונה ימינה, הוא מסמן 1. השולח משדר את רצף הביטים למקבל. ברור שהמקבל יכול מתוך רצף הביטים (והעץ) למצא את הצומת המתאים.
נשים לב שמספר הביטים שנשלח הוא בדיוק מספר הקשתות מצומת השורש לצומת הנבחר. לכן, יש לפחות מסלול אחד שארכו .
2
עריכהא'
עריכהזמן הריצה של Member
עריכהלהלן פסוודו-קוד אפשרי לMember
:
# Searches a tree (t) for a key (k).
# Returns whether the tree contains the key.
Find(t, k)
1 nd = t.root
ְ2 while nd != Nil
3 if k < nd.key
4 nd = nd.l-child
5 else if k > nd.key
6 nd = nd.r-child
7 else
8 return True
9 return False
אם מספר הצמתים בתת עץ בגובה הוא , אז קיים קבוע , כך שמספר הצמתים בתת-העץ הוא לכל היותר . נשים לב שבכל איטרציה, אם הצומת לא נמצא (7-8), אז באיטרציה הבאה נמצאים בצומת שגובהו נמוך ב1(בגלל 3-4 או 5-6). לכן:
- באיטרציה הראשונה, מספר הצמתים בתת-העץ הוא לכל היותר .
- באיטרציה השניה, מספר הצמתים בתת-העץ הוא לכל היותר .
- באיטרציה השלישית, מספר הצמתים בתת-העץ הוא לכל היותר .
- ...
נקח . אז , ולכן ב איטרציות נגיע לתת-עץ בגודל 1 (צומת יחיד).
בפרט, אם ניקח עץ בעל איברים, אז , ולכן .
דוגמה: התרשים הבא מראה עץ חיפוש בינרי דחוס. נניח שאנו מתחילים לחפש בשורש ו5-6 מתבצעות. אניטואיטבית, ברור ש"פסלנו" בערך חצי מהאיברים בעץ, ולכן מספר לוגריתמי של פעולות יספיקו. ההוכחה מפרמלת נקודה פשוטה זו. |
אופטימאליות
עריכהכבר ראינו שMember
צריך איטרציות בעץ דחוס, ולכן זהו חסם מלעיל על זמן ריצת חיפוש בעץ דחוס. מצד שני, בחסם תחתון על אורך המסלול הארוך ביותר ראינו בכל עץ חיפוש בינרי בעל איברים, יש מסלול אחד לפחות בעל איברים, וזהו חסם תחתון כללי על חיפוש בעץ במקרה הגרוע. החסמים, לכן, מתלכדים כאן.
ב'
עריכהמיקום הצומת הk-קטן ביותר
עריכה
משפט: הצומת ה בגובה במסלול השמאלי ביותר בעץ, הוא ראשו של תת-עץ המכיל את האיברים הקטנים ביותר בעץ. |
הוכחה:
היות שהעץ הוא דחוס, אז צומת בגובה (בפרט אחד במסלול השמאלי ביותר בעץ), מכיל איברים.
כעת נוכיח שהאיברים בתת-עץ הם האיברים הקטנים ביותר בעץ.
אם הצומת הוא ראש העץ, אז ברור שאין איברים גדולים מהם (כי אין איברים אחרים מהם).
אם הצומת אינו ראש העץ, אז יש לו אב, .
נשים לב ש אינו צאצא ימני של אף צומת (מדוע?), ולכן כל האיברים הקטנים ממנו נמצאים בתת עץ השמאלי שלו.
מהמשפט הקודם נובע שהאיבר ה הקטן ביותר נמצא בתת עץ שראשו הוא צומת בגובה במסלול השמאלי ביותר.
שינויים בעץ
עריכהנוסיף לעץ עוד (מצביע ל)צומת הקטן ביותר, min
:
Binary-Search-Tree
# The root (shoresh).
1 root
# Number of nodes.
2 size
# The smallest node.
3 min
נדאג לעדכן איבר זה בפעולות Insert
וDelete
(הדבר אינו מסובך במיוחד, ולא נציג את הפסוודו-קוד כאן).
שינויים בMember
עריכהכעת ברור מה עלינו לעשות כדי לממש את Member
בגרסה יעילה לאיברים קטנים יחסית. נתחיל בצומת min
, ונעלה למעלה לאורך המסלול השמאלי ביותר. כשנזהה שאין טעם לעלות עוד למעלה, נרד למטה בלולאה הרגילה של Member
.
Member(t, x)
# Loop upward starting from the leftmost node.
1 nd = t.min
2 while nd.parent != Nil and nd.parent.key < x
3 nd = nd.parent
# Now loop down again.
4 while nd != Nil
5 if k < nd.key
6 nd = nd.l-child
7 else if k > nd.key
8 nd = nd.r-child
9 else
10 return True
11 return False
3
עריכהמשפט כללי
עריכהכדי לפתור את התשובה, נוכיח ראשית את המשפט הפשוט הבא:
משפט: בעץ חיפוש בינרי בעל מפתחות שונים זה מזה, נניח שצומת מחזיק מפתח כלשהו. אז:
|
להלן דוגמה למה שהמשפט אומר.
דוגמה: בתרשים הבא, אינו צאצא ימני של אף צומת. צמתי המפתחות הקטנים ממנו (הצבועים באפור), כולם בתת העץ הימני שלו. לעומת זאת, בתרשים הבא, הוא צאצא ימני (לא ישיר) של . צמתי המפתחות הקטנים ממנו (הצבועים באפור), בתת העץ הימני שלו וגם בתת העץ השמאלי של . |
עכשיו תורכם: השתמש בתכונת הBST כדי להוכיח את המשפט. |
הקשר ללא מידע נוסף
עריכהמהמשפט הכללי ברור שאי אפשר לקבוע האם הצומת של הוא צאצא של הצומת של . ייתכן שהצומת של הוא צאצא שמאלי של הצומת של , אך אם הצומת של הוא צאצא ימני של צומת כלשהו, ייתכן שהצומת של הוא צאצא שמאלי של אותו הצומת.
הקשר עם מידע נוסף
עריכהראשית, ברור שכל אחד בנפרד מסדרו של צומת במעבר in-order, post-order, או מספר צאצאיו, אינו מספיק כדי לקבוע יחס צאצא-אב.
נתבונן שוב במקרה בו צומת הוא צאצא ימני של צומת .
- אם , אז יודפס לפני במעבר in-order בכל מקרה. אבל אם , אז יכול להיות צאצא שמאלי של , או צאצא שמאלי של הורה של (לדוגמה ).
- באופן דומה, אם , אז יודפס לפני במעבר post-order בכל מקרה. אבל שוב, אם , אז יכול להיות צאצא שמאלי של , או צאצא שמאלי של הורה של (לדוגמה ).
- כפי שהתרשים מראה, אם , אין בהכרח שום קשר בין מספר האיברים בתת-העץ של לבין זה של .
לעומת זאת, השילוב של סדר post-order וdescendants מספיק.
משפט: נגדיר:
אז כל צומת בתת העץ השמאלי הוא בעל מספר post-order ב . |
הוכחה: מספרי הpost-order של ילדו השמאלי של הם מספרים עוקבים, שהאחרון שבהם הוא .
מהמשפט האחרון ברור כיצד יש למצוא האם צומת כלשהו nd-y
הוא צאצא של צומת כלשהו nd-x
, בהנחה שתוכן הראשון הוא , תוכן השני הוא , ו :
- אם ל
nd-x
אין ילד שמאלי - התשובה שלילית. - אחרת:
- קובעים כ את
Postorder-Num(nd.l-child)
. - קובעים כ את
Descendants(nd.l-child)
. - בודקים האם
{Postorder-Num(nd-y)
גדול או שווה ל .
- קובעים כ את
עכשיו תורכם: חשוב האם שילובי פונקציות שונים יפתרו את הבעיה גם כן. |
4
עריכההמבנה הרקורסיבי
עריכהנגדיר כ את המכפלה הגדולה ביותר האפשרית של תת-סידרה בדלנית של (שים לב שתת-הסדרה אינה בהרכח משתמשת ב ).
משפט: מקיימת את נוסחת הנסיגה הבאה:
|
הוכחה: נשים לב לנקודות הבאות:
- אם , אז בוודאי שנבחר באיבר הראשון (כל איבר הוא לפחות 1); נקבל בהכרח .
- אם , אז בוודאי שנבחר באיבר הראשון או באיבר השני (כל איבר הוא לפחות 1); נקבל בהכרח .
- אם , אז או שנבחר ב או שלא (אלו שתי האפשרויות היחידות).
- אם נבחר ב , אז לא נוכל לבחור ב ; במקרה זה נרצה להכפיל את בתוצאה הטובה ביותר האפשרית עד , שהיא .
- אם לא נבחר את , אז נרצה את תת-הסדרה הטובה ביותר עד , שהיא, עפ"י ההגדרה, .
בנקודה השלישית, היות שאיננו יודעים אם כדאי לבחור את האיבר ב או לא - ניקח את המקסימום משתי האפשרויות.
מימוש רקורסיבי נאיבי
עריכהלהלן פסוודו-קוד המממש רעיון זה:
Max-Product(i)
1 if i == 1
2 return X[1]
3 if i == 2
4 return max(X[1], X[2])
6 guess-with = Max-Product(i - 2) * X[i]
7 guess-without = Max-Product(i - 1)
8 if guess-with > guess-without
9 return guess-with
10 else
11 return guess-without
קל לראות שסיבוכיות האלגוריתם גבוהה מדי. היא נתונה ע"י הפתרון לנוסחה (כלומר אקספוננציאלית, מפני שזו סדרת Fibonacci), וסיבתה היא הסיבה הקבועה: פתירת אותן תת-בעיות שוב ושוב.
שימוש בmemoization
עריכהלהלן פסוודו-קוד יעיל יותר:
1 L = Make-Array(n)
2 for i in [1, ..., n]
3 L[i] = Nil
Max-Product(i)
1 if L[i] != Nil
2 return L[i]
3 if i == 1
4 L[1] = X[1]
5 return L[1]
6 if i == 2
7 L[2] = max(X[1], X[2])
8 return L[2]
9 guess-with = Max-Product(i - 2) * X[i]
10 guess-without = Max-Product(i - 1)
11 if guess-with > guess-without
12 L[i] = guess-with
12 return L[i]
13 else
14 L[i] = guess-without
15 return L[i]
הדפסת האיברים
עריכהלהלן פסוודו-קוד המאפשר גם הדפסת תת-הסדרה:
1 L = Make-Array(n)
2 U = Make-Array(n)
3 for i in [1, ..., n]
4 L[i] = Nil
Max-Product(i)
1 if L[i] != Nil
2 return L[i]
3 if i == 1
4 L[1] = X[1]
5 U[1] = True
6 return L[1]
7 if i == 2
8 L[2] = max(X[1], X[2])
9 U[2] = X[2] == L[2]? True : False
10 return L[2]
11 guess-with = Max-Product(i - 2) * X[i]
12 guess-without = Max-Product(i - 1)
13 if guess-with > guess-without
14 L[i] = guess-with
15 U[i] = True
16 return L[i]
17 else
18 L[i] = guess-without
19 U[i] = False
20 return L[i]
המערך U
מחזיק בכניסה ה את ההכרעה האם תת-הסדרה הטובה ביותר עד אכן משתמשת באיבר ה או לא.
קטע הקוד הבא מראה כיצד להדפיס את האינדקסים של תת-הסידרה הטובה ביותר המסתיימת ב .
Print-Seq(i)
1 if U[i] == True
2 Print-Seq(i - 2)
3 Print(i)
4 return
5 Print-Seq(i - 1)
כיצד נשתמש בקוד, אם כן?
- נאתחל המשתנים הגלובליים, ונקרא ל
Max-Product(n)
. - נקרא ל
Print-Seq(n)
.
ניתוח סיבוכיות
עריכההסיבוכיות הכוללת הנה לינארית:
- מהתבוננות, ברור שהאתחול לינארי.
- הסיבוכיות של
Max-Product(n)
נתון על ידי , כאשר הוא זמן הריצה שלMax-Product(i)
בהנחה שכל קריאה רקורסיבית היא (ראו הניתוח בדג הסלמון). - מהתבוננות, קל לראות שהדפסת האיברים היא .
5
עריכהראשית נניח שהמערך M
ממויין, או, לחלופין, נמיין אותו בעזרת מיון מיזוג בסיבוכיות .
נגדיר כ את מירב הרווח שאפשר להרוויח מפתיחת סניפים הלקוחים מתוך הבתים הראשונים.
משפט: לפי הגדרת :
|
הוכחה: (2)
אם , אז ברור שהפתרון הרווחי ביותר הוא זה שבו בוחרים את הבית הראשון (והיחידי האפשרי לבחירה). אם , אז או שבוחרים את בית , או שלא. במקרה הראשון, מרוויחים מהבחירה בבית, אך לפניו אסור לבחור באף בית בעל מיקום שהפרשו מ קטן מ . במקרה השני, הרווח הוא בדיוק הרווח כשמותר לבחור מבין הבתים הראשונים.
להלן פסוודו-קוד המממש נוסחה זו.
Max-Profit(M, P, k, i)
1 if i == 0
2 return 0
3 if i == 1
4 return P[1]
5 guess-without = Max-Profit(i - 1)
6 guess-with = P[i]
7 if m[i] > k
8 j = Find-Index(M, M[i] - k)
9 guess-with = guess-with + Max-Profit(M, P, k, j)
10 if guess-with > guess-without
11 return guess-with
12 else
13 return guess-without
חשוב רק לשים לב לקריאה לפונקציה Find-Index
ב8. פונקציה זו מוצאת את האינדקס הגדול ביותר בM
שקטן מM[i] - k
. היות שM
ממויין, נוכל לעשות זאת על ידי שינוי קטן בחיפוש בינרי. נוסיף גם Memoization:
1 MP = Make-Array(n)
2 for i in [1, ..., n]
3 MP[i] = Nil
Max-Profit(M, P, k, i)
1 if i == 0
2 return 0
3 if MP[i] != Nil
4 return MP[i]
5 if i == 1
6 MP[1] = P[1]
7 return P[1]
8 guess-without = Max-Profit(i - 1)
9 guess-with = P[i]
10 if m[i] > k
11 j = Find-Index(M, M[i] - k)
12 guess-with = guess-with + Max-Profit(M, P, k, j)
13 if guess-with > guess-without
14 MP[i] = guess-with
15 return guess-with
16 else
17 MP[i] = guess-without
18 return guess-without
כעת נוסיף גם את הדפסת האיברים:
1 MP = Make-Array(n)
2 Used = Make-Array(n)
3 for i in [1, ..., n]
4 MP[i] = Nil
Max-Profit(M, P, k, i)
1 if i == 0
2 return 0
3 if MP[i] != Nil
4 return MP[i]
5 if i == 1
6 MP[1] = P[1]
7 Used[1] = True
8 return P[1]
9 guess-without = Max-Profit(i - 1)
10 guess-with = P[i]
11 if m[i] > k
12 j = Find-Index(M, M[i] - k)
13 guess-with = guess-with + Max-Profit(M, P, k, j)
14 if guess-with > guess-without
15 MP[i] = guess-with
16 Used[i] = True
17 return guess-with
18 else
19 MP[i] = guess-without
20 Used[i] = False
21 return guess-without
המערך Used
מתאר האם השתמשנו באיבר ה או לא. נשתמש בו ע"י קריאה לPrint-Nums(k, n)
, כאשר Print-Nums
מוגדרת כך:
Print-Nums(k, i)
1 if i &lt 1
2 return
3 if Used[i]
4 j = Find-Index(M, M[i] - k)
5 Print-Nums(k, j)
6 else
7 Print-Nums(k, i - 1)
8 if Used[i]
9 Print(i)
נותר רק לנתח את הסיבוכיות.
השורה j ← Find-Index(M, M[i] - k)
אורכת . אם נגדיר כ את זמן הקריאה לMax-Profit(M, P, k, i)
בהנחה שכל קריאה רקורסיבית היא , אז , והסיבוכיות היא . מצד שני, מיון המערך הוא , ולכן נקבל .
6
עריכההפתרון דומה מאד להוכחת הטענה שעצים אדומים-שחורים הם לוגריתמיים. נניח שגובה העץ הוא . מה מספר המפתחות הקטן ביותר היכול להיות בעץ? קל לראות שזה המספר המתקבל כאשר כל אחד מהצמתים הוא מסוג Bar
. במצב זה, היות שעל פי הנתונים כל המסלולים משורש לעלה הם בעלי אותו אורך, נקבל שמספר המפתחות הוא לכל הפחות . הפתרון, לכן, מתקבל על ידי פתירת אי-השוויון :