Date post: | 20-Dec-2015 |
Category: |
Documents |
View: | 228 times |
Download: | 4 times |
Abstract Data Type (ADT)
מנגנון אחסון למידע, ופעולות בסיסיות לטיפול וניהול המידע Vector, Matrix
Random r/w access to any element by coordinates Queue (Buffer)
First in, First out Set (unordered), Ordered Lists (Dictionary) Graphs (of nodes and vertices) …..
ADTs are not implementations
We can use different implementations for ADTs For instance: Stack (מחסנית)
Last in, first out Basic mechanism for function calls, delimiter
checks in compilers, etc. Operations: new, push, pop, peek, empty?
We will examine several implementations
++Cמימוש ב const int MAX_SIZE = 100;typedef int Type;class Stack {private:
Type data[MAX_SIZE];int top; // next item is placed
// at data[top]public:
stack();~stack();void make_empty();bool is_empty();bool is_full();void push(Type item);Type pop();Type top();
} ;
Stack Example:delimiter check
Legal delimiters: {,[,(,),],} Each opening delimiter must have a matching closing one. Proper nesting:
a{bc[d]e}f(g) OK a{bc[d}e]f(g) incorrect
We can perform this task easily using a stack!
בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית
Stack example:Delimiter check
1. for all characters c of a string2. if (c) is either ‘(’, ‘[’, or ‘{’:
push(c)3. if (c) is either ‘)’, ‘]’, ‘}’:
if (isEmpty()) error(…)
if (pop() does not match c) error(…)
4. if (not isEmpty()) error(…)
בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית
Stack implemented using array
Assume stack will not have more than K items Create array S[ ] of size K: S[K] Keep index of current head, h=0 New: h=0 Push(x): S[h] = x ; h++ Pop(): h-- ; return S[h] Peek(): return S[h-1] Empty(): return (h==0)
Note that all operations need check array bounds Pushing onto a full stack: Overflow
When h=K Popping an empty stack: Underflow
When h<0
Stack implemented in an array
דוגמה
:המשימה אותה עלינו להשלים היא לקרוא ביטוי אריתמטי (כדוגמת(4+2(*)12/2)(
:36ולהציג את ערכו (בדוגמה שלנו(:לשם ביצוע המשימה נידרש למבני הנתונים הבאים.מחסנית של אופרטורים – המשתמשת אותנו בשלב חישוב הביטוי מחסנית של מספרים – המשתמשת אותנו בשלב תרגום הביטוי מצורת
infix לצורת postfix.(הסבר על שתי צורות אלה יבוא מייד) :האלגוריתם בכללותו יכלול שני שלבים -תרגם את הביטוי הנקרא מinfix-ל postfix.-הערך את הביטוי בצורת הpostfix.
המשך-אם כותבים ביטוי (כהרגלנו, בinfix ,יש להשתמש בסוגריים ( נקבל את הביטוי 3(*4+2)לדוגמה אם נשמיט את הסוגרים מהביטוי
. 3*4+2השונה לגמרי Polish postfix notation או בקיצור ,postfix ובה איננו נזקקים לסוגריים ,
שני האופרנדים, הוא ניצב ביןוזאת משום שבמקום שהאופרטור יוצב . ולכן אין ספק לגבי הסדר בו יש לבצע את הפעולות.אחריהם
4 + 2 <=4 2+ , 6 * 3 <=6 3* :ולכן(4 + 2 * )3 <=4 2 + 3* :ובניגוד לכך 3 * 2 + 4 * + <= 3 2 4:וכן האלה(5 + 3( * )4 – 2) <=5 3 + 4 2* - ((2*3 + 1*)5(/)1-2-1) <=2 3 * 1 + 5 * 1 2 – 1/ -
האלגוריתם:
:חזור עד תום הקלט) קרא אסימוןToken.נוסף מהקלט ( אם אסימון זה הינו מספר אזי דחף אותו על-גבי
המחסנית.(קראת אופרטור) אחרת
.שלוף את שני המספרים (אופרנדים) שבראש המחסנית חשב את תוצאת הפעולה שנקראה עת היא מופעלת על
האופרנדים הנ"ל..דחף את התוצאה ע"ג המחסנית
.החזר את הערך (היחיד) המצוי במחסנית
דוגמה:
-נניח כי קלט הוא בטוי הpostfix :15 / 30 60 הבא 13* -
60.נקרא, ונדחף ע"ג המחסנית 30.נקרא, ונדחף ע"ג המחסנית :2 =60/30 נשלפים מהמחסנית, ו- 60, 30/ נקרא
נדחף ע"ג המחסנית.15.נקרא, ונדחף ע"ג המחסנית 13.נקרא, ונדחף ע"ג המחסנית :15- 13 2 = נשלפים מהמחסנית, ו- 15, 13- נקרא
נדחף ע"ג המחסנית. :נדחף 2*2 = 4 נשלפים מהמחסנית, ו- 2, 2* נקרא
ע"ג המחסנית. :המצוי בראש המחסנית נשלף ומוחזר 4הקלט תם
בתור ערכו של הביטוי.
4
2
13
15
2
30
60
:postfixהאלגוריתם להפיכת ביטוי ל
:חזור עד תום הקלטקרא נתון נוסף מהקלטאם נתון זה הוא מספר שלח אותו לפלט(נקרא אופרטור או סוגר) אחרת
קדימותו של האיבר שבראש וכןכל עוד המחסנית אינה ריקה המחסנית ≤ מקדימותו של האסימון שנקרא:
.שלוף את האיבר שבראש המחסנית.ופלוט אותו לפלט
אם האסימון שנקרא אינו סוגר ימני אזי דחוף אותו על המחסניתאחרת שלוף את ראש המחסנית (שהינו סוגר).
(אחרי תום הקלט) שלוף מהמחסנית ושלח לפלט את כל מה שמצוי בה.
)[2*3 + 1*(5][ הכנס( הכנס כל אופרנד\מספר שנקרא הכלל אותו נגזור - 2הדפס :
נשלח מיידית לפלט* הכנס 3הדפס קרא + , סדר קדימות של + קטן מ * לכן הוצא * הדפס
*3 2 :והכנס +. מצב פלט 1 * 3 2 : מצב פלט1 הדפס :1 * 3 2קרא ( ,הוצא והדפס כל המחסנית עד ) ,פלט+ * הכנס 5הדפס :1 * 3 2 *5קרא [ ,הוצא הדפס כל המחסנית עד ] , פלט+
Implementation complexity
What is the run-time complexity of each operation?
New: h=0 Push(x): S[h] = x ; h++ Pop(): h-- ; return S[h] Peek(): return S[h-1] Empty(): return (h==0)
O(1)
O(1)
O(1)
O(1)
O(1)
The Catch: Fixed Memory
Array implementation always allocates same amount of memory S(n) = K where K largest possible stack
But if too many items (n>K), we’re in trouble And if too few items (n<<K), we’re wasting space
Stack using dynamic memory allocation, and pointers
Key idea: allocate memory as required Keep pointer head, pointing to first item Each stack item stored in a node Node has two fields: item and next
X Y Z
Linked Lists
Each list element (node) holds a data item and a reference (pointer) to the next node:
Class ListNode {Object item;ListNode next = null;
}
A list consists of a chain of zero or more nodes:
Class SimplestLinkedList {ListNode head = null;
}
בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית
Stack using linked-list
New: head = NULL O(1) Empty: return (head == NULL) O(1) Peek: if (!empty()), return head.item O(1)
Stack using pointers – Push(x)
Create new node T // allocate memory for it T.item x T.next head head T
Let’s look at the blackboard
Stack using pointers – Pop()
If (empty(head)), return NULL X head.item T head // must save it to delete it later! head head.next delete T // free its memory
Let’s look at the blackboard
Queue ADT
Queue stores data according to order of arrival First In, First Out Basic operations:
Empty?, new, …. Enqueue(x) adds x to the back of the queue Dequeue() removes returns top of queue
Queue Application: Communications Buffer
Two processes: Writer: Puts information out Reader: Gets information in
We want each process to be independent Asynchronous: Work at their own pace
Reader
Writer
Queue Application: Communications Buffer
Two processes: Writer: Puts information out Reader: Gets information in
We want each process to be independent Asynchronous: Work at their own pace
Reader
Writer
Queue Application: Communications Buffer
Two processes: Writer: Puts information out Reader: Gets information in
We want each process to be independent Asynchronous: Work at their own pace
Reader
Writer
Queue Application: Communications Buffer
Two processes: Writer: Puts information out Reader: Gets information in
We want each process to be independent Asynchronous: Work at their own pace
Reader
Writer
Queue Application: Communications Buffer
Two processes: Writer: Puts information out Reader: Gets information in
We want each process to be independent Asynchronous: Work at their own pace
Reader
Writer
Queue Application: Communications Buffer
Two processes: Writer: Puts information out Reader: Gets information in
We want each process to be independent Asynchronous: Work at their own pace
Reader
Writer
Problem!
Buffering using a queue
Writer:1. While have information x to write:
2. if full?(), wait a bit
3. else enqueue(x)
Reader:1. While reading allowed:
2. if empty?(), wait a bit
3. else x = dequeue()
Queue Implementation 1:Circular Array
Again we use an array of fixed size K But now we keep two indexes: tail and head
head tail
K
Enqueue(x) -- Cont’d (Array)
But what happens when tail is at end of array We can put X in, but then what? How do we advance tail?
head tail
Y Z
X
Enqueue(x) -- Cont’d (Array)
tail is now moved to the beginning of the array tail (tail + 1) modulo K
In C, C++: tail = (tail+1) % K
head tail
Y Z X
Dequeue(x) -- Cont’d (Array)
And what happens when head reaches end? Same thing: head (head + 1) modulo K
head tail
Y Z X
Dequeue(x) -- Cont’d (Array)
And what happens when head reaches end? Same thing: head (head + 1) modulo K
head tail
Y Z X
Full? (Array)
Ooops! Looks exactly like empty? When tail catches up with head head == tail
So how do we tell the difference?
head tail
Full? (Array)
So how do we tell the difference? Solution: Keep a counter num for # of items
When we enqueue, num num + 1 When we dequeue, num num – 1
When we check empty or full, we look at num: empty? head==tail AND num == 0 full? head==tail AND num == K
Circular Array Summary
Run-time complexity is appealing: enqueue(), dequeue(), … are all O(1)
But storage complexity is a problem: S(n) = K, where K is size of largest array Can be wasteful (if K too large) Can be a problem (if K too small)
Queue Implementation 2:Doubly-linked list
Keep linked list, with pointers to head and tail
head tail Keep nodes pointing in both directions This allows us to move tail back ( ) And move head forward ( )
X Y Z
Queue as doubly-linked list
New: head = NULL, tail = NULL Empty: head == NULL Enqueue(x) -- same as stack push(x)
1. Create new node T // allocate memory
2. T.item x
3. T.next tail
4. tail.prev T
5. tail T5.5 (Instead of 4: tail.next.prev T)
6. T.prev NULL
Dequeue
1. If head == NULL, error(underflow)
2. X head.item
3. Temp head
4. head head.prev
5. if head != NULL
6. head.next NULL // New head
7. else tail = NULL
8. Delete Temp, return X