51
Data Structures and Algorithms Abstract Data Types I: Stacks and Queues

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

  • View
    230

  • Download
    4

Embed Size (px)

Citation preview

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

New Push

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

Basic operations of a queue

New Enqueue

Dequeue Empty? ?

Full?

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

New Queue (Array)

Head = 0 Tail = 0

head tail

Enqueue(x) (Array)

Put x into array at tail, then move tail

head tail

X

Dequeue(x) (Array)

Return element x at head Advance head

head tail

X Y Z W

Dequeue(x) (Array)

Return element x at head Advance head

head tail

X Y Z W

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

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)

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

Doubly-linked list summary

Enqueue(), Dequeue(), …. O(1) But slightly less efficient in practice Memory allocation overhead

Space: No wasted space!