290 likes | 455 Views
Data Structures - Part II. CS215 Lecture #8. Stacks. Last-In-First-Out (LIFO) Stacks are often used in programming when data will need to be used in the reverse order A stack has two operations: push (places a new item at the top of the stack) pop (retrieves the top item from the stack).
E N D
Data Structures - Part II CS215 Lecture #8
Stacks • Last-In-First-Out (LIFO) • Stacks are often used in programming when data will need to be used in the reverse order • A stack has two operations: • push (places a new item at the top of the stack) • pop (retrieves the top item from the stack)
Stack Implementation • A stack can be implemented in several ways, e.g., by an array or by a linked list. • A stack pointer is a variable that identifies the beginning of the part of the array that is currently unused • A stack pointer variable, therefore, holds an address.
Declaring a Stack stack: .word 0:maxstacksize sp: .word stack or stack: .word 0:maxstacksize sp: .word . . . la sp, stack sp uses the address bound to the label stack as the initial value (static initialization) dynamic initialization
Some variations to the implementation • In some implementations, a stack pointer points to the top of the stack • In our sample implementation, the stack pointer points to the next available space we can push an element into • A stack can be grown upward or downward, i.e., we can either add a value to the stack pointer or subtract a value from it when pushing an element
Pushing an element unto the stack This operation is called push. move M[sp],x add sp,sp,4
sp sp Example 16.1 initially 20 move M[sp],20 add sp,sp,4
sp sp Example 16.2 20 5 move M[sp],5 add sp,sp,4 move M[sp],10 add sp,sp,4 20 5 10
Taking an item off the stack This operation is called pop. add sp,sp,-4 move x,M[sp]
sp sp Example 16.3 initially 20 5 10 add sp,sp,-4 move x,M[sp] 20 5 10 Note: popping the stack means moving back the pointer to the next available space. There is no need to explicitly delete the data. It will get overwritten in the next push.
Full and Empty Stack • SAL does not provide any form of boundary check, i.e., there is no automatic feature that detects whether an element is “pushed” beyond the stack’s capacity • A robust program must check before a push whether the stack is full. • It should also check whether the stack is empty before a pop.
Example 16.4 P.187 textbook .data stack: .byte 0:50 sp: .word stack #static initialization bottom: .word stack bias: .word 48 top: .word number: .word digit: .word push_error: .asciiz “Full stack failure “ pop_error: .asciiz “Empty stack failure “
Example 16.4 P.187 textbook add top,bottom,50 loop_top: rem digit,number,10 add digit,digit,bias bge sp,top,bad_push #check if full move m[sp],digit add sp,sp,1 div number,number,10 bgtz number,loop_top bad_push: puts push_error done
Example 16.5 P.187 textbook print_it: blt sp,bottom,bad_pop #empty check add sp,sp,-1 putc m[sp] bgt sp,bottom,print_it done bad_pop: puts pop_error done
Queues • A queue is a data structure that maintains a “first-in first-out” (FIFO) ordering. • In contrast, a stack maintains a “last-in first-out” (LIFO) ordering. • A queue adds new elements at the end. An element can only be removed at the front. • This is an abstraction of the “first-come first-served” practice.
Queue operations • A queue has two operations: • enqueue • dequeue • An enqueue operation adds new elements at the end of the queue or its tail. This is similar to the stack operation push; only that push now is done at the end of the array instead of at the front (or top). • A dequeue operation removes an element from the front of the array or its head.
head tail Implementation • A queue can be implemented using an array. • A naïve implementation will allow the enqueued data to “walk” through the array.
head tail A circular queue • An array can be reused by allowing the enqueued data to “walk around” the array. This type of implementation is called a circular queue.
The particular implementation that will be illustrated here uses an empty element. This will simplify the check for a full or empty queue. • If the queue is empty, the dequeue operation must not return an invalid element. • If the queue is full, the enqueue operation must not destroy an element already in the queue.
0 1 head tail 0 1 2 Adding elements (Enqueue) • To add one element, we simply add 1 to the the current value of the tail, then execute: add address,base,tail move m[address],new_element #copy element into array B B = blank cell
0 1 2 B head tail 0 1 2 B Removing Elements (Dequeue) • To delete one element, add 1 to the current value of head then we execute add address,base,head move element,m[address] #copy it to element
0 1 2 B head tail 0 1 2 B Detecting an Empty Queue • During a dequeue, the first thing that is done is to check whether (head == tail). If this is true, then the queue is empty. not empty empty
head tail 0 1 2 B Detecting a Full Queue • Before an enqueue is done, the tail is incremented by 1. If after the increment (head == tail) then the queue is full. before increment 0 1 2 B after increment full queue detected
Making the address circular • Suppose we have the array • To make the address 4 equal to the address 0 we simply use modulo arithmetic, i.e., 4 modulo 4 = 0. 0 1 2 3 4
Using modulo arithmetic maps any address to an address within the allocated space, thus preventing access to out-of-the-range addresses • Also, the modulo conveniently gives us the offset from the base address. • We need the offset from the base address to access specific elements in the queue.
Example 17.1 .data queue: .byte 0:4 queueaddr: .word head: .word tail: .word linenumber: .byte nextline: .byte addr: .word newline: .byte string1: .asciiz "Which line is ringing ?" string2: .asciiz "The next line to be answered is " string3: .asciiz "Enqueueing line " empty: .asciiz "No calls waiting " full: .asciiz "ERROR: Queue is full. Exiting program. "
Example 17.2 .text __start: la queueaddr,queue loop: puts string1 get linenumber beq linenumber,'\n',dequeue get newline #reads the second character enqueue: add tail,tail,1 rem tail,tail,4 #uses modulo beq tail,head,full_queue puts string3 put linenumber put '\n' add addr,queueaddr,tail move m[addr],linenumber b loop
Example 17.3 dequeue: beq head,tail,empty_queue add head,head,1 rem head,head,4 #uses modulo add addr,queueaddr,head move nextline,m[addr] puts string2 put nextline put '\n' b loop empty_queue: puts empty put '\n' b loop full_queue: puts full done