You are on page 1of 32

‫ ‪ 255‬‬

‫פרק ‪9‬‬
‫רשימה‬
‫אוס לינארי‬

‫בפרקי הקודמי הכרנו שני סוגי אוספי כלליי ‪ ,‬מחסנית ותור‪ .‬ראינו כי ההבדל ביניה הוא‬
‫בנוהל ההכנסה וההוצאה של האיברי ‪ :‬במחסנית האיברי הוכנסו והוצאו מצד אחד בלבד של‬
‫המחסנית )ראש המחסנית(‪ ,‬ובתור הוכנסו האיברי מצד אחד והוצאו מצדו האחר‪ .‬המשות‬
‫למחסנית ולתור היה ההגבלה על הגישה לאיברי הסדרות השמורי בה ‪.‬‬

‫בפרק זה ברצוננו להגדיר אוס כללי מסוג חדש – רשימה )‪ .(List‬זהו אוס סדרתילינארי‪ ,‬ונית"‬
‫להכניס אליו ולהוציא ממנו איברי בכל מקו בסדרה ללא הגבלה‪ .‬סוג אוס זה שימושי‬
‫ביישומי רבי שבה יש צור‪ #‬בניהול רשימות מסוגי שוני ‪.‬‬

‫כדי לנהל רשימה נזדקק‪ ,‬בי" היתר‪ ,‬לפעולות האלה‪ :‬בנייה של רשימה ריקה ופעולות הכנסה‬
‫והוצאה של ערכי ‪ ,‬שיוכלו להתבצע בכל מקו ברשימה‪ .‬אחת הפעולות הנפוצות שייעשו על‬
‫רשימה היא סריקתה‪ ,‬וזאת כדי לאתר נתוני רצויי ולבצע עליה פעולות נוספות )עדכו"‪,‬‬
‫אחזור‪ ,‬הוצאה וכדומה(‪ .‬כדי לבצע סריקה של רשימה יש להתקד לאור‪ #‬הרשימה החל באיבר‬
‫הנמצא במקו הראשו" בה‪.‬‬

‫לפני שנציג ממשק המסכ את הפעולות על רשימה‪ ,‬עלינו להגדיר מושג חדש‪ :‬מקו )‪.(position‬‬
‫פעולות הממשק מזכירות מושג זה ומשתמשות בו‪ ,‬ולכ" עלינו להגדירו קוד להצגת הממשק‪ .‬כדי‬
‫לבחו" את האפשרויות להגדרת המושג מקו ‪ ,‬עלינו להקדי ולדו" בייצוגי אפשריי לרשימה‬
‫ובמשמעותו של מקו בכל אחד מייצוגי אלה‪.‬‬

‫א‪ .‬ייצוג הרשימה‬


‫נית" לייצג את הרשימה באמצעות מער‪ #‬ולגשת אל האיברי באוס לפי האינדקסי שלה ‪ .‬ייצוג‬
‫המקו כא" הוא אינדקס מספרי‪ .‬כבר ראינו כי פעולות ההכנסה למער‪ #‬וההוצאה ממער‪#‬‬
‫מסורבלות ובלתי יעילות‪ ,‬ולכ" נעדי ייצוג אחר התומ‪ #‬בדינמיות של האוס‪.‬‬

‫ייצוג כזה יתבסס על מבנה הנתוני המוכר‪ :‬שרשרת חוליות‪ .‬איברי הרשימה יאוחסנו בחוליות‬
‫השרשרת‪ .‬סדר האיברי יישמר על ידי הגדרת השרשרת‪ ,‬שבה כל חוליה מפנה אל העוקבת לה‪.‬‬
‫ברשימה המיוצגת כ‪ ,#‬המושג מקו של איבר הוא הפניה לחוליה המכילה אותו‪.‬‬

‫בגישה זו לייצוג‪ ,‬רשימה תיוצג באמצעות תכונה אחת שתחזיק את שרשרת החוליות‪ ,‬כלומר ערכה‬
‫יהיה הפניה לחוליה הראשונה בשרשרת‪:‬‬

‫>‪public class List<T‬‬


‫{‬
‫;‪private Node<T> first‬‬
‫‪...‬‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 256‬‬

‫לדוגמה‪ ,‬סדרת המספרי ]‪ [5 ,20 ,-3 ,8‬תתואר באמצעות הרשימה ‪:lst‬‬


‫‪lst‬‬
‫>‪List<int‬‬
‫‪first‬‬
‫>‪Node<int‬‬ ‫>‪Node<int‬‬ ‫>‪Node<int‬‬ ‫>‪Node<int‬‬
‫‪info‬‬ ‫‪next‬‬ ‫‪info‬‬ ‫‪next‬‬ ‫‪info‬‬ ‫‪next‬‬ ‫‪info‬‬ ‫‪next‬‬
‫‪8‬‬ ‫‪-3‬‬ ‫‪20‬‬ ‫‪5‬‬ ‫‪null‬‬

‫או בצורה המופשטת יותר‪:‬‬


‫‪lst‬‬
‫>‪List<int‬‬
‫‪first‬‬

‫‪8‬‬ ‫‪-3‬‬ ‫‪20‬‬ ‫‪5 null‬‬

‫רשימה המיוצגת על ידי שרשרת חוליות נקראת רשימה מקושרת )‪ .(linked list‬ייצוג זה ישמש‬
‫אותנו בפרק זה‪.‬‬

‫ב‪ .‬דיו בפעולות הרשימה‬


‫כדי לסרוק רשימה‪ ,‬כמו זו המתוארת באיור‪ ,‬יש להתחיל בחוליה הראשונה וממנה להתקד ‬
‫חוליה אחר חוליה עד סו הרשימה או עד המקו הרצוי‪ .‬כלומר‪ ,‬אנו זקוקי לפעולה )(‪GetFirst‬‬
‫המחזירה את המקו הראשו" ברשימה )הפנייה לחוליה הראשונה בשרשרת(‪ ,‬ולפעולה שתאפשר‬
‫להתקד מחוליה נתונה לחוליה הבאה אחריה‪ .‬את ההתקדמות נית" לבצע בעזרת הפעולה‬
‫)(‪ GetNext‬הקיימת בממשק החוליה‪ ,‬המוכר לנו‪.‬‬

‫הפעולה )(‪ GetNext‬היא פעולה על חוליה בודדת‪ ,‬ואול רוב הפעולות שאנו זקוקי לה" אינ"‬
‫מוגדרות על חוליה בודדת‪ ,‬וכמוב" אינ" קיימות בממשק המחלקה חוליה‪ .‬פעולות אלה קשורות‬
‫לניהול שרשרת חוליות שלמה‪ .‬כזו היא הפעולה )(‪ GetFirst‬שהוזכרה לעיל‪ .‬בפעולות אלה נכללות‬
‫ג פעולת בנייה של רשימה ריקה‪ ,‬פעולה לבדיקת ריקנות של רשימה – )(‪ ,IsEmpty‬פעולת הכנסה‬
‫– )…(‪ ,Insert‬פעולת הוצאה – )…(‪ ,Remove‬וכ" פעולה המחזירה תיאור של הרשימה –‬
‫בתכנות מונחה עצמי מקובל להגדיר מחלקה עבור כל סוג ישות שעובדי איתו‪ ,‬לכ"‬
‫)(‪ִ .ToString‬‬
‫נגדיר מחלקה ‪ ,List‬שבה יוגדרו הפעולות העוסקות בניהול שרשרת החוליות‪ .‬באמצעות מחלקה זו‬
‫נוכל לבנות עצמי מטיפוס רשימה ולטפל בה ‪ .‬כיוו" שהפעולות אינ" מבצעות על איברי הרשימה‬
‫פעולות ייחודיות לטיפוס מסוי ‪ ,‬המחלקה תהיה גנרית‪.‬‬

‫נציג את הממשק המסכ של המחלקה רשימה‪ ,‬הכולל את הפעולות שהוזכרו לעיל‪ .‬בתיאור‬
‫הממשק‪ ,‬משמעות המונח 'מקו ' היא הפניה לחוליה‪.‬‬
‫ ‪ 257‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫ממשק המחלקה רשימה >‪List<T‬‬


‫המחלקה מגדירה אוס סדרתילינארי שהגישה אל ערכיו מתבצעת בכל מקו באוס‪.‬‬

‫)(‪List‬‬ ‫הפעולה בונה רשימה ריקה‬

‫הפעולה מחזירה 'אמת' א הרשימה הנוכחית‬


‫)(‪bool IsEmpty‬‬ ‫ריקה‪ ,‬ו'שקר' אחרת‬

‫הפעולה מחזירה את המקו של החוליה‬


‫)(‪Node<T> GetFirst‬‬ ‫הראשונה ברשימה הנוכחית‪ .‬א הרשימה‬
‫ריקה‪ ,‬הפעולה מחזירה ‪null‬‬

‫הפעולה מכניסה לרשימה הנוכחית את הער‪x #‬‬


‫מקו אחד אחרי המקו ‪ .pos‬א ‪ pos‬הוא‬
‫‪ x ,null‬יוכנס למקו הראשו" ברשימה‪.‬‬
‫)‪Node<T> Insert (Node<T> pos, T x‬‬
‫הפעולה מחזירה את המקו של החוליה‬
‫החדשה שהוכנסה‪.‬‬
‫הנחה‪ pos :‬הוא מקו ברשימה הנוכחית או‬
‫‪null‬‬

‫הפעולה מוציאה מהרשימה הנוכחית את‬


‫האיבר הנמצא במקו ‪ ,pos‬ומחזירה את‬
‫המקו העוקב ל‪ .pos‬א הוצא האיבר‬
‫)‪Node<T> Remove (Node<T> pos‬‬ ‫האחרו" – יוחזר ‪.null‬‬
‫הנחה‪ pos :‬הוא מקו ברשימה הנוכחית ואינו‬
‫‪.null‬‬

‫הפעולה מחזירה תיאור של הרשימה‪ ,‬כסדרה‬


‫של ערכי ‪ ,‬במבנה הזה )‪ x1‬הוא האיבר הראשו"‬
‫)(‪string ToString‬‬ ‫ברשימה(‪:‬‬
‫]‪[x1, x2, …, xn‬‬

‫שימו לב‪ ,‬הפעולות בממשק המחלקה רשימה אינ" מאפשרות כשלעצמ" לבצע כל מה שאנו רוצי ‬
‫על רשימה‪ .‬כדי לשלו את הער‪ #‬בחוליה או לשנותו‪ ,‬וכ" כדי להתקד מחוליה אחת לבאה‬
‫אחריה‪ ,‬עלינו להשתמש בפעולות שבממשק החוליה‪ .‬א כ"‪ ,‬סוג האוס רשימה מוגדר על ידי שתי‬
‫מחלקות‪ Node :‬ו‪ .List‬אוס הפעולות של שתי המחלקות יחד מאפשר לבצע את כל מה שנרצה‬
‫לבצע על רשימה‪ ,‬כפי שיודג להל"‪.‬‬

‫ג‪ .‬שימוש בפעולות הממשקי‬


‫נדגי את פעולות ממשקי הרשימה והחוליה‪ ,‬תו‪ #‬הסבר מפורט של הפעולות בממשק הרשימה‪.‬‬

‫ג‪ .1.‬בניית רשימה‬


‫כמו בכל טיפוס גנרי‪ ,‬לפני שאנו בוני רשימה קונקרטית אנו חייבי להחליט מאיזה טיפוס יהיו‬
‫איבריה‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 258‬‬

‫ניצור רשימה של שמות‪ ,‬כלומר רשימה מטיפוס מחרוזת‪:‬‬


‫;)(>‪List<string> nameList = new List<string‬‬

‫‪nameList‬‬
‫>‪List<string‬‬
‫‪first‬‬
‫‪null‬‬

‫ג‪ .2.‬הכנסת ער לרשימה‬


‫כותרת פעולת הממשק נראית כ‪:#‬‬
‫)‪public Node<T> Insert(Node<T> pos, T x‬‬

‫הפרמטר ‪ pos‬הוא מקו ברשימה שאחריו רוצי להכניס איבר חדש המכיל את הער‪ .x #‬קיי ‬
‫מקרה קצה אחד והוא הכנסת איבר למקו הראשו" ברשימה‪ .‬כיוו" שאי" איברי לפני האיבר‬
‫הראשו"‪ ,‬אי" מקו שאחריו תתבצע ההכנסה‪ .‬למקרה קצה זה נגדיר שימוש ב‪ null‬כער‪ #‬מיוחד‬
‫ל‪ .pos‬כלומר‪ ,‬א ערכו של ‪ pos‬הוא ‪ ,null‬הכוונה היא שיש להכניס את האיבר במקו הראשו"‪.‬‬

‫כיוו" שפעמי רבות פעולת ההכנסה משולבת בפעולת הסריקה של הרשימה‪ ,‬ואנו מעונייני ‬
‫להמשי‪ #‬בסריקה ממקו ההכנסה‪ ,‬נקבע שפעולת ההכנסה תחזיר הפניה לחוליה שהוכנסה‪ .‬כ‪#‬‬
‫נית" להמשי‪ #‬את הסריקה של הרשימה ממקו זה‪.‬‬

‫נראה כמה דוגמאות של הכנסה לרשימה‪.‬‬

‫דוגמה ‪ – 1‬הכנסת המחרוזת "‪ "Moshe‬למקו הראשו ברשימה‬


‫;)"‪Node<string> pos = nameList.Insert(null,"Moshe‬‬

‫‪nameList‬‬ ‫‪pos‬‬
‫>‪List<string‬‬
‫‪first‬‬

‫‪"Moshe" null‬‬

‫דוגמה ‪ – 2‬הכנסת ערכי לרשימה‬


‫פעולת ההכנסה בסעי ‪ 1‬החזירה הפניה לאיבר שהוכנס‪ .‬ביצענו ה‪-‬מה של הפניה זו למשתנה ‪.pos‬‬
‫כדי להכניס איבר למקו השני ברשימה‪ ,‬עלינו לשלוח את ההפניה ‪ pos‬כפרמטר להכנסה‪:‬‬
‫;)"‪pos = nameList.Insert(pos,"Talia‬‬

‫‪nameList‬‬ ‫‪pos‬‬
‫>‪List<string‬‬
‫‪first‬‬

‫"‪"Moshe‬‬ ‫‪"Talia" null‬‬

‫נית" להמשי‪ #‬באופ" זה ולהכניס ערכי נוספי ‪:‬‬


‫;)"‪pos = nameList.Insert(pos,"Yaron‬‬
‫ ‪ 259‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫‪nameList‬‬ ‫‪pos‬‬
‫>‪List<string‬‬
‫‪first‬‬

‫"‪"Moshe‬‬ ‫"‪"Talia‬‬ ‫‪"Yaron" null‬‬

‫דוגמה ‪ – 3‬הכנסת ערכי בלולאה‬


‫באמצעות לולאה אפשר להכניס לרשימה סדרה של ערכי ‪ .‬הלולאה שלפניכ מכניסה רצ של‬
‫מספרי שלמי לרשימה ריקה של מספרי ‪:‬‬
‫;)(>‪List<int> numList = new List<int‬‬
‫;)(‪Node<int> pos = numList.GetFirst‬‬

‫)‪for (int i=0; i<3; i++‬‬


‫;)‪pos = numList.Insert(pos, i‬‬

‫המשתנה ‪ pos‬מאותחל ל‪ ,null‬כיוו" שהפעולה )(‪ GetFirst‬המופעלת על רשימה ריקה מחזירה ‪.null‬‬
‫לכ" בזימו" הראשו" של הפעולה )…(‪ ,Insert‬הער‪ #‬אפס יוכנס למקו הראשו"‪ pos .‬מתקד ועכשיו‬
‫הוא מפנה למקו הראשו" ברשימה‪ .‬הזימו" השני מכניס את הער‪ 1 #‬למקו השני‪ ,‬וכ‪ #‬הלאה‪.‬‬

‫‪numList‬‬ ‫‪pos‬‬
‫>‪List<int‬‬
‫‪first‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪2 null‬‬

‫נית" להשתמש בלולאות כדי להוסי לרשימה רצ המתקבל ממער‪ ,#‬מרשימה אחרת או מהקלט‪.‬‬
‫דוגמאות יוצגו בהמש‪ #‬הפרק‪.‬‬

‫? תארו את הרשימה המתקבלת מהפעלת הקוד שלפניכ על רשימת המספרי הריקה ‪:lst‬‬
‫)‪for (int i=0; i<3; i++‬‬
‫;)‪lst.Insert(null, i‬‬

‫ג‪ .3.‬הוצאת ער מרשימה‬


‫כותרת פעולת הממשק נראית כ‪:#‬‬
‫)‪public Node<T> Remove(Node<T> pos‬‬

‫‪ pos‬הוא המקו של הער‪ #‬שאנו רוצי להוציא מהרשימה‪ .‬לעתי קרובות אנו מוציאי ערכי ‬
‫מהרשימה תו‪ #‬סריקה‪ ,‬ומעונייני להמשי‪ #‬בה‪ .‬הער‪ #‬שהוצא כבר אינו ברשימה ולכ" הפעולה‬
‫מחזירה את המקו העוקב לו‪ .‬ממקו זה נית" להמשי‪ #‬את הסריקה‪.‬‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 260‬‬

‫דוגמה‪ :‬הוצאת ערכי מרשימה‬


‫לפניכ מצב רשימת השמות ומשתנה שבו מאוחס" מקו לפני פעולת ההוצאה‪:‬‬
‫‪nameList‬‬ ‫‪pos‬‬
‫>‪List<string‬‬
‫‪first‬‬

‫"‪"Moshe‬‬ ‫"‪"Talia‬‬ ‫‪"Yaron" null‬‬

‫זימו" פעולת ההוצאה כ‪:#‬‬


‫;)‪Node<string> pos = nameList.Remove(pos‬‬

‫ישנה את מצב הרשימה והמשתנה‪ ,‬וה ייראו כ‪:#‬‬

‫‪nameList‬‬ ‫‪pos‬‬
‫>‪List<string‬‬
‫‪first‬‬

‫"‪"Moshe‬‬ ‫‪"Yaron" null‬‬

‫כפי שנית" לראות באיור‪ ,‬הער‪ #‬הרצוי הוצא מהרשימה‪ pos .‬התעדכ" והוא נמצא במקו העוקב‬
‫למקו ההוצאה‪.‬‬

‫ג‪ .4.‬סריקה של רשימה‬


‫נית" לבצע סריקה של רשימה מתחילתה )בעזרת )(‪ (GetFirst‬או ממקו נתו" כלשהו ברשימה‪.‬‬
‫סריקת הרשימה תיפסק באחד מהמקרי האלה‪:‬‬
‫א‪ .‬הגענו לסו הרשימה‪.‬‬
‫ב‪ .‬מצאנו את הער‪ #‬המקיי את התנאי הרצוי‪.‬‬

‫נראה כמה דוגמאות של סריקת רשימה‪.‬‬

‫דוגמה ‪ :1‬סריקה עד סו‪ #‬הרשימה‬


‫נגדיר פעולה פנימית למחלקה רשימה‪ ,‬המחזירה את גודל הרשימה הנוכחית )מספר האיברי ‬
‫בה(‪:‬‬
‫)(‪public int Size‬‬
‫{‬
‫;‪int len = 0‬‬
‫;‪Node<T> pos = this.first‬‬

‫)‪while (pos != null‬‬


‫{‬
‫;‪len++‬‬
‫;)(‪pos = pos.GetNext‬‬
‫}‬

‫;‪return len‬‬
‫}‬
‫ ‪ 261‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫דוגמה ‪ :2‬סריקה עד קיו תנאי רצוי‬


‫נגדיר פעולה חיצונית המקבלת כפרמטר רשימה וכ" ער‪ ,x #‬ומחזירה את מקומו של ‪ x‬ברשימה‪.‬‬
‫א ‪ x‬אינו מופיע ברשימה‪ ,‬הפעולה תחזיר ‪:null‬‬
‫)‪public static Node<int> GetPosition(List<int> lst, int x‬‬
‫{‬
‫;)(‪Node<int> pos = lst.GetFirst‬‬

‫))‪while ((pos != null) && (pos.GetInfo() != x‬‬


‫;)(‪pos = pos.GetNext‬‬

‫;‪return pos‬‬
‫}‬

‫? הא נית" לכתוב את הפעולה )…(‪ GetPosition‬כפעולה פנימית?‬

‫ד‪ .‬כתיבת המחלקה רשימה‬


‫החלטנו לייצג את המחלקה רשימה באמצעות שרשרת חוליות‪ .‬להל" תרשי ‪ UML‬המתאר את‬
‫המחלקה רשימה‪:‬‬

‫>‪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‬‬

‫פעולה המחזירה את המקו הראשו ברשימה‬


‫הפעולה )(‪ GetFirst‬מחזירה את ער‪ #‬התכונה ‪ .first‬א הרשימה אינה ריקה תוחזר הפניה אל‬
‫החוליה הראשונה ברשימה‪ ,‬אחרת יוחזר הער‪:null #‬‬
‫)(‪public Node<T> GetFirst‬‬
‫{‬
‫;‪return this.first‬‬
‫}‬

‫פעולת הכנסה‬
‫פעולת זו מכניסה חוליה חדשה לשרשרת חוליות‪ .‬דוגמה של הפעולה ראינו בפרק ‪ 7‬בסעי א‪.2.2.‬‬
‫כעת עלינו לנסח פעולת הכנסה כללית‪ :‬ער‪ #‬החוליה יהיה ‪ x‬מטיפוס כלשהו וההכנסה תתבצע‬
‫אחרי המקו ‪.pos‬‬
‫בתהלי‪ #‬מימוש הפעולה יש להפריד בי" שני מקרי ‪ :‬כאשר ער‪ #‬הפרמטר ‪ pos‬הוא ‪ ,null‬במקרה‬
‫זה ההכנסה תתבצע למקו הראשו" בשרשרת; בכל מקרה אחר‪ ,‬ההכנסה תתבצע אחרי המקו ‬
‫‪:pos‬‬

‫)‪public Node<T> Insert(Node<T> pos, T x‬‬


‫{‬
‫;)‪Node<T> temp = new Node<T>(x‬‬

‫)‪if (pos == null‬‬


‫{‬
‫;)‪temp.SetNext(this.first‬‬
‫;‪this.first = temp‬‬
‫}‬

‫‪else‬‬
‫{‬
‫;))(‪temp.SetNext(pos.GetNext‬‬
‫;)‪pos.SetNext(temp‬‬
‫}‬

‫;‪return temp‬‬
‫}‬

‫פעולת הוצאה‬
‫פעולת זו מוציאה חוליה קיימת משרשרת חוליות‪ ,‬כפי שראינו בפרק ‪ 7‬בסעי א‪ .3.2.‬כעת עלינו‬
‫לנסח פעולת הוצאה כללית‪ :‬ההוצאה תתבצע על החוליה שבמקו ‪ ,pos‬המתקבל כפרמטר‪,‬‬
‫והמציי" מקו קיי ברשימה הנוכחית‪ ,‬שאינו ‪ .null‬במימוש הפעולה יש להבחי" בי" שני מקרי ‪:‬‬
‫כאשר הפרמטר ‪ pos‬הוא המקו הראשו" בשרשרת; בכל מקרה אחר‪ ,‬ההוצאה תתבצע על המקו ‬
‫‪ pos‬שאינו ראשו" בשרשרת‪:‬‬
‫ ‪ 263‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫)‪public Node<T> Remove(Node<T> pos‬‬


‫{‬
‫)‪if(this.first == pos‬‬
‫;)(‪this.first = pos.GetNext‬‬
‫‪else‬‬
‫{‬
‫;)(‪Node<T> prevPos = this.GetFirst‬‬
‫)‪while(prevPos.GetNext() != pos‬‬
‫;)(‪prevPos = prevPos.GetNext‬‬
‫;))(‪prevPos.SetNext(pos.GetNext‬‬
‫}‬

‫;)(‪Node<T> nextPos = pos.GetNext‬‬


‫;)‪pos.SetNext(null‬‬

‫;‪return nextPos‬‬
‫}‬

‫כפי שנית" לראות ממימוש הפעולה‪ ,‬הטיפול במקרה הראשו" )הוצאת החוליה הראשונה( הוא‬
‫פשוט‪ :‬א ‪ pos‬שווה בערכו לתכונה ‪ ,first‬כלומר שניה מפני לחוליה הראשונה‪ ,‬עלינו לעדכ" את‬
‫התכונה ‪ first‬כ‪ #‬שתכיל את ההפניה למקו העוקב של ‪.pos‬‬

‫לעומת זאת‪ ,‬בכל מקרה אחר‪ ,‬הוצאת החוליה מהמקו ‪ pos‬שאינו הראשו" מצריכה תהלי‪ #‬מורכב‬
‫יותר‪ .‬בשלב הראשו"‪ ,‬יש לאתר את החוליה הקודמת ל‪ pos‬ולשמור אותה ב‪ .prevPos‬בשלב השני‪,‬‬
‫נעדכ" את ההפניה העוקבת של ‪ prevPos‬להיות ההפניה העוקבת של ‪.pos‬‬

‫כדי שלא לשמור הפניות מיותרות לשרשרת החוליות‪ ,‬נסיי את פעולת ההוצאה בניתוק החוליה‬
‫‪ .pos‬נעדכ" את ההפניה לעוקב של ‪ pos‬להיות ‪.null‬‬

‫שימו לב שהחוליה שהוצאה מהרשימה אמנ מפסיקה להיות חלק מהרשימה‪ ,‬א‪ #‬ייתכ" שקיימת‬
‫אליה הפניה חיצונית אחרת‪.‬‬

‫פעולה לתיאור הרשימה‬


‫כמו בכל טיפוס נתוני ‪ ,‬יש להגדיר פעולה המחזירה מחרוזת המתארת את מופעי הטיפוס‪ .‬לצור‪#‬‬
‫כ‪ #‬יש להגדיר את הפעולה‪:‬‬
‫)(‪public override string ToString‬‬

‫? השלימו את קוד הפעולה )(‪.ToString‬‬


‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 264‬‬

‫ה‪.‬יעילות פעולות הממשק‬


‫היעילות של כל פעולות הממשק של המחלקה ‪ ,List‬פרט ל)(‪ Remove‬ו)(‪ ,ToString‬היא )‪.O(1‬‬

‫א נוציא את האיבר הראשו" באמצעות הפעולה )‪ ,Remove(...‬אזי ג היעילות שלה תהיה )‪,O(1‬‬
‫כיוו" שאי" צור‪ #‬לחפש את האיבר הקוד ‪ .‬כאשר ‪ n‬הוא מספר האיברי ברשימה‪ ,‬יעילות‬
‫ההוצאה של האיבר האחרו" היא מסדר גודל )‪ ,O(n‬שכ" יש לסרוק את כל הרשימה כדי למצוא את‬
‫המקו הקוד למקו ההוצאה‪ .‬יעילות הפעולה היא )‪ ,O(n‬כיוו" שאנו מחשבי יעילות לפי‬
‫המקרה הגרוע‪.‬‬

‫ו‪ .‬פעולות נוספות על רשימות‬


‫בסעי זה נציג כמה פעולות על רשימה‪ .‬פעולות אלה לא יתוספו לממשק המחלקה אלא יוגדרו‬
‫כפעולות חיצוניות‪ .‬הדוגמאות יחדדו נושאי שמ" הראוי לתת עליה את הדעת כאשר דני ‬
‫ברשימות‪.‬‬

‫דוגמה ‪ :1‬יצירת תת‪$‬רשימה ובה המספרי הזוגיי מרשימה נתונה‬


‫נתונה רשימה של מספרי שלמי ‪ .‬אנו רוצי לקבל תתרשימה שתכיל את המספרי הזוגיי ‬
‫מתו‪ #‬הרשימה הנתונה‪ .‬עומדות לפנינו שתי אפשרויות לביצוע משימה זו‪ :‬נית" לשנות את‬
‫הרשימה הנתונה ולמחוק ממנה את הערכי האיזוגיי ‪ ,‬או להשאיר את הרשימה המתקבלת‬
‫ללא שינוי וליצור רשימה חדשה‪.‬‬

‫א אנו מעונייני לא לפגוע ברשימה הקיימת‪ ,‬נבחר באפשרות השנייה‪ .‬נממש פעולה שמקבלת‬
‫רשימת מספרי שלמי ומחזירה רשימה חדשה המכילה את המספרי הזוגיי מתו‪ #‬הרשימה‬
‫שהתקבלה‪:‬‬

‫)‪public static List<int> GetEvenList(List<int> lst‬‬


‫{‬
‫;)(>‪List<int> evenList = new List<int‬‬

‫;)(‪Node<int> pos1 = lst.GetFirst‬‬


‫;)(‪Node<int> pos2 = evenList.GetFirst‬‬

‫)‪while (pos1 != null‬‬


‫{‬
‫)‪if ((pos1.GetInfo() % 2) == 0‬‬
‫;))(‪pos2 = evenList.Insert(pos2, pos1.GetInfo‬‬
‫;)(‪pos1 = pos1.GetNext‬‬
‫}‬

‫;‪return evenList‬‬
‫}‬

‫הפעולה מחזירה רשימה חדשה ואינה משנה את מצבה של הרשימה שהתקבלה כפרמטר‪ .‬יעילות‬
‫הפעולה לינארית בגודל הרשימה ‪.lst‬‬
‫ ‪ 265‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫פעולה זו מדגימה אופני שוני לקידו הפניות המשתתפות בסריקת הרשימה‪ .‬הקידו של ‪,pos1‬‬
‫שבאמצעותו סורקי את הרשימה המקורית‪ ,‬נעשה בעזרת הפעולה )(‪ .GetNext‬לעומת זאת‪,‬‬
‫קידומו של ‪ ,pos2‬המציי" את מקו ההכנסה ברשימה החדשה‪ ,‬נעשה על ידי עדכונו בער‪ #‬ההחזרה‬
‫של פעולת ההכנסה‪ .‬יש משמעויות שונות לאופ" הקידו של ההפניות‪ ,‬ולרוב לא נית" להחלי את‬
‫אופני הקידו שלה"‪.‬‬

‫דוגמה ‪ :2‬הכנסת ערכי לרשימה ממוינת‬


‫בדוגמה זו נכתוב פעולה המקבלת רשימת מחרוזות ממוינת בסדר אלפביתי‪ ,‬ומחרוזת נוספת‪.‬‬
‫הפעולה תכניס את המחרוזת הנוספת למקו המתאי לה ברשימת המחרוזות‪) .‬לש המחשה‪,‬‬
‫חשבו על רשימת שמות אלפביתית שאליה רוצי להכניס ש חדש במקו המתאי (‪ .‬הפע נניח‬
‫כי אנו מעונייני שהשינוי שמבצעת פעולת ההכנסה ייעשה על הרשימה עצמה‪ .‬אי לכ‪ ,#‬לפעולה לא‬
‫יהיה ער‪ #‬החזרה‪ .‬פעולה זו תשמש אותנו כפעולת עזר בדוגמה השלישית‪.‬‬

‫בפרק ‪ – 6‬הפניות ועצמי מורכבי ‪ ,‬בסעי ו‪ ,.3.‬דנו בהכנסה של ער‪ #‬לתו‪ #‬אוס ממוי"‪ .‬ראינו כי‬
‫בשלב הראשו" יש לבצע סריקה כדי למצוא את המקו שאליו צרי‪ #‬להכניס את הער‪ #‬החדש‪.‬‬
‫הסריקה מתבצעת כל עוד הערכי באוס קטני בערכ מהער‪ #‬החדש‪ .‬הסריקה מסתיימת א ‬
‫הער‪ #‬במקו הנוכחי גדול מהער‪ #‬המיועד להכנסה‪ .‬בפעולה שנכתוב עתה נשמור את המקו ‬
‫הקוד למקו ההכנסה במשתנה שייקרא ‪ prev .prev‬הוא פרמטר המקו לפעולת ההכנסה‬
‫שמתבצעת במקו שאחריו‪ .‬לפניכ הקוד המממש את ההכנסה של המחרוזת למקומה‪:‬‬
‫‪public static void InsertIntoSortedList(List<string> lst,‬‬
‫)‪string str‬‬
‫{‬
‫;‪Node<string> prev = null‬‬
‫;)(‪Node<string> pos = lst.GetFirst‬‬

‫))‪while ((pos != null) && (pos.GetInfo().CompareTo (str)<0‬‬


‫{‬
‫;‪prev = pos‬‬
‫;)(‪pos = pos.GetNext‬‬
‫}‬

‫;)‪lst.Insert(prev, str‬‬
‫}‬

‫יעילות הפעולה לינארית בגודל הרשימה ‪ ,lst‬מכיוו" שבמקרה הגרוע המחרוזת ‪ str‬תוכנס לסו‬
‫הרשימה‪.‬‬

‫? א‪ .‬הראו כי הפעולה תפעל כראוי בכל מקרי הקצה הקיימי ‪.‬‬


‫ב‪ .‬אילו שינויי יש לעשות בפעולה כ‪ #‬שתבצע את משימתה על רשימה של מספרי שלמי ?‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 266‬‬

‫דוגמה ‪ :3‬מיו רשימה באמצעות מיו הכנסה )‪(insertion sort‬‬


‫בדוגמה זו נממש פעולה שתמיי" רשימה קיימת של מספרי שלמי בעזרת אלגורית שנקרא מיו‬
‫הכנסה )‪ .(insertion sort‬בפנינו שתי אפשרויות‪:‬‬

‫‪ .1‬הפעולה תשאיר את הרשימה המקורית בצורתה‪ ,‬ותחזיר רשימה חדשה המכילה את הערכי ‬
‫המקוריי בסדר ממוי"‪.‬‬

‫‪ .2‬הפעולה תמיי" את הרשימה המקורית ללא שימוש בזיכרו" נוס‪ .‬מיו" כזה נקרא מיו ַ(מקו‬
‫)‪.(in place sort‬‬

‫נתחיל באפשרות הראשונה‪:‬‬

‫נמיי" את הרשימה המתקבלת בעזרת רשימה חדשה שאליה נכניס את הערכי המקוריי תו‪#‬‬
‫שמירה על הסדר שלה ‪ .‬הפעולה שנכתוב תמיי" את רשימת השלמי ותשאיר את הרשימה‬
‫שהתקבלה כפרמטר ללא שינוי‪ .‬הפעולה תחזיר רשימה חדשה המכילה את הערכי המקוריי ‬
‫בסדר ממוי"‪ .‬נתחיל כאשר הרשימה החדשה ריקה‪ .‬במצב זה היא בוודאי ממוינת‪ .‬מכא" ואיל‪#‬‬
‫ניעזר בפעולת העזר )…(‪ ,InsertIntoSortedList‬שאותה הגדרנו בסעי הקוד ‪ .‬הפע תקבל‬
‫הפעולה רשימה ממוינת של שלמי ‪ ,‬ותכניס כל ער‪ #‬נוס מהרשימה המקורית למקו המתאי ‬
‫לו ברשימה הממוינת‪:‬‬

‫)‪public static List<int> InsertionSort(List<int> lst‬‬


‫{‬
‫;)(>‪List<int> sortedList = new List<int‬‬

‫;)(‪Node<int> pos = lst.GetFirst‬‬


‫)‪while (pos != null‬‬
‫{‬
‫;))(‪InsertIntoSortedList(sortedList,pos.GetInfo‬‬
‫;)(‪pos = pos.GetNext‬‬
‫}‬

‫;‪return sortedList‬‬
‫}‬

‫יעילות הפעולה היא ריבועית בגודל הרשימה ‪ .lst‬הפעולה כוללת מעבר על כל איברי הרשימה‪,‬‬
‫ומכניסה כל איבר לרשימה החדשה באמצעות פעולת העזר )…(‪ ,insertIntoSortedList‬במחיר‬
‫)‪ ,O(n‬כפי שחישבנו לעיל‪.‬‬

‫נעבור לאפשרות השנייה‪ ,‬ונבצע מיו" ַ‪6‬מקו ‪:‬‬


‫נסתכל על הרשימה המקורית כבעלת שני חלקי ‪ .‬החלק הראשו"‪ ,‬בתחילת הרשימה‪ ,‬מכיל את‬
‫הערכי הממויני )בתחילת הפעולה חלק זה ריק – מספר הערכי בו הוא ‪ .(0‬החלק השני יכיל‬
‫את שארית הרשימה שאותה עדיי" יש למיי"‪ .‬בתחילת הפעולה חלק זה מכיל את הרשימה כולה‪,‬‬
‫ובהמש‪ #‬הוא הול‪ #‬ומצטמצ עד שאי" בו יותר איברי ‪ .‬בשלב זה כל הרשימה ממוינת‪ .‬את מקו ‬
‫ההפרדה בי" שני חלקי הרשימה נשמור במשתנה ‪ .untilPos‬החלק הממוי" של הרשימה מתחיל‬
‫בתחילת הרשימה ועד מקו אחד לפני ‪ .untilPos‬מ‪ untilPos‬ועד סו הרשימה – זה החלק הלא‬
‫ממוי"‪.‬‬
 267  ‫ אוס לינארי‬,‫ – רשימה‬9 ‫פרק‬

:#‫ לאחר שני שלבי היא תיראה כ‬.lst: [4, 2, 1, 20, -3] ‫ נתונה הרשימה‬,‫לדוגמה‬
lst untilPos
List<int>
first

2 4 1 20 -3 null

‫החלק הממוי‬ ‫החלק הלא הממוי‬

‫ הראשו" מהחלק הלא ממוי" של הרשימה לחלק הממוי" של‬#‫בכל שלב יש להכניס את הער‬
‫ פעולה זו דומה לפעולה‬.InsertIntoPartialSortedList(…) ‫הרשימה בעזרת פעולת עזר הנקראת‬
‫ במקומו החדש בחלק‬:‫ מופיע פעמיי ברשימה‬#‫ לאחר שלב זה הער‬.InsertIntoSortedList(…)
."‫ לכ" יש לבצע פעולת הוצאה של האיבר המקורי מהחלק הלא ממוי‬.‫ ובמקומו המקורי‬,"‫הממוי‬

:‫ שינוי הרשימה המקורית‬#‫ תו‬,‫להל" פעולה המבצעת מיו" של רשימה באמצעות מיו" הכנסה‬

public static void InsertionSortInPlace(List<int> lst)


{
Node<int> untilPos = lst.GetFirst();

while (untilPos != null)


{
InsertIntoPartialSortedList(lst, untilPos);
untilPos = lst.Remove(untilPos);
}
}

private static void InsertIntoPartialSortedList(List<int> lst,


Node<int> untilPos)
{
Node<int> prev = null;
Node<int> pos = lst.GetFirst();

while ((pos != untilPos) &&


(untilPos.GetInfo()> pos.GetInfo()))
{
prev = pos;
pos = pos.GetNext();
}

lst.Insert(prev, untilPos.GetInfo());
}
‫ בפעולה‬,"‫ ואילו כא‬,#‫( הפרמטר השני הוא ער‬2 ‫ )דוגמה‬InsertIntoSortedList(…) ‫ בפעולה‬.‫? א‬
.‫ הפרמטר השני הוא חוליה‬InsertIntoPartialSortedList(…)
.‫ בפרמטר מטיפוס חוליה‬#‫ יש צור‬InsertIntoPartialSortedList(…) ‫הסבירו מדוע בפעולה‬
?insertionSortInPlace(…) ‫ מהי יעילותה של הפעולה‬.‫ב‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 268‬‬

‫בדוגמה האחרונה‪ ,‬מיו" במקו ‪ ,‬הצגנו רעיו" תכנותי המסייע לנו כאשר אנו נדרשי לארג" מחדש‬
‫אוספי נתוני ‪ .‬כאשר מבצעי קטע קוד המשנה סדר של אוס נתו" המועבר כפרמטר‪ ,‬ואופ" ביצוע‬
‫התכנות נעשה‬‫הפעולה אינו מצרי‪ #‬שימוש בשטח זיכרו" שגודלו כגודל האוס המקורי‪ ,‬נאמר כי ִ‬
‫ַ‪6‬מקו ‪ .in place ,‬גישה תכנותית זו חוסכת במשאבי המקו בזיכרו"‪ .‬מעבר לכ‪ ,#‬היא מועילה‬
‫כיוו" שהעצ המקורי משק את השינוי והתהלי‪ #‬שהוא עבר‪ .‬חישבו עד כמה תהיה עבודתנו נוחה‪,‬‬
‫א הרשימה הכיתתית תמשי‪ #‬לשק את המיו"‪ ,‬ג לאחר כל פעולות ההכנסה אליה‪.‬‬

‫בכל בעיה שתעלה‪ ,‬יש לשקול הא תכנות ַ‪6‬מקו יועיל והא הוא נחו‪ 7‬לפתרו" הבעיה הנתונה‪.‬‬

‫ז‪ .‬ייצוג אוספי באמצעות רשימה‬


‫רשימה משמשת בעיקר לייצוג אוספי ‪ .‬בעזרת רשימה נית" לייצג אוספי ספציפיי הנדרשי ‬
‫ביישומי ‪ ,‬א‪ #‬ג סוגי אוספי כלליי ‪ ,‬כפי שנראה בסעיפי הבאי ‪.‬‬

‫ז‪ .1.‬ייצוג אוס‪ #‬ספציפי באמצעות רשימה‬


‫ניזכר במחלקה ‪ StudentList‬שהכרנו בפרקי הקודמי ‪ .‬כבר ראינו שני ייצוגי שוני למחלקה‬
‫‪ .StudentList‬הראשו" באמצעות מער‪ ,#‬והשני באמצעות שרשרת חוליות‪ .‬כעת נראה ייצוג נוס‬
‫באמצעות רשימה‪.‬‬

‫ממשק המחלקה ‪StudentList‬‬


‫המחלקה מגדירה קבוצה של תלמידי הנקראת "רשימה כיתתית"‪ .‬תלמיד ברשימה מזוהה על פי‬
‫שמו )אי" בכיתה תלמידי בעלי ש זהה‪ .‬אי" צור‪ #‬לבדוק הא קיי ברשימה מקו פנוי‬
‫להכנסה(‪:‬‬

‫)(‪StudentList‬‬ ‫הפעולה בונה רשימה כיתתית ריקה‬

‫)‪void Add (Student st‬‬ ‫הפעולה מוסיפה את התלמיד ‪ st‬לרשימה הכיתתית‬

‫הפעולה מוחקת את התלמיד ששמו ‪ name‬מתו‪#‬‬


‫הרשימה הכיתתית‪ .‬הפעולה מחזירה את התלמיד‬
‫)‪Student Del(string name‬‬ ‫שנמחק‪ .‬א התלמיד אינו קיי ברשימה‪ ,‬הפעולה‬
‫מחזירה ‪null‬‬

‫הפעולה מחזירה את התלמיד ששמו ‪ .name‬א ‬


‫)‪Student GetStudent (string name‬‬
‫התלמיד אינו קיי ברשימה‪ ,‬הפעולה מחזירה ‪null‬‬

‫הפעולה מחזירה מחרוזת המתארת ד קשר כיתתי‬


‫הממוי" בסדר אלפביתי‪ ,‬באופ" זה‪:‬‬
‫)(‪string ToString‬‬
‫>‪<name1> <tel1‬‬
‫>‪<name2> <tel2‬‬
‫ ‪ 269‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫להל" הייצוג החדש של המחלקה ומימוש הפעולה הבונה‪:‬‬


‫‪public class StudentList‬‬
‫{‬
‫;‪private List<Student> lst‬‬

‫)(‪public StudentList‬‬
‫{‬
‫;)(>‪this.lst = new List<Student‬‬
‫}‬
‫‪...‬‬
‫}‬
‫פעולת ההוספה‬
‫ראינו כי פעולת ההוספה של תלמיד לרשימה הכיתתית יכולה להיעשות בשתי דרכי ‪:‬‬
‫א‪ .‬הוספת התלמיד למקו כלשהו ברשימה‪.‬‬
‫כדי להוסי תלמיד לרשימה נוכל להשתמש בפעולת ההכנסה של הרשימה‪ ,‬ותמיד להכניס‬
‫תלמיד למקו הראשו" ברשימה‪.‬‬
‫)‪public void Add(Student st‬‬
‫{‬
‫;)‪this.lst.Insert(null, st‬‬
‫}‬

‫ב‪ .‬הוספת התלמיד באופ" ממוי" למקו המתאי לפי הסדר האלפביתי‪.‬‬
‫כדי לממש את ההכנסה הממוינת לרשימה הכיתתית יש להיעזר בפעולת העזר‬
‫)‪ InsertIntoSortedList(...‬שהגדרנו בסעי ו בדוגמה ‪.2‬‬

‫? ממשו את המחלקה כ‪ #‬שפעולת ההוספה )…(‪ Add‬תוסי את התלמיד למקומו הנכו" לפי הסדר‬
‫האלפביתי‪.‬‬

‫מה היתרו" של שימוש ברשימה לייצוג רשימה כיתתית‪ ,‬לעומת השימוש בשרשרת חוליות?‬
‫התשובה פשוטה‪ .‬לאחר שהגדרנו את המחלקה רשימה ובה קבוצת פעולות שימושיות‪ ,‬נוח‬
‫להשתמש במחלקה זו ובפעולותיה‪ ,‬במקו להשתמש במבנה נתוני )שרשרת חוליות(‪ ,‬ולכתוב‬
‫שוב את הפעולות שבה" אנו מעונייני ‪ .‬זהו יישו של עקרו" השימוש החוזר בקוד‪.‬‬

‫ז‪ .2.‬ייצוג אוס‪ #‬כללי באמצעות רשימה‬


‫ז‪ .1.2.‬ייצוג מחסנית‬
‫בפרק הקוד ראינו כי נית" לייצג מחסנית באמצעות שרשרת חוליות‪ .‬באופ" דומה נית" לעשות‬
‫זאת באמצעות רשימה‪ ,‬שתהיה התכונה היחידה במחלקה מחסנית‪.‬‬
‫מימוש הפעולה )(‪ IsEmpty‬של המחסנית ייעשה בעזרת הפעולה )(‪ IsEmpty‬של הרשימה‪ ,‬שהיא‬
‫תכונת המחלקה‪ .‬מימוש פעולות ההכנסה וההוצאה של מחסנית ייעשה באמצעות פעולות ההכנסה‬
‫וההוצאה המתבצעות בתחילת הרשימה‪ .‬נראה את המימוש כולו‪:‬‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 270‬‬

‫>‪public class Stack<T‬‬


‫{‬
‫;‪private List<T> data‬‬

‫)(‪public Stack‬‬
‫{‬
‫;)(>‪this.data = new List<T‬‬
‫}‬

‫)(‪public bool IsEmpty‬‬


‫{‬
‫;)(‪return this.data.IsEmpty‬‬
‫}‬

‫)‪public void Push (T x‬‬


‫{‬
‫השלימו בעצמכם ‪... //‬‬
‫}‬

‫)(‪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‬‬
‫}‬

‫)(‪public override string ToString‬‬


‫{‬
‫השלימו בעצמכם ‪... //‬‬
‫}‬

‫}‬

‫ז‪ .2.2.‬ייצוג תור‬


‫נייצג את התור באמצעות רשימה‪ ,‬בדומה לייצוג המחסנית בסעי הקוד ‪ .‬נגדיר את הרשימה‬
‫כתכונה פרטית של התור‪ ,‬כ‪ #‬שמשתמש במחלקת התור לא יוכל לגשת אליה ישירות‪ ,‬אלא רק‬
‫להשתמש בפעולות של התור‪.‬‬

‫את הוצאת האיברי מתחילת התור קל לממש בעזרת הפעולות של הרשימה‪ ,‬כיוו" שקל להגיע‬
‫לתחילת הרשימה ולהוציא את האיבר הנמצא ש ‪ .‬גישה לסו הרשימה כדי להכניס איבר חדש‬
‫היא מורכבת יותר‪ ,‬כיוו" שאי" ברשימה הפניה לסו הרשימה‪ .‬הגישה לסו הרשימה תיעשה על‬
‫ידי פעולת עזר פרטית שתוגדר במחלקה תור‪ .‬הפעולה תסרוק כל פע את הרשימה ותחזיר את‬
‫ההפניה לסו התור‪ .‬יעילותה של פעולה זו תהיה מסדר גודל )‪ ,O(n‬שכ" יש צור‪ #‬לעבור על כל‬
‫החוליות ברשימה‪ .‬לפיכ‪ ,#‬יעילותה של פעולת ההכנסה לתור תהיה )‪.O(n‬‬
‫ ‪ 271‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫? כתבו את פעולת העזר המתאימה‪ ,‬הסורקת את הרשימה ומחזירה את המקו האחרו" בה‪.‬‬

‫כדי לשפר את יעילות פעולת ההכנסה‪ ,‬נית" לשנות את ייצוג התור ולהגדיר בו תכונה נוספת‪.‬‬
‫תכונה זו תכיל הפניה למקו האחרו" ברשימה‪ .‬הפניה זו תתעדכ" בעת הכנסה של איברי לסו‬
‫התור‪ .‬הגדרת תכונה זו תשפר את יעילות פעולת ההכנסה לכדי )‪ ,O(1‬כיוו" ששוב אי" צור‪ #‬לחפש‬
‫את החוליה האחרונה‪ ,‬והפניה אליה היא ישירה‪.‬‬

‫לפניכ מוצג המימוש המלא של תור באמצעות רשימה והתכונה ‪:last‬‬

‫>‪public class Queue<T‬‬


‫{‬
‫;‪private List<T> data‬‬
‫;‪private Node<T> last‬‬

‫)(‪public Queue‬‬
‫{‬
‫;)(>‪this.data = new List<T‬‬
‫;‪this.last = null‬‬
‫}‬

‫)(‪public bool IsEmpty‬‬


‫{‬
‫;)‪return (this.last == null‬‬
‫}‬

‫)‪public void Insert (T x‬‬


‫{‬
‫;)‪this.last = this.data.Insert(this.last,x‬‬
‫}‬

‫)(‪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‬‬
‫}‬

‫)(‪public override string ToString‬‬


‫{‬
‫השלימו בעצמכם ‪... //‬‬

‫}‬

‫}‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 272‬‬

‫לפני סיו נדו" עוד ביעילותה של הפעולה )…(‪ Remove‬של תור‪ .‬על פי ניתוח פשוט של הפעולה‪,‬‬
‫נראה שיעילותה היא מסדר גודל )‪ .O(n‬זאת כיוו" שאנו פוני לפעולה )…(‪ Remove‬של הרשימה‬
‫שיעילותה במקרה הגרוע היא )‪ .O(n‬א נבח" היטב את הפעולה )…(‪ Remove‬של תור נראה שהיא‬
‫מוציאה תמיד את האיבר שבראש התור‪ ,‬שהוא האיבר הראשו" ברשימה המייצגת את התור‪.‬‬
‫יעילותה של הוצאה מראש הרשימה היא )‪ ,O(1‬כיוו" שבמקרה זה איננו מחפשי את המקו ‬
‫שלפני מקו ההוצאה‪ .‬לכ" יעילות ההוצאה מתו‪ #‬תור תהיה מסדר גודל )‪.O(1‬‬

‫ז‪ .3.2.‬סיכו‪ :‬ייצוג באמצעות שרשרת חוליות או ייצוג באמצעות רשימה‬


‫הערה‪ :‬נית" להסתכל על מחסנית ועל תור כסוגי מיוחדי של רשימות‪ ,‬שעל דר‪ #‬ההכנסה‬
‫וההוצאה של איברי אליה ומה יש הגבלות‪ .‬א ‪ ,‬ביישו שבו אנו זקוקי למחסנית‪ ,‬נשתמש‬
‫ישירות ברשימה‪ ,‬אזי קיו ההגבלות יהיה תלוי ברצונו הטוב ובזיכרונו של מתכנת היישו ‪ .‬זו‬
‫אינה גישה טובה‪ .‬הגישה שהצגנו כא" היא ייצוג של טיפוסי אלה על ידי שימוש ברשימות‪ .‬גישה‬
‫זו עדיפה‪ ,‬שכ" היא מונעת שימוש שגוי ביכולות הכלליות של רשימה‪ ,‬במקו ששימוש כזה אינו‬
‫רצוי‪.‬‬

‫כאשר משתמשי ברשימה לייצוג בתו‪ #‬מחלקה ‪ ,A‬המממשת טיפוס נתוני מופשט כגו" מחסנית‬
‫או תור‪ ,‬הפעולות האפשריות על רשימה מוסתרות מהמשתמש‪ .‬כ‪ #‬המשתמש יכול לבצע רק את‬
‫הפעולות המוגדרות בממשק המחלקה ‪ .A‬הסתרה זו מתקיימת בגלל עקרו" הסתרת המידע הקיי ‬
‫במחלקות‪ .‬על פי עיקרו" זה‪ ,‬אי" גישה ישירה לתכונת המחלקה והפעולות שהיא מאפשרת )ובלבד‬
‫שתוגדר כתכונה פרטית(‪ ,‬אלא רק דר‪ #‬הפעולות המוגדרות בממשק המחלקה שבה מוגדרת‬
‫התכונה‪ :‬הפעולות המוגדרות במחלקה ‪ Stack‬תומכות בפרוטוקול ה‪ ,LIFO‬הפעולות המוגדרות‬
‫במחלקה ‪ Queue‬תומכות בפרוטוקול ה‪ ,FIFO‬ובשני המקרי פעולות הממשק אינ" מאפשרות‬
‫גישה חופשית לכל האיברי ‪ .‬כ‪ #‬אי" אפשרות לבצע סריקה או הוספת חוליה בכל מקו ‪ ,‬א על פי‬
‫שהייצוג נעשה באמצעות רשימה‪ .‬כ‪ #‬הדבר ג לגבי השימוש ברשימה לייצוג תור‪ .‬אי" בכ‪ #‬כל פג ‬
‫– כאשר הרשימה משמשת לייצוג של מחלקה אחרת‪ ,‬אותה המחלקה תחשו בפני המשתמש את‬
‫פעולותיה שלה‪ ,‬ולא את אלה של הרשימה‪.‬‬

‫כפי ששאלנו על רשימה כיתתית‪ ,‬נשאל ג כא"‪ :‬מה היתרו" של ייצוג מחסנית או של תור‬
‫באמצעות רשימה‪ ,‬ולא באמצעות שרשרת חוליות?‬

‫התשובה ישימה לכל מחלקה‪ ,‬המממשת סוג אוס כלשהו‪ .‬א מימשנו את מחלקה ‪ A‬באמצעות‬
‫שרשרת חוליות‪ ,‬אי" כל סיבה שלא נמשי‪ #‬להשתמש בה‪ .‬אבל א עומדת לרשותנו המחלקה‬
‫רשימה ע פעולותיה‪ ,‬ואנו מבקשי לכתוב את המחלקה ‪ ,A‬נעדי לייצגה באמצעות רשימה‪.‬‬
‫בעשותנו זאת אנו משתמשי שוב בקוד שכבר כתבנו ובדקנו את נכונותו‪ ,‬ואי" לנו צור‪ #‬לכתוב קוד‬
‫חדש‪ .‬ג א המחלקה החדשה ‪ A‬משתמשת רק בחלק קט" מהפעולות של הרשימה‪ ,‬אי" בכ‪ #‬כל‬
‫רע‪ .‬עדי להשתמש בקוד קיי ובד;ק מאשר לכתוב קוד חדש‪ .‬כל זה מותנה בכ‪ #‬שרשימה היא‬
‫ייצוג מתאי למחלקה ‪.A‬‬
‫ ‪ 273‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫ח‪ .‬מימוש רקורסיבי של פעולות ברשימה‬


‫כפי שלמדנו בפרק ייצוג אוספי ‪ ,‬נית" לכתוב פעולות רקורסיביות על שרשרת חוליות‪ .‬כיוו"‬
‫שבייצוג שאנו מציעי לרשימה בפרק זה התכונה של המחלקה היא שרשרת חוליות‪ ,‬נית" להציע‬
‫מימושי רקורסיביי לחלק מפעולות הממשק של הרשימה‪ ,‬וכ" לפעולות פנימיות אחרות‪ .‬כיוו"‬
‫שנית" להגיע לשרשרת החוליות ג מחו‪ 7‬למחלקה רשימה‪ ,‬על ידי שימוש בפעולות כגו" )(‪GetFirst‬‬
‫ו)(‪ ,GetNext‬נית" לממש ג פעולות חיצוניות מסוימות באופ" רקורסיבי‪ .‬ע זאת‪ ,‬אי" זה סביר‬
‫להגדיר את הפעולות הדרושות לנו כפעולות על שרשרת החוליות‪ ,‬כיוו" שמעצ הגדרת" הפעולות‬
‫ה" על רשימה ולא על הייצוג שלה‪ .‬לכ"‪ ,‬הגישה הכללית שאנו מציעי למימוש רקורסיבי של‬
‫פעולה ברשימה היא‪ :‬יש לכתוב תחילה פעולת עזר רקורסיבית לביצוע הפעולה על שרשרת‬
‫החוליות‪ .‬אחר כ‪ #‬יש לכתוב את הקוד לפעולה שבה אנו מעונייני ‪ ,‬ובו זימו" לפעולת העזר‬
‫הרקורסיבית‪.‬‬

‫נדגי את הגישה על ידי מימוש הפעולה )(‪ ,Size‬שהגדרנו בסעי ג‪ ,.4.‬כפעולה פנימית המחזירה‬
‫את גודל הרשימה הנוכחית‪ .‬הפעולה )(‪ Size‬היא פעולה על רשימה‪ ,‬ולכ" הסבירות שהיא תקבל את‬
‫שרשרת החוליות המייצגת את הרשימה כפרמטר – נמוכה‪ .‬כדי להציג מימוש רקורסיבי לפעולה‬
‫נגדיר פעולת עזר במחלקה רשימה‪:‬‬
‫‪//‬‬ ‫הנחה‪ node :‬אינו שווה ‪null‬‬
‫)‪private int SizeHelp(Node<T> node‬‬
‫{‬

‫)‪if (node.GetNext() == null‬‬


‫;‪return 1‬‬
‫;))(‪return 1 + SizeHelp(node.GetNext‬‬
‫}‬

‫פעולה זו היא למעשה הכללה לפעולה גנרית של הפעולה‪:‬‬


‫)‪GetChainLength(Node<string> chain‬‬

‫את הפעולה הזו הגדרנו בפרק אוספי ‪ ,‬בסעי ג‪ .‬הפעולה מוגדרת כפעולה פרטית‪ ,‬כיוו" שכל‬
‫תפקידה הוא לשמש כפעולת עזר לפעולה )(‪.Size‬‬

‫הפעולה )(‪ Size‬של מחלקת רשימה תפעיל את פעולת העזר ותשלח אליה כפרמטר את החוליה‬
‫הראשונה בשרשרת החוליות‪ .‬לפעולה של הרשימה יש להוסי בדיקה למקרה מיוחד של רשימה‬
‫ריקה‪ .‬בדיקה זו לא נדרשה כאשר חישבנו אור‪ #‬של שרשרת חוליות כיוו" ששרשרת חוליות לעול ‬
‫אינה ריקה‪:‬‬

‫)(‪public int Size‬‬


‫{‬
‫)‪if (this.first == null‬‬
‫;‪return 0‬‬
‫;)‪return SizeHelp(this.first‬‬
‫}‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 274‬‬

‫נשי לב כי הפעולה )(‪ Size‬היא הפעולה היחידה המזמנת את )…(‪) SizeHelp‬כיוו" שפעולת העזר‬
‫פרטית ולכ" אינה מוכרת מחו‪ 7‬למחלקה(‪ ,‬והיא תמיד מעבירה פרמטר שערכו אינו ‪ .null‬א כ‪#‬‬
‫ההנחה של פעולת העזר מתקיימת תמיד‪ .‬הנחה זו הכרחית לפעולה תקינה של פעולת העזר‪ .‬א ‬
‫יועבר אליה כפרמטר הער‪ null #‬נקבל שגיאת זמ" ריצה‪.‬‬

‫נית" לממש ג פעולות חיצוניות באופ" רקורסיבי‪ ,‬על פי אותה הגישה‪ .‬הנה לדוגמה‪ ,‬מימוש של‬
‫הפעולה )…(‪ Size‬כפעולה חיצונית המקבלת רשימה של מספרי ‪.‬‬

‫‪//‬‬ ‫הנחה‪ node :‬אינו שווה ‪null‬‬


‫)‪public static int SizeHelp(Node<int> node‬‬
‫{‬
‫)‪if (node.GetNext() == null‬‬
‫;‪return 1‬‬
‫;))(‪return 1 + SizeHelp(node.GetNext‬‬
‫}‬

‫)‪public static int Size(List<int> lst‬‬


‫{‬
‫))(‪if (lst.IsEmpty‬‬
‫;‪return 0‬‬
‫;))(‪return SizeHelp(lst.GetFirst‬‬
‫}‬

‫ג כא"‪ ,‬א הפעולה )…(‪ Size‬היא היחידה הקוראת ל)…(‪ ,SizeHelp‬אזי ההנחה שער‪ #‬הפרמטר‬
‫אינו ‪ null‬מתקיימת‪ .‬כיוו" שפעולת העזר במקרה זה היא פעולה פומבית‪ ,‬יש חשש מסוי )א כי‬
‫מזערי(‪ ,‬שהיא תזומ" על ידי פעולה אחרת שתעביר אליה כפרמטר את ‪.null‬‬

‫? הפעולה שלפניכ מומשה בסעי ג‪ .4.‬דוגמה ‪.2‬‬


‫)‪public static Node<int> GetPosition(List<int> lst, int x‬‬

‫כתבו את הפעולה כפעולה רקורסיבית )מותר להגדיר פעולות עזר רקורסיביות(‪.‬‬

‫ט‪ .‬רשימה דו‪$‬כיוונית‬


‫כפי שראינו בפרק ‪ – 7‬ייצוג אוספי ‪ ,‬שרשרת חוליות יכולה להיות דוכיוונית‪ ,‬כלומר כל חוליה בה‬
‫תכיל הפניה שתצביע "קדימה" אל החוליה העוקבת‪ ,‬והפניה נוספת שתצביע "אחורה" אל החוליה‬
‫הקודמת לה‪ .‬רשימה המיוצגת באמצעות שרשרת חוליות דוכיוונית נקראת "רשימה דוכיוונית"‬
‫)‪ .(doubly linked list‬בתרגול המצור לפרק תתבקשו לממש רשימה שכזו‪.‬‬

‫א מייצגי את הרשימה באמצעות שרשרת חוליות דוכיוונית‪ ,‬נית" לשפר את היעילות של‬
‫פעולת ההוצאה שבממשק המחלקה‪ ,‬הפעולה )‪ ,Remove(...‬בסדר גודל‪ ,‬מ)‪ O(n‬ל)‪ .O(1‬כזכור‪,‬‬
‫פעולה זו דורשת את מציאת האיבר הקוד לזה שמוציאי ‪ .‬במימוש שהצגנו לפעולה‪ ,‬דבר זה‬
‫מתבצע על ידי סריקה הרשימה מתחילתה‪ .‬במקרה של שרשרת חוליות דוכיוונית מציאת איבר‬
‫קוד בשרשרת מתבצעת ביעילות )‪ – O(1‬עוברי מהחוליה הנוכחית לקודמתה‪ .‬שימו לב כי‬
‫ ‪ 275‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫הפעולות האחרות תהפוכנה למורכבות יותר‪ ,‬כיוו" שבכל שינוי שייעשה על הרשימה יהיה צור‪#‬‬
‫לטפל בשתי ההפניות‪.‬‬

‫? אי‪ #‬תשתנה היעילות של הפעולה )…(‪ InsertIntoSortedList‬שראינו בדוגמה ‪ ,2‬בסעי ו‪ ,‬כאשר‬


‫הרשימה תיוצג באמצעות שרשרת חוליות דוכיוונית? הסבירו‪.‬‬

‫י‪ .‬הרשימה – טיפוס נתוני מופשט?‬


‫הגדרנו שתי תכונות מרכזיות המאפיינות טיפוס נתוני מופשט‪:‬‬
‫‪ .1‬הממשק של טיפוס נתוני מופשט מסתיר את אופ" הייצוג והמימוש של הטיפוס‪ .‬כתוצאה‬
‫מכ‪ #‬נית" לשנות את הייצוג מבלי לשנות את הממשק‪.‬‬
‫‪ .2‬פעולות הממשק של טיפוס נתוני מופשט שומרות על נכונותו של הטיפוס )מבחינת המבנה‬
‫שלו והגישה אל איבריו(‪ .‬לא נית" לקלקל את מבנה טיפוס הנתוני המופשט אגב שימוש‬
‫בפעולותיו‪.‬‬
‫הא הרשימה‪ ,‬כפי שהגדרנו אותה בפרק זה‪ ,‬מקיימת את התכונות הללו ונית" להגדירה כטיפוס‬
‫נתוני מופשט?‬

‫ייצגנו את הרשימה באמצעות שרשרת חוליות‪ .‬ייצוג זה חשו למשתמש‪ ,‬כיוו" שהפעולה‬
‫)(‪ 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‬‬ ‫רשימה‬

‫‪linked list‬‬ ‫רשימה מקושרת‬


‫עיצוב תוכנה מבוסס עצמי – סישרפ‬  278 

‫תרגילי‬

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();

while (pos2 != null)


{
if (pos2.GetInfo().Length > pos1.GetInfo().Length)
pos1 = pos2;
pos2 = pos2.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;

while (pos1 != null)


{
pos2 = lstRes.Insert(pos2, pos1.GetInfo()[0]);
pos1 = pos1.GetNext();
}

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‬‬

‫)‪while (pos != null‬‬


‫{‬
‫;)(‪x = pos.GetInfo‬‬
‫;)‪pos = lst.Remove(pos‬‬
‫;)‪lst.Insert(null,x‬‬
‫}‬
‫}‬
‫א‪ .‬רשמו את טענת היציאה של הפעולה‪.‬‬
‫ב‪ .‬נתחו את יעילות הפעולה‪.‬‬
‫ג‪ .‬ממשו את הפעולה מחדש כ‪ #‬שתשפר את יעילות הפעולה המקורית בסדר גודל‪.‬‬
‫ד‪ .‬ממשו את הפעולה המקורית כפעולה פנימית למחלקה >‪ List<T‬המופיעה בסעי ד בפרק זה‪.‬‬
‫ ‪ 281‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫שאלה ‪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‬פעמי וכ" הלאה‪.‬‬

‫א‪ .‬ממשו את הפעולה שלפניכ ‪ ,‬המכווצת רשימת תווי ‪:‬‬


‫)‪public static List<ZipInfo> Zip(List<char> lst‬‬

‫הפעולה מקבלת רשימה של תווי ומחזירה רשימה מכווצת‪ .‬איבריה של הרשימה המכווצת ה ‬
‫מטיפוס המחלקה ‪) ZipInfo‬שאותה יש לכתוב(‪ .‬הרשימה מגדירה זוג‪ :‬תו וכמות מופעיו ברצ‪,‬‬
‫כמתואר בתרשי ה‪:UML‬‬

‫‪ZipInfo‬‬

‫‪char ch‬‬
‫‪long times‬‬

‫)‪ZipInfo (char ch, long times‬‬


‫)(‪char GetChar‬‬
‫)(‪long GetTimes‬‬
‫)(‪string ToString‬‬

‫ב‪ .‬תהלי‪ #‬הפו‪ #‬לפעולת הכיוו‪ 7‬היא פעולת הפריסה‪ .‬ממשו את הפעולה‪:‬‬
‫)‪public static List<char> Unzip(List<ZipInfo> lst‬‬

‫הפעולה מקבלת רשימה מכווצת‪ ,‬פורסת אותה‪ ,‬ומחזירה את רשימת התווי המקורית כפי‬
‫שהייתה לפני הכיוו‪.7‬‬
‫עיצוב תוכנה מבוסס עצמי – סישרפ‬ ‫ ‪ 284‬‬

‫שאלה ‪18‬‬
‫קבוצה היא אוס לא סדור של ערכי ללא חזרות )כלומר כל ער‪ #‬מופיע פע אחת בלבד ואי" סדר‬
‫מחייב של הערכי (‪ .‬הכנסה של ער‪ #‬שקיי בקבוצה לא תשנה את הקבוצה‪ .‬הקבוצה יכולה להיות‬
‫ריקה – ללא ערכי ‪ .‬לפניכ ממשק המחלקה ‪.IntSet‬‬

‫ממשק המחלקה ‪IntSet‬‬


‫המחלקה מגדירה קבוצה של מספרי שלמי ‪.‬‬

‫)(‪IntSet‬‬ ‫הפעולה בונה קבוצה ריקה‬

‫הפעולה מוסיפה לקבוצה את המספר ‪ ,x‬בתנאי שאינו נמצא‬


‫)‪void Add (int x‬‬
‫בה‪ .‬א ‪ x‬נמצא בקבוצה הפעולה אינה מבצעת דבר‬

‫)‪bool Exists (int x‬‬


‫הפעולה מחזירה 'אמת' א המספר ‪ x‬נמצא בקבוצה ו‬
‫'שקר' אחרת‬

‫)‪void Delete (int x‬‬


‫הפעולה מוחקת את המספר ‪ x‬מהקבוצה‪ .‬א ‪ x‬אינו מופיע‬
‫בקבוצה הפעולה אינה מבצעת דבר‬

‫הפעולה מקבלת קבוצה ‪ set‬ומחזירה את קבוצת האיחוד של‬


‫)‪IntSet Unify (IntSet set‬‬
‫‪ set‬ושל הקבוצה הנוכחית‬

‫הפעולה מקבלת קבוצה ‪ set‬ומחזירה את קבוצת החיתו של‬


‫)‪IntSet Intersect (IntSet set‬‬
‫‪ set‬ושל הקבוצה הנוכחית‬

‫הפעולה מחזירה מחרוזת המתארת את הקבוצה כ‪:#‬‬


‫)(‪string ToString‬‬
‫}… ‪{x1, x2, x3,‬‬

‫הערות‪:‬‬
‫‪ .1‬קבוצת האיחוד של שתי קבוצות ‪ set1‬ו‪ set2‬היא קבוצת כל המספרי המופיעי בקבוצה ‪set1‬‬
‫או בקבוצה ‪.set2‬‬
‫‪ .2‬קבוצת החיתו של שתי קבוצות ‪ set1‬ו‪ set2‬היא קבוצת המספרי המופיעי בקבוצה ‪ set1‬וג‬
‫בקבוצה ‪.set2‬‬

‫א‪ .‬כתבו את המחלקה ‪ .IntSet‬הסבירו את השיקולי בבחירת הייצוג‪.‬‬


‫ב‪ .‬נתחו את היעילות של כל אחת מפעולות המחלקה ‪ .IntSet‬ציינו מה סדר הגודל של כל פעולה‪.‬‬
‫ג‪ .‬השאלה עוסקת בקבוצות של מספרי שלמי ‪ .‬הא נית" להכליל את ההגדרה על קבוצה‬
‫גנרית? נמקו‪.‬‬
‫ ‪ 285‬‬ ‫פרק ‪ – 9‬רשימה‪ ,‬אוס לינארי‬

‫שאלה ‪19‬‬
‫אוס ממוי" הוא אוס של ערכי הממויני בסדר עולה או יורד‪.‬‬

‫ממשק המחלקה ‪IntSortedCollection‬‬


‫המחלקה מגדירה אוס ממוי" בסדר עולה של מספרי שלמי ‪.‬‬

‫)(‪IntSortedCollection‬‬ ‫הפעולה בונה אוס ממוי" ריק‬

‫)‪void Insert (int x‬‬ ‫הפעולה מכניסה למקו המתאי באוס הממוי" את המספר ‪x‬‬

‫הפעולה מחזירה 'אמת' א המספר ‪ x‬נמצא באוס הממוי"‬


‫)‪bool Exists (int x‬‬
‫ו'שקר' אחרת‬

‫הפעולה מוחקת את כל המופעי של המספר ‪ x‬מהאוס‬


‫)‪void Delete (int x‬‬ ‫הממוי"‪.‬‬
‫א ‪ x‬אינו קיי באוס הפעולה אינה מבצעת דבר‬

‫הפעולה מחזירה מער‪ #‬המכיל את כל המספרי באוס‬


‫)(‪int[] GetAll‬‬ ‫ממויני בסדר עולה‬

‫הפעולה מחזירה מחרוזת המתארת את האוס הממוי"‪:‬‬


‫)(‪string ToString‬‬ ‫]… ‪[x1, x2, x3,‬‬
‫המספרי ממויני בסדר עולה‪.‬‬

‫כתבו את המחלקה ‪ .IntSortedCollection‬הסבירו את השיקולי בבחירת הייצוג‪.‬‬ ‫א‪.‬‬


‫נתחו את היעילות של כל אחת מפעולות המחלקה ‪ .IntSortedCollection‬ציינו מה סדר הגודל‬ ‫ב‪.‬‬
‫של כל פעולה ונמקו‪.‬‬
‫השאלה עוסקת באוס של מספרי שלמי ‪ .‬הא נית" להכליל את ההגדרה על אוס גנרי?‬ ‫ג‪.‬‬
‫נמקו‪.‬‬

‫שאלה ‪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 :‬‬

‫הרשימה החשבונית ‪ lst‬מכילה את ארבעת הביטויי החשבוניי הנזכרי ‪:‬‬


‫‪lst‬‬
‫>‪List<Expr‬‬
‫‪first‬‬

‫‪null‬‬

‫‪Expr‬‬ ‫‪Expr‬‬ ‫‪Expr‬‬ ‫‪Expr‬‬


‫‪num1‬‬ ‫‪op‬‬ ‫‪num2‬‬ ‫‪num1‬‬ ‫‪op‬‬ ‫‪num2‬‬ ‫‪num1‬‬ ‫‪op‬‬ ‫‪num2‬‬ ‫‪num1‬‬ ‫‪op‬‬ ‫‪num2‬‬
‫‪3‬‬ ‫'‪'+‬‬ ‫‪4‬‬ ‫‪8‬‬ ‫'‪'/‬‬ ‫‪2‬‬ ‫‪7‬‬ ‫'‪'-‬‬ ‫‪10‬‬ ‫‪9‬‬ ‫'*'‬ ‫‪100‬‬

‫א‪ .‬כתבו את המחלקה ‪ .Expr‬המחלקה תכלול פעולה בונה ליצירת ביטוי חשבוני פשוט‪ ,‬פעולות‬
‫לאחזור המספרי ולאחזור הפעולה החשבונית‪ ,‬וכ" פעולה המחזירה מחרוזת המתארת את‬
‫הביטוי החשבוני הפשוט‪.‬‬
‫ב‪ .‬הפעולה )(‪ public double Calculate‬מחזירה את ערכו של ביטוי חשבוני פשוט‪.‬‬
‫‪ .1‬היכ" תוגדר פעולה זו? הסבירו‪.‬‬
‫‪ .2‬ממשו את הפעולה‪.‬‬
‫ג‪ .‬נגדיר את הפעולה )…(‪ SumExpressions‬המחזירה את הסכו הכולל של ערכי הביטויי ‬
‫החשבוניי הנמצאי ברשימה "חשבונית"‪ .‬א הרשימה "החשבונית" ריקה‪ ,‬יוחזר הער‪.0 #‬‬
‫לדוגמה‪ ,‬עבור הרשימה ה"חשבונית" ‪ lst‬המתוארת באיור הנ"ל‪ ,‬הפעולה )(‪SumExpressions‬‬
‫תחזיר את הער‪.908 #‬‬
‫‪ .1‬היכ" תוגדר הפעולה )באיזו מחלקה( – הסבירו‪.‬‬
‫‪ .2‬ממשו את הפעולה )הפעולה צריכה להשתמש בפעולה )(‪ Calculate‬שכתבת בסעי ב(‪.‬‬

You might also like