Download presentation
Presentation is loading. Please wait.
1
הרצאה 21: Queue, Iterator & Iterable
מבוא למדעי המחשב הרצאה 21: Queue, Iterator & Iterable
3
תור – מבנה נתונים אבסטרקטי
public interface Queue { public void enqueue(Object o); public Object dequeue(); public boolean isEmpty(); } כמו מחסנית, גם תור הוא מבנה נותנים בעל חשיבות מרכזית במדעי המחשב. לעומת מחסנית, תור עובד על עיקרון FIFO(First In First Out), הראשון להגיע הוא הראשון שיצא. כמו את המחסנית, גם את התור נתאר באמצעות ממשק 3
4
תור – שימושים בעולם התוכנה
השימושים של תורים בעולם התוכנה מזכירים מאוד תורים במציאות: מקלדת שידור סרט באינטרנט (YouTube) שימוש ברשת לטובת מימוש של טלפון (VoIP) ועוד... השימושים של תורים בעולם התוכנה מזכירים מאוד תורים במציאות. לדוגמא, כאשר אנו מקלידים במקלדת והאפליקציה בה אנו משתמשים אינה פנויה לקבל את התווים, הם נאגרים בתור. כאשר האפליקציה מתפנה, היא מוציאה את התווים מהתור בסדר בוא הגיעו. שימושים דומים הם לשידור סרט ברשת (5 הדקות הבאות נאגרות בתור). שימוש ברשת לטובת מימוש של טלפון (העברת קול) ועוד. 4
5
מימוש נאיבי לתור front = 0 numOfElements = 0 5
נחזיק את נתוני התור במערך. נחזיק מצביע front על האיבר הראשון בתור ואת מספר האיברים בתור. נשתמש במערך דינאמי, בכדי שהמקום לא יגמר. numOfElements = 0 front = 0 5
6
מימוש נאיבי לתור enqueue (A) A numOfElements = 1 front = 0 6
7
מימוש נאיבי לתור enqueue (B) A B numOfElements = 2 front = 0 7
8
מימוש נאיבי לתור enqueue (C) A B C numOfElements = 3 front = 0 8
9
מימוש נאיבי לתור dequeue () B C numOfElements = 2 front = 1 9
10
מימוש נאיבי לתור enqueue (D) B C D numOfElements = 3 front = 1 10
11
מימוש נאיבי לתור dequeue () C D front = 2 numOfElements = 2 11
חיסרון: מאוד לא חסכוני במקום, וגם הזמן הדרוש לכל פעולה לא משהו (בגלל הגדלת המערך הדינאמי). בסוף התהליך, גודל המערך שווה למספר ההכנסות שבוצעו. רצוי היה שגודל התור יהיה פרופורציוני למספר המקסימאלי של האיברים שנמצאו בו זמנית בתור (בתור לבנק יכולים לעמוד בו זמנית כ-10 איש, למרות שבמהלך היום עוברים בבנק כמה עשרות). numOfElements = 2 front = 2 11
12
תור מעגלי בעל קיבולת חסומה
numOfElements = 3 3 2 C על מנת להמשיך לנצל את האיברים הראשונים במערך, נחשוב על המערך בתור מבנה מעגלי. נשתמש במערך בעל קיבולת קבועה. נשתמש במודולו data.length וכך הצבעה על המקום ה-data.length בעצם תצביע על המקום ה-0 כפי שרצינו. משמעות המשתנים: front – מצביע על האיבר שבראש התור. numOfElements – מספר האיברים בתור. ??? איך מבצעים הכנסה? 1. מכניסים את האיבר החדש למקום ה-(front+numOfelements)%n (רק אם numOfelements < n). 2. numOfelements =numOfelements + 1 . ??? איך מבצעים הוצאה? מוציאים מהמקום ה-(front)%n, מגדילים את front ב-1, ומקטינים את numOfelements ב-1 חסרון: המקום בתור מוגבל. האם ניתן להשתמש במערך דינאמי? כן, אבל זה לא טריביאלי. פתרון אפשרי נוסף הוא טיפול בפעולת ensureCapacity בתוך הקוד של התור. front 1 B A n-1 12
13
תור מעגלי בעל קיבולת חסומה
numOfElements = 2 3 front 2 dequeue () C על מנת להמשיך לנצל את האיברים הראשונים במערך, נחשוב על המערך בתור מבנה מעגלי. נשתמש במערך בעל קיבולת קבועה. נשתמש במודולו data.length וכך הצבעה על המקום ה-data.length בעצם תצביע על המקום ה-0 כפי שרצינו. משמעות המשתנים: front – מצביע על האיבר שבראש התור. numOfElements – מספר האיברים בתור. ??? איך מבצעים הכנסה? 1. מכניסים את האיבר החדש למקום ה-(front+numOfelements)%n (רק אם numOfelements < n). 2. numOfelements =numOfelements + 1 . ??? איך מבצעים הוצאה? מוציאים מהמקום ה-(front)%n, מגדילים את front ב-1, ומקטינים את numOfelements ב-1 חסרון: המקום בתור מוגבל. האם ניתן להשתמש במערך דינאמי? כן, אבל זה לא טריביאלי. פתרון אפשרי נוסף הוא טיפול בפעולת ensureCapacity בתוך הקוד של התור. 1 B n-1 13
14
תור מעגלי בעל קיבולת חסומה
numOfElements = 3 3 enqueue (D) front 2 D C על מנת להמשיך לנצל את האיברים הראשונים במערך, נחשוב על המערך בתור מבנה מעגלי. נשתמש במערך בעל קיבולת קבועה. נשתמש במודולו data.length וכך הצבעה על המקום ה-data.length בעצם תצביע על המקום ה-0 כפי שרצינו. משמעות המשתנים: front – מצביע על האיבר שבראש התור. numOfElements – מספר האיברים בתור. ??? איך מבצעים הכנסה? 1. מכניסים את האיבר החדש למקום ה-(front+numOfelements)%n (רק אם numOfelements < n). 2. numOfelements =numOfelements + 1 . ??? איך מבצעים הוצאה? מוציאים מהמקום ה-(front)%n, מגדילים את front ב-1, ומקטינים את numOfelements ב-1 חסרון: המקום בתור מוגבל. האם ניתן להשתמש במערך דינאמי? כן, אבל זה לא טריביאלי. פתרון אפשרי נוסף הוא טיפול בפעולת ensureCapacity בתוך הקוד של התור. 1 B n-1 14
15
תור מעגלי בעל קיבולת חסומה
numOfElements = 4 1 על מנת להמשיך לנצל את האיברים הראשונים במערך, נחשוב על המערך בתור מבנה מעגלי. נשתמש במערך בעל קיבולת קבועה. נשתמש במודולו data.length וכך הצבעה על המקום ה-data.length בעצם תצביע על המקום ה-0 כפי שרצינו. משמעות המשתנים: front – מצביע על האיבר שבראש התור. numOfElements – מספר האיברים בתור. ??? איך מבצעים הכנסה? 1. מכניסים את האיבר החדש למקום ה-(front+numOfelements)%n (רק אם numOfelements < n). 2. numOfelements =numOfelements + 1 . ??? איך מבצעים הוצאה? מוציאים מהמקום ה-(front)%n, מגדילים את front ב-1, ומקטינים את numOfelements ב-1 חסרון: המקום בתור מוגבל. האם ניתן להשתמש במערך דינאמי? כן, אבל זה לא טריביאלי. פתרון אפשרי נוסף הוא טיפול בפעולת ensureCapacity בתוך הקוד של התור. X W V n-1 U n-2 front 15
16
תור מעגלי בעל קיבולת חסומה
public class CircularQueue implements Queue{ private Array arr; private int front, numOfElements, capacity; public CircularQueue(int capacity){ this.capacity = capacity; arr = new FixedSizeArray(capacity); front = 0; numOfElements = 0; } 16
17
תור מעגלי בעל קיבולת חסומה
public Object dequeue(){ if (isEmpty()){ throw new EmptyQueueException(); } Object res = arr.get(front); arr.set(front, null); front = (front+1) % capacity; numOfElements = numOfElements-1; return res; 17
18
תור מעגלי בעל קיבולת חסומה
public void enqueue(Object o){ if (numOfElements == arr.size()){ throw new RuntimeException( "Queue is full!"); } arr.set((front + numOfElements) % capacity, o); numOfElements = numOfElements+1; public boolean isEmpty(){ return numOfElements == 0; } //class CircularQueue 18
19
יצירת סוג חדש של Exception
class EmptyQueueException extends RuntimeException{ public EmptyQueueException(){ super(); } }//class EmptyQueueException 19
20
מימוש תור בעזרת מחסנית public class QueueAsStack implements Queue{
private Stack stack; public QueueAsStack () { stack = new StackAsArray(); } public boolean isEmpty() {// easy... return stack.isEmpty(); public void enqueue(Object o) {//quit easy as well... stack.push(o); חסרון: זמן ריצה ארוך ב-dequeue. 20
21
מימוש תור בעזרת מחסנית public Object dequeue() { // hard work...
if (stack.isEmpty()) throw new EmptyQueueException(); Stack auxStack = new StackAsArray(); while(!stack.isEmpty()) auxStack.push(stack.pop()); Object ret = auxStack.pop(); while(!auxStack.isEmpty()) stack.push(auxStack.pop()); return ret; } }//class QueueAsStack חסרון: זמן ריצה ארוך ב-dequeue. כמו שבמחסנית דאגנו לזרוק EmptyStackException, הפעם אנו זורקים EmptyQueueException. הבעיה היא שזה לא עובר קומפילציה. ??? מדוע? !!! כי EmptyStackException קיימת ב-Java, אבל EmptyQueueExceptionדווקא לא קיימת. ??? אז מה עושים? !!! נדאג ליצור מחלקה חדשה בשם EmptyQueueException בהמשך ??? ומדוע לא הוספנו בתור פרמטר מחרוזת המתארת את סוג הבעיה? !!! מפני ששם המחלקה (EmptyQueueException) מתאר כבר את הכל... 21
22
ניתוח הפעולות (לתור ולמחסנית)
פעולות יעילות הכנסת מספר קטן של פריטים הוצאת איבר ראשון פעולות לא יעילות הכנסת מספר גדול של פריטים (תלוי במימוש המערך) מציאת איבר בעל ערך מינימאלי מציאת איבר בעל מפתח מסוים
23
Iterator כיצד ניתן לאפשר בנאי מעתיק של מבנה הנתונים Set שלמדנו?
כיצד ניתן לבצע חיתוך או איחוד בין שתי קבוצות? ישנו צורך בפונקציונאליות חשובה ברוב מבני הנתונים שעד כה התעלמנו ממנה – היכולת לעבור על כל האיברים. Set הוא מבנה נתונים שעם הכלים שיש לנו כרגע לא ניתן לממש עבורו בנאי מעתיק. ??? מדוע? !!! מפני שאין אף פעולה שמראה איבר ב-Set. באמצעות ידע מוקדם על האיבר ניתן לבדוק אם הוא בקבוצה (contains) או לחילופין ניתן להוציאו מהקבוצה (remove). אך אין אף שיטה ב-Set שמחזירה איבר מתוך הקבוצה. ??? אז כיצד נעתיק קבוצה? !!! אנו זקוקים ליכולת לעבור על כל איברי הקבוצה...
24
Iterator public interface Iterator{ public boolean hasNext();
public Object next(); public void remove(); } הממשקים Iterator ו-Iterable "הולכים ביחד", ומתארים פתרון כללי לאופן מעבר על נתונים במבנה נתונים. Iterator מגדיר אובייקט האחראי לספק את הנתונים במבנה אחד אחרי השני, ו-Iterable מגדיר מבני נתונים בעלי התכונה "אני יכול לספק איטרטור". שימו לב כי על מבני הנתונים לממש את הממשק Iterable, ולא את הממשק Iterator. את remove לא באמת נממש בקורס הזה, אך הוא חלק מהממשק (שני הממשקים כבר קיימים ב-Java) ??? אז מה נעשה? !!! נראה עוד מעט... public interface Iterable { public Iterator iterator(); }
25
נעדכן את ממשק הקבוצה public interface Set extends Iterable{
public void add(Object data); public void remove(Object data); public boolean contains(Object data); public int size(); } התוספת היחידה היא ההרחבה.
26
נוסיף את השיטה הדרושה במימוש הקבוצה
public class SetAsArray implements Set { private Array arr; private int size; // ... public Iterator iterator() { return new ArrayIterator(arr, size); }
27
נגדיר איטרטור עבור מערכים
public class ArrayIterator implements Iterator { private Array arr; private int nextIx, size; public ArrayIterator(Array arr, int size) { this.arr = arr; this.size = size; nextIx = 0; } חישבו מדוע size הכרחי. מה קורה אם המערך דינאמי?
28
נגדיר איטרטור עבור מערכים
public boolean hasNext() { return nextIx < size; } public Object next() { if (!hasNext()) throw new NoSuchElementException(); nextIx = nextIx+1; return arr.get(nextIx-1); public void remove() { throw new UnsupportedOperationException(); } //class ArrayIterator שני ה-Exceptions שאנו זורקים כבר קיימים ב-Java, ולכן לא צריך ליצור אותם. שימו לב כיצד פתרנו את הבעיה של remove – לא באמת מימשנו, אך הקוד יתקמפל
29
נוסיף בנאי מעתיק במימוש הקבוצה
public class SetAsArray implements Set { private Array arr; int size; public SetAsArray(){ arr = new DynamicArray(); size = 0; } public SetAsArray(Set toCopy){ this(); if (toCopy == null) throw new NullPointerException(“arguemnt to constructor is null”); Iterator iter = toCopy.iterator(); while (iter.hasNext()) add(iter.next()); }... שימו לב כי הבנאי המעתיק מקבל Set כלשהו, ולאו דווקא אובייקט מטיפוס SetAsArray. ??? איך זה עובר קומפילציה? !!! שימו לב שלא רק SetAsArray מממש את Iterable, אלא הממשק Set מרחיב את Iterable, ולכן לכל מימוש קונקרטי של קבוצה יש איטרטור חישבו: כיצד ניתן לממש איחוד? וחיתוך?
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.