510 likes | 652 Vues
Data Structures and Algorithms. Abstract Data Types I: Stacks and Queues. Abstract Data Type (ADT). מנגנון אחסון למידע, ופעולות בסיסיות לטיפול וניהול המידע Vector, Matrix Random r/w access to any element by coordinates Queue (Buffer) First in, First out
E N D
Data Structures and Algorithms Abstract Data Types I: Stacks and Queues
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
Basic operations of a Stack Push New ? Pop Peek ? Empty
מימוש ב 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 string 2. 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)
Stack implemented in an array • 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
דוגמה • המשימה אותה עלינו להשלים היא לקרוא ביטוי אריתמטי (כדוגמת: (4+2)*(12/2)) • ולהציג את ערכו (בדוגמה שלנו: 36) • לשם ביצוע המשימה נידרש למבני הנתונים הבאים: • מחסנית של אופרטורים – המשתמשת אותנו בשלב חישוב הביטוי. • מחסנית של מספרים – המשתמשת אותנו בשלב תרגום הביטוי מצורת infix לצורת postfix (הסבר על שתי צורות אלה יבוא מייד). • האלגוריתם בכללותו יכלול שני שלבים: • תרגם את הביטוי הנקרא מ- infix ל-postfix. • הערך את הביטוי בצורת ה-postfix.
המשך • אם כותבים ביטוי (כהרגלנו, ב-infix) יש להשתמש בסוגריים, • לדוגמה אם נשמיט את הסוגרים מהביטוי (4+2)*3 נקבל את הביטוי השונה לגמרי 4+2*3. • 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 הבא: 60 30 / 15 13 - * • 60 נקרא, ונדחף ע"ג המחסנית. • 30 נקרא, ונדחף ע"ג המחסנית. • / נקרא: 30, 60 נשלפים מהמחסנית, ו- 60/30 =2 נדחף ע"ג המחסנית. • 15 נקרא, ונדחף ע"ג המחסנית. • 13 נקרא, ונדחף ע"ג המחסנית. • - נקרא: 13, 15 נשלפים מהמחסנית, ו- =2 13- 15 נדחף ע"ג המחסנית. • * נקרא: 2, 2 נשלפים מהמחסנית, ו- 4 = 2*2 נדחף ע"ג המחסנית. • הקלט תם: 4 המצוי בראש המחסנית נשלף ומוחזר בתור ערכו של הביטוי.
האלגוריתם להפיכת ביטוי ל postfix: • חזור עד תום הקלט: • קרא נתון נוסף מהקלט • אם נתון זה הוא מספר שלח אותו לפלט • אחרת (נקרא אופרטור או סוגר) • כל עוד המחסנית אינה ריקה וכן קדימותו של האיבר שבראש המחסנית ≥ מקדימותו של האסימון שנקרא: • שלוף את האיבר שבראש המחסנית. • ופלוט אותו לפלט. • אם האסימון שנקרא אינו סוגר ימני אזי דחוף אותו על המחסנית אחרת שלוף את ראש המחסנית (שהינו סוגר). • (אחרי תום הקלט) שלוף מהמחסנית ושלח לפלט את כל מה שמצוי בה.
[(2*3 + 1)*5] • הכנס ] • הכנס ) • הדפס 2 - הכלל אותו נגזור: כל אופרנד\מספר שנקרא נשלח מיידית לפלט • הכנס * • הדפס 3 • קרא + , סדר קדימות של + קטן מ * לכן הוצא * הדפס והכנס +. מצב פלט:2 3 * • הדפס 1 מצב פלט: 2 3 * 1 • קרא ( ,הוצא והדפס כל המחסנית עד ) ,פלט: 2 3 * 1 + • הכנס * • הדפס 5 • קרא [ ,הוצא הדפס כל המחסנית עד ] , פלט: 5 *2 3 * 1 +
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
X Y Z 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
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
Basic operations of a queue New Enqueue ? Empty? Dequeue Full?
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 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 Problem!
Buffering using a queue Writer: • While have information x to write: • if full?(), wait a bit • else enqueue(x) Reader: • While reading allowed: • if empty?(), wait a bit • 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 K head tail
New Queue (Array) • Head = 0 • Tail = 0 head tail
Enqueue(x) (Array) • Put x into array at tail, then move tail X head tail
Dequeue(x) (Array) • Return element x at head • Advance head X Y Z W head tail
Dequeue(x) (Array) • Return element x at head • Advance head X Y Z W head tail
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? X Y Z head tail
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 Y Z X head tail
Dequeue(x) -- Cont’d (Array) • And what happens when head reaches end? • Same thing: head (head + 1) modulo K Y Z X head tail
Dequeue(x) -- Cont’d (Array) • And what happens when head reaches end? • Same thing: head (head + 1) modulo K Y Z X head tail
Empty? (Array) • When head catches up with tail head tail
Empty? (Array) • When head catches up with tail head tail
Empty? (Array) • When head catches up with tail • head == tail head tail
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)
X Y Z 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 ( )
Queue as doubly-linked list • New: head = NULL, tail = NULL • Empty: head == NULL • Enqueue(x) -- same as stack push(x) • Create new node T// allocate memory • T.item x • T.next tail • tail.prev T • tail T 5.5 (Instead of 4: tail.next.prev T) • T.prev NULL
Dequeue • If head == NULL, error(underflow) • X head.item • Temp head • head head.prev • if head != NULL • head.next NULL // New head • else tail = NULL • Delete Temp, return X