Professional Documents
Culture Documents
פרק 9 - רשימה
פרק 9 - רשימה
פרק 9
רשימה
אוס
לינארי
בפרקי הקודמי הכרנו שני סוגי אוספי כלליי ,מחסנית ותור .ראינו כי ההבדל ביניה הוא
בנוהל ההכנסה וההוצאה של האיברי :במחסנית האיברי הוכנסו והוצאו מצד אחד בלבד של
המחסנית )ראש המחסנית( ,ובתור הוכנסו האיברי מצד אחד והוצאו מצדו האחר .המשות
למחסנית ולתור היה ההגבלה על הגישה לאיברי הסדרות השמורי בה.
בפרק זה ברצוננו להגדיר אוס כללי מסוג חדש – רשימה ) .(Listזהו אוס סדרתילינארי ,ונית"
להכניס אליו ולהוציא ממנו איברי בכל מקו בסדרה ללא הגבלה .סוג אוס זה שימושי
ביישומי רבי שבה יש צור #בניהול רשימות מסוגי שוני.
כדי לנהל רשימה נזדקק ,בי" היתר ,לפעולות האלה :בנייה של רשימה ריקה ופעולות הכנסה
והוצאה של ערכי ,שיוכלו להתבצע בכל מקו ברשימה .אחת הפעולות הנפוצות שייעשו על
רשימה היא סריקתה ,וזאת כדי לאתר נתוני רצויי ולבצע עליה פעולות נוספות )עדכו",
אחזור ,הוצאה וכדומה( .כדי לבצע סריקה של רשימה יש להתקד לאור #הרשימה החל באיבר
הנמצא במקו הראשו" בה.
לפני שנציג ממשק המסכ את הפעולות על רשימה ,עלינו להגדיר מושג חדש :מקו ).(position
פעולות הממשק מזכירות מושג זה ומשתמשות בו ,ולכ" עלינו להגדירו קוד להצגת הממשק .כדי
לבחו" את האפשרויות להגדרת המושג מקו ,עלינו להקדי ולדו" בייצוגי אפשריי לרשימה
ובמשמעותו של מקו בכל אחד מייצוגי אלה.
ייצוג כזה יתבסס על מבנה הנתוני המוכר :שרשרת חוליות .איברי הרשימה יאוחסנו בחוליות
השרשרת .סדר האיברי יישמר על ידי הגדרת השרשרת ,שבה כל חוליה מפנה אל העוקבת לה.
ברשימה המיוצגת כ ,#המושג מקו של איבר הוא הפניה לחוליה המכילה אותו.
בגישה זו לייצוג ,רשימה תיוצג באמצעות תכונה אחת שתחזיק את שרשרת החוליות ,כלומר ערכה
יהיה הפניה לחוליה הראשונה בשרשרת:
רשימה המיוצגת על ידי שרשרת חוליות נקראת רשימה מקושרת ) .(linked listייצוג זה ישמש
אותנו בפרק זה.
הפעולה )( GetNextהיא פעולה על חוליה בודדת ,ואול רוב הפעולות שאנו זקוקי לה" אינ"
מוגדרות על חוליה בודדת ,וכמוב" אינ" קיימות בממשק המחלקה חוליה .פעולות אלה קשורות
לניהול שרשרת חוליות שלמה .כזו היא הפעולה )( GetFirstשהוזכרה לעיל .בפעולות אלה נכללות
ג פעולת בנייה של רשימה ריקה ,פעולה לבדיקת ריקנות של רשימה – )( ,IsEmptyפעולת הכנסה
– )…( ,Insertפעולת הוצאה – )…( ,Removeוכ" פעולה המחזירה תיאור של הרשימה –
בתכנות מונחה עצמי מקובל להגדיר מחלקה עבור כל סוג ישות שעובדי איתו ,לכ"
)(ִ .ToString
נגדיר מחלקה ,Listשבה יוגדרו הפעולות העוסקות בניהול שרשרת החוליות .באמצעות מחלקה זו
נוכל לבנות עצמי מטיפוס רשימה ולטפל בה .כיוו" שהפעולות אינ" מבצעות על איברי הרשימה
פעולות ייחודיות לטיפוס מסוי ,המחלקה תהיה גנרית.
נציג את הממשק המסכ של המחלקה רשימה ,הכולל את הפעולות שהוזכרו לעיל .בתיאור
הממשק ,משמעות המונח 'מקו' היא הפניה לחוליה.
257 פרק – 9רשימה ,אוס לינארי
שימו לב ,הפעולות בממשק המחלקה רשימה אינ" מאפשרות כשלעצמ" לבצע כל מה שאנו רוצי
על רשימה .כדי לשלו את הער #בחוליה או לשנותו ,וכ" כדי להתקד מחוליה אחת לבאה
אחריה ,עלינו להשתמש בפעולות שבממשק החוליה .א כ" ,סוג האוס רשימה מוגדר על ידי שתי
מחלקות Node :ו .Listאוס הפעולות של שתי המחלקות יחד מאפשר לבצע את כל מה שנרצה
לבצע על רשימה ,כפי שיודג להל".
nameList
>List<string
first
null
הפרמטר posהוא מקו ברשימה שאחריו רוצי להכניס איבר חדש המכיל את הער .x #קיי
מקרה קצה אחד והוא הכנסת איבר למקו הראשו" ברשימה .כיוו" שאי" איברי לפני האיבר
הראשו" ,אי" מקו שאחריו תתבצע ההכנסה .למקרה קצה זה נגדיר שימוש ב nullכער #מיוחד
ל .posכלומר ,א ערכו של posהוא ,nullהכוונה היא שיש להכניס את האיבר במקו הראשו".
כיוו" שפעמי רבות פעולת ההכנסה משולבת בפעולת הסריקה של הרשימה ,ואנו מעונייני
להמשי #בסריקה ממקו ההכנסה ,נקבע שפעולת ההכנסה תחזיר הפניה לחוליה שהוכנסה .כ#
נית" להמשי #את הסריקה של הרשימה ממקו זה.
nameList pos
>List<string
first
"Moshe" null
nameList pos
>List<string
first
nameList pos
>List<string
first
המשתנה posמאותחל ל ,nullכיוו" שהפעולה )( GetFirstהמופעלת על רשימה ריקה מחזירה .null
לכ" בזימו" הראשו" של הפעולה )…( ,Insertהער #אפס יוכנס למקו הראשו" pos .מתקד ועכשיו
הוא מפנה למקו הראשו" ברשימה .הזימו" השני מכניס את הער 1 #למקו השני ,וכ #הלאה.
numList pos
>List<int
first
נית" להשתמש בלולאות כדי להוסי לרשימה רצ המתקבל ממער ,#מרשימה אחרת או מהקלט.
דוגמאות יוצגו בהמש #הפרק.
? תארו את הרשימה המתקבלת מהפעלת הקוד שלפניכ על רשימת המספרי הריקה :lst
)for (int i=0; i<3; i++
;)lst.Insert(null, i
posהוא המקו של הער #שאנו רוצי להוציא מהרשימה .לעתי קרובות אנו מוציאי ערכי
מהרשימה תו #סריקה ,ומעונייני להמשי #בה .הער #שהוצא כבר אינו ברשימה ולכ" הפעולה
מחזירה את המקו העוקב לו .ממקו זה נית" להמשי #את הסריקה.
עיצוב תוכנה מבוסס עצמי – סישרפ 260
nameList pos
>List<string
first
כפי שנית" לראות באיור ,הער #הרצוי הוצא מהרשימה pos .התעדכ" והוא נמצא במקו העוקב
למקו ההוצאה.
;return len
}
261 פרק – 9רשימה ,אוס לינארי
;return pos
}
>List<T
Node<T> first
)(List
)(bool IsEmpty
)(Node<T> GetFirst
)Node<T> Insert(Node<T> pos, T x
)Node<T> Remove(Node<T> pos
)(string ToString
פעולה בונה
הפעולה הבונה מייצרת רשימה ריקה ,שבה ער #התכונה firstהוא :null
)(public List
{
;this.first = null
}
עיצוב תוכנה מבוסס עצמי – סישרפ 262
פעולת הכנסה
פעולת זו מכניסה חוליה חדשה לשרשרת חוליות .דוגמה של הפעולה ראינו בפרק 7בסעי א.2.2.
כעת עלינו לנסח פעולת הכנסה כללית :ער #החוליה יהיה xמטיפוס כלשהו וההכנסה תתבצע
אחרי המקו .pos
בתהלי #מימוש הפעולה יש להפריד בי" שני מקרי :כאשר ער #הפרמטר posהוא ,nullבמקרה
זה ההכנסה תתבצע למקו הראשו" בשרשרת; בכל מקרה אחר ,ההכנסה תתבצע אחרי המקו
:pos
else
{
;))(temp.SetNext(pos.GetNext
;)pos.SetNext(temp
}
;return temp
}
פעולת הוצאה
פעולת זו מוציאה חוליה קיימת משרשרת חוליות ,כפי שראינו בפרק 7בסעי א .3.2.כעת עלינו
לנסח פעולת הוצאה כללית :ההוצאה תתבצע על החוליה שבמקו ,posהמתקבל כפרמטר,
והמציי" מקו קיי ברשימה הנוכחית ,שאינו .nullבמימוש הפעולה יש להבחי" בי" שני מקרי:
כאשר הפרמטר posהוא המקו הראשו" בשרשרת; בכל מקרה אחר ,ההוצאה תתבצע על המקו
posשאינו ראשו" בשרשרת:
263 פרק – 9רשימה ,אוס לינארי
;return nextPos
}
כפי שנית" לראות ממימוש הפעולה ,הטיפול במקרה הראשו" )הוצאת החוליה הראשונה( הוא
פשוט :א posשווה בערכו לתכונה ,firstכלומר שניה מפני לחוליה הראשונה ,עלינו לעדכ" את
התכונה firstכ #שתכיל את ההפניה למקו העוקב של .pos
לעומת זאת ,בכל מקרה אחר ,הוצאת החוליה מהמקו posשאינו הראשו" מצריכה תהלי #מורכב
יותר .בשלב הראשו" ,יש לאתר את החוליה הקודמת ל posולשמור אותה ב .prevPosבשלב השני,
נעדכ" את ההפניה העוקבת של prevPosלהיות ההפניה העוקבת של .pos
כדי שלא לשמור הפניות מיותרות לשרשרת החוליות ,נסיי את פעולת ההוצאה בניתוק החוליה
.posנעדכ" את ההפניה לעוקב של posלהיות .null
שימו לב שהחוליה שהוצאה מהרשימה אמנ מפסיקה להיות חלק מהרשימה ,א #ייתכ" שקיימת
אליה הפניה חיצונית אחרת.
א נוציא את האיבר הראשו" באמצעות הפעולה ) ,Remove(...אזי ג היעילות שלה תהיה ),O(1
כיוו" שאי" צור #לחפש את האיבר הקוד .כאשר nהוא מספר האיברי ברשימה ,יעילות
ההוצאה של האיבר האחרו" היא מסדר גודל ) ,O(nשכ" יש לסרוק את כל הרשימה כדי למצוא את
המקו הקוד למקו ההוצאה .יעילות הפעולה היא ) ,O(nכיוו" שאנו מחשבי יעילות לפי
המקרה הגרוע.
א אנו מעונייני לא לפגוע ברשימה הקיימת ,נבחר באפשרות השנייה .נממש פעולה שמקבלת
רשימת מספרי שלמי ומחזירה רשימה חדשה המכילה את המספרי הזוגיי מתו #הרשימה
שהתקבלה:
;return evenList
}
הפעולה מחזירה רשימה חדשה ואינה משנה את מצבה של הרשימה שהתקבלה כפרמטר .יעילות
הפעולה לינארית בגודל הרשימה .lst
265 פרק – 9רשימה ,אוס לינארי
פעולה זו מדגימה אופני שוני לקידו הפניות המשתתפות בסריקת הרשימה .הקידו של ,pos1
שבאמצעותו סורקי את הרשימה המקורית ,נעשה בעזרת הפעולה )( .GetNextלעומת זאת,
קידומו של ,pos2המציי" את מקו ההכנסה ברשימה החדשה ,נעשה על ידי עדכונו בער #ההחזרה
של פעולת ההכנסה .יש משמעויות שונות לאופ" הקידו של ההפניות ,ולרוב לא נית" להחלי את
אופני הקידו שלה".
בפרק – 6הפניות ועצמי מורכבי ,בסעי ו ,.3.דנו בהכנסה של ער #לתו #אוס ממוי" .ראינו כי
בשלב הראשו" יש לבצע סריקה כדי למצוא את המקו שאליו צרי #להכניס את הער #החדש.
הסריקה מתבצעת כל עוד הערכי באוס קטני בערכ מהער #החדש .הסריקה מסתיימת א
הער #במקו הנוכחי גדול מהער #המיועד להכנסה .בפעולה שנכתוב עתה נשמור את המקו
הקוד למקו ההכנסה במשתנה שייקרא prev .prevהוא פרמטר המקו לפעולת ההכנסה
שמתבצעת במקו שאחריו .לפניכ הקוד המממש את ההכנסה של המחרוזת למקומה:
public static void InsertIntoSortedList(List<string> lst,
)string str
{
;Node<string> prev = null
;)(Node<string> pos = lst.GetFirst
;)lst.Insert(prev, str
}
יעילות הפעולה לינארית בגודל הרשימה ,lstמכיוו" שבמקרה הגרוע המחרוזת strתוכנס לסו
הרשימה.
.1הפעולה תשאיר את הרשימה המקורית בצורתה ,ותחזיר רשימה חדשה המכילה את הערכי
המקוריי בסדר ממוי".
.2הפעולה תמיי" את הרשימה המקורית ללא שימוש בזיכרו" נוס .מיו" כזה נקרא מיו ַ(מקו
).(in place sort
נמיי" את הרשימה המתקבלת בעזרת רשימה חדשה שאליה נכניס את הערכי המקוריי תו#
שמירה על הסדר שלה .הפעולה שנכתוב תמיי" את רשימת השלמי ותשאיר את הרשימה
שהתקבלה כפרמטר ללא שינוי .הפעולה תחזיר רשימה חדשה המכילה את הערכי המקוריי
בסדר ממוי" .נתחיל כאשר הרשימה החדשה ריקה .במצב זה היא בוודאי ממוינת .מכא" ואיל#
ניעזר בפעולת העזר )…( ,InsertIntoSortedListשאותה הגדרנו בסעי הקוד .הפע תקבל
הפעולה רשימה ממוינת של שלמי ,ותכניס כל ער #נוס מהרשימה המקורית למקו המתאי
לו ברשימה הממוינת:
;return sortedList
}
יעילות הפעולה היא ריבועית בגודל הרשימה .lstהפעולה כוללת מעבר על כל איברי הרשימה,
ומכניסה כל איבר לרשימה החדשה באמצעות פעולת העזר )…( ,insertIntoSortedListבמחיר
) ,O(nכפי שחישבנו לעיל.
:# לאחר שני שלבי היא תיראה כ.lst: [4, 2, 1, 20, -3] נתונה הרשימה,לדוגמה
lst untilPos
List<int>
first
2 4 1 20 -3 null
הראשו" מהחלק הלא ממוי" של הרשימה לחלק הממוי" של#בכל שלב יש להכניס את הער
פעולה זו דומה לפעולה.InsertIntoPartialSortedList(…) הרשימה בעזרת פעולת עזר הנקראת
במקומו החדש בחלק: מופיע פעמיי ברשימה# לאחר שלב זה הער.InsertIntoSortedList(…)
." לכ" יש לבצע פעולת הוצאה של האיבר המקורי מהחלק הלא ממוי. ובמקומו המקורי,"הממוי
: שינוי הרשימה המקורית# תו,להל" פעולה המבצעת מיו" של רשימה באמצעות מיו" הכנסה
lst.Insert(prev, untilPos.GetInfo());
}
בפעולה," ואילו כא,#( הפרמטר השני הוא ער2 )דוגמהInsertIntoSortedList(…) בפעולה.? א
. הפרמטר השני הוא חוליהInsertIntoPartialSortedList(…)
. בפרמטר מטיפוס חוליה# יש צורInsertIntoPartialSortedList(…) הסבירו מדוע בפעולה
?insertionSortInPlace(…) מהי יעילותה של הפעולה.ב
עיצוב תוכנה מבוסס עצמי – סישרפ 268
בדוגמה האחרונה ,מיו" במקו ,הצגנו רעיו" תכנותי המסייע לנו כאשר אנו נדרשי לארג" מחדש
אוספי נתוני .כאשר מבצעי קטע קוד המשנה סדר של אוס נתו" המועבר כפרמטר ,ואופ" ביצוע
התכנות נעשההפעולה אינו מצרי #שימוש בשטח זיכרו" שגודלו כגודל האוס המקורי ,נאמר כי ִ
ַ6מקו .in place ,גישה תכנותית זו חוסכת במשאבי המקו בזיכרו" .מעבר לכ ,#היא מועילה
כיוו" שהעצ המקורי משק את השינוי והתהלי #שהוא עבר .חישבו עד כמה תהיה עבודתנו נוחה,
א הרשימה הכיתתית תמשי #לשק את המיו" ,ג לאחר כל פעולות ההכנסה אליה.
בכל בעיה שתעלה ,יש לשקול הא תכנות ַ6מקו יועיל והא הוא נחו 7לפתרו" הבעיה הנתונה.
)(public StudentList
{
;)(>this.lst = new List<Student
}
...
}
פעולת ההוספה
ראינו כי פעולת ההוספה של תלמיד לרשימה הכיתתית יכולה להיעשות בשתי דרכי:
א .הוספת התלמיד למקו כלשהו ברשימה.
כדי להוסי תלמיד לרשימה נוכל להשתמש בפעולת ההכנסה של הרשימה ,ותמיד להכניס
תלמיד למקו הראשו" ברשימה.
)public void Add(Student st
{
;)this.lst.Insert(null, st
}
ב .הוספת התלמיד באופ" ממוי" למקו המתאי לפי הסדר האלפביתי.
כדי לממש את ההכנסה הממוינת לרשימה הכיתתית יש להיעזר בפעולת העזר
) InsertIntoSortedList(...שהגדרנו בסעי ו בדוגמה .2
? ממשו את המחלקה כ #שפעולת ההוספה )…( Addתוסי את התלמיד למקומו הנכו" לפי הסדר
האלפביתי.
מה היתרו" של שימוש ברשימה לייצוג רשימה כיתתית ,לעומת השימוש בשרשרת חוליות?
התשובה פשוטה .לאחר שהגדרנו את המחלקה רשימה ובה קבוצת פעולות שימושיות ,נוח
להשתמש במחלקה זו ובפעולותיה ,במקו להשתמש במבנה נתוני )שרשרת חוליות( ,ולכתוב
שוב את הפעולות שבה" אנו מעונייני .זהו יישו של עקרו" השימוש החוזר בקוד.
)(public Stack
{
;)(>this.data = new List<T
}
)(public T Pop
{
;)(Node<T> first = this.data.GetFirst
;)(T x = first.GetInfo
;)this.data.Remove (first
;return x
}
)(public T Top
{
;)(return this.data.GetFirst().GetInfo
}
}
את הוצאת האיברי מתחילת התור קל לממש בעזרת הפעולות של הרשימה ,כיוו" שקל להגיע
לתחילת הרשימה ולהוציא את האיבר הנמצא ש .גישה לסו הרשימה כדי להכניס איבר חדש
היא מורכבת יותר ,כיוו" שאי" ברשימה הפניה לסו הרשימה .הגישה לסו הרשימה תיעשה על
ידי פעולת עזר פרטית שתוגדר במחלקה תור .הפעולה תסרוק כל פע את הרשימה ותחזיר את
ההפניה לסו התור .יעילותה של פעולה זו תהיה מסדר גודל ) ,O(nשכ" יש צור #לעבור על כל
החוליות ברשימה .לפיכ ,#יעילותה של פעולת ההכנסה לתור תהיה ).O(n
271 פרק – 9רשימה ,אוס לינארי
? כתבו את פעולת העזר המתאימה ,הסורקת את הרשימה ומחזירה את המקו האחרו" בה.
כדי לשפר את יעילות פעולת ההכנסה ,נית" לשנות את ייצוג התור ולהגדיר בו תכונה נוספת.
תכונה זו תכיל הפניה למקו האחרו" ברשימה .הפניה זו תתעדכ" בעת הכנסה של איברי לסו
התור .הגדרת תכונה זו תשפר את יעילות פעולת ההכנסה לכדי ) ,O(1כיוו" ששוב אי" צור #לחפש
את החוליה האחרונה ,והפניה אליה היא ישירה.
)(public Queue
{
;)(>this.data = new List<T
;this.last = null
}
)(public T Remove
{
;)(Node<T> first = this.data.GetFirst
;)this.data.Remove(first
)if (first == this.last
עם הוצאת האיבר האחרון התרוקן התור //
;last = null
;)(return first.GetInfo
}
)(public T Head
{
;)(return this.data.GetFirst().GetInfo
}
}
}
עיצוב תוכנה מבוסס עצמי – סישרפ 272
לפני סיו נדו" עוד ביעילותה של הפעולה )…( Removeשל תור .על פי ניתוח פשוט של הפעולה,
נראה שיעילותה היא מסדר גודל ) .O(nזאת כיוו" שאנו פוני לפעולה )…( Removeשל הרשימה
שיעילותה במקרה הגרוע היא ) .O(nא נבח" היטב את הפעולה )…( Removeשל תור נראה שהיא
מוציאה תמיד את האיבר שבראש התור ,שהוא האיבר הראשו" ברשימה המייצגת את התור.
יעילותה של הוצאה מראש הרשימה היא ) ,O(1כיוו" שבמקרה זה איננו מחפשי את המקו
שלפני מקו ההוצאה .לכ" יעילות ההוצאה מתו #תור תהיה מסדר גודל ).O(1
כאשר משתמשי ברשימה לייצוג בתו #מחלקה ,Aהמממשת טיפוס נתוני מופשט כגו" מחסנית
או תור ,הפעולות האפשריות על רשימה מוסתרות מהמשתמש .כ #המשתמש יכול לבצע רק את
הפעולות המוגדרות בממשק המחלקה .Aהסתרה זו מתקיימת בגלל עקרו" הסתרת המידע הקיי
במחלקות .על פי עיקרו" זה ,אי" גישה ישירה לתכונת המחלקה והפעולות שהיא מאפשרת )ובלבד
שתוגדר כתכונה פרטית( ,אלא רק דר #הפעולות המוגדרות בממשק המחלקה שבה מוגדרת
התכונה :הפעולות המוגדרות במחלקה Stackתומכות בפרוטוקול ה ,LIFOהפעולות המוגדרות
במחלקה Queueתומכות בפרוטוקול ה ,FIFOובשני המקרי פעולות הממשק אינ" מאפשרות
גישה חופשית לכל האיברי .כ #אי" אפשרות לבצע סריקה או הוספת חוליה בכל מקו ,א על פי
שהייצוג נעשה באמצעות רשימה .כ #הדבר ג לגבי השימוש ברשימה לייצוג תור .אי" בכ #כל פג
– כאשר הרשימה משמשת לייצוג של מחלקה אחרת ,אותה המחלקה תחשו בפני המשתמש את
פעולותיה שלה ,ולא את אלה של הרשימה.
כפי ששאלנו על רשימה כיתתית ,נשאל ג כא" :מה היתרו" של ייצוג מחסנית או של תור
באמצעות רשימה ,ולא באמצעות שרשרת חוליות?
התשובה ישימה לכל מחלקה ,המממשת סוג אוס כלשהו .א מימשנו את מחלקה Aבאמצעות
שרשרת חוליות ,אי" כל סיבה שלא נמשי #להשתמש בה .אבל א עומדת לרשותנו המחלקה
רשימה ע פעולותיה ,ואנו מבקשי לכתוב את המחלקה ,Aנעדי לייצגה באמצעות רשימה.
בעשותנו זאת אנו משתמשי שוב בקוד שכבר כתבנו ובדקנו את נכונותו ,ואי" לנו צור #לכתוב קוד
חדש .ג א המחלקה החדשה Aמשתמשת רק בחלק קט" מהפעולות של הרשימה ,אי" בכ #כל
רע .עדי להשתמש בקוד קיי ובד;ק מאשר לכתוב קוד חדש .כל זה מותנה בכ #שרשימה היא
ייצוג מתאי למחלקה .A
273 פרק – 9רשימה ,אוס לינארי
נדגי את הגישה על ידי מימוש הפעולה )( ,Sizeשהגדרנו בסעי ג ,.4.כפעולה פנימית המחזירה
את גודל הרשימה הנוכחית .הפעולה )( Sizeהיא פעולה על רשימה ,ולכ" הסבירות שהיא תקבל את
שרשרת החוליות המייצגת את הרשימה כפרמטר – נמוכה .כדי להציג מימוש רקורסיבי לפעולה
נגדיר פעולת עזר במחלקה רשימה:
// הנחה node :אינו שווה null
)private int SizeHelp(Node<T> node
{
את הפעולה הזו הגדרנו בפרק אוספי ,בסעי ג .הפעולה מוגדרת כפעולה פרטית ,כיוו" שכל
תפקידה הוא לשמש כפעולת עזר לפעולה )(.Size
הפעולה )( Sizeשל מחלקת רשימה תפעיל את פעולת העזר ותשלח אליה כפרמטר את החוליה
הראשונה בשרשרת החוליות .לפעולה של הרשימה יש להוסי בדיקה למקרה מיוחד של רשימה
ריקה .בדיקה זו לא נדרשה כאשר חישבנו אור #של שרשרת חוליות כיוו" ששרשרת חוליות לעול
אינה ריקה:
נשי לב כי הפעולה )( Sizeהיא הפעולה היחידה המזמנת את )…() SizeHelpכיוו" שפעולת העזר
פרטית ולכ" אינה מוכרת מחו 7למחלקה( ,והיא תמיד מעבירה פרמטר שערכו אינו .nullא כ#
ההנחה של פעולת העזר מתקיימת תמיד .הנחה זו הכרחית לפעולה תקינה של פעולת העזר .א
יועבר אליה כפרמטר הער null #נקבל שגיאת זמ" ריצה.
נית" לממש ג פעולות חיצוניות באופ" רקורסיבי ,על פי אותה הגישה .הנה לדוגמה ,מימוש של
הפעולה )…( Sizeכפעולה חיצונית המקבלת רשימה של מספרי.
ג כא" ,א הפעולה )…( Sizeהיא היחידה הקוראת ל)…( ,SizeHelpאזי ההנחה שער #הפרמטר
אינו nullמתקיימת .כיוו" שפעולת העזר במקרה זה היא פעולה פומבית ,יש חשש מסוי )א כי
מזערי( ,שהיא תזומ" על ידי פעולה אחרת שתעביר אליה כפרמטר את .null
א מייצגי את הרשימה באמצעות שרשרת חוליות דוכיוונית ,נית" לשפר את היעילות של
פעולת ההוצאה שבממשק המחלקה ,הפעולה ) ,Remove(...בסדר גודל ,מ) O(nל) .O(1כזכור,
פעולה זו דורשת את מציאת האיבר הקוד לזה שמוציאי .במימוש שהצגנו לפעולה ,דבר זה
מתבצע על ידי סריקה הרשימה מתחילתה .במקרה של שרשרת חוליות דוכיוונית מציאת איבר
קוד בשרשרת מתבצעת ביעילות ) – O(1עוברי מהחוליה הנוכחית לקודמתה .שימו לב כי
275 פרק – 9רשימה ,אוס לינארי
הפעולות האחרות תהפוכנה למורכבות יותר ,כיוו" שבכל שינוי שייעשה על הרשימה יהיה צור#
לטפל בשתי ההפניות.
ייצגנו את הרשימה באמצעות שרשרת חוליות .ייצוג זה חשו למשתמש ,כיוו" שהפעולה
)( GetFirstמחזירה ער #מטיפוס – Nodeהפניה לחוליה הראשונה .יתרה מזו ,הפעולות לטיפול
ברשימות כוללות ה" את פעולות המחלקה רשימה וה" את פעולות המחלקה ,Nodeכגו" הפעולה
)( GetNextהמאפשרת לעבור מחוליה אחת לחוליה העוקבת לה ,והפעולה )( GetInfoהשולפת ער#
מחוליה .כפי שהובהר על ידי הדוגמאות שהצגנו ,אי" אפשרות לוותר על פעולות המחלקה .Node
כלומר טיפול ברשימה מתבצע ג על ידי פעולות החוליות המרכיבות אותה .ייצוג הרשימה אינו
מוסתר ,ולא נית" לשנותו מבלי לשנות את הממשק של הרשימה.
כתוצאה ישירה מכ #נית" להרוס את הרשימה על ידי שימוש בפעולות החוליה .הדבר יכול לקרות
בשגגה או במזיד .נדגי שימוש בפעולות החוליה הגור לתוצאות לא רצויות לרשימה.
דוגמה :1יעקב יוצר רשימה lstשל מספרי .בכוונתו להכניס אליה את המספרי הראשוניי,
לפי סדר .לאחר כמה פעולות הכנסה ,הרשימה נראית כ .lst = [2, 3, 7, 11] :#יעקב ש לב
שהמספר 5חסר בסדרה וכדי לתק" את המעוות הוא כותב את קטע הקוד:
;)(Node<int> pos = lst.GetFirst
;)(pos = pos.GetNext
;)Node<int> n = new Node<int>(5
;)pos.SetNext(n
;))(n.SetNext(pos.GetNext
עיצוב תוכנה מבוסס עצמי – סישרפ 276
לאחר הרצת קטע הקוד ,יעקב משוכנע שהבעיה נפתרה וכעת הרשימה מכילה ג את המספר .5
ליתר ביטחו" ,הוא מוסי את השורה Console.Write(lst) :לסו הקוד ,כדי שהערכי יוצגו
ברשימה על המס.#
דוגמה :2כדי לשפר את היעילות של פעולת ההוצאה מרשימה ,הוחלט לייצג את הרשימה
כרשימה דוכיוונית ,וכמוב" לממש מחדש את כל פעולות הרשימה בהתא לייצוג החדש.
בינתיי ,תיק" יעקב את הטעות שהייתה בתוכניתו ,על ידי החלפת סדר שתי הפעולות האחרונות.
א #המחלקה החדשה של הרשימה הדוכיוונית שכתב שוב אינה נכונה ,מדוע?
המימוש שלנו לרשימה ולפעולותיה נכו" – הפעולות כול" שומרות על מבנה הרשימה ,א #במימוש
שלנו אי" הסתרה .המימוש חשו ומתכנת המשתמש ברשימה יכול להגיע למימוש ולהשתמש בו
ישירות ,כפי שעשה יעקב .ייתכ" שמתכנת כזה טועה בשוגג ,וכמוב" שיש מצבי של פגיעה במזיד.
בכל מקרה ,קוד הפועל ישירות על הייצוג הפנימי עלול לפגוע ברשימה :עלולי לאבד חלק מאיברי
הרשימה ,עלולי להפו #את הרשימה או חלק ממנה למעגל ,וכ" הלאה .כמו כ" ,א בעתיד יוחל
ייצוג הרשימה בייצוג אחר ,תוכניות המשתמשות ישירות בייצוג לא יהיו נכונות.
מסקנה :הרשימה היא טיפוס נתוני מועיל ומעניי" ,א #זכרו שהיא משמשת כמבנה נתוני בלבד,
ולא טיפוס מופשט! שמירה על הרשימה על ידי כתיבת תוכניות המשתמשות רק בפעולות הממשק
אפשרית כמוב" ,א #היא עניי" של הסכמה ורצו" טוב ,ואינה נכפית על ידי מנגנוני השפה .תוכנית
שאינה מקפידה על הסכמה זו עלולה לסכ" את נכונות מבנה הרשימה.
שימו לב ,א על פי שהרשימה אינה טיפוס נתוני מופשט ,היא עדיפה לצרכי שהועלו בפרק זה,
לצור #ייצוג רשימות – על שרשרת חוליות – כיוו" שהיא מחלקה העוטפת שרשרת חוליות.
הרשימה מציעה פתרונות לכמה מהבעיות המתעוררות בשימוש בשרשרת חוליות .כ #למשל
רשימה יכולה להיות ריקה ,בעוד שרשרת חוליות אינה יכולה להיות ריקה .כמו כ" ,נית" להוסי
איבר לתחילת הרשימה ,או להוציא את האיבר הראשו" מהרשימה ,מה שלא אפשרי לביצוע
בשרשרת חוליות ,כפי שראינו.
במה שונה הרשימה מהמחסנית והתור? מה מאפשר למחסנית ולתור להיות טיפוסי נתוני
מופשטי? התשובה פשוטה למדי .ג מחסנית ותור ה אוספי לינאריי .א ,#בניגוד לרשימה,
אי" צור #בייצוג של מקו בתור או ברשימה מחו 7למחלקות אלה ,ואי" צור #בהעברת מקו
כפרמטר לפעולות המחסנית והתור .באוספי אלה פעולות הכנסה ,הוצאה או שליפת ער #נעשי
תמיד במקומות קבועי – בראש המחסנית או בראש התור וזנבו .הידע על המקו בו תתבצע
פעולה נמצא בקוד של הפעולה ,ואי" צור #להעבירו כפרמטר .כיוו" שכ ,#במחלקות אלה אי" צור#
לחשו את הייצוג.
277 פרק – 9רשימה ,אוס לינארי
יא .סיכו
בפרק זה הצגנו את סוג האוס רשימה .רשימה היא אוס לינארי גנרי של נתוני שאינו •
מוגבל בגודלו ,והוא מאורג" כסדרה .נית" להכניס איברי לכל מקו ולהוציא איברי מכל
מקו ברשימה.
נית" לייצג את הרשימה באופני שוני .אנו הצגנו בפרק את הייצוג באמצעות שרשרת •
חוליות .בייצוג זה למחלקת הרשימה יש תכונה אחת ,first :שערכה הפניה לחוליה הראשונה
ברשימה ,או nullכאשר הרשימה ריקה .רשימה הממומשת באמצעות חוליות נקראת רשימה
מקושרת.
אוס הפעולות על רשימה כולל את הפעולות שבממשקי של המחלקות חוליה ורשימה ג •
יחד.
סריקת רשימה מקושרת נעשית באמצעות הפניה מטיפוס ,Nodeשנית" לאתחל לכל מקו •
ברשימה ולקד באמצעות הפעולה )( .GetNextבכל שלב ,נית" לאחזר את הער #שבחוליה
הנוכחית על ידי שימוש בפעולה )( ,GetInfoאו לשנותו על ידי שימוש בפעולה )( .SetInfoכמו
כ" ,נית" להוסי חוליה חדשה אחרי החוליה הנוכחית ,או להוציא את החוליה הנוכחית מ"
הרשימה.
נית" להשתמש ברשימה לייצוג אוספי במגוו" יישומי ,וכ" לייצוג של סוגי אוספי אחרי, •
כגו" מחסנית או תור.
מושגי
position מקו
List רשימה
תרגילי
1 שאלה
:כתבו את הפלט המתקבל לאחר ביצוע כל קוד
.א
List<int> lst = new List<int>();
lst.Insert(null, 10);
Node<int> pos = lst.Insert(null, -5);
lst.Insert(lst.GetFirst(), 17);
pos = lst.Insert(pos, 8);
lst.Insert(null, pos.GetInfo());
Console.WriteLine(lst);
.ב
List<int> lst = new List<int>();
for (int i = 0; i < 4; i++)
lst.Insert(null, i*i);
Console.WriteLine(lst);
.ג
List<int> lst = new List<int>();
Node<int> pos = lst.GetFirst();
for (int i = 0; i < 4; i++)
pos = lst.Insert(pos, i*i);
Console.WriteLine(lst);
2 שאלה
לאחר ביצועlst תיראה הרשימה# אי.lst = [6, -3, 7, -8, -5, 10, 14, -1] :נתונה רשימת המספרי
:הקוד שלפניכ
Node<int> pos = lst.GetFirst();
while (pos != null)
{
int x = pos.GetInfo();
if (x%2 != 0)
lst.Remove(pos);
else
if (x < 0)
pos.SetInfo(-x);
pos = pos.GetNext();
}
279 אוס לינארי, – רשימה9 פרק
3 שאלה
:רשמו את טענת היציאה של הפעולה
// הרשימה אינה ריקה:הנחה
public static string Mystery(List<string> lst)
{
Node<string> pos1 = lst.GetFirst();
Node<string> pos2 = pos1.GetNext();
return pos1.GetInfo();
}
4 שאלה
:נתונה הפעולה
public static List<char> Mystery(List<string> lst)
{
Node<string> pos1 = lst.GetFirst();
List<char> lstRes = new List<char>();
Node<char> pos2 = null;
return lstRes;
}
: עבור הרשימה,Mystery(…) תיראה רשימת התווי שתוחזר לאחר זימו" הפעולה# אי.א
[“Hello”, “world”, “here”, “I”, “am”]
. רשמו את טענת היציאה של הפעולה.ב
. נתחו את יעילות הפעולה.ג
עיצוב תוכנה מבוסס עצמי – סישרפ 280
שאלה 5
נתונה הפעולה הרקורסיבית:
)public static bool Secret(List<int> lst, int x
{
;bool res
;int tmp
))(if (lst.IsEmpty
;res = false
else
{
;)(tmp = lst.GetFirst().GetInfo
;))(lst.Remove(lst.GetFirst
;)res = (tmp == x) || Secret(lst, x
;)lst.Insert(null,tmp
}
;return res
}
א .מה תחזיר הפעולה עבור הזימו" ) ,Secret(lst,10עבור הרשימה? lst = [60, 13, 97, 15, 36] :
ב .תנו דוגמה לזימו" הפעולה )…( Secretכ #שתחזיר ער #שונה מזה שהוחזר בסעי א ,א #עבור
אותה רשימה .lst
ג .הא לאחר ביצוע הפעולה )…( ,Secretהרשימה שהועברה כפרמטר השתנתה? הסבירו.
ד .רשמו את טענת היציאה של הפעולה.
שאלה 6
נתונה הפעולה:
)public static void What(List<int> lst
{
;int x
;)(Node<int> pos = lst.GetFirst
שאלה 7
כתבו פעולה המקבלת רשימת מספרי שלמי ומחזירה את סכו הערכי במקומות הזוגיי.
הניחו שהרשימה המתקבלת אינה ריקה.
שאלה 8
כתבו פעולה המקבלת רשימת מחרוזות ומחזירה 'אמת' א הרשימה ממוינת בסדר אלפביתי
ו'שקר' אחרת.
שאלה 9
כתבו פעולה המקבלת רשימה של תווי ומנפה אותה מערכי חוזרי .כלומר ,הרשימה המתקבלת
תעודכ" כ #שתכיל רק ער #אחד מכל המופעי החוזרי.
שאלה 10
א .כתבו פעולה המקבלת רשימה של נקודות ) (Pointומדפיסה את כל הנקודות שסכו ערכי
הקואורדינטות שלה" אינו גדול מעשרי.
ב .נניח שהנקודות שנשמרו ברשימה בסעי א ה" נקודות על גר רצי .כתבו פעולה המחזירה את
הנקודה הגבוהה ביותר בגר המתקבל )זו שער #ה yשלה הגדול ביותר( .הניחו שיש רק נקודה
אחת כזו.
שאלה 11
כתבו פעולה המקבלת שתי רשימות של מספרי שלמי ומחזירה רשימה חדשה הנבנית כ :#ער#
של כל חוליה במקו ה nברשימה החדשה הוא סכו הערכי בחוליות שבמקו ה nברשימות
הנתונות .א החל ממקו מסוי ייוותרו איברי ברשימה אחת בלבד ה יועתקו לרשימה
החדשה.
לדוגמה:
נתונות שתי רשימות:
]lst1 = [-3, 6, 8, 10, -5, 3
]lst2 = [-4, 6, 2, 76
הפעולה תחזיר את הרשימה החדשה הזו[-7, 12, 10, 86, -5, 3] :
שאלה 12
כתבו פעולה המקבלת שתי רשימות של מספרי שלמי ,ממוינות בסדר עולה ,וממזגת אות"
לרשימה אחת חדשה ,שג היא ממוינת בסדר עולה .הפעולה תחזיר את הרשימה החדשה.
עיצוב תוכנה מבוסס עצמי – סישרפ 282
שאלה 13
נוסי את הפעולה שלפניכ כפעולה פנימית במחלקה >:List<T
)public Node<T> GetPrev (Node<T> pos
הפעולה מקבלת מקו ברשימה ומחזירה את המקו של החוליה הקודמת ברשימה .א posהוא
החוליה הראשונה יוחזר הער.null #
א .ממשו את הפעולה בתו #המחלקה > List<Tהמופיעה בסעי ד בפרק זה.
ב .אילו הנחות עומדות בבסיס ההגדרה של הפעולה?
ג .ממשו מחדש את הפעולה )…( Removeשל הרשימה בעזרת הפעולה )…(.GetPrev
ד .מדוע הפעולה ) GetPrev(...היא פעולה של המחלקה רשימה ולא של המחלקה חוליה ,א
שלכאורה היא נראית כפעולה סימטרית של פעולת החוליה )(?GetNext
שאלה 14
נוסי את הפעולה שלפניכ כפעולה פנימית במחלקה >:List<T
)(public Node<T> GetLast
הפעולה מחזירה את המקו של האיבר האחרו" ברשימה .הנחה :הרשימה אינה ריקה.
א .ממשו את הפעולה בתו #המחלקה > List<Tהמופיעה בסעי ד בפרק זה.
ב .הא נית" לשנות את הייצוג של הרשימה ,כ #שסדר הגודל של הפעולה יהיה ) .O(1א כ",
יצגו את הרשימה מחדש והסבירו את השינויי שהכנסת.
שאלה 15
כתבו פעולה בונה מעתיקה למחלקה StudentListשהוצגה בפרק .את רשאי להוסי פעולות
נוספות למחלקה .Student
שאלה 16
בסעי ט בפרק זה הצגנו את הרשימה הדוכיוונית ,DoublyLinkedListהמאפשרת סריקה של
האיברי בשני הכיווני )הלו #וחזור( .המחלקה > DoublyLinkedList<Tתיוצג על ידי שרשרת
חוליות דוכיוונית .שרשרת זו מבוססת על החוליה שהכרת בפרק ייצוג אוספי ,BiDirNode
שהכילה ער #ושתי הפניות :אחת לחוליה הבאה ,ואחת לחוליה הקודמת.
א .כתבו את המחלקה >.BiDirNode<T
ב .כתבו את המחלקה > DoublyLinkedList<Tתו #שימוש במחלקה > .BiDirNode<Tממשו
במחלקה > DoublyLinkedList<Tאת הפעולות המופיעות בממשק המחלקה – רשימה
ופעולות נוספות שיאפשרו ג את סריקת הרשימה הדוכיוונית הלו #וחזור.
ג .נתחו את פעולות הממשק של הרשימה הדוכיוונית .מה תוכלו להגיד על יעילות הפעולות
בהשוואה לפעולות הרשימה >?List<T
283 פרק – 9רשימה ,אוס לינארי
שאלה 17
ביישומי רבי אנו רוצי לכוו (zip) 7מידע קיי ובשלב מסוי לפרוס ) (unzipאותו חזרה לגודלו
המקורי .בתרגיל זה נעסוק בכיוו 7ופריסה של סדרות תווי.
בהינת" סדרת תווי ,נית" לכוו 7אותה באופ" זה :כל רצ תווי זהי בסדרה יהפו #לזוג המורכב
מתו ומכמות מופעיו ברצ הזה.
לדוגמה ,את סדרת התווי )משמאל לימי"(:
A, A, A, A, G, G, G, A, A, C, C, C, C, C, C, C, C, T, T, T, A, A, A, A, A, A, A, A, A, A, C
נית" לכוו 7ולהפו #לסדרה המכווצת:
A:4, G:3, A:2, C:8, T:3, A:10, C:1
נית" לראות שבסדרת התווי הראשונה קיימי 7רצפי ,וה הפכו ל 7זוגות תווי בסדרה
השנייה .ברצ הראשו" מופיע התו 4 Aפעמי ,ברצ השני מופיע התו 3 Gפעמי וכ" הלאה.
הפעולה מקבלת רשימה של תווי ומחזירה רשימה מכווצת .איבריה של הרשימה המכווצת ה
מטיפוס המחלקה ) ZipInfoשאותה יש לכתוב( .הרשימה מגדירה זוג :תו וכמות מופעיו ברצ,
כמתואר בתרשי ה:UML
ZipInfo
char ch
long times
ב .תהלי #הפו #לפעולת הכיוו 7היא פעולת הפריסה .ממשו את הפעולה:
)public static List<char> Unzip(List<ZipInfo> lst
הפעולה מקבלת רשימה מכווצת ,פורסת אותה ,ומחזירה את רשימת התווי המקורית כפי
שהייתה לפני הכיוו.7
עיצוב תוכנה מבוסס עצמי – סישרפ 284
שאלה 18
קבוצה היא אוס לא סדור של ערכי ללא חזרות )כלומר כל ער #מופיע פע אחת בלבד ואי" סדר
מחייב של הערכי( .הכנסה של ער #שקיי בקבוצה לא תשנה את הקבוצה .הקבוצה יכולה להיות
ריקה – ללא ערכי .לפניכ ממשק המחלקה .IntSet
הערות:
.1קבוצת האיחוד של שתי קבוצות set1ו set2היא קבוצת כל המספרי המופיעי בקבוצה set1
או בקבוצה .set2
.2קבוצת החיתו של שתי קבוצות set1ו set2היא קבוצת המספרי המופיעי בקבוצה set1וג
בקבוצה .set2
שאלה 19
אוס ממוי" הוא אוס של ערכי הממויני בסדר עולה או יורד.
)void Insert (int x הפעולה מכניסה למקו המתאי באוס הממוי" את המספר x
שאלה 20
חיזרו ובצעו שוב את ד עבודה 5מפרק " – 6ספר טלפוני" .בצעו מחדש את כל הסעיפי תו#
התייחסות לנקודות אלה:
א .השתמשו ברשימה לייצוג אוס אנשי הקשר בספר הטלפוני.
ב .ממשו את פעולות המחלקה כ #שהפעולה )( ToStringתתבצע ביעילות לינארית בגודל ספר
הטלפוני .רמז :בדקו הא הפעולה )…( InsertIntoSortedListשראית בפרק ,יכולה לסייע
לכ.
עיצוב תוכנה מבוסס עצמי – סישרפ 286
שאלה 21
)שאלה זו מבוססת על בחינת הבגרות של קי(2005 7
רשימה "חשבונית" היא רשימה שאיבריה מטיפוס המחלקה Exprוהיא מגדירה ביטוי חשבוני
פשוט .ביטוי חשבוני פשוט בנוי משלושה חלקי.<num1> <op> <num2> :
num2 ,num1ה מספרי שלמי גדולי מ op .0הוא אחד מארבעת התווי+, -, *, / :
המייצגי פעולה חשבונית המופעלת על שני המספרי.
להל" ארבע דוגמאות לביטויי חשבוניי פשוטי.3 + 4, 8 / 2, 7 - 10, 9*100 :
null
א .כתבו את המחלקה .Exprהמחלקה תכלול פעולה בונה ליצירת ביטוי חשבוני פשוט ,פעולות
לאחזור המספרי ולאחזור הפעולה החשבונית ,וכ" פעולה המחזירה מחרוזת המתארת את
הביטוי החשבוני הפשוט.
ב .הפעולה )( public double Calculateמחזירה את ערכו של ביטוי חשבוני פשוט.
.1היכ" תוגדר פעולה זו? הסבירו.
.2ממשו את הפעולה.
ג .נגדיר את הפעולה )…( SumExpressionsהמחזירה את הסכו הכולל של ערכי הביטויי
החשבוניי הנמצאי ברשימה "חשבונית" .א הרשימה "החשבונית" ריקה ,יוחזר הער.0 #
לדוגמה ,עבור הרשימה ה"חשבונית" lstהמתוארת באיור הנ"ל ,הפעולה )(SumExpressions
תחזיר את הער.908 #
.1היכ" תוגדר הפעולה )באיזו מחלקה( – הסבירו.
.2ממשו את הפעולה )הפעולה צריכה להשתמש בפעולה )( Calculateשכתבת בסעי ב(.