Document 7551744

Download Report

Transcript Document 7551744

Data Structures and
Algorithms
Abstract Data Types I:
Stacks and Queues
Abstract Data Type (ADT)
‫ ופעולות בסיסיות לטיפול וניהול המידע‬,‫מנגנון אחסון למידע‬
 Vector, Matrix


Queue (Buffer)




Random r/w access to any element by coordinates
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;
public:
stack();
~stack();
void make_empty();
bool is_empty();
bool is_full();
void push(Type item);
Type pop();
Type top();
};
// next item is placed
// at data[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
2.
if (c) is either ‘(’,
push(c)
3.
if (c) is either ‘)’,
if (isEmpty())
error(…)
if (pop() does not
error(…)
4. if (not isEmpty())
error(…)
string
‘[’, or ‘{’:
‘]’, ‘}’:
match c)
‫בתודה למתרגל אלמוני באוניברסיטה העברית על השקופית‬
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‬ובה איננו נזקקים לסוגריים‬
‫וזאת משום שבמקום שהאופרטור יוצב בין שני האופרנדים‪ ,‬הוא ניצב‬
‫אחריהם‪ .‬ולכן אין ספק לגבי הסדר בו יש לבצע את הפעולות‪.‬‬
‫*‪63‬‬
‫‪>= 6 * 3‬‬
‫=> ‪, 4 2 +‬‬
‫‪4+2‬‬
‫ולכן‪:‬‬
‫* ‪ 4 2 + 3‬ובניגוד לכך‪:‬‬
‫‪>= (4 + 2) * 3‬‬
‫‪423*+‬‬
‫=>‬
‫‪4+2* 3‬‬
‫וכן האלה‪:‬‬
‫*‪53+42-‬‬
‫=>‬
‫)‪(5 + 3) * (4 – 2‬‬
‫‪23*1+5*12–1-/‬‬
‫=>‬
‫)‪((2*3 + 1)*5)/(1-2-1‬‬
‫האלגוריתם‪:‬‬
‫חזור עד תום הקלט‪:‬‬
‫‪‬‬
‫קרא אסימון (‪ )Token‬נוסף מהקלט‪.‬‬
‫אם אסימון זה הינו מספר אזי דחף אותו על‪-‬גבי‬
‫המחסנית‪.‬‬
‫אחרת (קראת אופרטור)‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫שלוף את שני המספרים (אופרנדים) שבראש המחסנית‪.‬‬
‫חשב את תוצאת הפעולה שנקראה עת היא מופעלת על‬
‫האופרנדים הנ"ל‪.‬‬
‫דחף את התוצאה ע"ג המחסנית‪.‬‬
‫החזר את הערך (היחיד) המצוי במחסנית ‪.‬‬
‫דוגמה‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫נניח כי קלט הוא בטוי ה‪ postfix -‬הבא‪60 30 / 15 13 - :‬‬
‫*‬
‫‪ 60‬נקרא‪ ,‬ונדחף ע"ג המחסנית‪.‬‬
‫‪ 30‬נקרא‪ ,‬ונדחף ע"ג המחסנית‪.‬‬
‫‪ /‬נקרא‪ 60 ,30 :‬נשלפים מהמחסנית‪ ,‬ו‪ 60/30 =2 -‬נדחף‬
‫ע"ג המחסנית‪.‬‬
‫‪ 15‬נקרא‪ ,‬ונדחף ע"ג המחסנית‪.‬‬
‫‪ 13‬נקרא‪ ,‬ונדחף ע"ג המחסנית‪.‬‬
‫ נקרא‪ 15 ,13 :‬נשלפים מהמחסנית‪ ,‬ו‪ 15 -13 =2 -‬נדחף‬‫ע"ג המחסנית‪.‬‬
‫* נקרא‪ 2 ,2 :‬נשלפים מהמחסנית‪ ,‬ו‪ 2*2 = 4 -‬נדחף ע"ג‬
‫המחסנית‪.‬‬
‫הקלט תם‪ 4 :‬המצוי בראש המחסנית נשלף ומוחזר בתור‬
‫ערכו של הביטוי‪.‬‬
‫‪4‬‬
‫‪2‬‬
‫‪13‬‬
‫‪15‬‬
‫‪2‬‬
‫‪30‬‬
‫‪60‬‬
‫האלגוריתם להפיכת ביטוי ל ‪:postfix‬‬
‫חזור עד תום הקלט‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫קרא נתון נוסף מהקלט‬
‫אם נתון זה הוא מספר שלח אותו לפלט‬
‫אחרת (נקרא אופרטור או סוגר)‬
‫‪ ‬כל עוד המחסנית אינה ריקה וכן קדימותו של האיבר שבראש‬
‫המחסנית ≥ מקדימותו של האסימון שנקרא‪:‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫שלוף את האיבר שבראש המחסנית‪.‬‬
‫ופלוט אותו לפלט‪.‬‬
‫אם האסימון שנקרא אינו סוגר ימני אזי דחוף אותו על המחסנית‬
‫אחרת שלוף את ראש המחסנית (שהינו סוגר)‪.‬‬
‫(אחרי תום הקלט) שלוף מהמחסנית ושלח לפלט את כל מה‬
‫שמצוי בה ‪.‬‬
‫]‪[(2*3 + 1)*5‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫‪‬‬
‫הכנס ]‬
‫הכנס )‬
‫הדפס ‪ - 2‬הכלל אותו נגזור‪ :‬כל אופרנד\מספר שנקרא נשלח‬
‫מיידית לפלט‬
‫הכנס *‬
‫הדפס ‪3‬‬
‫קרא ‪ , +‬סדר קדימות של ‪ +‬קטן מ * לכן הוצא * הדפס והכנס‬
‫‪ .+‬מצב פלט‪2 3 * :‬‬
‫הדפס ‪ 1‬מצב פלט‪2 3 * 1 :‬‬
‫קרא ( ‪,‬הוצא והדפס כל המחסנית עד ) ‪,‬פלט‪2 3 * 1 + :‬‬
‫הכנס *‬
‫הדפס ‪5‬‬
‫קרא [ ‪,‬הוצא הדפס כל המחסנית עד ] ‪ ,‬פלט‪2 3 * 1 + 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)
Dequeue()
adds x to the back of the queue
removes returns top of queue
Basic operations of a queue
Enqueue
New
Empty?
Dequeue
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
Problem!
Writer
Buffering using a queue
Writer:
1.
2.
3.
While have information x to write:
if full?(), wait a bit
else enqueue(x)
Reader:
1.
2.
3.
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
head
Z
W
tail
Dequeue(x)


(Array)
Return element x at head
Advance head
X
Y
head
Z
W
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
head
Z
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
head
Z
tail
X
Dequeue(x) -- Cont’d


(Array)
And what happens when head reaches end?
Same thing: head  (head + 1) modulo K
Y
Z
head
X
tail
Dequeue(x) -- Cont’d


(Array)
And what happens when head reaches end?
Same thing: head  (head + 1) modulo K
Y
Z
head
X
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?

Ooops! Looks exactly like empty?



(Array)
When tail catches up with head
head == tail
So how do we tell the difference?
head
tail
Full?


So how do we tell the difference?
Solution: Keep a counter num for # of items



(Array)
When we enqueue, num  num + 1
When we dequeue, num  num – 1
When we check empty or full, we look at num:


empty?
full?
head==tail
head==tail
AND
AND
num == 0
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
X



Y
head
tail
Keep nodes pointing in both directions
This allows us to move tail back (  )
And move head forward
()
Z
Queue as doubly-linked list
head = NULL,
head == NULL
tail = NULL

New:
Empty:

Enqueue(x) -- same as stack push(x)

1.
2.
3.
4.
5.
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)
6.
T.prev  NULL
Dequeue
1.
2.
3.
4.
5.
6.
7.
8.
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
Doubly-linked list summary

Enqueue(), Dequeue(), ….  O(1)



But slightly less efficient in practice
Memory allocation overhead
Space:

No wasted space!