1 / 51

Data Structures and Algorithms

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

Télécharger la présentation

Data Structures and Algorithms

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Data Structures and Algorithms Abstract Data Types I: Stacks and Queues

  2. 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) • …..

  3. 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

  4. Basic operations of a Stack Push New ? Pop Peek ? Empty

  5. מימוש ב 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(); } ;

  6. 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! בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית

  7. 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(…) בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית

  8. 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)

  9. 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

  10. דוגמה • המשימה אותה עלינו להשלים היא לקרוא ביטוי אריתמטי (כדוגמת: (4+2)*(12/2)) • ולהציג את ערכו (בדוגמה שלנו: 36) • לשם ביצוע המשימה נידרש למבני הנתונים הבאים: • מחסנית של אופרטורים – המשתמשת אותנו בשלב חישוב הביטוי. • מחסנית של מספרים – המשתמשת אותנו בשלב תרגום הביטוי מצורת infix לצורת postfix (הסבר על שתי צורות אלה יבוא מייד). • האלגוריתם בכללותו יכלול שני שלבים: • תרגם את הביטוי הנקרא מ- infix ל-postfix. • הערך את הביטוי בצורת ה-postfix.

  11. המשך • אם כותבים ביטוי (כהרגלנו, ב-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 - /

  12. האלגוריתם: • חזור עד תום הקלט: • קרא אסימון (Token) נוסף מהקלט. • אם אסימון זה הינו מספר אזי דחף אותו על-גבי המחסנית. • אחרת (קראת אופרטור) • שלוף את שני המספרים (אופרנדים) שבראש המחסנית. • חשב את תוצאת הפעולה שנקראה עת היא מופעלת על האופרנדים הנ"ל. • דחף את התוצאה ע"ג המחסנית. • החזר את הערך (היחיד) המצוי במחסנית.

  13. דוגמה: • נניח כי קלט הוא בטוי ה- 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 המצוי בראש המחסנית נשלף ומוחזר בתור ערכו של הביטוי.

  14. האלגוריתם להפיכת ביטוי ל postfix: • חזור עד תום הקלט: • קרא נתון נוסף מהקלט • אם נתון זה הוא מספר שלח אותו לפלט • אחרת (נקרא אופרטור או סוגר) • כל עוד המחסנית אינה ריקה וכן קדימותו של האיבר שבראש המחסנית ≥ מקדימותו של האסימון שנקרא: • שלוף את האיבר שבראש המחסנית. • ופלוט אותו לפלט. • אם האסימון שנקרא אינו סוגר ימני אזי דחוף אותו על המחסנית אחרת שלוף את ראש המחסנית (שהינו סוגר). • (אחרי תום הקלט) שלוף מהמחסנית ושלח לפלט את כל מה שמצוי בה.

  15. [(2*3 + 1)*5] • הכנס ] • הכנס ) • הדפס 2 - הכלל אותו נגזור: כל אופרנד\מספר שנקרא נשלח מיידית לפלט • הכנס * • הדפס 3 • קרא + , סדר קדימות של + קטן מ * לכן הוצא * הדפס והכנס +. מצב פלט:2 3 * • הדפס 1 מצב פלט: 2 3 * 1 • קרא ( ,הוצא והדפס כל המחסנית עד ) ,פלט: 2 3 * 1 + • הכנס * • הדפס 5 • קרא [ ,הוצא הדפס כל המחסנית עד ] , פלט: 5 *2 3 * 1 +

  16. 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)

  17. 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

  18. 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

  19. 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; } בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית

  20. Stack using linked-list • New: head = NULL O(1) • Empty: return (head == NULL) O(1) • Peek: if (!empty()), return head.item O(1)

  21. 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

  22. 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

  23. 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

  24. Basic operations of a queue New Enqueue ? Empty? Dequeue Full?

  25. 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

  26. 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

  27. 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

  28. 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

  29. 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

  30. 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!

  31. 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()

  32. 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

  33. New Queue (Array) • Head = 0 • Tail = 0 head tail

  34. Enqueue(x) (Array) • Put x into array at tail, then move tail X head tail

  35. Dequeue(x) (Array) • Return element x at head • Advance head X Y Z W head tail

  36. Dequeue(x) (Array) • Return element x at head • Advance head X Y Z W head tail

  37. 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

  38. 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

  39. Dequeue(x) -- Cont’d (Array) • And what happens when head reaches end? • Same thing: head  (head + 1) modulo K Y Z X head tail

  40. Dequeue(x) -- Cont’d (Array) • And what happens when head reaches end? • Same thing: head  (head + 1) modulo K Y Z X head tail

  41. Empty? (Array) • When head catches up with tail head tail

  42. Empty? (Array) • When head catches up with tail head tail

  43. Empty? (Array) • When head catches up with tail • head == tail head tail

  44. Full? (Array) • Ooops! Looks exactly like empty? • When tail catches up with head • head == tail • So how do we tell the difference? head tail

  45. 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

  46. 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)

  47. 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 (  )

  48. 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

  49. 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

More Related